Merge "Add support for MTP perceived device type property."
diff --git a/Android.mk b/Android.mk
index 5dfa58a..a07fd01 100644
--- a/Android.mk
+++ b/Android.mk
@@ -128,9 +128,11 @@
 	core/java/android/bluetooth/IBluetoothInputHost.aidl \
 	core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
-	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
-	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
+	core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl \
+	core/java/android/bluetooth/IBluetoothGattServerCallbackExt.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 \
 	core/java/android/content/IClipboard.aidl \
 	core/java/android/content/IContentService.aidl \
@@ -435,8 +437,11 @@
 	telephony/java/com/android/ims/internal/IImsEcbm.aidl \
 	telephony/java/com/android/ims/internal/IImsEcbmListener.aidl \
         telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl \
+        telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl \
         telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl \
 	telephony/java/com/android/ims/internal/IImsService.aidl \
+	telephony/java/com/android/ims/internal/IImsServiceController.aidl \
+	telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl \
 	telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
 	telephony/java/com/android/ims/internal/IImsUt.aidl \
 	telephony/java/com/android/ims/internal/IImsUtListener.aidl \
@@ -511,6 +516,7 @@
 
 LOCAL_MODULE := framework
 
+LOCAL_DX_FLAGS := --core-library --multi-dex
 LOCAL_JACK_FLAGS := --multi-dex native
 
 LOCAL_RMTYPEDEFS := true
@@ -1346,6 +1352,8 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ext
 
+LOCAL_DX_FLAGS := --core-library
+
 ifneq ($(INCREMENTAL_BUILDS),)
     LOCAL_PROGUARD_ENABLED := disabled
     LOCAL_JACK_ENABLED := incremental
diff --git a/api/current.txt b/api/current.txt
index df8919c..b3d9fc1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6694,7 +6694,9 @@
     method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
     method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
     method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter();
+    method public int getLeMaximumAdvertisingDataLength();
     method public java.lang.String getName();
+    method public android.bluetooth.le.PeriodicAdvertisingManager getPeriodicAdvertisingManager();
     method public int getProfileConnectionState(int);
     method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
     method public android.bluetooth.BluetoothDevice getRemoteDevice(java.lang.String);
@@ -6703,6 +6705,10 @@
     method public int getState();
     method public boolean isDiscovering();
     method public boolean isEnabled();
+    method public boolean isLe2MPhySupported();
+    method public boolean isLeCodedPhySupported();
+    method public boolean isLeExtendedAdvertisingSupported();
+    method public boolean isLePeriodicAdvertisingSupported();
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
@@ -7071,6 +7077,9 @@
   public final class BluetoothDevice implements android.os.Parcelable {
     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.BluetoothGattCallbackExt);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallbackExt, int);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallbackExt, int, int);
     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;
@@ -7114,6 +7123,13 @@
     field public static final java.lang.String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
     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_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_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
     field public static final int TRANSPORT_AUTO = 0; // 0x0
     field public static final int TRANSPORT_BREDR = 1; // 0x1
     field public static final int TRANSPORT_LE = 2; // 0x2
@@ -7136,10 +7152,12 @@
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
     method public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method public void readPhy();
     method public boolean readRemoteRssi();
     method public boolean requestConnectionPriority(int);
     method public boolean requestMtu(int);
     method public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method public void setPreferredPhy(int, int, int);
     method public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
     method public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
     field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
@@ -7157,8 +7175,12 @@
     field public static final int GATT_WRITE_NOT_PERMITTED = 3; // 0x3
   }
 
-  public abstract class BluetoothGattCallback {
+  public abstract deprecated class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallbackExt {
     ctor public BluetoothGattCallback();
+  }
+
+  public abstract class BluetoothGattCallbackExt {
+    ctor public BluetoothGattCallbackExt();
     method public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
     method public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
     method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
@@ -7166,6 +7188,8 @@
     method public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onMtuChanged(android.bluetooth.BluetoothGatt, int, int);
+    method public void onPhyRead(android.bluetooth.BluetoothGatt, int, int, int);
+    method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int);
     method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int);
     method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int);
     method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
@@ -7259,12 +7283,18 @@
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method public void readPhy(android.bluetooth.BluetoothDevice);
     method public boolean removeService(android.bluetooth.BluetoothGattService);
     method public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
+    method public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
   }
 
-  public abstract class BluetoothGattServerCallback {
+  public abstract deprecated class BluetoothGattServerCallback extends android.bluetooth.BluetoothGattServerCallbackExt {
     ctor public BluetoothGattServerCallback();
+  }
+
+  public abstract class BluetoothGattServerCallbackExt {
+    ctor public BluetoothGattServerCallbackExt();
     method public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice, int, int, android.bluetooth.BluetoothGattCharacteristic);
     method public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice, int, android.bluetooth.BluetoothGattCharacteristic, boolean, boolean, int, byte[]);
     method public void onConnectionStateChange(android.bluetooth.BluetoothDevice, int, int);
@@ -7273,6 +7303,8 @@
     method public void onExecuteWrite(android.bluetooth.BluetoothDevice, int, boolean);
     method public void onMtuChanged(android.bluetooth.BluetoothDevice, int);
     method public void onNotificationSent(android.bluetooth.BluetoothDevice, int);
+    method public void onPhyRead(android.bluetooth.BluetoothDevice, int, int, int);
+    method public void onPhyUpdate(android.bluetooth.BluetoothDevice, int, int, int);
     method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
   }
 
@@ -7473,10 +7505,85 @@
     method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
   }
 
+  public final class AdvertisingSet {
+    method public void enableAdvertising(boolean, int);
+    method public void periodicAdvertisingEnable(boolean);
+    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 setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
+    method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
+  }
+
+  public abstract class AdvertisingSetCallback {
+    ctor public AdvertisingSetCallback();
+    method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
+    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
+    method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
+    method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
+    method public void onPeriodicAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
+    method public void onScanResponseDataSet(android.bluetooth.le.AdvertisingSet, int);
+    field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
+    field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; // 0x1
+    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
+    field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
+    field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
+    field public static final int ADVERTISE_SUCCESS = 0; // 0x0
+  }
+
+  public final class AdvertisingSetParameters implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getInterval();
+    method public int getPrimaryPhy();
+    method public int getSecondaryPhy();
+    method public int getTxPowerLevel();
+    method public boolean includeTxPower();
+    method public boolean isAnonymous();
+    method public boolean isConnectable();
+    method public boolean isLegacy();
+    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_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
+    field public static final int TX_POWER_MEDIUM = -7; // 0xfffffff9
+    field public static final int TX_POWER_MIN = -127; // 0xffffff81
+    field public static final int TX_POWER_ULTRA_LOW = -21; // 0xffffffeb
+  }
+
+  public static final class AdvertisingSetParameters.Builder {
+    ctor public AdvertisingSetParameters.Builder();
+    method public android.bluetooth.le.AdvertisingSetParameters build();
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setAnonymous(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setConnectable(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setIncludeTxPower(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
+  }
+
   public final class BluetoothLeAdvertiser {
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
+    method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
 
   public final class BluetoothLeScanner {
@@ -7486,6 +7593,53 @@
     method public void stopScan(android.bluetooth.le.ScanCallback);
   }
 
+  public abstract class PeriodicAdvertisingCallback {
+    ctor public PeriodicAdvertisingCallback();
+    method public void onPeriodicAdvertisingReport(android.bluetooth.le.PeriodicAdvertisingReport);
+    method public void onSyncEstablished(int, android.bluetooth.BluetoothDevice, int, int, int, int);
+    method public void onSyncLost(int);
+    field public static final int SYNC_NO_RESOURCES = 2; // 0x2
+    field public static final int SYNC_NO_RESPONSE = 1; // 0x1
+  }
+
+  public final class PeriodicAdvertisingManager {
+    method public void registerSync(android.bluetooth.le.ScanResult, int, int, android.bluetooth.le.PeriodicAdvertisingCallback);
+    method public void registerSync(android.bluetooth.le.ScanResult, int, int, android.bluetooth.le.PeriodicAdvertisingCallback, android.os.Handler);
+    method public void unregisterSync(android.bluetooth.le.PeriodicAdvertisingCallback);
+  }
+
+  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);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingParameters> CREATOR;
+  }
+
+  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);
+  }
+
+  public final class PeriodicAdvertisingReport implements android.os.Parcelable {
+    ctor public PeriodicAdvertisingReport(int, int, int, int, android.bluetooth.le.ScanRecord);
+    method public int describeContents();
+    method public android.bluetooth.le.ScanRecord getData();
+    method public int getDataStatus();
+    method public int getRssi();
+    method public int getSyncHandle();
+    method public long getTimestampNanos();
+    method public int getTxPower();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingReport> CREATOR;
+    field public static final int DATA_COMPLETE = 0; // 0x0
+    field public static final int DATA_INCOMPLETE_TRUNCATED = 2; // 0x2
+  }
+
   public abstract class ScanCallback {
     ctor public ScanCallback();
     method public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult>);
@@ -7540,19 +7694,37 @@
   }
 
   public final class ScanResult implements android.os.Parcelable {
-    ctor public ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
+    ctor public deprecated ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
+    ctor public ScanResult(android.bluetooth.BluetoothDevice, int, int, int, int, int, int, int, android.bluetooth.le.ScanRecord, long);
     method public int describeContents();
+    method public int getAdvertisingSid();
+    method public int getDataStatus();
     method public android.bluetooth.BluetoothDevice getDevice();
+    method public int getPeriodicAdvertisingInterval();
+    method public int getPrimaryPhy();
     method public int getRssi();
     method public android.bluetooth.le.ScanRecord getScanRecord();
+    method public int getSecondaryPhy();
     method public long getTimestampNanos();
+    method public int getTxPower();
+    method public boolean isConnectable();
+    method public boolean isLegacy();
     method public void writeToParcel(android.os.Parcel, int);
     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 PHY_UNUSED = 0; // 0x0
+    field public static final int SID_NOT_PRESENT = 255; // 0xff
   }
 
   public final class ScanSettings implements android.os.Parcelable {
     method public int describeContents();
     method public int getCallbackType();
+    method public boolean getLegacy();
+    method public int getPhy();
     method public long getReportDelayMillis();
     method public int getScanMode();
     method public int getScanResultType();
@@ -7566,6 +7738,9 @@
     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
@@ -7576,8 +7751,10 @@
     ctor public ScanSettings.Builder();
     method public android.bluetooth.le.ScanSettings build();
     method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
+    method public android.bluetooth.le.ScanSettings.Builder setLegacy(boolean);
     method public android.bluetooth.le.ScanSettings.Builder setMatchMode(int);
     method public android.bluetooth.le.ScanSettings.Builder setNumOfMatches(int);
+    method public android.bluetooth.le.ScanSettings.Builder setPhy(int);
     method public android.bluetooth.le.ScanSettings.Builder setReportDelay(long);
     method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
   }
@@ -8188,6 +8365,7 @@
     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";
@@ -8553,6 +8731,7 @@
     field public static final java.lang.String ACTION_CALL = "android.intent.action.CALL";
     field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
     field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
+    field public static final java.lang.String ACTION_CARRIER_SETUP = "android.intent.action.CARRIER_SETUP";
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
@@ -17017,6 +17196,15 @@
     method public boolean isTransitionalDifferent();
   }
 
+  public final class ListFormatter {
+    method public java.lang.String format(java.lang.Object...);
+    method public java.lang.String format(java.util.Collection<?>);
+    method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+    method public static android.icu.text.ListFormatter getInstance();
+    method public java.lang.String getPatternForNumItems(int);
+  }
+
   public abstract class LocaleDisplayNames {
     method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
     method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17026,6 +17214,8 @@
     method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
     method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
     method public abstract android.icu.util.ULocale getLocale();
+    method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+    method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
     method public abstract java.lang.String keyDisplayName(java.lang.String);
     method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
     method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17045,9 +17235,19 @@
     enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
   }
 
+  public static class LocaleDisplayNames.UiListItem {
+    ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+    field public final android.icu.util.ULocale minimized;
+    field public final android.icu.util.ULocale modified;
+    field public final java.lang.String nameInDisplayLocale;
+    field public final java.lang.String nameInSelf;
+  }
+
   public class MeasureFormat extends android.icu.text.UFormat {
     method public final boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+    method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
     method public final java.lang.String formatMeasures(android.icu.util.Measure...);
     method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
     method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -17516,6 +17716,14 @@
     method public void setUpperCaseFirst(boolean);
   }
 
+  public final class ScientificNumberFormatter {
+    method public java.lang.String format(java.lang.Object);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+  }
+
   public abstract class SearchIterator {
     ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
     method public final int first();
@@ -18291,6 +18499,34 @@
     method public long getToDate();
   }
 
+  public final class EthiopicCalendar extends android.icu.util.CECalendar {
+    ctor public EthiopicCalendar();
+    ctor public EthiopicCalendar(android.icu.util.TimeZone);
+    ctor public EthiopicCalendar(java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.ULocale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+    ctor public EthiopicCalendar(int, int, int);
+    ctor public EthiopicCalendar(java.util.Date);
+    ctor public EthiopicCalendar(int, int, int, int, int, int);
+    method protected deprecated int handleGetExtendedYear();
+    method public boolean isAmeteAlemEra();
+    method public void setAmeteAlemEra(boolean);
+    field public static final int GENBOT = 8; // 0x8
+    field public static final int HAMLE = 10; // 0xa
+    field public static final int HEDAR = 2; // 0x2
+    field public static final int MEGABIT = 6; // 0x6
+    field public static final int MESKEREM = 0; // 0x0
+    field public static final int MIAZIA = 7; // 0x7
+    field public static final int NEHASSE = 11; // 0xb
+    field public static final int PAGUMEN = 12; // 0xc
+    field public static final int SENE = 9; // 0x9
+    field public static final int TAHSAS = 3; // 0x3
+    field public static final int TEKEMT = 1; // 0x1
+    field public static final int TER = 4; // 0x4
+    field public static final int YEKATIT = 5; // 0x5
+  }
+
   public abstract interface Freezable<T> implements java.lang.Cloneable {
     method public abstract T cloneAsThawed();
     method public abstract T freeze();
@@ -18666,6 +18902,8 @@
     field public static final int SHORT_COMMONLY_USED = 6; // 0x6
     field public static final int SHORT_GENERIC = 2; // 0x2
     field public static final int SHORT_GMT = 4; // 0x4
+    field public static final int TIMEZONE_ICU = 0; // 0x0
+    field public static final int TIMEZONE_JDK = 1; // 0x1
     field public static final android.icu.util.TimeZone UNKNOWN_ZONE;
     field public static final java.lang.String UNKNOWN_ZONE_ID = "Etc/Unknown";
   }
@@ -18819,6 +19057,35 @@
     enum_constant public static final android.icu.util.ULocale.Category FORMAT;
   }
 
+  public final class UniversalTimeScale {
+    method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+    method public static long from(long, int);
+    method public static long getTimeScaleValue(int, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+    method public static long toLong(long, int);
+    field public static final int DB2_TIME = 8; // 0x8
+    field public static final int DOTNET_DATE_TIME = 4; // 0x4
+    field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+    field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+    field public static final int EXCEL_TIME = 7; // 0x7
+    field public static final int FROM_MAX_VALUE = 3; // 0x3
+    field public static final int FROM_MIN_VALUE = 2; // 0x2
+    field public static final int ICU4C_TIME = 2; // 0x2
+    field public static final int JAVA_TIME = 0; // 0x0
+    field public static final int MAC_OLD_TIME = 5; // 0x5
+    field public static final int MAC_TIME = 6; // 0x6
+    field public static final int MAX_SCALE = 10; // 0xa
+    field public static final int TO_MAX_VALUE = 5; // 0x5
+    field public static final int TO_MIN_VALUE = 4; // 0x4
+    field public static final int UNITS_VALUE = 0; // 0x0
+    field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+    field public static final int UNIX_TIME = 1; // 0x1
+    field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+  }
+
   public abstract interface ValueIterator {
     method public abstract boolean next(android.icu.util.ValueIterator.Element);
     method public abstract void reset();
@@ -23546,7 +23813,9 @@
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
     method public void registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback);
+    method public void registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
+    method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.app.PendingIntent);
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
@@ -23554,6 +23823,9 @@
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     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, int, 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, android.os.Handler);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
@@ -23601,6 +23873,7 @@
     method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties);
     method public void onLosing(android.net.Network, int);
     method public void onLost(android.net.Network);
+    method public void onUnavailable();
   }
 
   public static abstract interface ConnectivityManager.OnNetworkActiveListener {
@@ -23637,6 +23910,68 @@
     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(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, 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();
@@ -24482,6 +24817,16 @@
 
 package android.net.wifi {
 
+  public final class IconInfo implements android.os.Parcelable {
+    ctor public IconInfo(java.lang.String, byte[]);
+    ctor public IconInfo(android.net.wifi.IconInfo);
+    method public int describeContents();
+    method public byte[] getData();
+    method public java.lang.String getFilename();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.IconInfo> CREATOR;
+  }
+
   public class ScanResult implements android.os.Parcelable {
     method public int describeContents();
     method public boolean is80211mcResponder();
@@ -24543,9 +24888,10 @@
     field public java.util.BitSet allowedProtocols;
     field public android.net.wifi.WifiEnterpriseConfig enterpriseConfig;
     field public boolean hiddenSSID;
+    field public boolean isHomeProviderNetwork;
     field public int networkId;
     field public java.lang.String preSharedKey;
-    field public int priority;
+    field public deprecated int priority;
     field public java.lang.String providerFriendlyName;
     field public long[] roamingConsortiumIds;
     field public int status;
@@ -24610,6 +24956,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -24623,6 +24970,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -24648,11 +24996,14 @@
   }
 
   public static final class WifiEnterpriseConfig.Phase2 {
+    field public static final int AKA = 6; // 0x6
+    field public static final int AKA_PRIME = 7; // 0x7
     field public static final int GTC = 4; // 0x4
     field public static final int MSCHAP = 2; // 0x2
     field public static final int MSCHAPV2 = 3; // 0x3
     field public static final int NONE = 0; // 0x0
     field public static final int PAP = 1; // 0x1
+    field public static final int SIM = 5; // 0x5
   }
 
   public class WifiInfo implements android.os.Parcelable {
@@ -24675,6 +25026,7 @@
 
   public class WifiManager {
     method public int addNetwork(android.net.wifi.WifiConfiguration);
+    method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method public static int calculateSignalLevel(int, int);
     method public void cancelWps(android.net.wifi.WifiManager.WpsCallback);
     method public static int compareSignalLevel(int, int);
@@ -24687,6 +25039,7 @@
     method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
+    method public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public int getWifiState();
     method public boolean is5GHzBandSupported();
@@ -24697,28 +25050,41 @@
     method public boolean isScanAlwaysAvailable();
     method public boolean isTdlsSupported();
     method public boolean isWifiEnabled();
-    method public boolean pingSupplicant();
+    method public deprecated boolean pingSupplicant();
+    method public void queryPasspointIcon(long, java.lang.String);
     method public boolean reassociate();
     method public boolean reconnect();
     method public boolean removeNetwork(int);
-    method public boolean saveConfiguration();
+    method public void removePasspointConfiguration(java.lang.String);
+    method public deprecated boolean saveConfiguration();
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiEnabled(boolean);
     method public boolean startScan();
     method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
     method public int updateNetwork(android.net.wifi.WifiConfiguration);
+    field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+    field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+    field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+    field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
     field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
     field public static final int ERROR_AUTHENTICATING = 1; // 0x1
+    field public static final java.lang.String EXTRA_ANQP_ELEMENT_DATA = "android.net.wifi.extra.ANQP_ELEMENT_DATA";
     field public static final java.lang.String EXTRA_BSSID = "bssid";
+    field public static final java.lang.String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG";
+    field public static final java.lang.String EXTRA_DELAY = "android.net.wifi.extra.DELAY";
+    field public static final java.lang.String EXTRA_ESS = "android.net.wifi.extra.ESS";
+    field public static final java.lang.String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO";
     field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
     field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
     field public static final java.lang.String EXTRA_NEW_STATE = "newState";
     field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
     field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+    field public static final java.lang.String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD";
     field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
     field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError";
+    field public static final java.lang.String EXTRA_URL = "android.net.wifi.extra.URL";
     field public static final java.lang.String EXTRA_WIFI_INFO = "wifiInfo";
     field public static final java.lang.String EXTRA_WIFI_STATE = "wifi_state";
     field public static final java.lang.String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED";
@@ -24801,10 +25167,9 @@
   }
 
   public class DiscoverySession {
-    method public java.lang.String createNetworkSpecifier(android.net.wifi.aware.PeerHandle, byte[]);
+    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 void destroy();
-    method public static int getMaxSendRetryCount();
-    method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[], int);
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
 
@@ -24891,7 +25256,8 @@
   }
 
   public class WifiAwareSession {
-    method public java.lang.String createNetworkSpecifier(int, byte[], byte[]);
+    method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
+    method public java.lang.String 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);
@@ -24899,6 +25265,232 @@
 
 }
 
+package android.net.wifi.hotspot2 {
+
+  public final class ConfigParser {
+    method public static android.net.wifi.hotspot2.PasspointConfiguration parsePasspointConfig(java.lang.String, byte[]);
+  }
+
+  public final class PasspointConfiguration implements android.os.Parcelable {
+    ctor public PasspointConfiguration();
+    ctor public PasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
+    method public int describeContents();
+    method public android.net.wifi.hotspot2.pps.Credential getCredential();
+    method public int getCredentialPriority();
+    method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+    method public android.net.wifi.hotspot2.pps.Policy getPolicy();
+    method public long getSubscriptionCreationTimeInMs();
+    method public long getSubscriptionExpirationTimeInMs();
+    method public java.lang.String getSubscriptionType();
+    method public android.net.wifi.hotspot2.pps.UpdateParameter getSubscriptionUpdate();
+    method public java.util.Map<java.lang.String, byte[]> getTrustRootCertList();
+    method public int getUpdateIdentifier();
+    method public long getUsageLimitDataLimit();
+    method public long getUsageLimitStartTimeInMs();
+    method public long getUsageLimitTimeLimitInMinutes();
+    method public long getUsageLimitUsageTimePeriodInMinutes();
+    method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
+    method public void setCredentialPriority(int);
+    method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
+    method public void setPolicy(android.net.wifi.hotspot2.pps.Policy);
+    method public void setSubscriptionCreationTimeInMs(long);
+    method public void setSubscriptionExpirationTimeInMs(long);
+    method public void setSubscriptionType(java.lang.String);
+    method public void setSubscriptionUpdate(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public void setTrustRootCertList(java.util.Map<java.lang.String, byte[]>);
+    method public void setUpdateIdentifier(int);
+    method public void setUsageLimitDataLimit(long);
+    method public void setUsageLimitStartTimeInMs(long);
+    method public void setUsageLimitTimeLimitInMinutes(long);
+    method public void setUsageLimitUsageTimePeriodInMinutes(long);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR;
+  }
+
+}
+
+package android.net.wifi.hotspot2.omadm {
+
+  public final class PpsMoParser {
+    method public static android.net.wifi.hotspot2.PasspointConfiguration parseMoText(java.lang.String);
+  }
+
+}
+
+package android.net.wifi.hotspot2.pps {
+
+  public final class Credential implements android.os.Parcelable {
+    ctor public Credential();
+    ctor public Credential(android.net.wifi.hotspot2.pps.Credential);
+    method public int describeContents();
+    method public java.security.cert.X509Certificate getCaCertificate();
+    method public android.net.wifi.hotspot2.pps.Credential.CertificateCredential getCertCredential();
+    method public boolean getCheckAaaServerCertStatus();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
+    method public java.security.PrivateKey getClientPrivateKey();
+    method public long getCreationTimeInMs();
+    method public long getExpirationTimeInMs();
+    method public java.lang.String getRealm();
+    method public android.net.wifi.hotspot2.pps.Credential.SimCredential getSimCredential();
+    method public android.net.wifi.hotspot2.pps.Credential.UserCredential getUserCredential();
+    method public void setCaCertificate(java.security.cert.X509Certificate);
+    method public void setCertCredential(android.net.wifi.hotspot2.pps.Credential.CertificateCredential);
+    method public void setCheckAaaServerCertStatus(boolean);
+    method public void setClientCertificateChain(java.security.cert.X509Certificate[]);
+    method public void setClientPrivateKey(java.security.PrivateKey);
+    method public void setCreationTimeInMs(long);
+    method public void setExpirationTimeInMs(long);
+    method public void setRealm(java.lang.String);
+    method public void setSimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential);
+    method public void setUserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential> CREATOR;
+  }
+
+  public static final class Credential.CertificateCredential implements android.os.Parcelable {
+    ctor public Credential.CertificateCredential();
+    ctor public Credential.CertificateCredential(android.net.wifi.hotspot2.pps.Credential.CertificateCredential);
+    method public int describeContents();
+    method public byte[] getCertSha256Fingerprint();
+    method public java.lang.String getCertType();
+    method public void setCertSha256Fingerprint(byte[]);
+    method public void setCertType(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.CertificateCredential> CREATOR;
+  }
+
+  public static final class Credential.SimCredential implements android.os.Parcelable {
+    ctor public Credential.SimCredential();
+    ctor public Credential.SimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential);
+    method public int describeContents();
+    method public int getEapType();
+    method public java.lang.String getImsi();
+    method public void setEapType(int);
+    method public void setImsi(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.SimCredential> CREATOR;
+  }
+
+  public static final class Credential.UserCredential implements android.os.Parcelable {
+    ctor public Credential.UserCredential();
+    ctor public Credential.UserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential);
+    method public int describeContents();
+    method public boolean getAbleToShare();
+    method public int getEapType();
+    method public boolean getMachineManaged();
+    method public java.lang.String getNonEapInnerMethod();
+    method public java.lang.String getPassword();
+    method public java.lang.String getSoftTokenApp();
+    method public java.lang.String getUsername();
+    method public void setAbleToShare(boolean);
+    method public void setEapType(int);
+    method public void setMachineManaged(boolean);
+    method public void setNonEapInnerMethod(java.lang.String);
+    method public void setPassword(java.lang.String);
+    method public void setSoftTokenApp(java.lang.String);
+    method public void setUsername(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.UserCredential> CREATOR;
+  }
+
+  public final class HomeSp implements android.os.Parcelable {
+    ctor public HomeSp();
+    ctor public HomeSp(android.net.wifi.hotspot2.pps.HomeSp);
+    method public int describeContents();
+    method public java.lang.String getFqdn();
+    method public java.lang.String getFriendlyName();
+    method public java.util.Map<java.lang.String, java.lang.Long> getHomeNetworkIds();
+    method public java.lang.String getIconUrl();
+    method public long[] getMatchAllOis();
+    method public long[] getMatchAnyOis();
+    method public java.lang.String[] getOtherHomePartners();
+    method public long[] getRoamingConsortiumOis();
+    method public void setFqdn(java.lang.String);
+    method public void setFriendlyName(java.lang.String);
+    method public void setHomeNetworkIds(java.util.Map<java.lang.String, java.lang.Long>);
+    method public void setIconUrl(java.lang.String);
+    method public void setMatchAllOis(long[]);
+    method public void setMatchAnyOis(long[]);
+    method public void setOtherHomePartners(java.lang.String[]);
+    method public void setRoamingConsortiumOis(long[]);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR;
+  }
+
+  public final class Policy implements android.os.Parcelable {
+    ctor public Policy();
+    ctor public Policy(android.net.wifi.hotspot2.pps.Policy);
+    method public int describeContents();
+    method public java.lang.String[] getExcludedSsidList();
+    method public int getMaximumBssLoadValue();
+    method public long getMinHomeDownlinkBandwidth();
+    method public long getMinHomeUplinkBandwidth();
+    method public long getMinRoamingDownlinkBandwidth();
+    method public long getMinRoamingUplinkBandwidth();
+    method public android.net.wifi.hotspot2.pps.UpdateParameter getPolicyUpdate();
+    method public java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> getPreferredRoamingPartnerList();
+    method public java.util.Map<java.lang.Integer, java.lang.String> getRequiredProtoPortMap();
+    method public void setExcludedSsidList(java.lang.String[]);
+    method public void setMaximumBssLoadValue(int);
+    method public void setMinHomeDownlinkBandwidth(long);
+    method public void setMinHomeUplinkBandwidth(long);
+    method public void setMinRoamingDownlinkBandwidth(long);
+    method public void setMinRoamingUplinkBandwidth(long);
+    method public void setPolicyUpdate(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public void setPreferredRoamingPartnerList(java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner>);
+    method public void setRequiredProtoPortMap(java.util.Map<java.lang.Integer, java.lang.String>);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy> CREATOR;
+  }
+
+  public static final class Policy.RoamingPartner implements android.os.Parcelable {
+    ctor public Policy.RoamingPartner();
+    ctor public Policy.RoamingPartner(android.net.wifi.hotspot2.pps.Policy.RoamingPartner);
+    method public int describeContents();
+    method public java.lang.String getCountries();
+    method public java.lang.String getFqdn();
+    method public boolean getFqdnExactMatch();
+    method public int getPriority();
+    method public void setCountries(java.lang.String);
+    method public void setFqdn(java.lang.String);
+    method public void setFqdnExactMatch(boolean);
+    method public void setPriority(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> CREATOR;
+  }
+
+  public final class UpdateParameter implements android.os.Parcelable {
+    ctor public UpdateParameter();
+    ctor public UpdateParameter(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public int describeContents();
+    method public java.lang.String getBase64EncodedPassword();
+    method public java.lang.String getRestriction();
+    method public java.lang.String getServerUri();
+    method public byte[] getTrustRootCertSha256Fingerprint();
+    method public java.lang.String getTrustRootCertUrl();
+    method public long getUpdateIntervalInMinutes();
+    method public java.lang.String getUpdateMethod();
+    method public java.lang.String getUsername();
+    method public void setBase64EncodedPassword(java.lang.String);
+    method public void setRestriction(java.lang.String);
+    method public void setServerUri(java.lang.String);
+    method public void setTrustRootCertSha256Fingerprint(byte[]);
+    method public void setTrustRootCertUrl(java.lang.String);
+    method public void setUpdateIntervalInMinutes(long);
+    method public void setUpdateMethod(java.lang.String);
+    method public void setUsername(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.UpdateParameter> CREATOR;
+    field public static final long UPDATE_CHECK_INTERVAL_NEVER = 4294967295L; // 0xffffffffL
+    field public static final java.lang.String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    field public static final java.lang.String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+    field public static final java.lang.String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    field public static final java.lang.String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    field public static final java.lang.String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+  }
+
+}
+
 package android.net.wifi.p2p {
 
   public class WifiP2pConfig implements android.os.Parcelable {
@@ -36234,9 +36826,11 @@
     method public android.telecom.Call.Details getDetails();
     method public android.telecom.Call getParent();
     method public java.lang.String getRemainingPostDialSequence();
+    method public android.telecom.Call.RttCall getRttCall();
     method public int getState();
     method public android.telecom.InCallService.VideoCall getVideoCall();
     method public void hold();
+    method public boolean isRttActive();
     method public void mergeConference();
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
@@ -36248,9 +36842,12 @@
     method public void reject(boolean, java.lang.String);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void respondToRttRequest(int, boolean);
     method public void sendCallEvent(java.lang.String, android.os.Bundle);
+    method public void sendRttRequest();
     method public void splitFromConference();
     method public void stopDtmfTone();
+    method public void stopRtt();
     method public void swapConference();
     method public void unhold();
     method public void unregisterCallback(android.telecom.Call.Callback);
@@ -36277,6 +36874,10 @@
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
+    method public void onRttInitiationFailure(android.telecom.Call, int);
+    method public void onRttModeChanged(android.telecom.Call, int);
+    method public void onRttRequest(android.telecom.Call, int);
+    method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
     method public void onStateChanged(android.telecom.Call, int);
     method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall);
   }
@@ -36327,9 +36928,20 @@
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
+    field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
 
+  public static final class Call.RttCall {
+    method public int getRttAudioMode();
+    method public java.lang.String read();
+    method public void setRttMode(int);
+    method public void write(java.lang.String) throws java.io.IOException;
+    field public static final int RTT_MODE_FULL = 1; // 0x1
+    field public static final int RTT_MODE_HCO = 2; // 0x2
+    field public static final int RTT_MODE_VCO = 3; // 0x3
+  }
+
   public final class CallAudioState implements android.os.Parcelable {
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
@@ -36529,6 +37141,15 @@
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
+  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
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+    field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
+  }
+
   public static abstract class Connection.VideoProvider {
     ctor public Connection.VideoProvider();
     method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
@@ -36549,6 +37170,7 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
     method public void setCallDataUsage(long);
     field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
+    field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
     field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
     field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
     field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
@@ -36585,9 +37207,9 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConference(android.telecom.Connection, android.telecom.Connection);
     method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
+    method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
+    method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
     method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -36700,6 +37322,7 @@
     field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40
     field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
     field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
+    field public static final int CAPABILITY_RTT = 4096; // 0x1000
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
@@ -36882,6 +37505,7 @@
     method public boolean handleMmi(java.lang.String);
     method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
     method public boolean isInCall();
+    method public boolean isInManagedCall();
     method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
     method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
     method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -36894,7 +37518,7 @@
     field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
     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 java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+    field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
     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";
@@ -36912,11 +37536,13 @@
     field public static final java.lang.String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
     field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
     field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
+    field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
     field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
     field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+    field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37004,6 +37630,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+    field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
@@ -37064,6 +37691,7 @@
     field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
     field public static final java.lang.String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+    field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
     field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
@@ -37583,6 +38211,7 @@
     method public java.lang.String getDeviceId();
     method public java.lang.String getDeviceId(int);
     method public java.lang.String getDeviceSoftwareVersion();
+    method public java.lang.String[] getForbiddenPlmns();
     method public java.lang.String getGroupIdLevel1();
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
     method public java.lang.String getLine1Number();
@@ -38388,7 +39017,7 @@
 
 package android.test.suitebuilder {
 
-  public class TestMethod {
+  public deprecated class TestMethod {
     ctor public TestMethod(java.lang.reflect.Method, java.lang.Class<? extends junit.framework.TestCase>);
     ctor public TestMethod(java.lang.String, java.lang.Class<? extends junit.framework.TestCase>);
     ctor public TestMethod(junit.framework.TestCase);
@@ -38399,7 +39028,7 @@
     method public java.lang.String getName();
   }
 
-  public class TestSuiteBuilder {
+  public deprecated class TestSuiteBuilder {
     ctor public TestSuiteBuilder(java.lang.Class);
     ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader);
     method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>);
@@ -38412,7 +39041,7 @@
     method public android.test.suitebuilder.TestSuiteBuilder named(java.lang.String);
   }
 
-  public static class TestSuiteBuilder.FailedToCreateTests extends junit.framework.TestCase {
+  public static deprecated class TestSuiteBuilder.FailedToCreateTests extends junit.framework.TestCase {
     ctor public TestSuiteBuilder.FailedToCreateTests(java.lang.Exception);
     method public void testSuiteConstructionFailed();
   }
@@ -48782,7 +49411,7 @@
 
 package com.android.internal.util {
 
-  public abstract interface Predicate<T> {
+  public abstract deprecated interface Predicate<T> {
     method public abstract boolean apply(T);
   }
 
@@ -48920,6 +49549,8 @@
     field public static final int OP_INT_TO_FLOAT = 130; // 0x82
     field public static final int OP_INT_TO_LONG = 129; // 0x81
     field public static final int OP_INT_TO_SHORT = 143; // 0x8f
+    field public static final int OP_INVOKE_CUSTOM = 252; // 0xfc
+    field public static final int OP_INVOKE_CUSTOM_RANGE = 253; // 0xfd
     field public static final int OP_INVOKE_DIRECT = 112; // 0x70
     field public static final deprecated int OP_INVOKE_DIRECT_EMPTY = 240; // 0xf0
     field public static final int OP_INVOKE_DIRECT_JUMBO = 9471; // 0x24ff
@@ -48927,6 +49558,8 @@
     field public static final int OP_INVOKE_INTERFACE = 114; // 0x72
     field public static final int OP_INVOKE_INTERFACE_JUMBO = 9983; // 0x26ff
     field public static final int OP_INVOKE_INTERFACE_RANGE = 120; // 0x78
+    field public static final int OP_INVOKE_POLYMORPHIC = 250; // 0xfa
+    field public static final int OP_INVOKE_POLYMORPHIC_RANGE = 251; // 0xfb
     field public static final int OP_INVOKE_STATIC = 113; // 0x71
     field public static final int OP_INVOKE_STATIC_JUMBO = 9727; // 0x25ff
     field public static final int OP_INVOKE_STATIC_RANGE = 119; // 0x77
@@ -49108,7 +49741,7 @@
     method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
   }
 
-  public final class InMemoryDexClassLoader extends java.lang.ClassLoader {
+  public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
     ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
   }
 
@@ -50326,6 +50959,13 @@
     field public static final java.lang.Class<java.lang.Boolean> TYPE;
   }
 
+  public class BootstrapMethodError extends java.lang.LinkageError {
+    ctor public BootstrapMethodError();
+    ctor public BootstrapMethodError(java.lang.String);
+    ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+    ctor public BootstrapMethodError(java.lang.Throwable);
+  }
+
   public final class Byte extends java.lang.Number implements java.lang.Comparable {
     ctor public Byte(byte);
     ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -52212,6 +52852,21 @@
 
 package java.lang.invoke {
 
+  public abstract class CallSite {
+    method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+    method public abstract java.lang.invoke.MethodHandle getTarget();
+    method public abstract void setTarget(java.lang.invoke.MethodHandle);
+    method public java.lang.invoke.MethodType type();
+  }
+
+  public class ConstantCallSite extends java.lang.invoke.CallSite {
+    ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+    ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public final void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class LambdaConversionException extends java.lang.Exception {
     ctor public LambdaConversionException();
     ctor public LambdaConversionException(java.lang.String);
@@ -52221,12 +52876,15 @@
   }
 
   public abstract class MethodHandle {
+    method public java.lang.invoke.MethodHandle asCollector(java.lang.Class<?>, int);
     method public java.lang.invoke.MethodHandle asFixedArity();
+    method public java.lang.invoke.MethodHandle asSpreader(java.lang.Class<?>, int);
     method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
     method public java.lang.invoke.MethodHandle asVarargsCollector(java.lang.Class<?>);
     method public java.lang.invoke.MethodHandle bindTo(java.lang.Object);
     method public final java.lang.Object invoke(java.lang.Object...) throws java.lang.Throwable;
     method public final java.lang.Object invokeExact(java.lang.Object...) throws java.lang.Throwable;
+    method public java.lang.Object invokeWithArguments(java.lang.Object...) throws java.lang.Throwable;
     method public java.lang.Object invokeWithArguments(java.util.List<?>) throws java.lang.Throwable;
     method public boolean isVarargsCollector();
     method public java.lang.invoke.MethodType type();
@@ -52260,17 +52918,23 @@
     method public static java.lang.invoke.MethodHandle arrayElementGetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
     method public static java.lang.invoke.MethodHandle arrayElementSetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
     method public static java.lang.invoke.MethodHandle catchException(java.lang.invoke.MethodHandle, java.lang.Class<? extends java.lang.Throwable>, java.lang.invoke.MethodHandle);
+    method public static java.lang.invoke.MethodHandle collectArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle constant(java.lang.Class<?>, java.lang.Object);
     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 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);
     method public static java.lang.invoke.MethodHandle guardWithTest(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle identity(java.lang.Class<?>);
+    method public static java.lang.invoke.MethodHandle insertArguments(java.lang.invoke.MethodHandle, int, java.lang.Object...);
     method public static java.lang.invoke.MethodHandle invoker(java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandles.Lookup lookup();
     method public static java.lang.invoke.MethodHandle permuteArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType, int...);
     method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
+    method public static <T extends java.lang.reflect.Member> T reflectAs(java.lang.Class<T>, java.lang.invoke.MethodHandle);
+    method public static java.lang.invoke.MethodHandle spreadInvoker(java.lang.invoke.MethodType, int);
     method public static java.lang.invoke.MethodHandle throwException(java.lang.Class<?>, java.lang.Class<? extends java.lang.Throwable>);
   }
 
@@ -52287,7 +52951,7 @@
     method public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?>);
     method public java.lang.Class<?> lookupClass();
     method public int lookupModes();
-    method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
+    method public java.lang.invoke.MethodHandleInfo revealDirect(java.lang.invoke.MethodHandle);
     method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
     method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
     method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
@@ -52330,6 +52994,22 @@
     method public java.lang.invoke.MethodType wrap();
   }
 
+  public class MutableCallSite extends java.lang.invoke.CallSite {
+    ctor public MutableCallSite(java.lang.invoke.MethodType);
+    ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
+  public class VolatileCallSite extends java.lang.invoke.CallSite {
+    ctor public VolatileCallSite(java.lang.invoke.MethodType);
+    ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class WrongMethodTypeException extends java.lang.RuntimeException {
     ctor public WrongMethodTypeException();
     ctor public WrongMethodTypeException(java.lang.String);
@@ -60618,6 +61298,9 @@
     method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
     method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
     method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+    method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
     method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
     method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
     method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -60628,7 +61311,11 @@
     method public static final <T> java.util.List<T> emptyList();
     method public static <T> java.util.ListIterator<T> emptyListIterator();
     method public static final <K, V> java.util.Map<K, V> emptyMap();
+    method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+    method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
     method public static final <T> java.util.Set<T> emptySet();
+    method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+    method public static <E> java.util.SortedSet<E> emptySortedSet();
     method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
     method public static <T> void fill(java.util.List<? super T>, T);
     method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -60657,12 +61344,16 @@
     method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
     method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
     method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+    method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+    method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
     method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
     method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
     method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
     method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
     method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+    method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+    method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
     method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
     method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/system-current.txt b/api/system-current.txt
index 308fa6b..55d4f4e2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -36,6 +36,7 @@
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH";
     field public static final java.lang.String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
+    field public static final java.lang.String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
     field public static final java.lang.String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
     field public static final java.lang.String BIND_KEYGUARD_APPWIDGET = "android.permission.BIND_KEYGUARD_APPWIDGET";
@@ -6991,7 +6992,9 @@
     method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
     method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
     method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter();
+    method public int getLeMaximumAdvertisingDataLength();
     method public java.lang.String getName();
+    method public android.bluetooth.le.PeriodicAdvertisingManager getPeriodicAdvertisingManager();
     method public int getProfileConnectionState(int);
     method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
     method public android.bluetooth.BluetoothDevice getRemoteDevice(java.lang.String);
@@ -7001,7 +7004,11 @@
     method public boolean isBleScanAlwaysAvailable();
     method public boolean isDiscovering();
     method public boolean isEnabled();
+    method public boolean isLe2MPhySupported();
+    method public boolean isLeCodedPhySupported();
     method public boolean isLeEnabled();
+    method public boolean isLeExtendedAdvertisingSupported();
+    method public boolean isLePeriodicAdvertisingSupported();
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
@@ -7372,6 +7379,9 @@
   public final class BluetoothDevice implements android.os.Parcelable {
     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.BluetoothGattCallbackExt);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallbackExt, int);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallbackExt, int, int);
     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;
@@ -7417,6 +7427,13 @@
     field public static final java.lang.String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
     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_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_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
     field public static final int TRANSPORT_AUTO = 0; // 0x0
     field public static final int TRANSPORT_BREDR = 1; // 0x1
     field public static final int TRANSPORT_LE = 2; // 0x2
@@ -7439,10 +7456,12 @@
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
     method public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method public void readPhy();
     method public boolean readRemoteRssi();
     method public boolean requestConnectionPriority(int);
     method public boolean requestMtu(int);
     method public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method public void setPreferredPhy(int, int, int);
     method public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
     method public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
     field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
@@ -7460,8 +7479,12 @@
     field public static final int GATT_WRITE_NOT_PERMITTED = 3; // 0x3
   }
 
-  public abstract class BluetoothGattCallback {
+  public abstract deprecated class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallbackExt {
     ctor public BluetoothGattCallback();
+  }
+
+  public abstract class BluetoothGattCallbackExt {
+    ctor public BluetoothGattCallbackExt();
     method public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
     method public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
     method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
@@ -7469,6 +7492,8 @@
     method public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onMtuChanged(android.bluetooth.BluetoothGatt, int, int);
+    method public void onPhyRead(android.bluetooth.BluetoothGatt, int, int, int);
+    method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int);
     method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int);
     method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int);
     method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
@@ -7562,12 +7587,18 @@
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method public void readPhy(android.bluetooth.BluetoothDevice);
     method public boolean removeService(android.bluetooth.BluetoothGattService);
     method public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
+    method public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
   }
 
-  public abstract class BluetoothGattServerCallback {
+  public abstract deprecated class BluetoothGattServerCallback extends android.bluetooth.BluetoothGattServerCallbackExt {
     ctor public BluetoothGattServerCallback();
+  }
+
+  public abstract class BluetoothGattServerCallbackExt {
+    ctor public BluetoothGattServerCallbackExt();
     method public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice, int, int, android.bluetooth.BluetoothGattCharacteristic);
     method public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice, int, android.bluetooth.BluetoothGattCharacteristic, boolean, boolean, int, byte[]);
     method public void onConnectionStateChange(android.bluetooth.BluetoothDevice, int, int);
@@ -7576,6 +7607,8 @@
     method public void onExecuteWrite(android.bluetooth.BluetoothDevice, int, boolean);
     method public void onMtuChanged(android.bluetooth.BluetoothDevice, int);
     method public void onNotificationSent(android.bluetooth.BluetoothDevice, int);
+    method public void onPhyRead(android.bluetooth.BluetoothDevice, int, int, int);
+    method public void onPhyUpdate(android.bluetooth.BluetoothDevice, int, int, int);
     method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
   }
 
@@ -7776,10 +7809,85 @@
     method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
   }
 
+  public final class AdvertisingSet {
+    method public void enableAdvertising(boolean, int);
+    method public void periodicAdvertisingEnable(boolean);
+    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 setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
+    method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
+  }
+
+  public abstract class AdvertisingSetCallback {
+    ctor public AdvertisingSetCallback();
+    method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
+    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
+    method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
+    method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
+    method public void onPeriodicAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
+    method public void onScanResponseDataSet(android.bluetooth.le.AdvertisingSet, int);
+    field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
+    field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; // 0x1
+    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
+    field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
+    field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
+    field public static final int ADVERTISE_SUCCESS = 0; // 0x0
+  }
+
+  public final class AdvertisingSetParameters implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getInterval();
+    method public int getPrimaryPhy();
+    method public int getSecondaryPhy();
+    method public int getTxPowerLevel();
+    method public boolean includeTxPower();
+    method public boolean isAnonymous();
+    method public boolean isConnectable();
+    method public boolean isLegacy();
+    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_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
+    field public static final int TX_POWER_MEDIUM = -7; // 0xfffffff9
+    field public static final int TX_POWER_MIN = -127; // 0xffffff81
+    field public static final int TX_POWER_ULTRA_LOW = -21; // 0xffffffeb
+  }
+
+  public static final class AdvertisingSetParameters.Builder {
+    ctor public AdvertisingSetParameters.Builder();
+    method public android.bluetooth.le.AdvertisingSetParameters build();
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setAnonymous(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setConnectable(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setIncludeTxPower(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
+  }
+
   public final class BluetoothLeAdvertiser {
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
+    method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
 
   public final class BluetoothLeScanner {
@@ -7792,6 +7900,53 @@
     method public void stopScan(android.bluetooth.le.ScanCallback);
   }
 
+  public abstract class PeriodicAdvertisingCallback {
+    ctor public PeriodicAdvertisingCallback();
+    method public void onPeriodicAdvertisingReport(android.bluetooth.le.PeriodicAdvertisingReport);
+    method public void onSyncEstablished(int, android.bluetooth.BluetoothDevice, int, int, int, int);
+    method public void onSyncLost(int);
+    field public static final int SYNC_NO_RESOURCES = 2; // 0x2
+    field public static final int SYNC_NO_RESPONSE = 1; // 0x1
+  }
+
+  public final class PeriodicAdvertisingManager {
+    method public void registerSync(android.bluetooth.le.ScanResult, int, int, android.bluetooth.le.PeriodicAdvertisingCallback);
+    method public void registerSync(android.bluetooth.le.ScanResult, int, int, android.bluetooth.le.PeriodicAdvertisingCallback, android.os.Handler);
+    method public void unregisterSync(android.bluetooth.le.PeriodicAdvertisingCallback);
+  }
+
+  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);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingParameters> CREATOR;
+  }
+
+  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);
+  }
+
+  public final class PeriodicAdvertisingReport implements android.os.Parcelable {
+    ctor public PeriodicAdvertisingReport(int, int, int, int, android.bluetooth.le.ScanRecord);
+    method public int describeContents();
+    method public android.bluetooth.le.ScanRecord getData();
+    method public int getDataStatus();
+    method public int getRssi();
+    method public int getSyncHandle();
+    method public long getTimestampNanos();
+    method public int getTxPower();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingReport> CREATOR;
+    field public static final int DATA_COMPLETE = 0; // 0x0
+    field public static final int DATA_INCOMPLETE_TRUNCATED = 2; // 0x2
+  }
+
   public final class ResultStorageDescriptor implements android.os.Parcelable {
     ctor public ResultStorageDescriptor(int, int, int);
     method public int describeContents();
@@ -7856,19 +8011,37 @@
   }
 
   public final class ScanResult implements android.os.Parcelable {
-    ctor public ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
+    ctor public deprecated ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
+    ctor public ScanResult(android.bluetooth.BluetoothDevice, int, int, int, int, int, int, int, android.bluetooth.le.ScanRecord, long);
     method public int describeContents();
+    method public int getAdvertisingSid();
+    method public int getDataStatus();
     method public android.bluetooth.BluetoothDevice getDevice();
+    method public int getPeriodicAdvertisingInterval();
+    method public int getPrimaryPhy();
     method public int getRssi();
     method public android.bluetooth.le.ScanRecord getScanRecord();
+    method public int getSecondaryPhy();
     method public long getTimestampNanos();
+    method public int getTxPower();
+    method public boolean isConnectable();
+    method public boolean isLegacy();
     method public void writeToParcel(android.os.Parcel, int);
     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 PHY_UNUSED = 0; // 0x0
+    field public static final int SID_NOT_PRESENT = 255; // 0xff
   }
 
   public final class ScanSettings implements android.os.Parcelable {
     method public int describeContents();
     method public int getCallbackType();
+    method public boolean getLegacy();
+    method public int getPhy();
     method public long getReportDelayMillis();
     method public int getScanMode();
     method public int getScanResultType();
@@ -7882,6 +8055,9 @@
     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
@@ -7894,8 +8070,10 @@
     ctor public ScanSettings.Builder();
     method public android.bluetooth.le.ScanSettings build();
     method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
+    method public android.bluetooth.le.ScanSettings.Builder setLegacy(boolean);
     method public android.bluetooth.le.ScanSettings.Builder setMatchMode(int);
     method public android.bluetooth.le.ScanSettings.Builder setNumOfMatches(int);
+    method public android.bluetooth.le.ScanSettings.Builder setPhy(int);
     method public android.bluetooth.le.ScanSettings.Builder setReportDelay(long);
     method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
     method public android.bluetooth.le.ScanSettings.Builder setScanResultType(int);
@@ -8521,6 +8699,7 @@
     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";
@@ -8894,6 +9073,7 @@
     field public static final java.lang.String ACTION_CALL = "android.intent.action.CALL";
     field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
     field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
+    field public static final java.lang.String ACTION_CARRIER_SETUP = "android.intent.action.CARRIER_SETUP";
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
@@ -10253,6 +10433,7 @@
     field public static final java.lang.String FEATURE_SIP = "android.software.sip";
     field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip";
     field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
+    field public static final java.lang.String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
     field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
     field public static final deprecated java.lang.String FEATURE_TELEVISION = "android.hardware.type.television";
@@ -18229,6 +18410,15 @@
     method public boolean isTransitionalDifferent();
   }
 
+  public final class ListFormatter {
+    method public java.lang.String format(java.lang.Object...);
+    method public java.lang.String format(java.util.Collection<?>);
+    method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+    method public static android.icu.text.ListFormatter getInstance();
+    method public java.lang.String getPatternForNumItems(int);
+  }
+
   public abstract class LocaleDisplayNames {
     method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
     method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -18238,6 +18428,8 @@
     method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
     method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
     method public abstract android.icu.util.ULocale getLocale();
+    method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+    method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
     method public abstract java.lang.String keyDisplayName(java.lang.String);
     method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
     method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -18257,9 +18449,19 @@
     enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
   }
 
+  public static class LocaleDisplayNames.UiListItem {
+    ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+    field public final android.icu.util.ULocale minimized;
+    field public final android.icu.util.ULocale modified;
+    field public final java.lang.String nameInDisplayLocale;
+    field public final java.lang.String nameInSelf;
+  }
+
   public class MeasureFormat extends android.icu.text.UFormat {
     method public final boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+    method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
     method public final java.lang.String formatMeasures(android.icu.util.Measure...);
     method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
     method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -18728,6 +18930,14 @@
     method public void setUpperCaseFirst(boolean);
   }
 
+  public final class ScientificNumberFormatter {
+    method public java.lang.String format(java.lang.Object);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+  }
+
   public abstract class SearchIterator {
     ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
     method public final int first();
@@ -19503,6 +19713,34 @@
     method public long getToDate();
   }
 
+  public final class EthiopicCalendar extends android.icu.util.CECalendar {
+    ctor public EthiopicCalendar();
+    ctor public EthiopicCalendar(android.icu.util.TimeZone);
+    ctor public EthiopicCalendar(java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.ULocale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+    ctor public EthiopicCalendar(int, int, int);
+    ctor public EthiopicCalendar(java.util.Date);
+    ctor public EthiopicCalendar(int, int, int, int, int, int);
+    method protected deprecated int handleGetExtendedYear();
+    method public boolean isAmeteAlemEra();
+    method public void setAmeteAlemEra(boolean);
+    field public static final int GENBOT = 8; // 0x8
+    field public static final int HAMLE = 10; // 0xa
+    field public static final int HEDAR = 2; // 0x2
+    field public static final int MEGABIT = 6; // 0x6
+    field public static final int MESKEREM = 0; // 0x0
+    field public static final int MIAZIA = 7; // 0x7
+    field public static final int NEHASSE = 11; // 0xb
+    field public static final int PAGUMEN = 12; // 0xc
+    field public static final int SENE = 9; // 0x9
+    field public static final int TAHSAS = 3; // 0x3
+    field public static final int TEKEMT = 1; // 0x1
+    field public static final int TER = 4; // 0x4
+    field public static final int YEKATIT = 5; // 0x5
+  }
+
   public abstract interface Freezable<T> implements java.lang.Cloneable {
     method public abstract T cloneAsThawed();
     method public abstract T freeze();
@@ -19878,6 +20116,8 @@
     field public static final int SHORT_COMMONLY_USED = 6; // 0x6
     field public static final int SHORT_GENERIC = 2; // 0x2
     field public static final int SHORT_GMT = 4; // 0x4
+    field public static final int TIMEZONE_ICU = 0; // 0x0
+    field public static final int TIMEZONE_JDK = 1; // 0x1
     field public static final android.icu.util.TimeZone UNKNOWN_ZONE;
     field public static final java.lang.String UNKNOWN_ZONE_ID = "Etc/Unknown";
   }
@@ -20031,6 +20271,35 @@
     enum_constant public static final android.icu.util.ULocale.Category FORMAT;
   }
 
+  public final class UniversalTimeScale {
+    method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+    method public static long from(long, int);
+    method public static long getTimeScaleValue(int, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+    method public static long toLong(long, int);
+    field public static final int DB2_TIME = 8; // 0x8
+    field public static final int DOTNET_DATE_TIME = 4; // 0x4
+    field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+    field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+    field public static final int EXCEL_TIME = 7; // 0x7
+    field public static final int FROM_MAX_VALUE = 3; // 0x3
+    field public static final int FROM_MIN_VALUE = 2; // 0x2
+    field public static final int ICU4C_TIME = 2; // 0x2
+    field public static final int JAVA_TIME = 0; // 0x0
+    field public static final int MAC_OLD_TIME = 5; // 0x5
+    field public static final int MAC_TIME = 6; // 0x6
+    field public static final int MAX_SCALE = 10; // 0xa
+    field public static final int TO_MAX_VALUE = 5; // 0x5
+    field public static final int TO_MIN_VALUE = 4; // 0x4
+    field public static final int UNITS_VALUE = 0; // 0x0
+    field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+    field public static final int UNIX_TIME = 1; // 0x1
+    field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+  }
+
   public abstract interface ValueIterator {
     method public abstract boolean next(android.icu.util.ValueIterator.Element);
     method public abstract void reset();
@@ -25331,7 +25600,9 @@
     method public static deprecated boolean isNetworkTypeValid(int);
     method public boolean isTetheringSupported();
     method public void registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback);
+    method public void registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
+    method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.app.PendingIntent);
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
@@ -25339,6 +25610,9 @@
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     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, int, 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, android.os.Handler);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
@@ -25392,6 +25666,7 @@
     method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties);
     method public void onLosing(android.net.Network, int);
     method public void onLost(android.net.Network);
+    method public void onUnavailable();
   }
 
   public static abstract interface ConnectivityManager.OnNetworkActiveListener {
@@ -25472,6 +25747,73 @@
     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 void applyTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
+    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 void removeTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(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 buildTunnelModeTransform(java.net.InetAddress, java.net.InetAddress);
+    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, 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();
@@ -25759,9 +26101,14 @@
   public final class RecommendationRequest implements android.os.Parcelable {
     ctor protected RecommendationRequest(android.os.Parcel);
     method public int describeContents();
-    method public android.net.wifi.WifiConfiguration getCurrentSelectedConfig();
-    method public android.net.NetworkCapabilities getRequiredCapabilities();
+    method public android.net.wifi.WifiConfiguration[] getConnectableConfigs();
+    method public android.net.wifi.WifiConfiguration getConnectedConfig();
+    method public android.net.wifi.WifiConfiguration getDefaultWifiConfig();
+    method public int getLastSelectedNetworkId();
+    method public long getLastSelectedNetworkTimestamp();
     method public android.net.wifi.ScanResult[] getScanResults();
+    method public void setConnectableConfigs(android.net.wifi.WifiConfiguration[]);
+    method public void setConnectedConfig(android.net.wifi.WifiConfiguration);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.RecommendationRequest> CREATOR;
   }
@@ -25769,8 +26116,10 @@
   public static final class RecommendationRequest.Builder {
     ctor public RecommendationRequest.Builder();
     method public android.net.RecommendationRequest build();
-    method public android.net.RecommendationRequest.Builder setCurrentRecommendedWifiConfig(android.net.wifi.WifiConfiguration);
-    method public android.net.RecommendationRequest.Builder setNetworkCapabilities(android.net.NetworkCapabilities);
+    method public android.net.RecommendationRequest.Builder setConnectableConfigs(android.net.wifi.WifiConfiguration[]);
+    method public android.net.RecommendationRequest.Builder setConnectedWifiConfig(android.net.wifi.WifiConfiguration);
+    method public android.net.RecommendationRequest.Builder setDefaultWifiConfig(android.net.wifi.WifiConfiguration);
+    method public android.net.RecommendationRequest.Builder setLastSelectedNetwork(int, long);
     method public android.net.RecommendationRequest.Builder setScanResults(android.net.wifi.ScanResult[]);
   }
 
@@ -26620,6 +26969,16 @@
     field public boolean truncated;
   }
 
+  public final class IconInfo implements android.os.Parcelable {
+    ctor public IconInfo(java.lang.String, byte[]);
+    ctor public IconInfo(android.net.wifi.IconInfo);
+    method public int describeContents();
+    method public byte[] getData();
+    method public java.lang.String getFilename();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.IconInfo> CREATOR;
+  }
+
   public class RttManager {
     method public void disableResponder(android.net.wifi.RttManager.ResponderCallback);
     method public void enableResponder(android.net.wifi.RttManager.ResponderCallback);
@@ -26884,6 +27243,7 @@
     field public int creatorUid;
     field public android.net.wifi.WifiEnterpriseConfig enterpriseConfig;
     field public boolean hiddenSSID;
+    field public boolean isHomeProviderNetwork;
     field public java.lang.String lastUpdateName;
     field public int lastUpdateUid;
     field public boolean meteredHint;
@@ -26892,7 +27252,7 @@
     field public int numScorerOverride;
     field public int numScorerOverrideAndSwitchedNetwork;
     field public java.lang.String preSharedKey;
-    field public int priority;
+    field public deprecated int priority;
     field public java.lang.String providerFriendlyName;
     field public long[] roamingConsortiumIds;
     field public int status;
@@ -26974,6 +27334,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -26987,6 +27348,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -27012,11 +27374,14 @@
   }
 
   public static final class WifiEnterpriseConfig.Phase2 {
+    field public static final int AKA = 6; // 0x6
+    field public static final int AKA_PRIME = 7; // 0x7
     field public static final int GTC = 4; // 0x4
     field public static final int MSCHAP = 2; // 0x2
     field public static final int MSCHAPV2 = 3; // 0x3
     field public static final int NONE = 0; // 0x0
     field public static final int PAP = 1; // 0x1
+    field public static final int SIM = 5; // 0x5
   }
 
   public class WifiInfo implements android.os.Parcelable {
@@ -27039,6 +27404,7 @@
 
   public class WifiManager {
     method public int addNetwork(android.net.wifi.WifiConfiguration);
+    method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method public static int calculateSignalLevel(int, int);
     method public void cancelWps(android.net.wifi.WifiManager.WpsCallback);
     method public static int compareSignalLevel(int, int);
@@ -27054,6 +27420,7 @@
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
     method public android.net.DhcpInfo getDhcpInfo();
+    method public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
     method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
@@ -27072,11 +27439,13 @@
     method public boolean isWifiApEnabled();
     method public boolean isWifiEnabled();
     method public boolean isWifiScannerSupported();
-    method public boolean pingSupplicant();
+    method public deprecated boolean pingSupplicant();
+    method public void queryPasspointIcon(long, java.lang.String);
     method public boolean reassociate();
     method public boolean reconnect();
     method public boolean removeNetwork(int);
-    method public boolean saveConfiguration();
+    method public void removePasspointConfiguration(java.lang.String);
+    method public deprecated boolean saveConfiguration();
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
@@ -27087,6 +27456,10 @@
     method public boolean startScan(android.os.WorkSource);
     method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
     method public int updateNetwork(android.net.wifi.WifiConfiguration);
+    field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+    field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+    field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+    field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
     field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
     field public static final int CHANGE_REASON_ADDED = 0; // 0x0
@@ -27094,8 +27467,13 @@
     field public static final int CHANGE_REASON_REMOVED = 1; // 0x1
     field public static final java.lang.String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
     field public static final int ERROR_AUTHENTICATING = 1; // 0x1
+    field public static final java.lang.String EXTRA_ANQP_ELEMENT_DATA = "android.net.wifi.extra.ANQP_ELEMENT_DATA";
     field public static final java.lang.String EXTRA_BSSID = "bssid";
+    field public static final java.lang.String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG";
     field public static final java.lang.String EXTRA_CHANGE_REASON = "changeReason";
+    field public static final java.lang.String EXTRA_DELAY = "android.net.wifi.extra.DELAY";
+    field public static final java.lang.String EXTRA_ESS = "android.net.wifi.extra.ESS";
+    field public static final java.lang.String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO";
     field public static final java.lang.String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
     field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
     field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
@@ -27103,8 +27481,10 @@
     field public static final java.lang.String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
     field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
     field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+    field public static final java.lang.String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD";
     field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
     field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError";
+    field public static final java.lang.String EXTRA_URL = "android.net.wifi.extra.URL";
     field public static final java.lang.String EXTRA_WIFI_AP_STATE = "wifi_state";
     field public static final java.lang.String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
     field public static final java.lang.String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et";
@@ -27345,10 +27725,10 @@
   }
 
   public class DiscoverySession {
-    method public java.lang.String createNetworkSpecifier(android.net.wifi.aware.PeerHandle, byte[]);
+    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 void destroy();
-    method public static int getMaxSendRetryCount();
-    method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[], int);
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
 
@@ -27435,7 +27815,9 @@
   }
 
   public class WifiAwareSession {
-    method public java.lang.String createNetworkSpecifier(int, byte[], byte[]);
+    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 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);
@@ -27443,6 +27825,232 @@
 
 }
 
+package android.net.wifi.hotspot2 {
+
+  public final class ConfigParser {
+    method public static android.net.wifi.hotspot2.PasspointConfiguration parsePasspointConfig(java.lang.String, byte[]);
+  }
+
+  public final class PasspointConfiguration implements android.os.Parcelable {
+    ctor public PasspointConfiguration();
+    ctor public PasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
+    method public int describeContents();
+    method public android.net.wifi.hotspot2.pps.Credential getCredential();
+    method public int getCredentialPriority();
+    method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+    method public android.net.wifi.hotspot2.pps.Policy getPolicy();
+    method public long getSubscriptionCreationTimeInMs();
+    method public long getSubscriptionExpirationTimeInMs();
+    method public java.lang.String getSubscriptionType();
+    method public android.net.wifi.hotspot2.pps.UpdateParameter getSubscriptionUpdate();
+    method public java.util.Map<java.lang.String, byte[]> getTrustRootCertList();
+    method public int getUpdateIdentifier();
+    method public long getUsageLimitDataLimit();
+    method public long getUsageLimitStartTimeInMs();
+    method public long getUsageLimitTimeLimitInMinutes();
+    method public long getUsageLimitUsageTimePeriodInMinutes();
+    method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
+    method public void setCredentialPriority(int);
+    method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
+    method public void setPolicy(android.net.wifi.hotspot2.pps.Policy);
+    method public void setSubscriptionCreationTimeInMs(long);
+    method public void setSubscriptionExpirationTimeInMs(long);
+    method public void setSubscriptionType(java.lang.String);
+    method public void setSubscriptionUpdate(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public void setTrustRootCertList(java.util.Map<java.lang.String, byte[]>);
+    method public void setUpdateIdentifier(int);
+    method public void setUsageLimitDataLimit(long);
+    method public void setUsageLimitStartTimeInMs(long);
+    method public void setUsageLimitTimeLimitInMinutes(long);
+    method public void setUsageLimitUsageTimePeriodInMinutes(long);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR;
+  }
+
+}
+
+package android.net.wifi.hotspot2.omadm {
+
+  public final class PpsMoParser {
+    method public static android.net.wifi.hotspot2.PasspointConfiguration parseMoText(java.lang.String);
+  }
+
+}
+
+package android.net.wifi.hotspot2.pps {
+
+  public final class Credential implements android.os.Parcelable {
+    ctor public Credential();
+    ctor public Credential(android.net.wifi.hotspot2.pps.Credential);
+    method public int describeContents();
+    method public java.security.cert.X509Certificate getCaCertificate();
+    method public android.net.wifi.hotspot2.pps.Credential.CertificateCredential getCertCredential();
+    method public boolean getCheckAaaServerCertStatus();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
+    method public java.security.PrivateKey getClientPrivateKey();
+    method public long getCreationTimeInMs();
+    method public long getExpirationTimeInMs();
+    method public java.lang.String getRealm();
+    method public android.net.wifi.hotspot2.pps.Credential.SimCredential getSimCredential();
+    method public android.net.wifi.hotspot2.pps.Credential.UserCredential getUserCredential();
+    method public void setCaCertificate(java.security.cert.X509Certificate);
+    method public void setCertCredential(android.net.wifi.hotspot2.pps.Credential.CertificateCredential);
+    method public void setCheckAaaServerCertStatus(boolean);
+    method public void setClientCertificateChain(java.security.cert.X509Certificate[]);
+    method public void setClientPrivateKey(java.security.PrivateKey);
+    method public void setCreationTimeInMs(long);
+    method public void setExpirationTimeInMs(long);
+    method public void setRealm(java.lang.String);
+    method public void setSimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential);
+    method public void setUserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential> CREATOR;
+  }
+
+  public static final class Credential.CertificateCredential implements android.os.Parcelable {
+    ctor public Credential.CertificateCredential();
+    ctor public Credential.CertificateCredential(android.net.wifi.hotspot2.pps.Credential.CertificateCredential);
+    method public int describeContents();
+    method public byte[] getCertSha256Fingerprint();
+    method public java.lang.String getCertType();
+    method public void setCertSha256Fingerprint(byte[]);
+    method public void setCertType(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.CertificateCredential> CREATOR;
+  }
+
+  public static final class Credential.SimCredential implements android.os.Parcelable {
+    ctor public Credential.SimCredential();
+    ctor public Credential.SimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential);
+    method public int describeContents();
+    method public int getEapType();
+    method public java.lang.String getImsi();
+    method public void setEapType(int);
+    method public void setImsi(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.SimCredential> CREATOR;
+  }
+
+  public static final class Credential.UserCredential implements android.os.Parcelable {
+    ctor public Credential.UserCredential();
+    ctor public Credential.UserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential);
+    method public int describeContents();
+    method public boolean getAbleToShare();
+    method public int getEapType();
+    method public boolean getMachineManaged();
+    method public java.lang.String getNonEapInnerMethod();
+    method public java.lang.String getPassword();
+    method public java.lang.String getSoftTokenApp();
+    method public java.lang.String getUsername();
+    method public void setAbleToShare(boolean);
+    method public void setEapType(int);
+    method public void setMachineManaged(boolean);
+    method public void setNonEapInnerMethod(java.lang.String);
+    method public void setPassword(java.lang.String);
+    method public void setSoftTokenApp(java.lang.String);
+    method public void setUsername(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.UserCredential> CREATOR;
+  }
+
+  public final class HomeSp implements android.os.Parcelable {
+    ctor public HomeSp();
+    ctor public HomeSp(android.net.wifi.hotspot2.pps.HomeSp);
+    method public int describeContents();
+    method public java.lang.String getFqdn();
+    method public java.lang.String getFriendlyName();
+    method public java.util.Map<java.lang.String, java.lang.Long> getHomeNetworkIds();
+    method public java.lang.String getIconUrl();
+    method public long[] getMatchAllOis();
+    method public long[] getMatchAnyOis();
+    method public java.lang.String[] getOtherHomePartners();
+    method public long[] getRoamingConsortiumOis();
+    method public void setFqdn(java.lang.String);
+    method public void setFriendlyName(java.lang.String);
+    method public void setHomeNetworkIds(java.util.Map<java.lang.String, java.lang.Long>);
+    method public void setIconUrl(java.lang.String);
+    method public void setMatchAllOis(long[]);
+    method public void setMatchAnyOis(long[]);
+    method public void setOtherHomePartners(java.lang.String[]);
+    method public void setRoamingConsortiumOis(long[]);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR;
+  }
+
+  public final class Policy implements android.os.Parcelable {
+    ctor public Policy();
+    ctor public Policy(android.net.wifi.hotspot2.pps.Policy);
+    method public int describeContents();
+    method public java.lang.String[] getExcludedSsidList();
+    method public int getMaximumBssLoadValue();
+    method public long getMinHomeDownlinkBandwidth();
+    method public long getMinHomeUplinkBandwidth();
+    method public long getMinRoamingDownlinkBandwidth();
+    method public long getMinRoamingUplinkBandwidth();
+    method public android.net.wifi.hotspot2.pps.UpdateParameter getPolicyUpdate();
+    method public java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> getPreferredRoamingPartnerList();
+    method public java.util.Map<java.lang.Integer, java.lang.String> getRequiredProtoPortMap();
+    method public void setExcludedSsidList(java.lang.String[]);
+    method public void setMaximumBssLoadValue(int);
+    method public void setMinHomeDownlinkBandwidth(long);
+    method public void setMinHomeUplinkBandwidth(long);
+    method public void setMinRoamingDownlinkBandwidth(long);
+    method public void setMinRoamingUplinkBandwidth(long);
+    method public void setPolicyUpdate(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public void setPreferredRoamingPartnerList(java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner>);
+    method public void setRequiredProtoPortMap(java.util.Map<java.lang.Integer, java.lang.String>);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy> CREATOR;
+  }
+
+  public static final class Policy.RoamingPartner implements android.os.Parcelable {
+    ctor public Policy.RoamingPartner();
+    ctor public Policy.RoamingPartner(android.net.wifi.hotspot2.pps.Policy.RoamingPartner);
+    method public int describeContents();
+    method public java.lang.String getCountries();
+    method public java.lang.String getFqdn();
+    method public boolean getFqdnExactMatch();
+    method public int getPriority();
+    method public void setCountries(java.lang.String);
+    method public void setFqdn(java.lang.String);
+    method public void setFqdnExactMatch(boolean);
+    method public void setPriority(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> CREATOR;
+  }
+
+  public final class UpdateParameter implements android.os.Parcelable {
+    ctor public UpdateParameter();
+    ctor public UpdateParameter(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public int describeContents();
+    method public java.lang.String getBase64EncodedPassword();
+    method public java.lang.String getRestriction();
+    method public java.lang.String getServerUri();
+    method public byte[] getTrustRootCertSha256Fingerprint();
+    method public java.lang.String getTrustRootCertUrl();
+    method public long getUpdateIntervalInMinutes();
+    method public java.lang.String getUpdateMethod();
+    method public java.lang.String getUsername();
+    method public void setBase64EncodedPassword(java.lang.String);
+    method public void setRestriction(java.lang.String);
+    method public void setServerUri(java.lang.String);
+    method public void setTrustRootCertSha256Fingerprint(byte[]);
+    method public void setTrustRootCertUrl(java.lang.String);
+    method public void setUpdateIntervalInMinutes(long);
+    method public void setUpdateMethod(java.lang.String);
+    method public void setUsername(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.UpdateParameter> CREATOR;
+    field public static final long UPDATE_CHECK_INTERVAL_NEVER = 4294967295L; // 0xffffffffL
+    field public static final java.lang.String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    field public static final java.lang.String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+    field public static final java.lang.String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    field public static final java.lang.String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    field public static final java.lang.String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+  }
+
+}
+
 package android.net.wifi.p2p {
 
   public class WifiP2pConfig implements android.os.Parcelable {
@@ -35480,6 +36088,7 @@
     field public static final java.lang.String BOOT_COUNT = "boot_count";
     field public static final java.lang.String CONTACT_METADATA_SYNC_ENABLED = "contact_metadata_sync_enabled";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final java.lang.String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
     field public static final java.lang.String DATA_ROAMING = "data_roaming";
     field public static final java.lang.String DEBUG_APP = "debug_app";
     field public static final java.lang.String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
@@ -39191,9 +39800,11 @@
     method public android.telecom.Call.Details getDetails();
     method public android.telecom.Call getParent();
     method public java.lang.String getRemainingPostDialSequence();
+    method public android.telecom.Call.RttCall getRttCall();
     method public int getState();
     method public android.telecom.InCallService.VideoCall getVideoCall();
     method public void hold();
+    method public boolean isRttActive();
     method public void mergeConference();
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
@@ -39206,9 +39817,12 @@
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
     method public deprecated void removeListener(android.telecom.Call.Listener);
+    method public void respondToRttRequest(int, boolean);
     method public void sendCallEvent(java.lang.String, android.os.Bundle);
+    method public void sendRttRequest();
     method public void splitFromConference();
     method public void stopDtmfTone();
+    method public void stopRtt();
     method public void swapConference();
     method public void unhold();
     method public void unregisterCallback(android.telecom.Call.Callback);
@@ -39236,6 +39850,10 @@
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
+    method public void onRttInitiationFailure(android.telecom.Call, int);
+    method public void onRttModeChanged(android.telecom.Call, int);
+    method public void onRttRequest(android.telecom.Call, int);
+    method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
     method public void onStateChanged(android.telecom.Call, int);
     method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall);
   }
@@ -39286,6 +39904,7 @@
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
+    field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
 
@@ -39293,6 +39912,16 @@
     ctor public Call.Listener();
   }
 
+  public static final class Call.RttCall {
+    method public int getRttAudioMode();
+    method public java.lang.String read();
+    method public void setRttMode(int);
+    method public void write(java.lang.String) throws java.io.IOException;
+    field public static final int RTT_MODE_FULL = 1; // 0x1
+    field public static final int RTT_MODE_HCO = 2; // 0x2
+    field public static final int RTT_MODE_VCO = 3; // 0x3
+  }
+
   public final class CallAudioState implements android.os.Parcelable {
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
@@ -39499,6 +40128,15 @@
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
+  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
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+    field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
+  }
+
   public static abstract class Connection.VideoProvider {
     ctor public Connection.VideoProvider();
     method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
@@ -39519,6 +40157,7 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
     method public void setCallDataUsage(long);
     field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
+    field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
     field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
     field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
     field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
@@ -39555,9 +40194,9 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConference(android.telecom.Connection, android.telecom.Connection);
     method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
+    method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
+    method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
     method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -39794,6 +40433,7 @@
     field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
     field public static final int CAPABILITY_MULTI_USER = 32; // 0x20
     field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
+    field public static final int CAPABILITY_RTT = 4096; // 0x1000
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
@@ -40033,6 +40673,7 @@
     method public boolean handleMmi(java.lang.String);
     method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
     method public boolean isInCall();
+    method public boolean isInManagedCall();
     method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
     method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
     method public boolean isRinging();
@@ -40047,7 +40688,7 @@
     field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
     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 java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+    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";
@@ -40070,11 +40711,13 @@
     field public static final java.lang.String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
     field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
     field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
+    field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
     field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
     field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+    field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -40164,6 +40807,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+    field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
@@ -40224,6 +40868,7 @@
     field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
     field public static final java.lang.String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+    field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
     field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
@@ -40574,7 +41219,9 @@
     method public void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
     method public void sendMultimediaMessage(android.content.Context, android.net.Uri, java.lang.String, android.os.Bundle, android.app.PendingIntent);
     method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
+    method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+    method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
     field public static final java.lang.String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
     field public static final java.lang.String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
     field public static final java.lang.String MMS_CONFIG_ALIAS_ENABLED = "aliasEnabled";
@@ -40762,6 +41409,7 @@
     method public void enableVideoCalling(boolean);
     method public boolean endCall();
     method public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+    method public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int);
     method public int getCallState();
     method public android.os.PersistableBundle getCarrierConfig();
     method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent);
@@ -40781,6 +41429,7 @@
     method public java.lang.String getDeviceId();
     method public java.lang.String getDeviceId(int);
     method public java.lang.String getDeviceSoftwareVersion();
+    method public java.lang.String[] getForbiddenPlmns();
     method public java.lang.String getGroupIdLevel1();
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
     method public java.lang.String getImei();
@@ -40835,6 +41484,7 @@
     method public void listen(android.telephony.PhoneStateListener, int);
     method public boolean needsOtaServiceProvisioning();
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
+    method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
     method public void setDataEnabled(boolean);
     method public void setDataEnabled(int, boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
@@ -41039,6 +41689,15 @@
 
 }
 
+package android.telephony.ims {
+
+  public class ImsServiceBase extends android.app.Service {
+    ctor public ImsServiceBase();
+    method public android.os.IBinder onBind(android.content.Intent);
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -41631,7 +42290,7 @@
 
 package android.test.suitebuilder {
 
-  public class TestMethod {
+  public deprecated class TestMethod {
     ctor public TestMethod(java.lang.reflect.Method, java.lang.Class<? extends junit.framework.TestCase>);
     ctor public TestMethod(java.lang.String, java.lang.Class<? extends junit.framework.TestCase>);
     ctor public TestMethod(junit.framework.TestCase);
@@ -41642,7 +42301,7 @@
     method public java.lang.String getName();
   }
 
-  public class TestSuiteBuilder {
+  public deprecated class TestSuiteBuilder {
     ctor public TestSuiteBuilder(java.lang.Class);
     ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader);
     method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>);
@@ -41655,7 +42314,7 @@
     method public android.test.suitebuilder.TestSuiteBuilder named(java.lang.String);
   }
 
-  public static class TestSuiteBuilder.FailedToCreateTests extends junit.framework.TestCase {
+  public static deprecated class TestSuiteBuilder.FailedToCreateTests extends junit.framework.TestCase {
     ctor public TestSuiteBuilder.FailedToCreateTests(java.lang.Exception);
     method public void testSuiteConstructionFailed();
   }
@@ -52382,7 +53041,7 @@
 
 package com.android.internal.util {
 
-  public abstract interface Predicate<T> {
+  public abstract deprecated interface Predicate<T> {
     method public abstract boolean apply(T);
   }
 
@@ -52520,6 +53179,8 @@
     field public static final int OP_INT_TO_FLOAT = 130; // 0x82
     field public static final int OP_INT_TO_LONG = 129; // 0x81
     field public static final int OP_INT_TO_SHORT = 143; // 0x8f
+    field public static final int OP_INVOKE_CUSTOM = 252; // 0xfc
+    field public static final int OP_INVOKE_CUSTOM_RANGE = 253; // 0xfd
     field public static final int OP_INVOKE_DIRECT = 112; // 0x70
     field public static final deprecated int OP_INVOKE_DIRECT_EMPTY = 240; // 0xf0
     field public static final int OP_INVOKE_DIRECT_JUMBO = 9471; // 0x24ff
@@ -52527,6 +53188,8 @@
     field public static final int OP_INVOKE_INTERFACE = 114; // 0x72
     field public static final int OP_INVOKE_INTERFACE_JUMBO = 9983; // 0x26ff
     field public static final int OP_INVOKE_INTERFACE_RANGE = 120; // 0x78
+    field public static final int OP_INVOKE_POLYMORPHIC = 250; // 0xfa
+    field public static final int OP_INVOKE_POLYMORPHIC_RANGE = 251; // 0xfb
     field public static final int OP_INVOKE_STATIC = 113; // 0x71
     field public static final int OP_INVOKE_STATIC_JUMBO = 9727; // 0x25ff
     field public static final int OP_INVOKE_STATIC_RANGE = 119; // 0x77
@@ -52708,7 +53371,7 @@
     method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
   }
 
-  public final class InMemoryDexClassLoader extends java.lang.ClassLoader {
+  public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
     ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
   }
 
@@ -53926,6 +54589,13 @@
     field public static final java.lang.Class<java.lang.Boolean> TYPE;
   }
 
+  public class BootstrapMethodError extends java.lang.LinkageError {
+    ctor public BootstrapMethodError();
+    ctor public BootstrapMethodError(java.lang.String);
+    ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+    ctor public BootstrapMethodError(java.lang.Throwable);
+  }
+
   public final class Byte extends java.lang.Number implements java.lang.Comparable {
     ctor public Byte(byte);
     ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -55812,6 +56482,21 @@
 
 package java.lang.invoke {
 
+  public abstract class CallSite {
+    method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+    method public abstract java.lang.invoke.MethodHandle getTarget();
+    method public abstract void setTarget(java.lang.invoke.MethodHandle);
+    method public java.lang.invoke.MethodType type();
+  }
+
+  public class ConstantCallSite extends java.lang.invoke.CallSite {
+    ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+    ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public final void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class LambdaConversionException extends java.lang.Exception {
     ctor public LambdaConversionException();
     ctor public LambdaConversionException(java.lang.String);
@@ -55821,12 +56506,15 @@
   }
 
   public abstract class MethodHandle {
+    method public java.lang.invoke.MethodHandle asCollector(java.lang.Class<?>, int);
     method public java.lang.invoke.MethodHandle asFixedArity();
+    method public java.lang.invoke.MethodHandle asSpreader(java.lang.Class<?>, int);
     method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
     method public java.lang.invoke.MethodHandle asVarargsCollector(java.lang.Class<?>);
     method public java.lang.invoke.MethodHandle bindTo(java.lang.Object);
     method public final java.lang.Object invoke(java.lang.Object...) throws java.lang.Throwable;
     method public final java.lang.Object invokeExact(java.lang.Object...) throws java.lang.Throwable;
+    method public java.lang.Object invokeWithArguments(java.lang.Object...) throws java.lang.Throwable;
     method public java.lang.Object invokeWithArguments(java.util.List<?>) throws java.lang.Throwable;
     method public boolean isVarargsCollector();
     method public java.lang.invoke.MethodType type();
@@ -55860,17 +56548,23 @@
     method public static java.lang.invoke.MethodHandle arrayElementGetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
     method public static java.lang.invoke.MethodHandle arrayElementSetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
     method public static java.lang.invoke.MethodHandle catchException(java.lang.invoke.MethodHandle, java.lang.Class<? extends java.lang.Throwable>, java.lang.invoke.MethodHandle);
+    method public static java.lang.invoke.MethodHandle collectArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle constant(java.lang.Class<?>, java.lang.Object);
     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 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);
     method public static java.lang.invoke.MethodHandle guardWithTest(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle identity(java.lang.Class<?>);
+    method public static java.lang.invoke.MethodHandle insertArguments(java.lang.invoke.MethodHandle, int, java.lang.Object...);
     method public static java.lang.invoke.MethodHandle invoker(java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandles.Lookup lookup();
     method public static java.lang.invoke.MethodHandle permuteArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType, int...);
     method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
+    method public static <T extends java.lang.reflect.Member> T reflectAs(java.lang.Class<T>, java.lang.invoke.MethodHandle);
+    method public static java.lang.invoke.MethodHandle spreadInvoker(java.lang.invoke.MethodType, int);
     method public static java.lang.invoke.MethodHandle throwException(java.lang.Class<?>, java.lang.Class<? extends java.lang.Throwable>);
   }
 
@@ -55887,7 +56581,7 @@
     method public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?>);
     method public java.lang.Class<?> lookupClass();
     method public int lookupModes();
-    method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
+    method public java.lang.invoke.MethodHandleInfo revealDirect(java.lang.invoke.MethodHandle);
     method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
     method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
     method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
@@ -55930,6 +56624,22 @@
     method public java.lang.invoke.MethodType wrap();
   }
 
+  public class MutableCallSite extends java.lang.invoke.CallSite {
+    ctor public MutableCallSite(java.lang.invoke.MethodType);
+    ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
+  public class VolatileCallSite extends java.lang.invoke.CallSite {
+    ctor public VolatileCallSite(java.lang.invoke.MethodType);
+    ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class WrongMethodTypeException extends java.lang.RuntimeException {
     ctor public WrongMethodTypeException();
     ctor public WrongMethodTypeException(java.lang.String);
@@ -64218,6 +64928,9 @@
     method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
     method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
     method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+    method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
     method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
     method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
     method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -64228,7 +64941,11 @@
     method public static final <T> java.util.List<T> emptyList();
     method public static <T> java.util.ListIterator<T> emptyListIterator();
     method public static final <K, V> java.util.Map<K, V> emptyMap();
+    method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+    method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
     method public static final <T> java.util.Set<T> emptySet();
+    method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+    method public static <E> java.util.SortedSet<E> emptySortedSet();
     method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
     method public static <T> void fill(java.util.List<? super T>, T);
     method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -64257,12 +64974,16 @@
     method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
     method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
     method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+    method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+    method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
     method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
     method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
     method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
     method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
     method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+    method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+    method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
     method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
     method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/api/test-current.txt b/api/test-current.txt
index e1a4a06..b383c22 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6703,7 +6703,9 @@
     method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
     method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
     method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter();
+    method public int getLeMaximumAdvertisingDataLength();
     method public java.lang.String getName();
+    method public android.bluetooth.le.PeriodicAdvertisingManager getPeriodicAdvertisingManager();
     method public int getProfileConnectionState(int);
     method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
     method public android.bluetooth.BluetoothDevice getRemoteDevice(java.lang.String);
@@ -6712,6 +6714,10 @@
     method public int getState();
     method public boolean isDiscovering();
     method public boolean isEnabled();
+    method public boolean isLe2MPhySupported();
+    method public boolean isLeCodedPhySupported();
+    method public boolean isLeExtendedAdvertisingSupported();
+    method public boolean isLePeriodicAdvertisingSupported();
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
@@ -7080,6 +7086,9 @@
   public final class BluetoothDevice implements android.os.Parcelable {
     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.BluetoothGattCallbackExt);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallbackExt, int);
+    method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallbackExt, int, int);
     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;
@@ -7123,6 +7132,13 @@
     field public static final java.lang.String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
     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_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_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
     field public static final int TRANSPORT_AUTO = 0; // 0x0
     field public static final int TRANSPORT_BREDR = 1; // 0x1
     field public static final int TRANSPORT_LE = 2; // 0x2
@@ -7145,10 +7161,12 @@
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
     method public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method public void readPhy();
     method public boolean readRemoteRssi();
     method public boolean requestConnectionPriority(int);
     method public boolean requestMtu(int);
     method public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method public void setPreferredPhy(int, int, int);
     method public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
     method public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
     field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
@@ -7166,8 +7184,12 @@
     field public static final int GATT_WRITE_NOT_PERMITTED = 3; // 0x3
   }
 
-  public abstract class BluetoothGattCallback {
+  public abstract deprecated class BluetoothGattCallback extends android.bluetooth.BluetoothGattCallbackExt {
     ctor public BluetoothGattCallback();
+  }
+
+  public abstract class BluetoothGattCallbackExt {
+    ctor public BluetoothGattCallbackExt();
     method public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
     method public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
     method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
@@ -7175,6 +7197,8 @@
     method public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onMtuChanged(android.bluetooth.BluetoothGatt, int, int);
+    method public void onPhyRead(android.bluetooth.BluetoothGatt, int, int, int);
+    method public void onPhyUpdate(android.bluetooth.BluetoothGatt, int, int, int);
     method public void onReadRemoteRssi(android.bluetooth.BluetoothGatt, int, int);
     method public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt, int);
     method public void onServicesDiscovered(android.bluetooth.BluetoothGatt, int);
@@ -7268,12 +7292,18 @@
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method public void readPhy(android.bluetooth.BluetoothDevice);
     method public boolean removeService(android.bluetooth.BluetoothGattService);
     method public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
+    method public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
   }
 
-  public abstract class BluetoothGattServerCallback {
+  public abstract deprecated class BluetoothGattServerCallback extends android.bluetooth.BluetoothGattServerCallbackExt {
     ctor public BluetoothGattServerCallback();
+  }
+
+  public abstract class BluetoothGattServerCallbackExt {
+    ctor public BluetoothGattServerCallbackExt();
     method public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice, int, int, android.bluetooth.BluetoothGattCharacteristic);
     method public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice, int, android.bluetooth.BluetoothGattCharacteristic, boolean, boolean, int, byte[]);
     method public void onConnectionStateChange(android.bluetooth.BluetoothDevice, int, int);
@@ -7282,6 +7312,8 @@
     method public void onExecuteWrite(android.bluetooth.BluetoothDevice, int, boolean);
     method public void onMtuChanged(android.bluetooth.BluetoothDevice, int);
     method public void onNotificationSent(android.bluetooth.BluetoothDevice, int);
+    method public void onPhyRead(android.bluetooth.BluetoothDevice, int, int, int);
+    method public void onPhyUpdate(android.bluetooth.BluetoothDevice, int, int, int);
     method public void onServiceAdded(int, android.bluetooth.BluetoothGattService);
   }
 
@@ -7482,10 +7514,85 @@
     method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
   }
 
+  public final class AdvertisingSet {
+    method public void enableAdvertising(boolean, int);
+    method public void periodicAdvertisingEnable(boolean);
+    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 setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
+    method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
+  }
+
+  public abstract class AdvertisingSetCallback {
+    ctor public AdvertisingSetCallback();
+    method public void onAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingEnabled(android.bluetooth.le.AdvertisingSet, boolean, int);
+    method public void onAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingSetStarted(android.bluetooth.le.AdvertisingSet, int);
+    method public void onAdvertisingSetStopped(android.bluetooth.le.AdvertisingSet);
+    method public void onPeriodicAdvertisingDataSet(android.bluetooth.le.AdvertisingSet, int);
+    method public void onPeriodicAdvertisingEnable(android.bluetooth.le.AdvertisingSet, boolean, int);
+    method public void onPeriodicAdvertisingParametersUpdated(android.bluetooth.le.AdvertisingSet, int);
+    method public void onScanResponseDataSet(android.bluetooth.le.AdvertisingSet, int);
+    field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
+    field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; // 0x1
+    field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
+    field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
+    field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
+    field public static final int ADVERTISE_SUCCESS = 0; // 0x0
+  }
+
+  public final class AdvertisingSetParameters implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getInterval();
+    method public int getPrimaryPhy();
+    method public int getSecondaryPhy();
+    method public int getTxPowerLevel();
+    method public boolean includeTxPower();
+    method public boolean isAnonymous();
+    method public boolean isConnectable();
+    method public boolean isLegacy();
+    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_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
+    field public static final int TX_POWER_MEDIUM = -7; // 0xfffffff9
+    field public static final int TX_POWER_MIN = -127; // 0xffffff81
+    field public static final int TX_POWER_ULTRA_LOW = -21; // 0xffffffeb
+  }
+
+  public static final class AdvertisingSetParameters.Builder {
+    ctor public AdvertisingSetParameters.Builder();
+    method public android.bluetooth.le.AdvertisingSetParameters build();
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setAnonymous(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setConnectable(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setIncludeTxPower(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setInterval(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setLegacyMode(boolean);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setPrimaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setSecondaryPhy(int);
+    method public android.bluetooth.le.AdvertisingSetParameters.Builder setTxPowerLevel(int);
+  }
+
   public final class BluetoothLeAdvertiser {
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
     method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback);
+    method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
     method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
+    method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
   }
 
   public final class BluetoothLeScanner {
@@ -7495,6 +7602,53 @@
     method public void stopScan(android.bluetooth.le.ScanCallback);
   }
 
+  public abstract class PeriodicAdvertisingCallback {
+    ctor public PeriodicAdvertisingCallback();
+    method public void onPeriodicAdvertisingReport(android.bluetooth.le.PeriodicAdvertisingReport);
+    method public void onSyncEstablished(int, android.bluetooth.BluetoothDevice, int, int, int, int);
+    method public void onSyncLost(int);
+    field public static final int SYNC_NO_RESOURCES = 2; // 0x2
+    field public static final int SYNC_NO_RESPONSE = 1; // 0x1
+  }
+
+  public final class PeriodicAdvertisingManager {
+    method public void registerSync(android.bluetooth.le.ScanResult, int, int, android.bluetooth.le.PeriodicAdvertisingCallback);
+    method public void registerSync(android.bluetooth.le.ScanResult, int, int, android.bluetooth.le.PeriodicAdvertisingCallback, android.os.Handler);
+    method public void unregisterSync(android.bluetooth.le.PeriodicAdvertisingCallback);
+  }
+
+  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);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingParameters> CREATOR;
+  }
+
+  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);
+  }
+
+  public final class PeriodicAdvertisingReport implements android.os.Parcelable {
+    ctor public PeriodicAdvertisingReport(int, int, int, int, android.bluetooth.le.ScanRecord);
+    method public int describeContents();
+    method public android.bluetooth.le.ScanRecord getData();
+    method public int getDataStatus();
+    method public int getRssi();
+    method public int getSyncHandle();
+    method public long getTimestampNanos();
+    method public int getTxPower();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.bluetooth.le.PeriodicAdvertisingReport> CREATOR;
+    field public static final int DATA_COMPLETE = 0; // 0x0
+    field public static final int DATA_INCOMPLETE_TRUNCATED = 2; // 0x2
+  }
+
   public abstract class ScanCallback {
     ctor public ScanCallback();
     method public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult>);
@@ -7549,19 +7703,37 @@
   }
 
   public final class ScanResult implements android.os.Parcelable {
-    ctor public ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
+    ctor public deprecated ScanResult(android.bluetooth.BluetoothDevice, android.bluetooth.le.ScanRecord, int, long);
+    ctor public ScanResult(android.bluetooth.BluetoothDevice, int, int, int, int, int, int, int, android.bluetooth.le.ScanRecord, long);
     method public int describeContents();
+    method public int getAdvertisingSid();
+    method public int getDataStatus();
     method public android.bluetooth.BluetoothDevice getDevice();
+    method public int getPeriodicAdvertisingInterval();
+    method public int getPrimaryPhy();
     method public int getRssi();
     method public android.bluetooth.le.ScanRecord getScanRecord();
+    method public int getSecondaryPhy();
     method public long getTimestampNanos();
+    method public int getTxPower();
+    method public boolean isConnectable();
+    method public boolean isLegacy();
     method public void writeToParcel(android.os.Parcel, int);
     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 PHY_UNUSED = 0; // 0x0
+    field public static final int SID_NOT_PRESENT = 255; // 0xff
   }
 
   public final class ScanSettings implements android.os.Parcelable {
     method public int describeContents();
     method public int getCallbackType();
+    method public boolean getLegacy();
+    method public int getPhy();
     method public long getReportDelayMillis();
     method public int getScanMode();
     method public int getScanResultType();
@@ -7575,6 +7747,9 @@
     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
@@ -7585,8 +7760,10 @@
     ctor public ScanSettings.Builder();
     method public android.bluetooth.le.ScanSettings build();
     method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
+    method public android.bluetooth.le.ScanSettings.Builder setLegacy(boolean);
     method public android.bluetooth.le.ScanSettings.Builder setMatchMode(int);
     method public android.bluetooth.le.ScanSettings.Builder setNumOfMatches(int);
+    method public android.bluetooth.le.ScanSettings.Builder setPhy(int);
     method public android.bluetooth.le.ScanSettings.Builder setReportDelay(long);
     method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
   }
@@ -8199,6 +8376,7 @@
     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";
@@ -8565,6 +8743,7 @@
     field public static final java.lang.String ACTION_CALL = "android.intent.action.CALL";
     field public static final java.lang.String ACTION_CALL_BUTTON = "android.intent.action.CALL_BUTTON";
     field public static final java.lang.String ACTION_CAMERA_BUTTON = "android.intent.action.CAMERA_BUTTON";
+    field public static final java.lang.String ACTION_CARRIER_SETUP = "android.intent.action.CARRIER_SETUP";
     field public static final java.lang.String ACTION_CHOOSER = "android.intent.action.CHOOSER";
     field public static final java.lang.String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final java.lang.String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
@@ -17034,6 +17213,15 @@
     method public boolean isTransitionalDifferent();
   }
 
+  public final class ListFormatter {
+    method public java.lang.String format(java.lang.Object...);
+    method public java.lang.String format(java.util.Collection<?>);
+    method public static android.icu.text.ListFormatter getInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ListFormatter getInstance(java.util.Locale);
+    method public static android.icu.text.ListFormatter getInstance();
+    method public java.lang.String getPatternForNumItems(int);
+  }
+
   public abstract class LocaleDisplayNames {
     method public abstract android.icu.text.DisplayContext getContext(android.icu.text.DisplayContext.Type);
     method public abstract android.icu.text.LocaleDisplayNames.DialectHandling getDialectHandling();
@@ -17043,6 +17231,8 @@
     method public static android.icu.text.LocaleDisplayNames getInstance(android.icu.util.ULocale, android.icu.text.DisplayContext...);
     method public static android.icu.text.LocaleDisplayNames getInstance(java.util.Locale, android.icu.text.DisplayContext...);
     method public abstract android.icu.util.ULocale getLocale();
+    method public java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiList(java.util.Set<android.icu.util.ULocale>, boolean, java.util.Comparator<java.lang.Object>);
+    method public abstract java.util.List<android.icu.text.LocaleDisplayNames.UiListItem> getUiListCompareWholeItems(java.util.Set<android.icu.util.ULocale>, java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem>);
     method public abstract java.lang.String keyDisplayName(java.lang.String);
     method public abstract java.lang.String keyValueDisplayName(java.lang.String, java.lang.String);
     method public abstract java.lang.String languageDisplayName(java.lang.String);
@@ -17062,9 +17252,19 @@
     enum_constant public static final android.icu.text.LocaleDisplayNames.DialectHandling STANDARD_NAMES;
   }
 
+  public static class LocaleDisplayNames.UiListItem {
+    ctor public LocaleDisplayNames.UiListItem(android.icu.util.ULocale, android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static java.util.Comparator<android.icu.text.LocaleDisplayNames.UiListItem> getComparator(java.util.Comparator<java.lang.Object>, boolean);
+    field public final android.icu.util.ULocale minimized;
+    field public final android.icu.util.ULocale modified;
+    field public final java.lang.String nameInDisplayLocale;
+    field public final java.lang.String nameInSelf;
+  }
+
   public class MeasureFormat extends android.icu.text.UFormat {
     method public final boolean equals(java.lang.Object);
     method public java.lang.StringBuffer format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition);
+    method public java.lang.StringBuilder formatMeasurePerUnit(android.icu.util.Measure, android.icu.util.MeasureUnit, java.lang.StringBuilder, java.text.FieldPosition);
     method public final java.lang.String formatMeasures(android.icu.util.Measure...);
     method public java.lang.StringBuilder formatMeasures(java.lang.StringBuilder, java.text.FieldPosition, android.icu.util.Measure...);
     method public static android.icu.text.MeasureFormat getCurrencyFormat(android.icu.util.ULocale);
@@ -17533,6 +17733,14 @@
     method public void setUpperCaseFirst(boolean);
   }
 
+  public final class ScientificNumberFormatter {
+    method public java.lang.String format(java.lang.Object);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.util.ULocale, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getMarkupInstance(android.icu.text.DecimalFormat, java.lang.String, java.lang.String);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.util.ULocale);
+    method public static android.icu.text.ScientificNumberFormatter getSuperscriptInstance(android.icu.text.DecimalFormat);
+  }
+
   public abstract class SearchIterator {
     ctor protected SearchIterator(java.text.CharacterIterator, android.icu.text.BreakIterator);
     method public final int first();
@@ -18308,6 +18516,34 @@
     method public long getToDate();
   }
 
+  public final class EthiopicCalendar extends android.icu.util.CECalendar {
+    ctor public EthiopicCalendar();
+    ctor public EthiopicCalendar(android.icu.util.TimeZone);
+    ctor public EthiopicCalendar(java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.ULocale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, java.util.Locale);
+    ctor public EthiopicCalendar(android.icu.util.TimeZone, android.icu.util.ULocale);
+    ctor public EthiopicCalendar(int, int, int);
+    ctor public EthiopicCalendar(java.util.Date);
+    ctor public EthiopicCalendar(int, int, int, int, int, int);
+    method protected deprecated int handleGetExtendedYear();
+    method public boolean isAmeteAlemEra();
+    method public void setAmeteAlemEra(boolean);
+    field public static final int GENBOT = 8; // 0x8
+    field public static final int HAMLE = 10; // 0xa
+    field public static final int HEDAR = 2; // 0x2
+    field public static final int MEGABIT = 6; // 0x6
+    field public static final int MESKEREM = 0; // 0x0
+    field public static final int MIAZIA = 7; // 0x7
+    field public static final int NEHASSE = 11; // 0xb
+    field public static final int PAGUMEN = 12; // 0xc
+    field public static final int SENE = 9; // 0x9
+    field public static final int TAHSAS = 3; // 0x3
+    field public static final int TEKEMT = 1; // 0x1
+    field public static final int TER = 4; // 0x4
+    field public static final int YEKATIT = 5; // 0x5
+  }
+
   public abstract interface Freezable<T> implements java.lang.Cloneable {
     method public abstract T cloneAsThawed();
     method public abstract T freeze();
@@ -18683,6 +18919,8 @@
     field public static final int SHORT_COMMONLY_USED = 6; // 0x6
     field public static final int SHORT_GENERIC = 2; // 0x2
     field public static final int SHORT_GMT = 4; // 0x4
+    field public static final int TIMEZONE_ICU = 0; // 0x0
+    field public static final int TIMEZONE_JDK = 1; // 0x1
     field public static final android.icu.util.TimeZone UNKNOWN_ZONE;
     field public static final java.lang.String UNKNOWN_ZONE_ID = "Etc/Unknown";
   }
@@ -18836,6 +19074,35 @@
     enum_constant public static final android.icu.util.ULocale.Category FORMAT;
   }
 
+  public final class UniversalTimeScale {
+    method public static android.icu.math.BigDecimal bigDecimalFrom(double, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(long, int);
+    method public static android.icu.math.BigDecimal bigDecimalFrom(android.icu.math.BigDecimal, int);
+    method public static long from(long, int);
+    method public static long getTimeScaleValue(int, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(long, int);
+    method public static android.icu.math.BigDecimal toBigDecimal(android.icu.math.BigDecimal, int);
+    method public static long toLong(long, int);
+    field public static final int DB2_TIME = 8; // 0x8
+    field public static final int DOTNET_DATE_TIME = 4; // 0x4
+    field public static final int EPOCH_OFFSET_PLUS_1_VALUE = 6; // 0x6
+    field public static final int EPOCH_OFFSET_VALUE = 1; // 0x1
+    field public static final int EXCEL_TIME = 7; // 0x7
+    field public static final int FROM_MAX_VALUE = 3; // 0x3
+    field public static final int FROM_MIN_VALUE = 2; // 0x2
+    field public static final int ICU4C_TIME = 2; // 0x2
+    field public static final int JAVA_TIME = 0; // 0x0
+    field public static final int MAC_OLD_TIME = 5; // 0x5
+    field public static final int MAC_TIME = 6; // 0x6
+    field public static final int MAX_SCALE = 10; // 0xa
+    field public static final int TO_MAX_VALUE = 5; // 0x5
+    field public static final int TO_MIN_VALUE = 4; // 0x4
+    field public static final int UNITS_VALUE = 0; // 0x0
+    field public static final int UNIX_MICROSECONDS_TIME = 9; // 0x9
+    field public static final int UNIX_TIME = 1; // 0x1
+    field public static final int WINDOWS_FILE_TIME = 3; // 0x3
+  }
+
   public abstract interface ValueIterator {
     method public abstract boolean next(android.icu.util.ValueIterator.Element);
     method public abstract void reset();
@@ -23619,7 +23886,9 @@
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
     method public void registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback);
+    method public void registerDefaultNetworkCallback(android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
+    method public void registerNetworkCallback(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler);
     method public void registerNetworkCallback(android.net.NetworkRequest, android.app.PendingIntent);
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
@@ -23627,6 +23896,9 @@
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     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, int, 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, android.os.Handler);
     method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent);
     method public deprecated boolean requestRouteToHost(int, int);
     method public deprecated void setNetworkPreference(int);
@@ -23674,6 +23946,7 @@
     method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties);
     method public void onLosing(android.net.Network, int);
     method public void onLost(android.net.Network);
+    method public void onUnavailable();
   }
 
   public static abstract interface ConnectivityManager.OnNetworkActiveListener {
@@ -23710,6 +23983,68 @@
     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(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, 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();
@@ -24555,6 +24890,16 @@
 
 package android.net.wifi {
 
+  public final class IconInfo implements android.os.Parcelable {
+    ctor public IconInfo(java.lang.String, byte[]);
+    ctor public IconInfo(android.net.wifi.IconInfo);
+    method public int describeContents();
+    method public byte[] getData();
+    method public java.lang.String getFilename();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.IconInfo> CREATOR;
+  }
+
   public class ScanResult implements android.os.Parcelable {
     method public int describeContents();
     method public boolean is80211mcResponder();
@@ -24616,9 +24961,10 @@
     field public java.util.BitSet allowedProtocols;
     field public android.net.wifi.WifiEnterpriseConfig enterpriseConfig;
     field public boolean hiddenSSID;
+    field public boolean isHomeProviderNetwork;
     field public int networkId;
     field public java.lang.String preSharedKey;
-    field public int priority;
+    field public deprecated int priority;
     field public java.lang.String providerFriendlyName;
     field public long[] roamingConsortiumIds;
     field public int status;
@@ -24683,6 +25029,7 @@
     method public java.security.cert.X509Certificate getCaCertificate();
     method public java.security.cert.X509Certificate[] getCaCertificates();
     method public java.security.cert.X509Certificate getClientCertificate();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
     method public java.lang.String getDomainSuffixMatch();
     method public int getEapMethod();
     method public java.lang.String getIdentity();
@@ -24696,6 +25043,7 @@
     method public void setCaCertificate(java.security.cert.X509Certificate);
     method public void setCaCertificates(java.security.cert.X509Certificate[]);
     method public void setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate);
+    method public void setClientKeyEntryWithCertificateChain(java.security.PrivateKey, java.security.cert.X509Certificate[]);
     method public void setDomainSuffixMatch(java.lang.String);
     method public void setEapMethod(int);
     method public void setIdentity(java.lang.String);
@@ -24721,11 +25069,14 @@
   }
 
   public static final class WifiEnterpriseConfig.Phase2 {
+    field public static final int AKA = 6; // 0x6
+    field public static final int AKA_PRIME = 7; // 0x7
     field public static final int GTC = 4; // 0x4
     field public static final int MSCHAP = 2; // 0x2
     field public static final int MSCHAPV2 = 3; // 0x3
     field public static final int NONE = 0; // 0x0
     field public static final int PAP = 1; // 0x1
+    field public static final int SIM = 5; // 0x5
   }
 
   public class WifiInfo implements android.os.Parcelable {
@@ -24748,6 +25099,7 @@
 
   public class WifiManager {
     method public int addNetwork(android.net.wifi.WifiConfiguration);
+    method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method public static int calculateSignalLevel(int, int);
     method public void cancelWps(android.net.wifi.WifiManager.WpsCallback);
     method public static int compareSignalLevel(int, int);
@@ -24760,6 +25112,7 @@
     method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
     method public android.net.wifi.WifiInfo getConnectionInfo();
     method public android.net.DhcpInfo getDhcpInfo();
+    method public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
     method public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public int getWifiState();
     method public boolean is5GHzBandSupported();
@@ -24770,28 +25123,41 @@
     method public boolean isScanAlwaysAvailable();
     method public boolean isTdlsSupported();
     method public boolean isWifiEnabled();
-    method public boolean pingSupplicant();
+    method public deprecated boolean pingSupplicant();
+    method public void queryPasspointIcon(long, java.lang.String);
     method public boolean reassociate();
     method public boolean reconnect();
     method public boolean removeNetwork(int);
-    method public boolean saveConfiguration();
+    method public void removePasspointConfiguration(java.lang.String);
+    method public deprecated boolean saveConfiguration();
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean);
     method public boolean setWifiEnabled(boolean);
     method public boolean startScan();
     method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
     method public int updateNetwork(android.net.wifi.WifiConfiguration);
+    field public static final java.lang.String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+    field public static final java.lang.String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+    field public static final java.lang.String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+    field public static final java.lang.String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
     field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
     field public static final int ERROR_AUTHENTICATING = 1; // 0x1
+    field public static final java.lang.String EXTRA_ANQP_ELEMENT_DATA = "android.net.wifi.extra.ANQP_ELEMENT_DATA";
     field public static final java.lang.String EXTRA_BSSID = "bssid";
+    field public static final java.lang.String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG";
+    field public static final java.lang.String EXTRA_DELAY = "android.net.wifi.extra.DELAY";
+    field public static final java.lang.String EXTRA_ESS = "android.net.wifi.extra.ESS";
+    field public static final java.lang.String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO";
     field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo";
     field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi";
     field public static final java.lang.String EXTRA_NEW_STATE = "newState";
     field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
     field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+    field public static final java.lang.String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD";
     field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected";
     field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError";
+    field public static final java.lang.String EXTRA_URL = "android.net.wifi.extra.URL";
     field public static final java.lang.String EXTRA_WIFI_INFO = "wifiInfo";
     field public static final java.lang.String EXTRA_WIFI_STATE = "wifi_state";
     field public static final java.lang.String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED";
@@ -24874,10 +25240,9 @@
   }
 
   public class DiscoverySession {
-    method public java.lang.String createNetworkSpecifier(android.net.wifi.aware.PeerHandle, byte[]);
+    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 void destroy();
-    method public static int getMaxSendRetryCount();
-    method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[], int);
     method public void sendMessage(android.net.wifi.aware.PeerHandle, int, byte[]);
   }
 
@@ -24964,7 +25329,8 @@
   }
 
   public class WifiAwareSession {
-    method public java.lang.String createNetworkSpecifier(int, byte[], byte[]);
+    method public java.lang.String createNetworkSpecifierOpen(int, byte[]);
+    method public java.lang.String 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);
@@ -24972,6 +25338,232 @@
 
 }
 
+package android.net.wifi.hotspot2 {
+
+  public final class ConfigParser {
+    method public static android.net.wifi.hotspot2.PasspointConfiguration parsePasspointConfig(java.lang.String, byte[]);
+  }
+
+  public final class PasspointConfiguration implements android.os.Parcelable {
+    ctor public PasspointConfiguration();
+    ctor public PasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
+    method public int describeContents();
+    method public android.net.wifi.hotspot2.pps.Credential getCredential();
+    method public int getCredentialPriority();
+    method public android.net.wifi.hotspot2.pps.HomeSp getHomeSp();
+    method public android.net.wifi.hotspot2.pps.Policy getPolicy();
+    method public long getSubscriptionCreationTimeInMs();
+    method public long getSubscriptionExpirationTimeInMs();
+    method public java.lang.String getSubscriptionType();
+    method public android.net.wifi.hotspot2.pps.UpdateParameter getSubscriptionUpdate();
+    method public java.util.Map<java.lang.String, byte[]> getTrustRootCertList();
+    method public int getUpdateIdentifier();
+    method public long getUsageLimitDataLimit();
+    method public long getUsageLimitStartTimeInMs();
+    method public long getUsageLimitTimeLimitInMinutes();
+    method public long getUsageLimitUsageTimePeriodInMinutes();
+    method public void setCredential(android.net.wifi.hotspot2.pps.Credential);
+    method public void setCredentialPriority(int);
+    method public void setHomeSp(android.net.wifi.hotspot2.pps.HomeSp);
+    method public void setPolicy(android.net.wifi.hotspot2.pps.Policy);
+    method public void setSubscriptionCreationTimeInMs(long);
+    method public void setSubscriptionExpirationTimeInMs(long);
+    method public void setSubscriptionType(java.lang.String);
+    method public void setSubscriptionUpdate(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public void setTrustRootCertList(java.util.Map<java.lang.String, byte[]>);
+    method public void setUpdateIdentifier(int);
+    method public void setUsageLimitDataLimit(long);
+    method public void setUsageLimitStartTimeInMs(long);
+    method public void setUsageLimitTimeLimitInMinutes(long);
+    method public void setUsageLimitUsageTimePeriodInMinutes(long);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR;
+  }
+
+}
+
+package android.net.wifi.hotspot2.omadm {
+
+  public final class PpsMoParser {
+    method public static android.net.wifi.hotspot2.PasspointConfiguration parseMoText(java.lang.String);
+  }
+
+}
+
+package android.net.wifi.hotspot2.pps {
+
+  public final class Credential implements android.os.Parcelable {
+    ctor public Credential();
+    ctor public Credential(android.net.wifi.hotspot2.pps.Credential);
+    method public int describeContents();
+    method public java.security.cert.X509Certificate getCaCertificate();
+    method public android.net.wifi.hotspot2.pps.Credential.CertificateCredential getCertCredential();
+    method public boolean getCheckAaaServerCertStatus();
+    method public java.security.cert.X509Certificate[] getClientCertificateChain();
+    method public java.security.PrivateKey getClientPrivateKey();
+    method public long getCreationTimeInMs();
+    method public long getExpirationTimeInMs();
+    method public java.lang.String getRealm();
+    method public android.net.wifi.hotspot2.pps.Credential.SimCredential getSimCredential();
+    method public android.net.wifi.hotspot2.pps.Credential.UserCredential getUserCredential();
+    method public void setCaCertificate(java.security.cert.X509Certificate);
+    method public void setCertCredential(android.net.wifi.hotspot2.pps.Credential.CertificateCredential);
+    method public void setCheckAaaServerCertStatus(boolean);
+    method public void setClientCertificateChain(java.security.cert.X509Certificate[]);
+    method public void setClientPrivateKey(java.security.PrivateKey);
+    method public void setCreationTimeInMs(long);
+    method public void setExpirationTimeInMs(long);
+    method public void setRealm(java.lang.String);
+    method public void setSimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential);
+    method public void setUserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential> CREATOR;
+  }
+
+  public static final class Credential.CertificateCredential implements android.os.Parcelable {
+    ctor public Credential.CertificateCredential();
+    ctor public Credential.CertificateCredential(android.net.wifi.hotspot2.pps.Credential.CertificateCredential);
+    method public int describeContents();
+    method public byte[] getCertSha256Fingerprint();
+    method public java.lang.String getCertType();
+    method public void setCertSha256Fingerprint(byte[]);
+    method public void setCertType(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.CertificateCredential> CREATOR;
+  }
+
+  public static final class Credential.SimCredential implements android.os.Parcelable {
+    ctor public Credential.SimCredential();
+    ctor public Credential.SimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential);
+    method public int describeContents();
+    method public int getEapType();
+    method public java.lang.String getImsi();
+    method public void setEapType(int);
+    method public void setImsi(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.SimCredential> CREATOR;
+  }
+
+  public static final class Credential.UserCredential implements android.os.Parcelable {
+    ctor public Credential.UserCredential();
+    ctor public Credential.UserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential);
+    method public int describeContents();
+    method public boolean getAbleToShare();
+    method public int getEapType();
+    method public boolean getMachineManaged();
+    method public java.lang.String getNonEapInnerMethod();
+    method public java.lang.String getPassword();
+    method public java.lang.String getSoftTokenApp();
+    method public java.lang.String getUsername();
+    method public void setAbleToShare(boolean);
+    method public void setEapType(int);
+    method public void setMachineManaged(boolean);
+    method public void setNonEapInnerMethod(java.lang.String);
+    method public void setPassword(java.lang.String);
+    method public void setSoftTokenApp(java.lang.String);
+    method public void setUsername(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.UserCredential> CREATOR;
+  }
+
+  public final class HomeSp implements android.os.Parcelable {
+    ctor public HomeSp();
+    ctor public HomeSp(android.net.wifi.hotspot2.pps.HomeSp);
+    method public int describeContents();
+    method public java.lang.String getFqdn();
+    method public java.lang.String getFriendlyName();
+    method public java.util.Map<java.lang.String, java.lang.Long> getHomeNetworkIds();
+    method public java.lang.String getIconUrl();
+    method public long[] getMatchAllOis();
+    method public long[] getMatchAnyOis();
+    method public java.lang.String[] getOtherHomePartners();
+    method public long[] getRoamingConsortiumOis();
+    method public void setFqdn(java.lang.String);
+    method public void setFriendlyName(java.lang.String);
+    method public void setHomeNetworkIds(java.util.Map<java.lang.String, java.lang.Long>);
+    method public void setIconUrl(java.lang.String);
+    method public void setMatchAllOis(long[]);
+    method public void setMatchAnyOis(long[]);
+    method public void setOtherHomePartners(java.lang.String[]);
+    method public void setRoamingConsortiumOis(long[]);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR;
+  }
+
+  public final class Policy implements android.os.Parcelable {
+    ctor public Policy();
+    ctor public Policy(android.net.wifi.hotspot2.pps.Policy);
+    method public int describeContents();
+    method public java.lang.String[] getExcludedSsidList();
+    method public int getMaximumBssLoadValue();
+    method public long getMinHomeDownlinkBandwidth();
+    method public long getMinHomeUplinkBandwidth();
+    method public long getMinRoamingDownlinkBandwidth();
+    method public long getMinRoamingUplinkBandwidth();
+    method public android.net.wifi.hotspot2.pps.UpdateParameter getPolicyUpdate();
+    method public java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> getPreferredRoamingPartnerList();
+    method public java.util.Map<java.lang.Integer, java.lang.String> getRequiredProtoPortMap();
+    method public void setExcludedSsidList(java.lang.String[]);
+    method public void setMaximumBssLoadValue(int);
+    method public void setMinHomeDownlinkBandwidth(long);
+    method public void setMinHomeUplinkBandwidth(long);
+    method public void setMinRoamingDownlinkBandwidth(long);
+    method public void setMinRoamingUplinkBandwidth(long);
+    method public void setPolicyUpdate(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public void setPreferredRoamingPartnerList(java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner>);
+    method public void setRequiredProtoPortMap(java.util.Map<java.lang.Integer, java.lang.String>);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy> CREATOR;
+  }
+
+  public static final class Policy.RoamingPartner implements android.os.Parcelable {
+    ctor public Policy.RoamingPartner();
+    ctor public Policy.RoamingPartner(android.net.wifi.hotspot2.pps.Policy.RoamingPartner);
+    method public int describeContents();
+    method public java.lang.String getCountries();
+    method public java.lang.String getFqdn();
+    method public boolean getFqdnExactMatch();
+    method public int getPriority();
+    method public void setCountries(java.lang.String);
+    method public void setFqdn(java.lang.String);
+    method public void setFqdnExactMatch(boolean);
+    method public void setPriority(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> CREATOR;
+  }
+
+  public final class UpdateParameter implements android.os.Parcelable {
+    ctor public UpdateParameter();
+    ctor public UpdateParameter(android.net.wifi.hotspot2.pps.UpdateParameter);
+    method public int describeContents();
+    method public java.lang.String getBase64EncodedPassword();
+    method public java.lang.String getRestriction();
+    method public java.lang.String getServerUri();
+    method public byte[] getTrustRootCertSha256Fingerprint();
+    method public java.lang.String getTrustRootCertUrl();
+    method public long getUpdateIntervalInMinutes();
+    method public java.lang.String getUpdateMethod();
+    method public java.lang.String getUsername();
+    method public void setBase64EncodedPassword(java.lang.String);
+    method public void setRestriction(java.lang.String);
+    method public void setServerUri(java.lang.String);
+    method public void setTrustRootCertSha256Fingerprint(byte[]);
+    method public void setTrustRootCertUrl(java.lang.String);
+    method public void setUpdateIntervalInMinutes(long);
+    method public void setUpdateMethod(java.lang.String);
+    method public void setUsername(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.UpdateParameter> CREATOR;
+    field public static final long UPDATE_CHECK_INTERVAL_NEVER = 4294967295L; // 0xffffffffL
+    field public static final java.lang.String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    field public static final java.lang.String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+    field public static final java.lang.String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    field public static final java.lang.String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    field public static final java.lang.String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+  }
+
+}
+
 package android.net.wifi.p2p {
 
   public class WifiP2pConfig implements android.os.Parcelable {
@@ -36316,9 +36908,11 @@
     method public android.telecom.Call.Details getDetails();
     method public android.telecom.Call getParent();
     method public java.lang.String getRemainingPostDialSequence();
+    method public android.telecom.Call.RttCall getRttCall();
     method public int getState();
     method public android.telecom.InCallService.VideoCall getVideoCall();
     method public void hold();
+    method public boolean isRttActive();
     method public void mergeConference();
     method public void phoneAccountSelected(android.telecom.PhoneAccountHandle, boolean);
     method public void playDtmfTone(char);
@@ -36330,9 +36924,12 @@
     method public void reject(boolean, java.lang.String);
     method public final void removeExtras(java.util.List<java.lang.String>);
     method public final void removeExtras(java.lang.String...);
+    method public void respondToRttRequest(int, boolean);
     method public void sendCallEvent(java.lang.String, android.os.Bundle);
+    method public void sendRttRequest();
     method public void splitFromConference();
     method public void stopDtmfTone();
+    method public void stopRtt();
     method public void swapConference();
     method public void unhold();
     method public void unregisterCallback(android.telecom.Call.Callback);
@@ -36359,6 +36956,10 @@
     method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details);
     method public void onParentChanged(android.telecom.Call, android.telecom.Call);
     method public void onPostDialWait(android.telecom.Call, java.lang.String);
+    method public void onRttInitiationFailure(android.telecom.Call, int);
+    method public void onRttModeChanged(android.telecom.Call, int);
+    method public void onRttRequest(android.telecom.Call, int);
+    method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall);
     method public void onStateChanged(android.telecom.Call, int);
     method public void onVideoCallChanged(android.telecom.Call, android.telecom.InCallService.VideoCall);
   }
@@ -36409,9 +37010,20 @@
     field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80
     field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10
     field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40
+    field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
     field public static final int PROPERTY_WIFI = 8; // 0x8
   }
 
+  public static final class Call.RttCall {
+    method public int getRttAudioMode();
+    method public java.lang.String read();
+    method public void setRttMode(int);
+    method public void write(java.lang.String) throws java.io.IOException;
+    field public static final int RTT_MODE_FULL = 1; // 0x1
+    field public static final int RTT_MODE_HCO = 2; // 0x2
+    field public static final int RTT_MODE_VCO = 3; // 0x3
+  }
+
   public final class CallAudioState implements android.os.Parcelable {
     ctor public CallAudioState(boolean, int, int);
     method public static java.lang.String audioRouteToString(int);
@@ -36611,6 +37223,15 @@
     field public static final int STATE_RINGING = 2; // 0x2
   }
 
+  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
+    field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1
+    field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4
+  }
+
   public static abstract class Connection.VideoProvider {
     ctor public Connection.VideoProvider();
     method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities);
@@ -36631,6 +37252,7 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
     method public void setCallDataUsage(long);
     field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
+    field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
     field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
     field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
     field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2
@@ -36667,9 +37289,9 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method public void onConference(android.telecom.Connection, android.telecom.Connection);
     method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
+    method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
+    method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
     method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
     method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
     field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -36782,6 +37404,7 @@
     field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40
     field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
     field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
+    field public static final int CAPABILITY_RTT = 4096; // 0x1000
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
@@ -36964,6 +37587,7 @@
     method public boolean handleMmi(java.lang.String);
     method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
     method public boolean isInCall();
+    method public boolean isInManagedCall();
     method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
     method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
     method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -36976,7 +37600,7 @@
     field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
     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 java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
+    field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
     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";
@@ -36994,11 +37618,13 @@
     field public static final java.lang.String EXTRA_NOTIFICATION_PHONE_NUMBER = "android.telecom.extra.NOTIFICATION_PHONE_NUMBER";
     field public static final java.lang.String EXTRA_OUTGOING_CALL_EXTRAS = "android.telecom.extra.OUTGOING_CALL_EXTRAS";
     field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
+    field public static final java.lang.String EXTRA_START_CALL_WITH_RTT = "android.telecom.extra.START_CALL_WITH_RTT";
     field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
     field public static final java.lang.String EXTRA_START_CALL_WITH_VIDEO_STATE = "android.telecom.extra.START_CALL_WITH_VIDEO_STATE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
     field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+    field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
     field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
     field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37086,6 +37712,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
+    field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
     field public static final java.lang.String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
@@ -37146,6 +37773,7 @@
     field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
     field public static final java.lang.String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+    field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
     field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
@@ -37665,6 +38293,7 @@
     method public java.lang.String getDeviceId();
     method public java.lang.String getDeviceId(int);
     method public java.lang.String getDeviceSoftwareVersion();
+    method public java.lang.String[] getForbiddenPlmns();
     method public java.lang.String getGroupIdLevel1();
     method public java.lang.String getIccAuthentication(int, int, java.lang.String);
     method public java.lang.String getLine1Number();
@@ -38472,7 +39101,7 @@
 
 package android.test.suitebuilder {
 
-  public class TestMethod {
+  public deprecated class TestMethod {
     ctor public TestMethod(java.lang.reflect.Method, java.lang.Class<? extends junit.framework.TestCase>);
     ctor public TestMethod(java.lang.String, java.lang.Class<? extends junit.framework.TestCase>);
     ctor public TestMethod(junit.framework.TestCase);
@@ -38483,7 +39112,7 @@
     method public java.lang.String getName();
   }
 
-  public class TestSuiteBuilder {
+  public deprecated class TestSuiteBuilder {
     ctor public TestSuiteBuilder(java.lang.Class);
     ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader);
     method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>);
@@ -38496,7 +39125,7 @@
     method public android.test.suitebuilder.TestSuiteBuilder named(java.lang.String);
   }
 
-  public static class TestSuiteBuilder.FailedToCreateTests extends junit.framework.TestCase {
+  public static deprecated class TestSuiteBuilder.FailedToCreateTests extends junit.framework.TestCase {
     ctor public TestSuiteBuilder.FailedToCreateTests(java.lang.Exception);
     method public void testSuiteConstructionFailed();
   }
@@ -48873,7 +49502,7 @@
 
 package com.android.internal.util {
 
-  public abstract interface Predicate<T> {
+  public abstract deprecated interface Predicate<T> {
     method public abstract boolean apply(T);
   }
 
@@ -49011,6 +49640,8 @@
     field public static final int OP_INT_TO_FLOAT = 130; // 0x82
     field public static final int OP_INT_TO_LONG = 129; // 0x81
     field public static final int OP_INT_TO_SHORT = 143; // 0x8f
+    field public static final int OP_INVOKE_CUSTOM = 252; // 0xfc
+    field public static final int OP_INVOKE_CUSTOM_RANGE = 253; // 0xfd
     field public static final int OP_INVOKE_DIRECT = 112; // 0x70
     field public static final deprecated int OP_INVOKE_DIRECT_EMPTY = 240; // 0xf0
     field public static final int OP_INVOKE_DIRECT_JUMBO = 9471; // 0x24ff
@@ -49018,6 +49649,8 @@
     field public static final int OP_INVOKE_INTERFACE = 114; // 0x72
     field public static final int OP_INVOKE_INTERFACE_JUMBO = 9983; // 0x26ff
     field public static final int OP_INVOKE_INTERFACE_RANGE = 120; // 0x78
+    field public static final int OP_INVOKE_POLYMORPHIC = 250; // 0xfa
+    field public static final int OP_INVOKE_POLYMORPHIC_RANGE = 251; // 0xfb
     field public static final int OP_INVOKE_STATIC = 113; // 0x71
     field public static final int OP_INVOKE_STATIC_JUMBO = 9727; // 0x25ff
     field public static final int OP_INVOKE_STATIC_RANGE = 119; // 0x77
@@ -49199,7 +49832,7 @@
     method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
   }
 
-  public final class InMemoryDexClassLoader extends java.lang.ClassLoader {
+  public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
     ctor public InMemoryDexClassLoader(java.nio.ByteBuffer, java.lang.ClassLoader);
   }
 
@@ -50417,6 +51050,13 @@
     field public static final java.lang.Class<java.lang.Boolean> TYPE;
   }
 
+  public class BootstrapMethodError extends java.lang.LinkageError {
+    ctor public BootstrapMethodError();
+    ctor public BootstrapMethodError(java.lang.String);
+    ctor public BootstrapMethodError(java.lang.String, java.lang.Throwable);
+    ctor public BootstrapMethodError(java.lang.Throwable);
+  }
+
   public final class Byte extends java.lang.Number implements java.lang.Comparable {
     ctor public Byte(byte);
     ctor public Byte(java.lang.String) throws java.lang.NumberFormatException;
@@ -52303,6 +52943,21 @@
 
 package java.lang.invoke {
 
+  public abstract class CallSite {
+    method public abstract java.lang.invoke.MethodHandle dynamicInvoker();
+    method public abstract java.lang.invoke.MethodHandle getTarget();
+    method public abstract void setTarget(java.lang.invoke.MethodHandle);
+    method public java.lang.invoke.MethodType type();
+  }
+
+  public class ConstantCallSite extends java.lang.invoke.CallSite {
+    ctor public ConstantCallSite(java.lang.invoke.MethodHandle);
+    ctor protected ConstantCallSite(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle) throws java.lang.Throwable;
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public final void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class LambdaConversionException extends java.lang.Exception {
     ctor public LambdaConversionException();
     ctor public LambdaConversionException(java.lang.String);
@@ -52312,12 +52967,15 @@
   }
 
   public abstract class MethodHandle {
+    method public java.lang.invoke.MethodHandle asCollector(java.lang.Class<?>, int);
     method public java.lang.invoke.MethodHandle asFixedArity();
+    method public java.lang.invoke.MethodHandle asSpreader(java.lang.Class<?>, int);
     method public java.lang.invoke.MethodHandle asType(java.lang.invoke.MethodType);
     method public java.lang.invoke.MethodHandle asVarargsCollector(java.lang.Class<?>);
     method public java.lang.invoke.MethodHandle bindTo(java.lang.Object);
     method public final java.lang.Object invoke(java.lang.Object...) throws java.lang.Throwable;
     method public final java.lang.Object invokeExact(java.lang.Object...) throws java.lang.Throwable;
+    method public java.lang.Object invokeWithArguments(java.lang.Object...) throws java.lang.Throwable;
     method public java.lang.Object invokeWithArguments(java.util.List<?>) throws java.lang.Throwable;
     method public boolean isVarargsCollector();
     method public java.lang.invoke.MethodType type();
@@ -52351,17 +53009,23 @@
     method public static java.lang.invoke.MethodHandle arrayElementGetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
     method public static java.lang.invoke.MethodHandle arrayElementSetter(java.lang.Class<?>) throws java.lang.IllegalArgumentException;
     method public static java.lang.invoke.MethodHandle catchException(java.lang.invoke.MethodHandle, java.lang.Class<? extends java.lang.Throwable>, java.lang.invoke.MethodHandle);
+    method public static java.lang.invoke.MethodHandle collectArguments(java.lang.invoke.MethodHandle, int, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle constant(java.lang.Class<?>, java.lang.Object);
     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 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);
     method public static java.lang.invoke.MethodHandle guardWithTest(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle identity(java.lang.Class<?>);
+    method public static java.lang.invoke.MethodHandle insertArguments(java.lang.invoke.MethodHandle, int, java.lang.Object...);
     method public static java.lang.invoke.MethodHandle invoker(java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandles.Lookup lookup();
     method public static java.lang.invoke.MethodHandle permuteArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType, int...);
     method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
+    method public static <T extends java.lang.reflect.Member> T reflectAs(java.lang.Class<T>, java.lang.invoke.MethodHandle);
+    method public static java.lang.invoke.MethodHandle spreadInvoker(java.lang.invoke.MethodType, int);
     method public static java.lang.invoke.MethodHandle throwException(java.lang.Class<?>, java.lang.Class<? extends java.lang.Throwable>);
   }
 
@@ -52378,7 +53042,7 @@
     method public java.lang.invoke.MethodHandles.Lookup in(java.lang.Class<?>);
     method public java.lang.Class<?> lookupClass();
     method public int lookupModes();
-    method public void throwMakeAccessException(java.lang.String, java.lang.Object) throws java.lang.IllegalAccessException;
+    method public java.lang.invoke.MethodHandleInfo revealDirect(java.lang.invoke.MethodHandle);
     method public java.lang.invoke.MethodHandle unreflect(java.lang.reflect.Method) throws java.lang.IllegalAccessException;
     method public java.lang.invoke.MethodHandle unreflectConstructor(java.lang.reflect.Constructor<?>) throws java.lang.IllegalAccessException;
     method public java.lang.invoke.MethodHandle unreflectGetter(java.lang.reflect.Field) throws java.lang.IllegalAccessException;
@@ -52421,6 +53085,22 @@
     method public java.lang.invoke.MethodType wrap();
   }
 
+  public class MutableCallSite extends java.lang.invoke.CallSite {
+    ctor public MutableCallSite(java.lang.invoke.MethodType);
+    ctor public MutableCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
+  public class VolatileCallSite extends java.lang.invoke.CallSite {
+    ctor public VolatileCallSite(java.lang.invoke.MethodType);
+    ctor public VolatileCallSite(java.lang.invoke.MethodHandle);
+    method public final java.lang.invoke.MethodHandle dynamicInvoker();
+    method public final java.lang.invoke.MethodHandle getTarget();
+    method public void setTarget(java.lang.invoke.MethodHandle);
+  }
+
   public class WrongMethodTypeException extends java.lang.RuntimeException {
     ctor public WrongMethodTypeException();
     ctor public WrongMethodTypeException(java.lang.String);
@@ -60709,6 +61389,9 @@
     method public static <E> java.util.Collection<E> checkedCollection(java.util.Collection<E>, java.lang.Class<E>);
     method public static <E> java.util.List<E> checkedList(java.util.List<E>, java.lang.Class<E>);
     method public static <K, V> java.util.Map<K, V> checkedMap(java.util.Map<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <K, V> java.util.NavigableMap<K, V> checkedNavigableMap(java.util.NavigableMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
+    method public static <E> java.util.NavigableSet<E> checkedNavigableSet(java.util.NavigableSet<E>, java.lang.Class<E>);
+    method public static <E> java.util.Queue<E> checkedQueue(java.util.Queue<E>, java.lang.Class<E>);
     method public static <E> java.util.Set<E> checkedSet(java.util.Set<E>, java.lang.Class<E>);
     method public static <K, V> java.util.SortedMap<K, V> checkedSortedMap(java.util.SortedMap<K, V>, java.lang.Class<K>, java.lang.Class<V>);
     method public static <E> java.util.SortedSet<E> checkedSortedSet(java.util.SortedSet<E>, java.lang.Class<E>);
@@ -60719,7 +61402,11 @@
     method public static final <T> java.util.List<T> emptyList();
     method public static <T> java.util.ListIterator<T> emptyListIterator();
     method public static final <K, V> java.util.Map<K, V> emptyMap();
+    method public static final <K, V> java.util.NavigableMap<K, V> emptyNavigableMap();
+    method public static <E> java.util.NavigableSet<E> emptyNavigableSet();
     method public static final <T> java.util.Set<T> emptySet();
+    method public static final <K, V> java.util.SortedMap<K, V> emptySortedMap();
+    method public static <E> java.util.SortedSet<E> emptySortedSet();
     method public static <T> java.util.Enumeration<T> enumeration(java.util.Collection<T>);
     method public static <T> void fill(java.util.List<? super T>, T);
     method public static int frequency(java.util.Collection<?>, java.lang.Object);
@@ -60748,12 +61435,16 @@
     method public static <T> java.util.Collection<T> synchronizedCollection(java.util.Collection<T>);
     method public static <T> java.util.List<T> synchronizedList(java.util.List<T>);
     method public static <K, V> java.util.Map<K, V> synchronizedMap(java.util.Map<K, V>);
+    method public static <K, V> java.util.NavigableMap<K, V> synchronizedNavigableMap(java.util.NavigableMap<K, V>);
+    method public static <T> java.util.NavigableSet<T> synchronizedNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> synchronizedSet(java.util.Set<T>);
     method public static <K, V> java.util.SortedMap<K, V> synchronizedSortedMap(java.util.SortedMap<K, V>);
     method public static <T> java.util.SortedSet<T> synchronizedSortedSet(java.util.SortedSet<T>);
     method public static <T> java.util.Collection<T> unmodifiableCollection(java.util.Collection<? extends T>);
     method public static <T> java.util.List<T> unmodifiableList(java.util.List<? extends T>);
     method public static <K, V> java.util.Map<K, V> unmodifiableMap(java.util.Map<? extends K, ? extends V>);
+    method public static <K, V> java.util.NavigableMap<K, V> unmodifiableNavigableMap(java.util.NavigableMap<K, ? extends V>);
+    method public static <T> java.util.NavigableSet<T> unmodifiableNavigableSet(java.util.NavigableSet<T>);
     method public static <T> java.util.Set<T> unmodifiableSet(java.util.Set<? extends T>);
     method public static <K, V> java.util.SortedMap<K, V> unmodifiableSortedMap(java.util.SortedMap<K, ? extends V>);
     method public static <T> java.util.SortedSet<T> unmodifiableSortedSet(java.util.SortedSet<T>);
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index d6c0058..a66b0b9 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -110,6 +110,7 @@
     private String mProfileFile;
     private int mSamplingInterval;
     private boolean mAutoStop;
+    private boolean mStreaming;   // Streaming the profiling output to a file.
     private int mStackId;
 
     /**
@@ -127,7 +128,7 @@
         pw.println(
                 "usage: am [subcommand] [options]\n" +
                 "usage: am start [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]\n" +
-                "               [--sampling INTERVAL] [-R COUNT] [-S]\n" +
+                "               [--sampling INTERVAL] [--streaming] [-R COUNT] [-S]\n" +
                 "               [--track-allocation] [--user <USER_ID> | current] <INTENT>\n" +
                 "       am startservice [--user <USER_ID> | current] <INTENT>\n" +
                 "       am stopservice [--user <USER_ID> | current] <INTENT>\n" +
@@ -138,7 +139,8 @@
                 "       am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" +
                 "               [--user <USER_ID> | current]\n" +
                 "               [--no-window-animation] [--abi <ABI>] <COMPONENT>\n" +
-                "       am profile start [--user <USER_ID> current] [--sampling INTERVAL] <PROCESS> <FILE>\n" +
+                "       am profile start [--user <USER_ID> current] [--sampling INTERVAL]\n"+
+                "               [--streaming] <PROCESS> <FILE>\n" +
                 "       am profile stop [--user <USER_ID> current] [<PROCESS>]\n" +
                 "       am dumpheap [--user <USER_ID> current] [-n] <PROCESS> <FILE>\n" +
                 "       am set-debug-app [-w] [--persistent] <PACKAGE>\n" +
@@ -191,6 +193,8 @@
                 "    --start-profiler <FILE>: start profiler and send results to <FILE>\n" +
                 "    --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" +
                 "        between samples (use with --start-profiler)\n" +
+                "    --streaming: stream the profiling output to the specified file (use\n" +
+                "        with --start-profiler)\n" +
                 "    -P <FILE>: like above, but profiling stops when app goes idle\n" +
                 "    -R: repeat the activity launch <COUNT> times.  Prior to each repeat,\n" +
                 "        the top activity will be finished.\n" +
@@ -250,6 +254,9 @@
                 "  may be either a process name or pid.  Options are:\n" +
                 "    --user <USER_ID> | current: When supplying a process name,\n" +
                 "        specify user of process to profile; uses current user if not specified.\n" +
+                "    --sampling INTERVAL: use sample profiling with INTERVAL microseconds\n" +
+                "        between samples\n" +
+                "    --streaming: stream the profiling output to the specified file\n" +
                 "\n" +
                 "am dumpheap: dump the heap of a process.  The given <PROCESS> argument may\n" +
                 "  be either a process name or pid.  Options are:\n" +
@@ -483,6 +490,7 @@
         mProfileFile = null;
         mSamplingInterval = 0;
         mAutoStop = false;
+        mStreaming = false;
         mUserId = defUser;
         mStackId = INVALID_STACK_ID;
 
@@ -503,6 +511,8 @@
                     mAutoStop = false;
                 } else if (opt.equals("--sampling")) {
                     mSamplingInterval = Integer.parseInt(nextArgRequired());
+                } else if (opt.equals("--streaming")) {
+                    mStreaming = true;
                 } else if (opt.equals("-R")) {
                     mRepeat = Integer.parseInt(nextArgRequired());
                 } else if (opt.equals("-S")) {
@@ -615,7 +625,8 @@
                     System.err.println("Consider using a file under /data/local/tmp/");
                     return;
                 }
-                profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop);
+                profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop,
+                                                mStreaming);
             }
 
             IActivityManager.WaitResult result = null;
@@ -973,6 +984,7 @@
         int userId = UserHandle.USER_CURRENT;
         int profileType = 0;
         mSamplingInterval = 0;
+        mStreaming = false;
 
         String process = null;
 
@@ -986,6 +998,8 @@
                     userId = parseUserArg(nextArgRequired());
                 } else if (opt.equals("--wall")) {
                     wall = true;
+                } else if (opt.equals("--streaming")) {
+                    mStreaming = true;
                 } else if (opt.equals("--sampling")) {
                     mSamplingInterval = Integer.parseInt(nextArgRequired());
                 } else {
@@ -1037,7 +1051,7 @@
                 System.err.println("Consider using a file under /data/local/tmp/");
                 return;
             }
-            profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false);
+            profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming);
         }
 
         try {
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 4dcb05e..adbe9d0 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -36,8 +36,8 @@
     public String longHelp() {
         return shortHelp() + "\n"
                 + "\n"
-                + "usage: svc usb setFunction [function]\n"
-                + "         Set the current usb function.\n\n"
+                + "usage: svc usb setFunction [function] [usbDataUnlocked=false]\n"
+                + "         Set the current usb function and optionally the data lock state.\n\n"
                 + "       svc usb getFunction\n"
                 + "          Gets the list of currently enabled functions\n";
     }
@@ -49,8 +49,12 @@
             if ("setFunction".equals(args[1])) {
                 IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService(
                         Context.USB_SERVICE));
+                boolean unlockData = false;
+                if (args.length >= 4) {
+                    unlockData = Boolean.valueOf(args[3]);
+                }
                 try {
-                    usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null), false);
+                    usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null), unlockData);
                 } catch (RemoteException e) {
                     System.err.println("Error communicating with UsbManager: " + e);
                 }
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 63f6c92..8e9b91d 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.IUserManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -51,6 +50,7 @@
     private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
     private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
     private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
+    private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
 
     private ComponentName mComponent;
     private String mAccountId;
@@ -69,6 +69,7 @@
                 "usage: telecom set-default-dialer <PACKAGE>\n" +
                 "usage: telecom get-default-dialer\n" +
                 "usage: telecom get-system-dialer\n" +
+                "usage: telecom wait-on-handlers\n" +
                 "\n" +
                 "telecom set-phone-account-enabled: Enables the given phone account, if it has \n" +
                 " already been registered with Telecom.\n" +
@@ -80,7 +81,9 @@
                 "\n" +
                 "telecom get-default-dialer: Displays the current default dialer. \n" +
                 "\n" +
-                "telecom get-system-dialer: Displays the current system dialer. \n"
+                "telecom get-system-dialer: Displays the current system dialer. \n" +
+                "\n" +
+                "telecom wait-on-handlers: Wait until all handlers finish their work. \n"
                 );
     }
 
@@ -125,6 +128,9 @@
             case COMMAND_GET_SYSTEM_DIALER:
                 runGetSystemDialer();
                 break;
+            case COMMAND_WAIT_ON_HANDLERS:
+                runWaitOnHandler();
+                break;
             default:
                 throw new IllegalArgumentException ("unknown command '" + command + "'");
         }
@@ -192,6 +198,10 @@
         System.out.println(mTelecomService.getSystemDialerPackage());
     }
 
+    private void runWaitOnHandler() throws RemoteException {
+
+    }
+
     private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{
         final ComponentName component = parseComponentName(nextArgRequired());
         final String accountId = nextArgRequired();
diff --git a/cmds/uiautomator/library/Android.mk b/cmds/uiautomator/library/Android.mk
index 9017be2..f932388b 100644
--- a/cmds/uiautomator/library/Android.mk
+++ b/cmds/uiautomator/library/Android.mk
@@ -36,7 +36,7 @@
 # Generate the stub source files
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(uiautomator.core_src_files)
-LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries)
+LOCAL_JAVA_LIBRARIES := $(uiautomator.core_java_libraries) legacy-android-test
 LOCAL_MODULE_CLASS := JAVA_LIBRARIES
 LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/core-src \
 	$(LOCAL_PATH)/testrunner-src
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
index 73e46f1..28a5646 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/InteractionController.java
@@ -32,8 +32,6 @@
 import android.view.MotionEvent.PointerProperties;
 import android.view.accessibility.AccessibilityEvent;
 
-import com.android.internal.util.Predicate;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
@@ -261,7 +259,7 @@
     }
 
     /**
-     * Returns a Runnable for use in {@link #runAndWaitForEvents(Runnable, Predicate, long) to
+     * Returns a Runnable for use in {@link #runAndWaitForEvents(Runnable, AccessibilityEventFilter, long) to
      * perform a click.
      *
      * @param x coordinate
diff --git a/compiled-classes-phone b/compiled-classes-phone
index ec3371e..2cbfc22 100644
--- a/compiled-classes-phone
+++ b/compiled-classes-phone
@@ -633,6 +633,7 @@
 android.bluetooth.BluetoothClass
 android.bluetooth.BluetoothClass$1
 android.bluetooth.BluetoothCodecConfig
+android.bluetooth.BluetoothCodecStatus
 android.bluetooth.BluetoothDevice
 android.bluetooth.BluetoothDevice$1
 android.bluetooth.BluetoothDevice$2
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index af981f6..e1ff383 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -32,6 +32,7 @@
 import android.os.ParcelFileDescriptor;
 
 import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.RoSystemProperties;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.util.FastPrintWriter;
 
@@ -895,7 +896,7 @@
 
     /** @hide */
     public static boolean isLowRamDeviceStatic() {
-        return "true".equals(SystemProperties.get("ro.config.low_ram", "false"));
+        return RoSystemProperties.CONFIG_LOW_RAM;
     }
 
     /**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2d22f26..c7fc860 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -534,6 +534,7 @@
         ParcelFileDescriptor profileFd;
         int samplingInterval;
         boolean autoStopProfiler;
+        boolean streamingOutput;
         boolean profiling;
         boolean handlingProfiling;
         public void setProfiler(ProfilerInfo profilerInfo) {
@@ -559,6 +560,7 @@
             profileFd = fd;
             samplingInterval = profilerInfo.samplingInterval;
             autoStopProfiler = profilerInfo.autoStopProfiler;
+            streamingOutput = profilerInfo.streamingOutput;
         }
         public void startProfiling() {
             if (profileFd == null || profiling) {
@@ -567,7 +569,8 @@
             try {
                 int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
                 VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
-                        bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval);
+                        bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval,
+                        streamingOutput);
                 profiling = true;
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Profiling failed on path " + profileFile);
@@ -971,6 +974,10 @@
             sendMessage(H.DUMP_HEAP, dhd, managed ? 1 : 0, 0, true /*async*/);
         }
 
+        public void attachAgent(String agent) {
+            sendMessage(H.ATTACH_AGENT, agent);
+        }
+
         public void setSchedulingGroup(int group) {
             // Note: do this immediately, since going into the foreground
             // should happen regardless of what pending work we have to do
@@ -1405,6 +1412,7 @@
         public static final int MULTI_WINDOW_MODE_CHANGED = 152;
         public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
         public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
+        public static final int ATTACH_AGENT = 155;
 
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
@@ -1461,6 +1469,7 @@
                     case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
                     case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
                     case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
+                    case ATTACH_AGENT: return "ATTACH_AGENT";
                 }
             }
             return Integer.toString(code);
@@ -1716,6 +1725,9 @@
                     handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
                             (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
                     break;
+                case ATTACH_AGENT:
+                    handleAttachAgent((String) msg.obj);
+                    break;
             }
             Object obj = msg.obj;
             if (obj instanceof SomeArgs) {
@@ -2984,6 +2996,14 @@
         }
     }
 
+    static final void handleAttachAgent(String agent) {
+        try {
+            VMDebug.attachAgent(agent);
+        } catch (IOException e) {
+            Slog.e(TAG, "Attaching agent failed: " + agent);
+        }
+    }
+
     private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
 
     /**
@@ -5109,6 +5129,7 @@
             mProfiler.profileFd = data.initProfilerInfo.profileFd;
             mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
             mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
+            mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
         }
 
         // send up app name; do this *before* waiting for debugger
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 05d9d7e..ad7f577 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -502,6 +502,14 @@
             return true;
         }
 
+        case ATTACH_AGENT_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            String agent = data.readString();
+            attachAgent(agent);
+            return true;
+        }
+
         case DUMP_ACTIVITY_TRANSACTION: {
             data.enforceInterface(IApplicationThread.descriptor);
             ParcelFileDescriptor fd = data.readFileDescriptor();
@@ -1305,6 +1313,14 @@
         data.recycle();
     }
 
+    public void attachAgent(String agent) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeString(agent);
+        mRemote.transact(ATTACH_AGENT_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
+
     public void setCoreSettings(Bundle coreSettings) throws RemoteException {
         Parcel data = Parcel.obtain();
         data.writeInterfaceToken(IApplicationThread.descriptor);
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 3fa88ae..bfd97f8 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -123,6 +123,7 @@
             throws RemoteException;
     void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd)
             throws RemoteException;
+    void attachAgent(String path) throws RemoteException;
     void setSchedulingGroup(int group) throws RemoteException;
     // the package has been removed, clean up internal references
     static final int PACKAGE_REMOVED = 0;
@@ -225,4 +226,5 @@
     int SCHEDULE_MULTI_WINDOW_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58;
     int SCHEDULE_PICTURE_IN_PICTURE_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59;
     int SCHEDULE_LOCAL_VOICE_INTERACTION_STARTED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60;
+    int ATTACH_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+61;
 }
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index afe9651..c625756 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -266,7 +266,7 @@
         setApplicationInfo(aInfo);
 
         final List<String> newPaths = new ArrayList<>();
-        makePaths(mActivityThread, aInfo, newPaths, null /*libPaths*/);
+        makePaths(mActivityThread, aInfo, newPaths);
         final List<String> addedPaths = new ArrayList<>(newPaths.size());
 
         if (oldPaths != null) {
@@ -314,8 +314,17 @@
         mCredentialProtectedDataDirFile = FileUtils.newFileOrNull(aInfo.credentialProtectedDataDir);
     }
 
-    public static void makePaths(ActivityThread activityThread, ApplicationInfo aInfo,
-            List<String> outZipPaths, List<String> outLibPaths) {
+    public static void makePaths(ActivityThread activityThread,
+                                 ApplicationInfo aInfo,
+                                 List<String> outZipPaths) {
+        makePaths(activityThread, false, aInfo, outZipPaths, null);
+    }
+
+    public static void makePaths(ActivityThread activityThread,
+                                 boolean isBundledApp,
+                                 ApplicationInfo aInfo,
+                                 List<String> outZipPaths,
+                                 List<String> outLibPaths) {
         final String appDir = aInfo.sourceDir;
         final String[] splitAppDirs = aInfo.splitSourceDirs;
         final String libDir = aInfo.nativeLibraryDir;
@@ -398,7 +407,7 @@
                 }
             }
 
-            if (aInfo.isSystemApp() && !aInfo.isUpdatedSystemApp()) {
+            if (isBundledApp) {
                 // Add path to system libraries to libPaths;
                 // Access to system libs should be limited
                 // to bundled applications; this is why updated
@@ -471,11 +480,12 @@
         // space and initialize to a small value (instead of incurring growth code).
         final List<String> zipPaths = new ArrayList<>(10);
         final List<String> libPaths = new ArrayList<>(10);
-        makePaths(mActivityThread, mApplicationInfo, zipPaths, libPaths);
 
         final boolean isBundledApp = mApplicationInfo.isSystemApp()
                 && !mApplicationInfo.isUpdatedSystemApp();
 
+        makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
+
         String libraryPermittedPath = mDataDir;
         if (isBundledApp) {
             // This is necessary to grant bundled apps access to
@@ -596,19 +606,17 @@
         }
 
         final File profileFile = getPrimaryProfileFile(mPackageName);
-        final File foreignDexProfilesFile =
-                Environment.getDataProfilesDeForeignDexDirectory(UserHandle.myUserId());
 
-        VMRuntime.registerAppInfo(profileFile.getPath(), mApplicationInfo.dataDir,
-                codePaths.toArray(new String[codePaths.size()]), foreignDexProfilesFile.getPath());
+        VMRuntime.registerAppInfo(profileFile.getPath(),
+                codePaths.toArray(new String[codePaths.size()]));
 
         // Setup the reporter to notify package manager of any relevant dex loads.
         // At this point the primary apk is loaded and will not be reported.
         // Anything loaded from now on will be tracked as a potential secondary
         // or foreign dex file. The goal is to enable:
         //    1) monitoring and compilation of secondary dex file
-        //    2) track foreign dex file usage (used to determined the
-        //       compilation filter of apks).
+        //    2) track whether or not a dex file is used by other apps (used to
+        //       determined the compilation filter of apks).
         if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) {
             // Set the dex load reporter if not already set.
             // Note that during the app's life cycle different LoadedApks may be
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0dd9c63..bcdf3f4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1761,7 +1761,9 @@
         if (this.actions != null) {
             that.actions = new Action[this.actions.length];
             for(int i=0; i<this.actions.length; i++) {
-                that.actions[i] = this.actions[i].clone();
+                if ( this.actions[i] != null) {
+                    that.actions[i] = this.actions[i].clone();
+                }
             }
         }
 
@@ -3108,7 +3110,9 @@
          * @param action The action to add.
          */
         public Builder addAction(Action action) {
-            mActions.add(action);
+            if (action != null) {
+                mActions.add(action);
+            }
             return this;
         }
 
@@ -3122,7 +3126,9 @@
         public Builder setActions(Action... actions) {
             mActions.clear();
             for (int i = 0; i < actions.length; i++) {
-                mActions.add(actions[i]);
+                if (actions[i] != null) {
+                    mActions.add(actions[i]);
+                }
             }
             return this;
         }
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index cea7c3c..f3fe677 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -39,11 +39,16 @@
     /* Automatically stop the profiler when the app goes idle. */
     public final boolean autoStopProfiler;
 
-    public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop) {
+    /* Indicates whether to stream the profiling info to the out file continuously. */
+    public final boolean streamingOutput;
+
+    public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop,
+                        boolean streaming) {
         profileFile = filename;
         profileFd = fd;
         samplingInterval = interval;
         autoStopProfiler = autoStop;
+        streamingOutput = streaming;
     }
 
     public int describeContents() {
@@ -64,6 +69,7 @@
         }
         out.writeInt(samplingInterval);
         out.writeInt(autoStopProfiler ? 1 : 0);
+        out.writeInt(streamingOutput ? 1 : 0);
     }
 
     public static final Parcelable.Creator<ProfilerInfo> CREATOR =
@@ -82,5 +88,6 @@
         profileFd = in.readInt() != 0 ? ParcelFileDescriptor.CREATOR.createFromParcel(in) : null;
         samplingInterval = in.readInt();
         autoStopProfiler = in.readInt() != 0;
+        streamingOutput = in.readInt() != 0;
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 1165fce..4960159 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -105,10 +105,9 @@
      * Intent used to broadcast the change in the Audio Codec state of the
      * A2DP Source profile.
      *
-     * <p>This intent will have 3 extras:
+     * <p>This intent will have 2 extras:
      * <ul>
-     *   <li> {@link #EXTRA_CODEC_CONFIG} - The current codec configuration. </li>
-     *   <li> {@link #EXTRA_PREVIOUS_CODEC_CONFIG} - The previous codec configuration. </li>
+     *   <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li>
      *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
      *   connected, otherwise it is not included.</li>
      * </ul>
@@ -564,24 +563,24 @@
     }
 
     /**
-     * Gets the current codec configuration.
+     * Gets the current codec status (configuration and capability).
      *
-     * @return the current codec configuration
+     * @return the current codec status
      * @hide
      */
-    public BluetoothCodecConfig getCodecConfig() {
-        if (DBG) Log.d(TAG, "getCodecConfig");
+    public BluetoothCodecStatus getCodecStatus() {
+        if (DBG) Log.d(TAG, "getCodecStatus");
         try {
             mServiceLock.readLock().lock();
             if (mService != null && isEnabled()) {
-                return mService.getCodecConfig();
+                return mService.getCodecStatus();
             }
             if (mService == null) {
                 Log.w(TAG, "Proxy not attached to service");
             }
             return null;
         } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in getCodecConfig()", e);
+            Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
             return null;
         } finally {
             mServiceLock.readLock().unlock();
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index f9be3a1..27640e7 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -26,6 +26,7 @@
 import android.app.ActivityThread;
 import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.PeriodicAdvertisingManager;
 import android.bluetooth.le.ScanCallback;
 import android.bluetooth.le.ScanFilter;
 import android.bluetooth.le.ScanRecord;
@@ -525,6 +526,7 @@
 
     private static BluetoothLeScanner sBluetoothLeScanner;
     private static BluetoothLeAdvertiser sBluetoothLeAdvertiser;
+    private static PeriodicAdvertisingManager sPeriodicAdvertisingManager;
 
     private final IBluetoothManager mManagerService;
     private IBluetooth mService;
@@ -630,6 +632,30 @@
     }
 
     /**
+     * Returns a {@link PeriodicAdvertisingManager} object for Bluetooth LE Periodic Advertising
+     * operations. Will return null if Bluetooth is turned off or if Bluetooth LE Periodic
+     * Advertising is not supported on this device.
+     * <p>
+     * Use {@link #isLePeriodicAdvertisingSupported()} to check whether LE Periodic Advertising is
+     * supported on this device before calling this method.
+     */
+    public PeriodicAdvertisingManager getPeriodicAdvertisingManager() {
+      if (!getLeAccess())
+        return null;
+
+      if (!isLePeriodicAdvertisingSupported())
+        return null;
+
+      synchronized (mLock) {
+        if (sPeriodicAdvertisingManager == null) {
+          sPeriodicAdvertisingManager =
+              new PeriodicAdvertisingManager(mManagerService);
+        }
+      }
+      return sPeriodicAdvertisingManager;
+    }
+
+    /**
      * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
      */
     public BluetoothLeScanner getBluetoothLeScanner() {
@@ -1184,6 +1210,25 @@
     }
 
     /**
+     * Get the end time of the latest remote device discovery process.
+     * @return the latest time that the bluetooth adapter was/will be in discovery mode,
+     * in milliseconds since the epoch.
+     * This time can be in the future if {@link #startDiscovery()} has been called recently.
+     * @hide
+     */
+    public long getDiscoveryEndMillis() {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getDiscoveryEndMillis();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return -1;
+    }
+
+    /**
      * Start the remote device discovery process.
      * <p>The discovery process usually involves an inquiry scan of about 12
      * seconds, followed by a page scan of each new device to retrieve its
@@ -1366,6 +1411,97 @@
     }
 
     /**
+     * Return true if LE 2M PHY feature is supported.
+     *
+     * @return true if chipset supports LE 2M PHY feature
+     */
+    public boolean isLe2MPhySupported() {
+        if (!getLeAccess()) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.isLe2MPhySupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isExtendedAdvertisingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE Coded PHY feature is supported.
+     *
+     * @return true if chipset supports LE Coded PHY feature
+     */
+    public boolean isLeCodedPhySupported() {
+        if (!getLeAccess()) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.isLeCodedPhySupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isLeCodedPhySupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE Extended Advertising feature is supported.
+     *
+     * @return true if chipset supports LE Extended Advertising feature
+     */
+    public boolean isLeExtendedAdvertisingSupported() {
+        if (!getLeAccess()) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.isLeExtendedAdvertisingSupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isLeExtendedAdvertisingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE Periodic Advertising feature is supported.
+     *
+     * @return true if chipset supports LE Periodic Advertising feature
+     */
+    public boolean isLePeriodicAdvertisingSupported() {
+        if (!getLeAccess()) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.isLePeriodicAdvertisingSupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isLePeriodicAdvertisingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return the maximum LE advertising data length,
+     * if LE Extended Advertising feature is supported.
+     *
+     * @return the maximum LE advertising data length.
+     */
+    public int getLeMaximumAdvertisingDataLength() {
+        if (!getLeAccess()) return 0;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getLeMaximumAdvertisingDataLength();
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get getLeMaximumAdvertisingDataLength, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return 0;
+    }
+
+    /**
      * Return true if hardware has entries available for matching beacons
      *
      * @return true if there are hw entries available for matching beacons
@@ -1839,6 +1975,35 @@
         return listenUsingL2capOn(port, false, false);
     }
 
+
+    /**
+     * Construct an insecure L2CAP server socket.
+     * Call #accept to retrieve connections to this socket.
+     * <p>To auto assign a port without creating a SDP record use
+     * {@link SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number.
+     * @param port    the PSM to listen on
+     * @return An L2CAP BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or
+     *                     insufficient permissions.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException {
+        BluetoothServerSocket socket = new BluetoothServerSocket(
+                BluetoothSocket.TYPE_L2CAP, false, false, port, false, false);
+        int errno = socket.mSocket.bindListen();
+        if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            socket.setChannel(socket.mSocket.getPort());
+        }
+        if (errno != 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+
+    }
+
     /**
      * Read the local Out of Band Pairing Data
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 52cd2de..176e48f 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -29,34 +29,19 @@
  * {@hide}
  */
 public final class BluetoothCodecConfig implements Parcelable {
-
-    /**
-     * Extra for the codec configuration intents of the individual profiles.
-     *
-     * This extra represents the current codec configuration of the A2DP
-     * profile.
-     */
-    public static final String EXTRA_CODEC_CONFIG = "android.bluetooth.codec.extra.CODEC_CONFIG";
-
-    /**
-     * Extra for the codec configuration intents of the individual profiles.
-     *
-     * This extra represents the previous codec configuration of the A2DP
-     * profile.
-     */
-    public static final String EXTRA_PREVIOUS_CODEC_CONFIG =
-        "android.bluetooth.codec.extra.PREVIOUS_CODEC_CONFIG";
-
     // Add an entry for each source codec here.
     // NOTE: The values should be same as those listed in the following file:
     //   hardware/libhardware/include/hardware/bt_av.h
     public static final int SOURCE_CODEC_TYPE_SBC     = 0;
-    public static final int SOURCE_CODEC_TYPE_APTX    = 1;
-    public static final int SOURCE_CODEC_TYPE_APTX_HD = 2;
-    public static final int SOURCE_CODEC_TYPE_LDAC    = 3;
+    public static final int SOURCE_CODEC_TYPE_AAC     = 1;
+    public static final int SOURCE_CODEC_TYPE_APTX    = 2;
+    public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
+    public static final int SOURCE_CODEC_TYPE_LDAC    = 4;
+    public static final int SOURCE_CODEC_TYPE_MAX     = 5;
 
     public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
 
+    public static final int CODEC_PRIORITY_DISABLED = -1;
     public static final int CODEC_PRIORITY_DEFAULT = 0;
     public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
 
@@ -89,7 +74,7 @@
 
     public BluetoothCodecConfig(int codecType, int codecPriority,
                                 int sampleRate, int bitsPerSample,
-                                int channelMode,long codecSpecific1,
+                                int channelMode, long codecSpecific1,
                                 long codecSpecific2, long codecSpecific3,
                                 long codecSpecific4) {
         mCodecType = codecType;
@@ -127,13 +112,93 @@
                             mCodecSpecific2, mCodecSpecific3, mCodecSpecific4);
     }
 
+    /**
+     * Checks whether the object contains valid codec configuration.
+     *
+     * @return true if the object contains valid codec configuration,
+     * otherwise false.
+     */
+    public boolean isValid() {
+        return (mSampleRate != SAMPLE_RATE_NONE) &&
+            (mBitsPerSample != BITS_PER_SAMPLE_NONE) &&
+            (mChannelMode != CHANNEL_MODE_NONE);
+    }
+
+    /**
+     * Adds capability string to an existing string.
+     *
+     * @param prevStr the previous string with the capabilities. Can be
+     * a null pointer.
+     * @param capStr the capability string to append to prevStr argument.
+     * @return the result string in the form "prevStr|capStr".
+     */
+    private static String appendCapabilityToString(String prevStr,
+                                                   String capStr) {
+        if (prevStr == null) {
+            return capStr;
+        }
+        return prevStr + "|" + capStr;
+    }
+
     @Override
     public String toString() {
-        return "{mCodecType:" + mCodecType +
+        String sampleRateStr = null;
+        if (mSampleRate == SAMPLE_RATE_NONE) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "NONE");
+        }
+        if ((mSampleRate & SAMPLE_RATE_44100) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "44100");
+        }
+        if ((mSampleRate & SAMPLE_RATE_48000) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "48000");
+        }
+        if ((mSampleRate & SAMPLE_RATE_88200) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "88200");
+        }
+        if ((mSampleRate & SAMPLE_RATE_96000) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "96000");
+        }
+        if ((mSampleRate & SAMPLE_RATE_176400) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "176400");
+        }
+        if ((mSampleRate & SAMPLE_RATE_192000) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "192000");
+        }
+
+        String bitsPerSampleStr = null;
+        if (mBitsPerSample == BITS_PER_SAMPLE_NONE) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "NONE");
+        }
+        if ((mBitsPerSample & BITS_PER_SAMPLE_16) != 0) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "16");
+        }
+        if ((mBitsPerSample & BITS_PER_SAMPLE_24) != 0) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "24");
+        }
+        if ((mBitsPerSample & BITS_PER_SAMPLE_32) != 0) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "32");
+        }
+
+        String channelModeStr = null;
+        if (mChannelMode == CHANNEL_MODE_NONE) {
+            channelModeStr = appendCapabilityToString(channelModeStr, "NONE");
+        }
+        if ((mChannelMode & CHANNEL_MODE_MONO) != 0) {
+            channelModeStr = appendCapabilityToString(channelModeStr, "MONO");
+        }
+        if ((mChannelMode & CHANNEL_MODE_STEREO) != 0) {
+            channelModeStr = appendCapabilityToString(channelModeStr, "STEREO");
+        }
+
+        return "{codecName:" + getCodecName() +
+            ",mCodecType:" + mCodecType +
             ",mCodecPriority:" + mCodecPriority +
             ",mSampleRate:" + String.format("0x%x", mSampleRate) +
+            "(" + sampleRateStr + ")" +
             ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample) +
+            "(" + bitsPerSampleStr + ")" +
             ",mChannelMode:" + String.format("0x%x", mChannelMode) +
+            "(" + channelModeStr + ")" +
             ",mCodecSpecific1:" + mCodecSpecific1 +
             ",mCodecSpecific2:" + mCodecSpecific2 +
             ",mCodecSpecific3:" + mCodecSpecific3 +
@@ -180,7 +245,32 @@
     }
 
     /**
-     * Returns the codec type.
+     * Gets the codec name.
+     *
+     * @return the codec name
+     */
+    public String getCodecName() {
+        switch (mCodecType) {
+        case SOURCE_CODEC_TYPE_SBC:
+            return "SBC";
+        case SOURCE_CODEC_TYPE_AAC:
+            return "AAC";
+        case SOURCE_CODEC_TYPE_APTX:
+            return "aptX";
+        case SOURCE_CODEC_TYPE_APTX_HD:
+            return "aptX HD";
+        case SOURCE_CODEC_TYPE_LDAC:
+            return "LDAC";
+        case SOURCE_CODEC_TYPE_INVALID:
+            return "INVALID CODEC";
+        default:
+            break;
+        }
+        return "UNKNOWN CODEC(" + mCodecType + ")";
+    }
+
+    /**
+     * Gets the codec type.
      * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}.
      *
      * @return the codec type
@@ -190,7 +280,7 @@
     }
 
     /**
-     * Returns the codec selection priority.
+     * Gets the codec selection priority.
      * The codec selection priority is relative to other codecs: larger value
      * means higher priority. If 0, reset to default.
      *
@@ -201,7 +291,7 @@
     }
 
     /**
-     * Returns the codec sample rate. The value can be a bitmask with all
+     * Gets the codec sample rate. The value can be a bitmask with all
      * supported sample rates:
      * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or
      * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or
@@ -218,7 +308,7 @@
     }
 
     /**
-     * Returns the codec bits per sample. The value can be a bitmask with all
+     * Gets the codec bits per sample. The value can be a bitmask with all
      * bits per sample supported:
      * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or
      * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or
@@ -232,7 +322,7 @@
     }
 
     /**
-     * Returns the codec channel mode. The value can be a bitmask with all
+     * Gets the codec channel mode. The value can be a bitmask with all
      * supported channel modes:
      * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or
      * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or
@@ -245,7 +335,7 @@
     }
 
     /**
-     * Returns a codec specific value1.
+     * Gets a codec specific value1.
      *
      * @return a codec specific value1.
      */
@@ -254,7 +344,7 @@
     }
 
     /**
-     * Returns a codec specific value2.
+     * Gets a codec specific value2.
      *
      * @return a codec specific value2
      */
@@ -263,7 +353,7 @@
     }
 
     /**
-     * Returns a codec specific value3.
+     * Gets a codec specific value3.
      *
      * @return a codec specific value3
      */
@@ -272,7 +362,7 @@
     }
 
     /**
-     * Returns a codec specific value4.
+     * Gets a codec specific value4.
      *
      * @return a codec specific value4
      */
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/core/java/android/bluetooth/BluetoothCodecStatus.aidl
similarity index 74%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to core/java/android/bluetooth/BluetoothCodecStatus.aidl
index 62d5603..f9c3a3d 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.bluetooth;
 
-parcelable HomeSP;
+parcelable BluetoothCodecStatus;
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
new file mode 100644
index 0000000..c8cd8d1
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.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 android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Represents the codec status (configuration and capability) for a Bluetooth
+ * A2DP source device.
+ *
+ * {@see BluetoothA2dp}
+ *
+ * {@hide}
+ */
+public final class BluetoothCodecStatus implements Parcelable {
+    /**
+     * Extra for the codec configuration intents of the individual profiles.
+     *
+     * This extra represents the current codec status of the A2DP
+     * profile.
+     */
+    public static final String EXTRA_CODEC_STATUS =
+        "android.bluetooth.codec.extra.CODEC_STATUS";
+
+    private final BluetoothCodecConfig mCodecConfig;
+    private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
+    private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
+
+    public BluetoothCodecStatus(BluetoothCodecConfig codecConfig,
+                                BluetoothCodecConfig[] codecsLocalCapabilities,
+                                BluetoothCodecConfig[] codecsSelectableCapabilities) {
+        mCodecConfig = codecConfig;
+        mCodecsLocalCapabilities = codecsLocalCapabilities;
+        mCodecsSelectableCapabilities = codecsSelectableCapabilities;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothCodecStatus) {
+            BluetoothCodecStatus other = (BluetoothCodecStatus)o;
+            return (Objects.equals(other.mCodecConfig, mCodecConfig) &&
+                    Objects.equals(other.mCodecsLocalCapabilities,
+                                   mCodecsLocalCapabilities) &&
+                    Objects.equals(other.mCodecsSelectableCapabilities,
+                                   mCodecsSelectableCapabilities));
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCodecConfig, mCodecsLocalCapabilities,
+                            mCodecsLocalCapabilities);
+    }
+
+    @Override
+    public String toString() {
+        return "{mCodecConfig:" + mCodecConfig +
+            ",mCodecsLocalCapabilities:" + Arrays.toString(mCodecsLocalCapabilities) +
+            ",mCodecsSelectableCapabilities:" + Arrays.toString(mCodecsSelectableCapabilities) +
+            "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothCodecStatus> CREATOR =
+            new Parcelable.Creator<BluetoothCodecStatus>() {
+        public BluetoothCodecStatus createFromParcel(Parcel in) {
+            final BluetoothCodecConfig codecConfig = in.readTypedObject(BluetoothCodecConfig.CREATOR);
+            final BluetoothCodecConfig[] codecsLocalCapabilities = in.createTypedArray(BluetoothCodecConfig.CREATOR);
+            final BluetoothCodecConfig[] codecsSelectableCapabilities = in.createTypedArray(BluetoothCodecConfig.CREATOR);
+
+            return new BluetoothCodecStatus(codecConfig,
+                                            codecsLocalCapabilities,
+                                            codecsSelectableCapabilities);
+        }
+        public BluetoothCodecStatus[] newArray(int size) {
+            return new BluetoothCodecStatus[size];
+        }
+    };
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeTypedObject(mCodecConfig, 0);
+        out.writeTypedArray(mCodecsLocalCapabilities, 0);
+        out.writeTypedArray(mCodecsSelectableCapabilities, 0);
+    }
+
+    /**
+     * Gets the current codec configuration.
+     *
+     * @return the current codec configuration
+     */
+    public BluetoothCodecConfig getCodecConfig() {
+        return mCodecConfig;
+    }
+
+    /**
+     * Gets the codecs local capabilities.
+     *
+     * @return an array with the codecs local capabilities
+     */
+    public BluetoothCodecConfig[] getCodecsLocalCapabilities() {
+        return mCodecsLocalCapabilities;
+    }
+
+    /**
+     * Gets the codecs selectable capabilities.
+     *
+     * @return an array with the codecs selectable capabilities
+     */
+    public BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
+        return mCodecsSelectableCapabilities;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 5c9e2ee..31fc294 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -592,6 +592,42 @@
      */
     public static final int TRANSPORT_LE = 2;
 
+    /**
+     * 1M initiating PHY.
+     */
+    public static final int PHY_LE_1M = 1;
+
+    /**
+     * 2M initiating PHY.
+     */
+    public static final int PHY_LE_2M = 2;
+
+    /**
+     * LE Coded initiating PHY.
+     */
+    public static final int PHY_LE_CODED = 4;
+
+    /**
+     * Any LE PHY.
+     */
+    public static final int PHY_LE_ANY = PHY_LE_1M | PHY_LE_2M | PHY_LE_CODED;
+
+    /**
+     * No preferred coding when transmitting on the LE Coded PHY.
+     */
+    public static final int PHY_OPTION_NO_PREFERRED = 0;
+
+    /**
+     * Prefer the S=2 coding to be used when transmitting on the LE Coded PHY.
+     */
+    public static final int PHY_OPTION_S2 = 1;
+
+    /**
+     * Prefer the S=8 coding to be used when transmitting on the LE Coded PHY.
+     */
+    public static final int PHY_OPTION_S8 = 2;
+
+
     /** @hide */
     public static final String EXTRA_MAS_INSTANCE =
         "android.bluetooth.device.extra.MAS_INSTANCE";
@@ -1412,6 +1448,27 @@
     }
 
     /**
+     * Create an L2cap {@link BluetoothSocket} ready to start an insecure
+     * outgoing connection to this remote device on given channel.
+     * <p>The remote device will be not authenticated and communication on this
+     * socket will not be encrypted.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+     * connection.
+     * <p>Valid L2CAP PSM channels are in range 1 to 2^16.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param channel L2cap PSM/channel to connect to
+     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or
+     *                     insufficient permissions
+     * @hide
+     */
+    public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException {
+        return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel,
+                null);
+    }
+
+    /**
      * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
      * outgoing connection to this remote device using SDP lookup of uuid.
      * <p>This is designed to be used with {@link
@@ -1594,6 +1651,67 @@
      */
     public BluetoothGatt connectGatt(Context context, boolean autoConnect,
                                      BluetoothGattCallback callback, int transport) {
+        return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M));
+    }
+
+    /**
+     * 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).
+     * @throws IllegalArgumentException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+                                     BluetoothGattCallbackExt callback) {
+        return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO));
+    }
+
+    /**
+     * 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}
+     * @throws IllegalArgumentException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+                                     BluetoothGattCallbackExt callback, int transport) {
+        return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO, PHY_LE_1M));
+    }
+
+    /**
+     * 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}, {@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
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+                                     BluetoothGattCallbackExt callback, int transport, int phy) {
         // TODO(Bluetooth) check whether platform support BLE
         //     Do the check here or in GattServer?
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -1604,7 +1722,7 @@
                 // BLE is not supported
                 return null;
             }
-            BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport);
+            BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, phy);
             gatt.connect(autoConnect, callback);
             return gatt;
         } catch (RemoteException e) {Log.e(TAG, "", e);}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 97a3297..0cb69ae 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -31,7 +31,7 @@
  * <p>This class provides Bluetooth GATT functionality to enable communication
  * with Bluetooth Smart or Smart Ready devices.
  *
- * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
+ * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallbackExt}
  * and call {@link BluetoothDevice#connectGatt} to get a instance of this class.
  * GATT capable devices can be discovered using the Bluetooth device discovery or BLE
  * scan process.
@@ -42,7 +42,7 @@
     private static final boolean VDBG = false;
 
     private IBluetoothGatt mService;
-    private BluetoothGattCallback mCallback;
+    private BluetoothGattCallbackExt mCallback;
     private int mClientIf;
     private BluetoothDevice mDevice;
     private boolean mAutoConnect;
@@ -51,6 +51,7 @@
     private final Object mStateLock = new Object();
     private Boolean mDeviceBusy = false;
     private int mTransport;
+    private int mPhy;
 
     private static final int AUTH_RETRY_STATE_IDLE = 0;
     private static final int AUTH_RETRY_STATE_NO_MITM = 1;
@@ -132,10 +133,10 @@
     /*package*/ static final int AUTHENTICATION_MITM = 2;
 
     /**
-     * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation.
+     * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallbackExt implementation.
      */
-    private final IBluetoothGattCallback mBluetoothGattCallback =
-        new IBluetoothGattCallback.Stub() {
+    private final IBluetoothGattCallbackExt mBluetoothGattCallbackExt =
+        new IBluetoothGattCallbackExt.Stub() {
             /**
              * Application interface registered - app is ready to go
              * @hide
@@ -161,13 +162,51 @@
                 }
                 try {
                     mService.clientConnect(mClientIf, mDevice.getAddress(),
-                                           !mAutoConnect, mTransport); // autoConnect is inverse of "isDirect"
+                                           !mAutoConnect, mTransport, mPhy); // autoConnect is inverse of "isDirect"
                 } catch (RemoteException e) {
                     Log.e(TAG,"",e);
                 }
             }
 
             /**
+             * Phy update callback
+             * @hide
+             */
+            @Override
+            public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
+                if (DBG) Log.d(TAG, "onPhyUpdate() - status=" + status
+                                 + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
+                if (!address.equals(mDevice.getAddress())) {
+                    return;
+                }
+
+                try {
+                    mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception in callback", ex);
+                }
+            }
+
+            /**
+             * Phy read callback
+             * @hide
+             */
+            @Override
+            public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
+                if (DBG) Log.d(TAG, "onPhyRead() - status=" + status
+                                 + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
+                if (!address.equals(mDevice.getAddress())) {
+                    return;
+                }
+
+                try {
+                    mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception in callback", ex);
+                }
+            }
+
+            /**
              * Client connection state changed
              * @hide
              */
@@ -503,10 +542,11 @@
         };
 
     /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
-                                int transport) {
+                                int transport, int phy) {
         mService = iGatt;
         mDevice = device;
         mTransport = transport;
+        mPhy = phy;
         mServices = new ArrayList<BluetoothGattService>();
 
         mConnState = CONN_STATE_IDLE;
@@ -578,7 +618,7 @@
     /**
      * Register an application callback to start using GATT.
      *
-     * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
+     * <p>This is an asynchronous call. The callback {@link BluetoothGattCallbackExt#onAppRegistered}
      * is used to notify success or failure if the function returns true.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -587,7 +627,7 @@
      * @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(BluetoothGattCallbackExt callback) {
         if (DBG) Log.d(TAG, "registerApp()");
         if (mService == null) return false;
 
@@ -596,7 +636,7 @@
         if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
 
         try {
-            mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+            mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallbackExt);
         } catch (RemoteException e) {
             Log.e(TAG,"",e);
             return false;
@@ -626,7 +666,7 @@
      *
      * <p>The connection may not be established right away, but will be
      * completed when the remote device is available. A
-     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+     * {@link BluetoothGattCallbackExt#onConnectionStateChange} callback will be
      * invoked when the connection state changes as a result of this function.
      *
      * <p>The autoConnect parameter determines whether to actively connect to
@@ -644,7 +684,7 @@
      *                    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, BluetoothGattCallbackExt callback) {
         if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
         synchronized(mStateLock) {
             if (mConnState != CONN_STATE_IDLE) {
@@ -696,7 +736,7 @@
     public boolean connect() {
         try {
             mService.clientConnect(mClientIf, mDevice.getAddress(),
-                                   false, mTransport); // autoConnect is inverse of "isDirect"
+                                   false, mTransport, mPhy); // autoConnect is inverse of "isDirect"
             return true;
         } catch (RemoteException e) {
             Log.e(TAG,"",e);
@@ -705,6 +745,45 @@
     }
 
     /**
+     * Set the preferred connection PHY for this app. Please note that this is just a
+     * recommendation, wether the PHY change will happen depends on other applications peferences,
+     * local and remote controller capabilities. Controller can override these settings.
+     * <p>
+     * {@link BluetoothGattCallbackExt#onPhyUpdate} will be triggered as a result of this call, even
+     * 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}.
+     * @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}.
+     * @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}
+     */
+    public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
+        try {
+            mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy,
+                                           phyOptions);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
+     * in {@link BluetoothGattCallbackExt#onPhyRead}
+     */
+    public void readPhy() {
+        try {
+            mService.clientReadPhy(mClientIf, mDevice.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
      * Return the remote bluetooth device this GATT client targets to
      *
      * @return remote bluetooth device
@@ -718,7 +797,7 @@
      * characteristics and descriptors.
      *
      * <p>This is an asynchronous operation. Once service discovery is completed,
-     * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
+     * the {@link BluetoothGattCallbackExt#onServicesDiscovered} callback is
      * triggered. If the discovery was successful, the remote services can be
      * retrieved using the {@link #getServices} function.
      *
@@ -797,7 +876,7 @@
      * Reads the requested characteristic from the associated remote device.
      *
      * <p>This is an asynchronous operation. The result of the read operation
-     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
+     * is reported by the {@link BluetoothGattCallbackExt#onCharacteristicRead}
      * callback.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -839,7 +918,7 @@
      * Writes a given characteristic and its values to the associated remote device.
      *
      * <p>Once the write operation has been completed, the
-     * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
+     * {@link BluetoothGattCallbackExt#onCharacteristicWrite} callback is invoked,
      * reporting the result of the operation.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -883,7 +962,7 @@
      * Reads the value for a given descriptor from the associated remote device.
      *
      * <p>Once the read operation has been completed, the
-     * {@link BluetoothGattCallback#onDescriptorRead} callback is
+     * {@link BluetoothGattCallbackExt#onDescriptorRead} callback is
      * triggered, signaling the result of the operation.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -924,7 +1003,7 @@
     /**
      * Write the value of a given descriptor to the associated remote device.
      *
-     * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
+     * <p>A {@link BluetoothGattCallbackExt#onDescriptorWrite} callback is
      * triggered to report the result of the write operation.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -968,7 +1047,7 @@
      * <p>Once a reliable write transaction has been initiated, all calls
      * to {@link #writeCharacteristic} are sent to the remote device for
      * verification and queued up for atomic execution. The application will
-     * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
+     * receive an {@link BluetoothGattCallbackExt#onCharacteristicWrite} callback
      * in response to every {@link #writeCharacteristic} call and is responsible
      * for verifying if the value has been transmitted accurately.
      *
@@ -1002,7 +1081,7 @@
      * <p>This function will commit all queued up characteristic write
      * operations for a given remote device.
      *
-     * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
+     * <p>A {@link BluetoothGattCallbackExt#onReliableWriteCompleted} callback is
      * invoked to indicate whether the transaction has been executed correctly.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -1059,7 +1138,7 @@
      * Enable or disable notifications/indications for a given characteristic.
      *
      * <p>Once notifications are enabled for a characteristic, a
-     * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
+     * {@link BluetoothGattCallbackExt#onCharacteristicChanged} callback will be
      * triggered if the remote device indicates that the given characteristic
      * has changed.
      *
@@ -1114,7 +1193,7 @@
     /**
      * Read the RSSI for a connected remote device.
      *
-     * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
+     * <p>The {@link BluetoothGattCallbackExt#onReadRemoteRssi} callback will be
      * invoked when the RSSI value has been read.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
@@ -1142,7 +1221,7 @@
      * the data sent is truncated to the MTU size. This function may be used
      * to request a larger MTU size to be able to send more data at once.
      *
-     * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
+     * <p>A {@link BluetoothGattCallbackExt#onMtuChanged} callback will indicate
      * whether this operation was successful.
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index a915620..4da106d 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -18,138 +18,22 @@
 
 /**
  * This abstract class is used to implement {@link BluetoothGatt} callbacks.
+ * @deprecated use {@link BluetoothGattCallbackExt}
  */
-public abstract class BluetoothGattCallback {
+public abstract class BluetoothGattCallback extends BluetoothGattCallbackExt {
 
     /**
-     * Callback indicating when GATT client has connected/disconnected to/from a remote
-     * GATT server.
-     *
-     * @param gatt GATT client
-     * @param status Status of the connect or disconnect operation.
-     *               {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
-     * @param newState Returns the new connection state. Can be one of
-     *                  {@link BluetoothProfile#STATE_DISCONNECTED} or
-     *                  {@link BluetoothProfile#STATE_CONNECTED}
+     * @hide
      */
-    public void onConnectionStateChange(BluetoothGatt gatt, int status,
-                                        int newState) {
+    @Override
+    public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
     }
 
     /**
-     * Callback invoked when the list of remote services, characteristics and descriptors
-     * for the remote device have been updated, ie new services have been discovered.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices}
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
-     *               has been explored successfully.
+     * @hide
      */
-    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+    @Override
+    public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
     }
 
-    /**
-     * Callback reporting the result of a characteristic read operation.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic}
-     * @param characteristic Characteristic that was read from the associated
-     *                       remote device.
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
-     *               was completed successfully.
-     */
-    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
-                                     int status) {
-    }
-
-    /**
-     * Callback indicating the result of a characteristic write operation.
-     *
-     * <p>If this callback is invoked while a reliable write transaction is
-     * in progress, the value of the characteristic represents the value
-     * reported by the remote device. An application should compare this
-     * value to the desired value to be written. If the values don't match,
-     * the application must abort the reliable write transaction.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic}
-     * @param characteristic Characteristic that was written to the associated
-     *                       remote device.
-     * @param status The result of the write operation
-     *               {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
-     */
-    public void onCharacteristicWrite(BluetoothGatt gatt,
-                                      BluetoothGattCharacteristic characteristic, int status) {
-    }
-
-    /**
-     * Callback triggered as a result of a remote characteristic notification.
-     *
-     * @param gatt GATT client the characteristic is associated with
-     * @param characteristic Characteristic that has been updated as a result
-     *                       of a remote notification event.
-     */
-    public void onCharacteristicChanged(BluetoothGatt gatt,
-                                        BluetoothGattCharacteristic characteristic) {
-    }
-
-    /**
-     * Callback reporting the result of a descriptor read operation.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor}
-     * @param descriptor Descriptor that was read from the associated
-     *                   remote device.
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
-     *               was completed successfully
-     */
-    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
-                                 int status) {
-    }
-
-    /**
-     * Callback indicating the result of a descriptor write operation.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor}
-     * @param descriptor Descriptor that was writte to the associated
-     *                   remote device.
-     * @param status The result of the write operation
-     *               {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
-     */
-    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
-                                  int status) {
-    }
-
-    /**
-     * Callback invoked when a reliable write transaction has been completed.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite}
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write
-     *               transaction was executed successfully
-     */
-    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
-    }
-
-    /**
-     * Callback reporting the RSSI for a remote device connection.
-     *
-     * This callback is triggered in response to the
-     * {@link BluetoothGatt#readRemoteRssi} function.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi}
-     * @param rssi The RSSI value for the remote device
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully
-     */
-    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
-    }
-
-    /**
-     * Callback indicating the MTU for a given device connection has changed.
-     *
-     * This callback is triggered in response to the
-     * {@link BluetoothGatt#requestMtu} function, or in response to a connection
-     * event.
-     *
-     * @param gatt GATT client invoked {@link BluetoothGatt#requestMtu}
-     * @param mtu The new MTU size
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the MTU has been changed successfully
-     */
-    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
-    }
 }
diff --git a/core/java/android/bluetooth/BluetoothGattCallbackExt.java b/core/java/android/bluetooth/BluetoothGattCallbackExt.java
new file mode 100644
index 0000000..63774c8
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCallbackExt.java
@@ -0,0 +1,182 @@
+/*
+ * 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.bluetooth;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGatt} callbacks.
+ */
+public abstract class BluetoothGattCallbackExt {
+
+    /**
+     * Callback triggered as result of {@link BluetoothGatt#setPreferredPhy}, or as a result of
+     * remote device changing the PHY.
+     *
+     * @param gatt GATT client
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
+     *             {@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
+     */
+    public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback triggered as result of {@link BluetoothGatt#readPhy}
+     *
+     * @param gatt GATT client
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
+     *             {@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
+     */
+    public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback indicating when GATT client has connected/disconnected to/from a remote
+     * GATT server.
+     *
+     * @param gatt GATT client
+     * @param status Status of the connect or disconnect operation.
+     *               {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+     * @param newState Returns the new connection state. Can be one of
+     *                  {@link BluetoothProfile#STATE_DISCONNECTED} or
+     *                  {@link BluetoothProfile#STATE_CONNECTED}
+     */
+    public void onConnectionStateChange(BluetoothGatt gatt, int status,
+                                        int newState) {
+    }
+
+    /**
+     * Callback invoked when the list of remote services, characteristics and descriptors
+     * for the remote device have been updated, ie new services have been discovered.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices}
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
+     *               has been explored successfully.
+     */
+    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+    }
+
+    /**
+     * Callback reporting the result of a characteristic read operation.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic}
+     * @param characteristic Characteristic that was read from the associated
+     *                       remote device.
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+     *               was completed successfully.
+     */
+    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
+                                     int status) {
+    }
+
+    /**
+     * Callback indicating the result of a characteristic write operation.
+     *
+     * <p>If this callback is invoked while a reliable write transaction is
+     * in progress, the value of the characteristic represents the value
+     * reported by the remote device. An application should compare this
+     * value to the desired value to be written. If the values don't match,
+     * the application must abort the reliable write transaction.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic}
+     * @param characteristic Characteristic that was written to the associated
+     *                       remote device.
+     * @param status The result of the write operation
+     *               {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+     */
+    public void onCharacteristicWrite(BluetoothGatt gatt,
+                                      BluetoothGattCharacteristic characteristic, int status) {
+    }
+
+    /**
+     * Callback triggered as a result of a remote characteristic notification.
+     *
+     * @param gatt GATT client the characteristic is associated with
+     * @param characteristic Characteristic that has been updated as a result
+     *                       of a remote notification event.
+     */
+    public void onCharacteristicChanged(BluetoothGatt gatt,
+                                        BluetoothGattCharacteristic characteristic) {
+    }
+
+    /**
+     * Callback reporting the result of a descriptor read operation.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor}
+     * @param descriptor Descriptor that was read from the associated
+     *                   remote device.
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+     *               was completed successfully
+     */
+    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                                 int status) {
+    }
+
+    /**
+     * Callback indicating the result of a descriptor write operation.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor}
+     * @param descriptor Descriptor that was writte to the associated
+     *                   remote device.
+     * @param status The result of the write operation
+     *               {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+     */
+    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                                  int status) {
+    }
+
+    /**
+     * Callback invoked when a reliable write transaction has been completed.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite}
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write
+     *               transaction was executed successfully
+     */
+    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+    }
+
+    /**
+     * Callback reporting the RSSI for a remote device connection.
+     *
+     * This callback is triggered in response to the
+     * {@link BluetoothGatt#readRemoteRssi} function.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi}
+     * @param rssi The RSSI value for the remote device
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully
+     */
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+    }
+
+    /**
+     * Callback indicating the MTU for a given device connection has changed.
+     *
+     * This callback is triggered in response to the
+     * {@link BluetoothGatt#requestMtu} function, or in response to a connection
+     * event.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#requestMtu}
+     * @param mtu The new MTU size
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the MTU has been changed successfully
+     */
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 5ffceba..9ee739f 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -46,7 +46,7 @@
 
     private BluetoothAdapter mAdapter;
     private IBluetoothGatt mService;
-    private BluetoothGattServerCallback mCallback;
+    private BluetoothGattServerCallbackExt mCallback;
 
     private Object mServerIfLock = new Object();
     private int mServerIf;
@@ -59,8 +59,8 @@
     /**
      * Bluetooth GATT interface callbacks
      */
-    private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
-        new IBluetoothGattServerCallback.Stub() {
+    private final IBluetoothGattServerCallbackExt mBluetoothGattServerCallback =
+        new IBluetoothGattServerCallbackExt.Stub() {
             /**
              * Application interface registered - app is ready to go
              * @hide
@@ -292,6 +292,42 @@
                     Log.w(TAG, "Unhandled exception: " + ex);
                 }
             }
+
+            /**
+             * The PHY for a connection was updated
+             * @hide
+             */
+            public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
+                if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+                    + ", rxPHy=" + rxPhy);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onPhyUpdate(device, txPhy, rxPhy, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * The PHY for a connection was read
+             * @hide
+             */
+            public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
+                if (DBG) Log.d(TAG, "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+                    + ", rxPHy=" + rxPhy);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                if (device == null) return;
+
+                try {
+                    mCallback.onPhyRead(device, txPhy, rxPhy, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
         };
 
     /**
@@ -360,7 +396,7 @@
      * @return true, the callback will be called to notify success or failure,
      *         false on immediate error
      */
-    /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
+    /*package*/ boolean registerCallback(BluetoothGattServerCallbackExt callback) {
         if (DBG) Log.d(TAG, "registerCallback()");
         if (mService == null) {
             Log.e(TAG, "GATT service not available");
@@ -436,7 +472,7 @@
      *
      * <p>The connection may not be established right away, but will be
      * completed when the remote device is available. A
-     * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
+     * {@link BluetoothGattServerCallbackExt#onConnectionStateChange} callback will be
      * invoked when the connection state changes as a result of this function.
      *
      * <p>The autoConnect paramter determines whether to actively connect to
@@ -488,16 +524,58 @@
     }
 
     /**
+     * Set the preferred connection PHY for this app. Please note that this is just a
+     * recommendation, wether the PHY change will happen depends on other applications peferences,
+     * local and remote controller capabilities. Controller can override these settings.
+     * <p>
+     * {@link BluetoothGattServerCallbackExt#onPhyUpdate} will be triggered as a result of this call, even
+     * if no PHY change happens. It is also triggered when remote device updates the PHY.
+     *
+     * @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}.
+     * @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}.
+     * @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}
+     */
+    public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) {
+        try {
+            mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy,
+                                           phyOptions);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
+     * in {@link BluetoothGattServerCallbackExt#onPhyRead}
+     *
+     * @param device The remote device to send this response to
+     */
+    public void readPhy(BluetoothDevice device) {
+        try {
+            mService.serverReadPhy(mServerIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
      * Send a response to a read or write request to a remote device.
      *
      * <p>This function must be invoked in when a remote read/write request
      * is received by one of these callback methods:
      *
      * <ul>
-     *      <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
-     *      <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
-     *      <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
-     *      <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
+     *      <li>{@link BluetoothGattServerCallbackExt#onCharacteristicReadRequest}
+     *      <li>{@link BluetoothGattServerCallbackExt#onCharacteristicWriteRequest}
+     *      <li>{@link BluetoothGattServerCallbackExt#onDescriptorReadRequest}
+     *      <li>{@link BluetoothGattServerCallbackExt#onDescriptorWriteRequest}
      * </ul>
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 2afcf9a..75ceb52 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -20,141 +20,21 @@
 
 /**
  * This abstract class is used to implement {@link BluetoothGattServer} callbacks.
+ * @deprecated please use {@link BluetoothGattServerCallbackExt}
  */
-public abstract class BluetoothGattServerCallback {
+public abstract class BluetoothGattServerCallback extends BluetoothGattServerCallbackExt {
 
     /**
-     * Callback indicating when a remote device has been connected or disconnected.
-     *
-     * @param device Remote device that has been connected or disconnected.
-     * @param status Status of the connect or disconnect operation.
-     * @param newState Returns the new connection state. Can be one of
-     *                  {@link BluetoothProfile#STATE_DISCONNECTED} or
-     *                  {@link BluetoothProfile#STATE_CONNECTED}
+     * @hide
      */
-    public void onConnectionStateChange(BluetoothDevice device, int status,
-                                        int newState) {
+    @Override
+    public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
     }
 
     /**
-     * Indicates whether a local service has been added successfully.
-     *
-     * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service
-     *               was added successfully.
-     * @param service The service that has been added
+     * @hide
      */
-    public void onServiceAdded(int status, BluetoothGattService service) {
-    }
-
-    /**
-     * A remote client has requested to read a local characteristic.
-     *
-     * <p>An application must call {@link BluetoothGattServer#sendResponse}
-     * to complete the request.
-     *
-     * @param device The remote device that has requested the read operation
-     * @param requestId The Id of the request
-     * @param offset Offset into the value of the characteristic
-     * @param characteristic Characteristic to be read
-     */
-    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
-                        int offset, BluetoothGattCharacteristic characteristic) {
-    }
-
-    /**
-     * A remote client has requested to write to a local characteristic.
-     *
-     * <p>An application must call {@link BluetoothGattServer#sendResponse}
-     * to complete the request.
-     *
-     * @param device The remote device that has requested the write operation
-     * @param requestId The Id of the request
-     * @param characteristic Characteristic to be written to.
-     * @param preparedWrite true, if this write operation should be queued for
-     *                      later execution.
-     * @param responseNeeded true, if the remote device requires a response
-     * @param offset The offset given for the value
-     * @param value The value the client wants to assign to the characteristic
-     */
-    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
-                                             BluetoothGattCharacteristic characteristic,
-                                             boolean preparedWrite, boolean responseNeeded,
-                                             int offset, byte[] value) {
-    }
-
-    /**
-     * A remote client has requested to read a local descriptor.
-     *
-     * <p>An application must call {@link BluetoothGattServer#sendResponse}
-     * to complete the request.
-     *
-     * @param device The remote device that has requested the read operation
-     * @param requestId The Id of the request
-     * @param offset Offset into the value of the characteristic
-     * @param descriptor Descriptor to be read
-     */
-    public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
-                                        int offset, BluetoothGattDescriptor descriptor) {
-    }
-
-    /**
-     * A remote client has requested to write to a local descriptor.
-     *
-     * <p>An application must call {@link BluetoothGattServer#sendResponse}
-     * to complete the request.
-     *
-     * @param device The remote device that has requested the write operation
-     * @param requestId The Id of the request
-     * @param descriptor Descriptor to be written to.
-     * @param preparedWrite true, if this write operation should be queued for
-     *                      later execution.
-     * @param responseNeeded true, if the remote device requires a response
-     * @param offset The offset given for the value
-     * @param value The value the client wants to assign to the descriptor
-     */
-    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
-                                         BluetoothGattDescriptor descriptor,
-                                         boolean preparedWrite, boolean responseNeeded,
-                                         int offset,  byte[] value) {
-    }
-
-    /**
-     * Execute all pending write operations for this device.
-     *
-     * <p>An application must call {@link BluetoothGattServer#sendResponse}
-     * to complete the request.
-     *
-     * @param device The remote device that has requested the write operations
-     * @param requestId The Id of the request
-     * @param execute Whether the pending writes should be executed (true) or
-     *                cancelled (false)
-     */
-    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
-    }
-
-    /**
-     * Callback invoked when a notification or indication has been sent to
-     * a remote device.
-     *
-     * <p>When multiple notifications are to be sent, an application must
-     * wait for this callback to be received before sending additional
-     * notifications.
-     *
-     * @param device The remote device the notification has been sent to
-     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the operation was successful
-     */
-    public void onNotificationSent(BluetoothDevice device, int status) {
-    }
-
-    /**
-     * Callback indicating the MTU for a given device connection has changed.
-     *
-     * <p>This callback will be invoked if a remote client has requested to change
-     * the MTU for a given connection.
-     *
-     * @param device The remote device that requested the MTU change
-     * @param mtu The new MTU size
-     */
-    public void onMtuChanged(BluetoothDevice device, int mtu) {
+    @Override
+    public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallbackExt.java b/core/java/android/bluetooth/BluetoothGattServerCallbackExt.java
new file mode 100644
index 0000000..455cce0
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServerCallbackExt.java
@@ -0,0 +1,187 @@
+/*
+ * 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 android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGattServer} callbacks.
+ */
+public abstract class BluetoothGattServerCallbackExt {
+
+    /**
+     * Callback indicating when a remote device has been connected or disconnected.
+     *
+     * @param device Remote device that has been connected or disconnected.
+     * @param status Status of the connect or disconnect operation.
+     * @param newState Returns the new connection state. Can be one of
+     *                  {@link BluetoothProfile#STATE_DISCONNECTED} or
+     *                  {@link BluetoothProfile#STATE_CONNECTED}
+     */
+    public void onConnectionStateChange(BluetoothDevice device, int status,
+                                        int newState) {
+    }
+
+    /**
+     * Indicates whether a local service has been added successfully.
+     *
+     * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service
+     *               was added successfully.
+     * @param service The service that has been added
+     */
+    public void onServiceAdded(int status, BluetoothGattService service) {
+    }
+
+    /**
+     * A remote client has requested to read a local characteristic.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the read operation
+     * @param requestId The Id of the request
+     * @param offset Offset into the value of the characteristic
+     * @param characteristic Characteristic to be read
+     */
+    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+                        int offset, BluetoothGattCharacteristic characteristic) {
+    }
+
+    /**
+     * A remote client has requested to write to a local characteristic.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operation
+     * @param requestId The Id of the request
+     * @param characteristic Characteristic to be written to.
+     * @param preparedWrite true, if this write operation should be queued for
+     *                      later execution.
+     * @param responseNeeded true, if the remote device requires a response
+     * @param offset The offset given for the value
+     * @param value The value the client wants to assign to the characteristic
+     */
+    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+                                             BluetoothGattCharacteristic characteristic,
+                                             boolean preparedWrite, boolean responseNeeded,
+                                             int offset, byte[] value) {
+    }
+
+    /**
+     * A remote client has requested to read a local descriptor.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the read operation
+     * @param requestId The Id of the request
+     * @param offset Offset into the value of the characteristic
+     * @param descriptor Descriptor to be read
+     */
+    public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
+                                        int offset, BluetoothGattDescriptor descriptor) {
+    }
+
+    /**
+     * A remote client has requested to write to a local descriptor.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operation
+     * @param requestId The Id of the request
+     * @param descriptor Descriptor to be written to.
+     * @param preparedWrite true, if this write operation should be queued for
+     *                      later execution.
+     * @param responseNeeded true, if the remote device requires a response
+     * @param offset The offset given for the value
+     * @param value The value the client wants to assign to the descriptor
+     */
+    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+                                         BluetoothGattDescriptor descriptor,
+                                         boolean preparedWrite, boolean responseNeeded,
+                                         int offset,  byte[] value) {
+    }
+
+    /**
+     * Execute all pending write operations for this device.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operations
+     * @param requestId The Id of the request
+     * @param execute Whether the pending writes should be executed (true) or
+     *                cancelled (false)
+     */
+    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+    }
+
+    /**
+     * Callback invoked when a notification or indication has been sent to
+     * a remote device.
+     *
+     * <p>When multiple notifications are to be sent, an application must
+     * wait for this callback to be received before sending additional
+     * notifications.
+     *
+     * @param device The remote device the notification has been sent to
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the operation was successful
+     */
+    public void onNotificationSent(BluetoothDevice device, int status) {
+    }
+
+    /**
+     * Callback indicating the MTU for a given device connection has changed.
+     *
+     * <p>This callback will be invoked if a remote client has requested to change
+     * the MTU for a given connection.
+     *
+     * @param device The remote device that requested the MTU change
+     * @param mtu The new MTU size
+     */
+    public void onMtuChanged(BluetoothDevice device, int mtu) {
+    }
+
+    /**
+     * Callback triggered as result of {@link BluetoothGattServer#setPreferredPhy}, or as a result
+     * of remote device changing the PHY.
+     *
+     * @param device The remote device
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
+     *             {@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
+     */
+    public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback triggered as result of {@link BluetoothGattServer#readPhy}
+     *
+     * @param device The remote device that requested the PHY read
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M},
+     *             {@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
+     */
+    public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index 93790fe..353efff 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -964,38 +964,6 @@
     }
 
     /**
-     * Accept the incoming connection.
-     */
-    public boolean acceptIncomingConnect(BluetoothDevice device) {
-        if (DBG) log("acceptIncomingConnect");
-        if (mService != null && isEnabled()) {
-            try {
-                return mService.acceptIncomingConnect(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
-     * Reject the incoming connection.
-     */
-    public boolean rejectIncomingConnect(BluetoothDevice device) {
-        if (DBG) log("rejectIncomingConnect");
-        if (mService != null) {
-            try {
-                return mService.rejectIncomingConnect(device);
-            } catch (RemoteException e) {Log.e(TAG, e.toString());}
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
      * Returns current audio state of Audio Gateway.
      *
      * Note: This is an internal function and shouldn't be exposed
@@ -1016,13 +984,15 @@
     /**
      * Sets whether audio routing is allowed.
      *
+     * @param device    remote device
+     * @param allowed   if routing is allowed to the device
      * Note: This is an internal function and shouldn't be exposed
      */
-    public void setAudioRouteAllowed(boolean allowed) {
+    public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
         if (mService != null && isEnabled()) {
             try {
-                mService.setAudioRouteAllowed(allowed);
+                mService.setAudioRouteAllowed(device, allowed);
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
         } else {
             Log.w(TAG, "Proxy not attached to service");
@@ -1032,14 +1002,15 @@
 
     /**
      * Returns whether audio routing is allowed.
-     *
+     * @param device    remote device
+     * @return whether the command succeeded
      * Note: This is an internal function and shouldn't be exposed
      */
-    public boolean getAudioRouteAllowed() {
+    public boolean getAudioRouteAllowed(BluetoothDevice device) {
         if (VDBG) log("getAudioRouteAllowed");
         if (mService != null && isEnabled()) {
             try {
-                return mService.getAudioRouteAllowed();
+                return mService.getAudioRouteAllowed(device);
             } catch (RemoteException e) {Log.e(TAG, e.toString());}
         } else {
             Log.w(TAG, "Proxy not attached to service");
@@ -1053,15 +1024,16 @@
      *
      * It setup SCO channel with remote connected Handsfree AG device.
      *
+     * @param device    remote device
      * @return          <code>true</code> if command has been issued successfully;
      *                   <code>false</code> otherwise;
      *                   upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
      *                   intent;
      */
-    public boolean connectAudio() {
+    public boolean connectAudio(BluetoothDevice device) {
         if (mService != null && isEnabled()) {
             try {
-                return mService.connectAudio();
+                return mService.connectAudio(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -1077,15 +1049,16 @@
      *
      * It tears down the SCO channel from remote AG device.
      *
+     * @param   device  remote device
      * @return          <code>true</code> if command has been issued successfully;
      *                   <code>false</code> otherwise;
      *                   upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED}
      *                   intent;
      */
-    public boolean disconnectAudio() {
+    public boolean disconnectAudio(BluetoothDevice device) {
         if (mService != null && isEnabled()) {
             try {
-                return mService.disconnectAudio();
+                return mService.disconnectAudio(device);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 7c5458b..b337817 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -52,6 +52,7 @@
     boolean startDiscovery();
     boolean cancelDiscovery();
     boolean isDiscovering();
+    long getDiscoveryEndMillis();
 
     int getAdapterConnectionState();
     int getProfileConnectionState(int profile);
@@ -103,6 +104,11 @@
     boolean isOffloadedFilteringSupported();
     boolean isOffloadedScanBatchingSupported();
     boolean isActivityAndEnergyReportingSupported();
+    boolean isLe2MPhySupported();
+    boolean isLeCodedPhySupported();
+    boolean isLeExtendedAdvertisingSupported();
+    boolean isLePeriodicAdvertisingSupported();
+    int getLeMaximumAdvertisingDataLength();
     BluetoothActivityEnergyInfo reportActivityInfo();
 
     /**
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 5b524eb..dbb5b7d 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 
 /**
@@ -37,6 +38,6 @@
     oneway void adjustAvrcpAbsoluteVolume(int direction);
     oneway void setAvrcpAbsoluteVolume(int volume);
     boolean isA2dpPlaying(in BluetoothDevice device);
-    BluetoothCodecConfig getCodecConfig();
+    BluetoothCodecStatus getCodecStatus();
     oneway void setCodecConfigPreference(in BluetoothCodecConfig codecConfig);
 }
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index aa2291e..c281c7f 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -20,15 +20,20 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.le.AdvertiseSettings;
 import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
 import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.bluetooth.le.ResultStorageDescriptor;
 import android.os.ParcelUuid;
 import android.os.WorkSource;
 
-import android.bluetooth.IBluetoothGattCallback;
-import android.bluetooth.IBluetoothGattServerCallback;
+import android.bluetooth.IBluetoothGattCallbackExt;
+import android.bluetooth.IBluetoothGattServerCallbackExt;
 import android.bluetooth.le.IAdvertiserCallback;
+import android.bluetooth.le.IAdvertisingSetCallback;
+import android.bluetooth.le.IPeriodicAdvertisingCallback;
 import android.bluetooth.le.IScannerCallback;
 
 /**
@@ -45,18 +50,29 @@
     void stopScan(in int scannerId);
     void flushPendingBatchResults(in int scannerId);
 
-    void registerAdvertiser(in IAdvertiserCallback callback);
-    void unregisterAdvertiser(in int advertiserId);
-    void startMultiAdvertising(in int advertiserId,
-                               in AdvertiseData advertiseData,
-                               in AdvertiseData scanResponse,
-                               in AdvertiseSettings settings);
-    void stopMultiAdvertising(in int advertiserId);
+    void startAdvertisingSet(in AdvertisingSetParameters parameters, in AdvertiseData advertiseData,
+                                in AdvertiseData scanResponse, in PeriodicAdvertisingParameters periodicParameters,
+                                in AdvertiseData periodicData, in int timeout, in IAdvertisingSetCallback callback);
+    void stopAdvertisingSet(in IAdvertisingSetCallback callback);
 
-    void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
+    void enableAdverisingSet(in int advertiserId, in boolean enable, in int timeout);
+    void setAdvertisingData(in int advertiserId, in AdvertiseData data);
+    void setScanResponseData(in int advertiserId, in AdvertiseData data);
+    void setAdvertisingParameters(in int advertiserId, in AdvertisingSetParameters parameters);
+    void setPeriodicAdvertisingParameters(in int advertiserId, in PeriodicAdvertisingParameters parameters);
+    void setPeriodicAdvertisingData(in int advertiserId, in AdvertiseData data);
+    void periodicAdvertisingEnable(in int advertiserId, in boolean enable);
+
+    void registerSync(in ScanResult scanResult, in int skip, in int timeout, in IPeriodicAdvertisingCallback callback);
+    void unregisterSync(in IPeriodicAdvertisingCallback callback);
+
+    void registerClient(in ParcelUuid appId, in IBluetoothGattCallbackExt callback);
+
     void unregisterClient(in int clientIf);
-    void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport);
+    void clientConnect(in int clientIf, in String address, in boolean isDirect, in int transport, in int phy);
     void clientDisconnect(in int clientIf, in String address);
+    void clientSetPreferredPhy(in int clientIf, in String address, in int txPhy, in int rxPhy, in int phyOptions);
+    void clientReadPhy(in int clientIf, in String address);
     void refreshDevice(in int clientIf, in String address);
     void discoverServices(in int clientIf, in String address);
     void readCharacteristic(in int clientIf, in String address, in int handle, in int authReq);
@@ -72,10 +88,12 @@
     void configureMTU(in int clientIf, in String address, in int mtu);
     void connectionParameterUpdate(in int clientIf, in String address, in int connectionPriority);
 
-    void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
+    void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallbackExt callback);
     void unregisterServer(in int serverIf);
     void serverConnect(in int serverIf, in String address, in boolean isDirect, in int transport);
     void serverDisconnect(in int serverIf, in String address);
+    void serverSetPreferredPhy(in int clientIf, in String address, in int txPhy, in int rxPhy, in int phyOptions);
+    void serverReadPhy(in int clientIf, in String address);
     void addService(in int serverIf, in BluetoothGattService service);
     void removeService(in int serverIf, in int handle);
     void clearServices(in int serverIf);
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
similarity index 88%
rename from core/java/android/bluetooth/IBluetoothGattCallback.aidl
rename to core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
index 72cb618..736f4b2 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallbackExt.aidl
@@ -22,10 +22,12 @@
  * Callback definitions for interacting with BLE / GATT
  * @hide
  */
-oneway interface IBluetoothGattCallback {
+oneway interface IBluetoothGattCallbackExt {
     void onClientRegistered(in int status, in int clientIf);
     void onClientConnectionState(in int status, in int clientIf,
                                  in boolean connected, in String address);
+    void onPhyUpdate(in String address, in int txPhy, in int rxPhy, in int status);
+    void onPhyRead(in String address, in int txPhy, in int rxPhy, in int status);
     void onSearchComplete(in String address, in List<BluetoothGattService> services, in int status);
     void onCharacteristicRead(in String address, in int status, in int handle, in byte[] value);
     void onCharacteristicWrite(in String address, in int status, in int handle);
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
similarity index 87%
rename from core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
rename to core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
index 1a924fb..091ffb3 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallbackExt.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -21,7 +21,7 @@
  * Callback definitions for interacting with BLE / GATT
  * @hide
  */
-oneway interface IBluetoothGattServerCallback {
+oneway interface IBluetoothGattServerCallbackExt {
     void onServerRegistered(in int status, in int serverIf);
     void onServerConnectionState(in int status, in int serverIf,
                                  in boolean connected, in String address);
@@ -40,4 +40,6 @@
     void onExecuteWrite(in String address, in int transId, in boolean execWrite);
     void onNotificationSent(in String address, in int status);
     void onMtuChanged(in String address, in int mtu);
+    void onPhyUpdate(in String address, in int txPhy, in int rxPhy, in int status);
+    void onPhyRead(in String address, in int txPhy, in int rxPhy, in int status);
 }
diff --git a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
index a351bd2d..e571b00 100644
--- a/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -29,9 +29,6 @@
     boolean connect(in BluetoothDevice device);
     boolean disconnect(in BluetoothDevice device);
 
-    boolean acceptIncomingConnect(in BluetoothDevice device);
-    boolean rejectIncomingConnect(in BluetoothDevice device);
-
     List<BluetoothDevice> getConnectedDevices();
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
@@ -58,10 +55,10 @@
     boolean getLastVoiceTagNumber(in BluetoothDevice device);
 
     int getAudioState(in BluetoothDevice device);
-    boolean connectAudio();
-    boolean disconnectAudio();
-    void setAudioRouteAllowed(boolean allowed);
-    boolean getAudioRouteAllowed();
+    boolean connectAudio(in BluetoothDevice device);
+    boolean disconnectAudio(in BluetoothDevice device);
+    void setAudioRouteAllowed(in BluetoothDevice device, boolean allowed);
+    boolean getAudioRouteAllowed(in BluetoothDevice device);
 
     Bundle getCurrentAgFeatures(in BluetoothDevice device);
 }
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
new file mode 100644
index 0000000..5524a2b
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -0,0 +1,162 @@
+/*
+ * 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.bluetooth.le;
+
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.le.IAdvertisingSetCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides a way to control single Bluetooth LE advertising instance.
+ * <p>
+ * To get an instance of {@link AdvertisingSet}, call the
+ * {@link BluetoothLeAdvertiser#startAdvertisingSet} method.
+ * <p>
+ * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see AdvertiseData
+ */
+public final class AdvertisingSet {
+    private static final String TAG = "AdvertisingSet";
+
+    private final IBluetoothGatt gatt;
+    private int advertiserId;
+
+    /* package */ AdvertisingSet(int advertiserId,
+                                 IBluetoothManager bluetoothManager) {
+        this.advertiserId = advertiserId;
+
+        try {
+          this.gatt = bluetoothManager.getBluetoothGatt();
+        } catch (RemoteException e) {
+          Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+          throw new IllegalStateException("Failed to get Bluetooth");
+        }
+    }
+
+    /* package */ void setAdvertiserId(int advertiserId) {
+      this.advertiserId = advertiserId;
+    }
+
+    /**
+     * Enables Advertising. This method returns immediately, the operation status is
+     * delivered
+     * through {@code callback.onAdvertisingEnabled()}.
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     */
+    public void enableAdvertising(boolean enable, int timeout) {
+        try {
+            gatt.enableAdverisingSet(this.advertiserId, enable, timeout);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Set/update data being Advertised. Make sure that data doesn't exceed the size limit for
+     * specified AdvertisingSetParameters. This method returns immediately, the operation status is
+     * delivered through {@code callback.onAdvertisingDataSet()}.
+     * <p>
+     * Advertising data must be empty if non-legacy scannable advertising is used.
+     */
+    public void setAdvertisingData(AdvertiseData data) {
+        try {
+            gatt.setAdvertisingData(this.advertiserId, data);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Set/update scan response data. Make sure that data doesn't exceed the size limit for
+     * specified AdvertisingSetParameters. This method returns immediately, the operation status
+     * is delivered through {@code callback.onScanResponseDataSet()}.
+     */
+    public void setScanResponseData(AdvertiseData data) {
+        try {
+            gatt.setScanResponseData(this.advertiserId, data);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Update advertising parameters associated with this AdvertisingSet. Must be called when
+     * advertising is not active. This method returns immediately, the operation status is delivered
+     * through {@code callback.onAdvertisingParametersUpdated}.
+     */
+    public void setAdvertisingParameters(AdvertisingSetParameters parameters) {
+        try {
+            gatt.setAdvertisingParameters(this.advertiserId, parameters);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Update periodic advertising parameters associated with this set. Must be called when
+     * periodic advertising is not enabled. This method returns immediately, the operation
+     * status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}.
+     */
+    public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
+        try {
+            gatt.setPeriodicAdvertisingParameters(this.advertiserId, parameters);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Used to set periodic advertising data, must be called after setPeriodicAdvertisingParameters,
+     * or after advertising was started with periodic advertising data set. This method returns
+     * immediately, the operation status is delivered through
+     * {@code callback.onPeriodicAdvertisingDataSet()}.
+     */
+    public void setPeriodicAdvertisingData(AdvertiseData data) {
+        try {
+            gatt.setPeriodicAdvertisingData(this.advertiserId, data);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Used to enable/disable periodic advertising. This method returns immediately, the operation
+     * status is delivered through {@code callback.onPeriodicAdvertisingEnable()}.
+     */
+    public void periodicAdvertisingEnable(boolean enable) {
+        try {
+            gatt.periodicAdvertisingEnable(this.advertiserId, enable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Returns advertiserId associated with thsi advertising set.
+     *
+     * @hide
+     */
+    public int getAdvertiserId(){
+      return advertiserId;
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
new file mode 100644
index 0000000..ceed8d9
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
@@ -0,0 +1,144 @@
+/*
+ * 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.bluetooth.le;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * Bluetooth LE advertising set callbacks, used to deliver advertising operation
+ * status.
+ */
+public abstract class AdvertisingSetCallback {
+
+    /**
+     * The requested operation was successful.
+     */
+    public static final int ADVERTISE_SUCCESS = 0;
+
+    /**
+     * Failed to start advertising as the advertise data to be broadcasted is too
+     * large.
+     */
+    public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1;
+
+    /**
+     * Failed to start advertising because no advertising instance is available.
+     */
+    public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
+
+    /**
+     * Failed to start advertising as the advertising is already started.
+     */
+    public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+
+    /**
+     * Operation failed due to an internal error.
+     */
+    public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4;
+
+    /**
+     * This feature is not supported on this platform.
+     */
+    public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5;
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet}
+     * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertisingSet
+     * contains the started set and it is advertising. If error occured, advertisingSet is
+     * null, and status will be set to proper error code.
+     *
+     * @param advertisingSet The advertising set that was started or null if error.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {}
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet}
+     * indicating advertising set is stopped.
+     *
+     * @param advertisingSet The advertising set.
+     */
+    public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {}
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet} indicating
+     * result of the operation. If status is ADVERTISE_SUCCESS, then advertising set is advertising.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating
+     * result of the operation. If status is ADVERTISE_SUCCESS, then data was changed.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating
+     * result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setAdvertisingParameters}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
+                                               int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void
+    onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
+                                           int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingData}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet,
+                                             int status) {}
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#periodicAdvertisingEnable}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onPeriodicAdvertisingEnable(AdvertisingSet advertisingSet, boolean enable,
+                                            int status) {}
+}
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/core/java/android/bluetooth/le/AdvertisingSetParameters.aidl
similarity index 73%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to core/java/android/bluetooth/le/AdvertisingSetParameters.aidl
index 62d5603..39034a0 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.bluetooth.le;
 
-parcelable HomeSP;
+parcelable AdvertisingSetParameters;
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
new file mode 100644
index 0000000..59fef8d
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -0,0 +1,379 @@
+/*
+ * 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.bluetooth.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link AdvertisingSetParameters} provide a way to adjust advertising
+ * preferences for each
+ * Bluetooth LE advertising set. Use {@link AdvertisingSetParameters.Builder} to
+ * create an
+ * instance of this class.
+ */
+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;
+
+    /**
+     * Advertise on medium frequency, around every 250ms. This is balanced
+     * between advertising frequency and power consumption.
+     */
+    public static final int INTERVAL_MEDIUM = 400;
+
+    /**
+     * Perform high frequency, low latency advertising, around every 100ms. This
+     * has the highest power consumption and should not be used for continuous
+     * background advertising.
+     */
+    public static final int INTERVAL_HIGH = 160;
+
+    /**
+     * Minimum value for advertising interval.
+     */
+    public static final int INTERVAL_MIN = 160;
+
+    /**
+     * Maximum value for advertising interval.
+     */
+    public static final int INTERVAL_MAX = 16777215;
+
+    /**
+     * Advertise using the lowest transmission (TX) power level. Low transmission
+     * power can be used to restrict the visibility range of advertising packets.
+     */
+    public static final int TX_POWER_ULTRA_LOW = -21;
+
+    /**
+     * Advertise using low TX power level.
+     */
+    public static final int TX_POWER_LOW = -15;
+
+    /**
+     * Advertise using medium TX power level.
+     */
+    public static final int TX_POWER_MEDIUM = -7;
+
+    /**
+     * Advertise using high TX power level. This corresponds to largest visibility
+     * range of the advertising packet.
+     */
+    public static final int TX_POWER_HIGH = 1;
+
+    /**
+     * Minimum value for TX power.
+     */
+    public static final int TX_POWER_MIN = -127;
+
+    /**
+     * Maximum value for TX power.
+     */
+    public static final int TX_POWER_MAX = 1;
+
+    /**
+     * The maximum limited advertisement duration as specified by the Bluetooth
+     * SIG
+     */
+    private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
+
+    private final boolean isLegacy;
+    private final boolean isAnonymous;
+    private final boolean includeTxPower;
+    private final int primaryPhy;
+    private final int secondaryPhy;
+    private final boolean connectable;
+    private final int interval;
+    private final int txPowerLevel;
+
+    private AdvertisingSetParameters(boolean connectable, boolean isLegacy,
+                                     boolean isAnonymous, boolean includeTxPower,
+                                     int primaryPhy, int secondaryPhy,
+                                     int interval, int txPowerLevel) {
+        this.connectable = connectable;
+        this.isLegacy = isLegacy;
+        this.isAnonymous = isAnonymous;
+        this.includeTxPower = includeTxPower;
+        this.primaryPhy = primaryPhy;
+        this.secondaryPhy = secondaryPhy;
+        this.interval = interval;
+        this.txPowerLevel = txPowerLevel;
+    }
+
+    private AdvertisingSetParameters(Parcel in) {
+        connectable = in.readInt() != 0 ? true : false;
+        isLegacy = in.readInt() != 0 ? true : false;
+        isAnonymous = in.readInt() != 0 ? true : false;
+        includeTxPower = in.readInt() != 0 ? true : false;
+        primaryPhy = in.readInt();
+        secondaryPhy = in.readInt();
+        interval = in.readInt();
+        txPowerLevel = in.readInt();
+    }
+
+    /**
+     * Returns whether the advertisement will be connectable.
+     */
+    public boolean isConnectable() { return connectable; }
+
+    /**
+     * Returns whether the legacy advertisement will be used.
+     */
+    public boolean isLegacy() { return isLegacy; }
+
+    /**
+     * Returns whether the advertisement will be anonymous.
+     */
+    public boolean isAnonymous() { return isAnonymous; }
+
+    /**
+     * Returns whether the TX Power will be included.
+     */
+    public boolean includeTxPower() { return includeTxPower; }
+
+    /**
+     * Returns the primary advertising phy.
+     */
+    public int getPrimaryPhy() { return primaryPhy; }
+
+    /**
+     * Returns the secondary advertising phy.
+     */
+    public int getSecondaryPhy() { return secondaryPhy; }
+
+    /**
+     * Returns the advertising interval.
+     */
+    public int getInterval() { return interval; }
+
+    /**
+     * Returns the TX power level for advertising.
+     */
+    public int getTxPowerLevel() { return txPowerLevel; }
+
+    @Override
+    public String toString() {
+        return "AdvertisingSetParameters [connectable=" + connectable
+             + ", isLegacy=" + isLegacy
+             + ", isAnonymous=" + isAnonymous
+             + ", includeTxPower=" + includeTxPower
+             + ", primaryPhy=" + primaryPhy
+             + ", secondaryPhy=" + secondaryPhy
+             + ", interval=" + interval
+             + ", txPowerLevel=" + txPowerLevel + "]";
+    }
+
+    @Override
+    public int describeContents() {
+       return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(connectable ? 1 : 0);
+        dest.writeInt(isLegacy ? 1 : 0);
+        dest.writeInt(isAnonymous ? 1 : 0);
+        dest.writeInt(includeTxPower ? 1 : 0);
+        dest.writeInt(primaryPhy);
+        dest.writeInt(secondaryPhy);
+        dest.writeInt(interval);
+        dest.writeInt(txPowerLevel);
+    }
+
+    public static final Parcelable.Creator<AdvertisingSetParameters> CREATOR =
+        new Creator<AdvertisingSetParameters>() {
+          @Override
+          public AdvertisingSetParameters[] newArray(int size) {
+            return new AdvertisingSetParameters[size];
+          }
+
+          @Override
+          public AdvertisingSetParameters createFromParcel(Parcel in) {
+            return new AdvertisingSetParameters(in);
+          }
+        };
+
+    /**
+     * Builder class for {@link AdvertisingSetParameters}.
+     */
+    public static final class Builder {
+
+        private boolean connectable = true;
+        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 interval = INTERVAL_LOW;
+        private int txPowerLevel = TX_POWER_MEDIUM;
+
+        /**
+         * Set whether the advertisement type should be connectable or
+         * non-connectable.
+         * Legacy advertisements can be both connectable and scannable. Other
+         * advertisements can be connectable only if not scannable.
+         * @param connectable Controls whether the advertisment type will be
+         * connectable (true) or non-connectable (false).
+         */
+        public Builder setConnectable(boolean connectable) {
+            this.connectable = connectable;
+            return this;
+        }
+
+        /**
+         * When set to true, advertising set will advertise 4.x Spec compliant
+         * advertisements.
+         *
+         * @param isLegacy wether legacy advertising mode should be used.
+         */
+        public Builder setLegacyMode(boolean isLegacy) {
+            this.isLegacy = isLegacy;
+            return this;
+        }
+
+        /**
+         * Set wether advertiser address should be ommited from all packets. If this
+         * mode is used, periodic advertising can't be enabled for this set.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * @param isAnonymous wether anonymous advertising should be used.
+         */
+        public Builder setAnonymous(boolean isAnonymous) {
+            this.isAnonymous = isAnonymous;
+            return this;
+        }
+
+        /**
+         * Set wether TX power should be included in the extended header.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * @param includeTxPower wether TX power should be included in extended
+         * header
+         */
+        public Builder setIncludeTxPower(boolean includeTxPower) {
+            this.includeTxPower = includeTxPower;
+            return this;
+        }
+
+        /**
+         * Set the primary physical channel used for this advertising set.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * @param primaryPhy Primary advertising physical channel, can only be
+         *            {@link AdvertisingSetParameters#PHY_LE_1M} or
+         *            {@link AdvertisingSetParameters#PHY_LE_CODED}.
+         * @throws IllegalArgumentException If the primaryPhy is invalid.
+         */
+        public Builder setPrimaryPhy(int primaryPhy) {
+            if (primaryPhy != PHY_LE_1M && primaryPhy != PHY_LE_CODED) {
+               throw new IllegalArgumentException("bad primaryPhy " + primaryPhy);
+            }
+            this.primaryPhy = primaryPhy;
+            return this;
+        }
+
+        /**
+         * Set the secondary physical channel used for this advertising set.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * @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}.
+         * @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) {
+               throw new IllegalArgumentException("bad secondaryPhy " + secondaryPhy);
+            }
+            this.secondaryPhy = secondaryPhy;
+            return this;
+        }
+
+        /**
+         * Set advertising interval.
+         *
+         * @param interval Bluetooth LE Advertising interval, in 0.625ms unit. Valid
+         *            range is from 160 (100ms) to 16777215 (10,485.759375 s).
+         *            Recommended values are:
+         *            {@link AdvertisingSetParameters#INTERVAL_LOW},
+         *            {@link AdvertisingSetParameters#INTERVAL_MEDIUM}, or
+         *            {@link AdvertisingSetParameters#INTERVAL_HIGH}.
+         * @throws IllegalArgumentException If the interval is invalid.
+         */
+        public Builder setInterval(int interval) {
+            if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) {
+               throw new IllegalArgumentException("unknown interval " + interval);
+            }
+            this.interval = interval;
+            return this;
+        }
+
+        /**
+         * Set the transmission power level for the advertising.
+         * @param txPowerLevel Transmission power of Bluetooth LE Advertising, in
+         *             dBm. The valid range is [-127, 1] Recommended values are:
+         *             {@link AdvertisingSetParameters#TX_POWER_ULTRA_LOW},
+         *             {@link AdvertisingSetParameters#TX_POWER_LOW},
+         *             {@link AdvertisingSetParameters#TX_POWER_MEDIUM}, or
+         *             {@link AdvertisingSetParameters#TX_POWER_HIGH}.
+         *
+         * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
+         */
+        public Builder setTxPowerLevel(int txPowerLevel) {
+            if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) {
+                throw new IllegalArgumentException("unknown txPowerLevel " +
+                                                   txPowerLevel);
+            }
+            this.txPowerLevel = txPowerLevel;
+            return this;
+        }
+
+        /**
+         * Build the {@link AdvertisingSetParameters} object.
+         */
+        public AdvertisingSetParameters build() {
+            return new AdvertisingSetParameters(connectable, isLegacy, isAnonymous,
+                                                includeTxPower, primaryPhy,
+                                                secondaryPhy, interval, txPowerLevel);
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 94d03e5..67fd1c8 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -28,6 +28,7 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
@@ -60,8 +61,12 @@
     private final IBluetoothManager mBluetoothManager;
     private final Handler mHandler;
     private BluetoothAdapter mBluetoothAdapter;
-    private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
-            mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
+    private final Map<AdvertiseCallback, AdvertisingSetCallback>
+            mLegacyAdvertisers = new HashMap<>();
+    private final Map<AdvertisingSetCallback, IAdvertisingSetCallback>
+            mCallbackWrappers = Collections.synchronizedMap(new HashMap<>());
+    private final Map<Integer, AdvertisingSet>
+            mAdvertisingSets = Collections.synchronizedMap(new HashMap<>());
 
     /**
      * Use BluetoothAdapter.getLeAdvertiser() instead.
@@ -106,7 +111,7 @@
     public void startAdvertising(AdvertiseSettings settings,
             AdvertiseData advertiseData, AdvertiseData scanResponse,
             final AdvertiseCallback callback) {
-        synchronized (mLeAdvertisers) {
+        synchronized (mLegacyAdvertisers) {
             BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
             if (callback == null) {
                 throw new IllegalArgumentException("callback cannot be null");
@@ -117,25 +122,64 @@
                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
                 return;
             }
-            if (mLeAdvertisers.containsKey(callback)) {
+            if (mLegacyAdvertisers.containsKey(callback)) {
                 postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
                 return;
             }
 
-            IBluetoothGatt gatt;
-            try {
-                gatt = mBluetoothManager.getBluetoothGatt();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
-                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
-                return;
+            AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
+            parameters.setLegacyMode(true);
+            parameters.setConnectable(isConnectable);
+            if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
+                parameters.setInterval(1600); // 1s
+            } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
+                parameters.setInterval(400); // 250ms
+            } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) {
+                parameters.setInterval(160); // 100ms
             }
-            AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
-                    scanResponse, settings, gatt);
-            wrapper.startRegisteration();
+
+            if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) {
+                parameters.setTxPowerLevel(-21);
+            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) {
+                parameters.setTxPowerLevel(-15);
+            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) {
+                parameters.setTxPowerLevel(-7);
+            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) {
+                parameters.setTxPowerLevel(1);
+            }
+
+            AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
+            mLegacyAdvertisers.put(callback, wrapped);
+            startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null,
+                                settings.getTimeout(), wrapped);
         }
     }
 
+    AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
+        return new AdvertisingSetCallback() {
+            public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) {
+                if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                    postStartFailure(callback, status);
+                    return;
+                }
+
+                postStartSuccess(callback, settings);
+            }
+
+            /* Legacy advertiser is disabled on timeout */
+            public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
+                if (enabled == true) {
+                    Log.e(TAG, "Legacy advertiser should be only disabled on timeout," +
+                        " but was enabled!");
+                    return;
+                }
+
+                stopAdvertising(callback);
+            }
+
+        };
+    }
+
     /**
      * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
      * {@link BluetoothLeAdvertiser#startAdvertising}.
@@ -145,13 +189,146 @@
      * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
      */
     public void stopAdvertising(final AdvertiseCallback callback) {
-        synchronized (mLeAdvertisers) {
+        synchronized (mLegacyAdvertisers) {
             if (callback == null) {
                 throw new IllegalArgumentException("callback cannot be null");
             }
-            AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
+            AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback);
             if (wrapper == null) return;
-            wrapper.stopAdvertising();
+
+            stopAdvertisingSet(wrapper);
+        }
+    }
+
+    /**
+    * Creates a new advertising set. If operation succeed, device will start advertising. This
+    * method returns immediately, the operation status is delivered through
+    * {@code callback.onAdvertisingSetStarted()}.
+    * <p>
+    * @param parameters advertising set parameters.
+    * @param advertiseData Advertisement data to be broadcasted.
+    * @param scanResponse Scan response associated with the advertisement data.
+    * @param periodicData Periodic advertising data.
+    * @param callback Callback for advertising set.
+    */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+                                    AdvertiseData advertiseData, AdvertiseData scanResponse,
+                                    PeriodicAdvertisingParameters periodicParameters,
+                                    AdvertiseData periodicData, AdvertisingSetCallback callback) {
+            startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                            periodicData, 0, callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+    * Creates a new advertising set. If operation succeed, device will start advertising. This
+    * method returns immediately, the operation status is delivered through
+    * {@code callback.onAdvertisingSetStarted()}.
+    * <p>
+    * @param parameters advertising set parameters.
+    * @param advertiseData Advertisement data to be broadcasted.
+    * @param scanResponse Scan response associated with the advertisement data.
+    * @param periodicData Periodic advertising data.
+    * @param callback Callback for advertising set.
+    * @param handler thread upon which the callbacks will be invoked.
+    */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+                                    AdvertiseData advertiseData, AdvertiseData scanResponse,
+                                    PeriodicAdvertisingParameters periodicParameters,
+                                    AdvertiseData periodicData, AdvertisingSetCallback callback,
+                                    Handler handler) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                            periodicData, 0, callback, handler);
+    }
+
+    /**
+    * Creates a new advertising set. If operation succeed, device will start advertising. This
+    * method returns immediately, the operation status is delivered through
+    * {@code callback.onAdvertisingSetStarted()}.
+    * <p>
+    * @param parameters advertising set parameters.
+    * @param advertiseData Advertisement data to be broadcasted.
+    * @param scanResponse Scan response associated with the advertisement data.
+    * @param periodicData Periodic advertising data.
+    * @param timeoutMillis Advertising time limit. May not exceed 180000
+    * @param callback Callback for advertising set.
+    */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+                                    AdvertiseData advertiseData, AdvertiseData scanResponse,
+                                    PeriodicAdvertisingParameters periodicParameters,
+                                    AdvertiseData periodicData, int timeoutMillis,
+                                    AdvertisingSetCallback callback) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                            periodicData, timeoutMillis, callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+    * Creates a new advertising set. If operation succeed, device will start advertising. This
+    * method returns immediately, the operation status is delivered through
+    * {@code callback.onAdvertisingSetStarted()}.
+    * <p>
+    * @param parameters advertising set parameters.
+    * @param advertiseData Advertisement data to be broadcasted.
+    * @param scanResponse Scan response associated with the advertisement data.
+    * @param periodicData Periodic advertising data.
+    * @param timeoutMillis Advertising time limit. May not exceed 180000
+    * @param callback Callback for advertising set.
+    * @param handler thread upon which the callbacks will be invoked.
+    */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+                                    AdvertiseData advertiseData, AdvertiseData scanResponse,
+                                    PeriodicAdvertisingParameters periodicParameters,
+                                    AdvertiseData periodicData, int timeoutMillis,
+                                    AdvertisingSetCallback callback, Handler handler) {
+        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+
+        if (callback == null) {
+          throw new IllegalArgumentException("callback cannot be null");
+        }
+
+        IBluetoothGatt gatt;
+        try {
+          gatt = mBluetoothManager.getBluetoothGatt();
+        } catch (RemoteException e) {
+          Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+          throw new IllegalStateException("Failed to get Bluetooth");
+        }
+
+        IAdvertisingSetCallback wrapped = wrap(callback, handler);
+        if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) {
+            throw new IllegalArgumentException(
+                "callback instance already associated with advertising");
+        }
+
+        try {
+            gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                                     periodicData, timeoutMillis, wrapped);
+        } catch (RemoteException e) {
+          Log.e(TAG, "Failed to start advertising set - ", e);
+          throw new IllegalStateException("Failed to start advertising set");
+        }
+    }
+
+    /**
+     * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
+     * BluetoothLeAdvertiser#startAdvertisingSet}.
+     */
+    public void stopAdvertisingSet(AdvertisingSetCallback callback) {
+        if (callback == null) {
+          throw new IllegalArgumentException("callback cannot be null");
+        }
+
+        IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback);
+        if (wrapped == null) {
+            return;
+        }
+
+        IBluetoothGatt gatt;
+        try {
+            gatt = mBluetoothManager.getBluetoothGatt();
+            gatt.stopAdvertisingSet(wrapped);
+       } catch (RemoteException e) {
+            Log.e(TAG, "Failed to stop advertising - ", e);
+            throw new IllegalStateException("Failed to stop advertising");
         }
     }
 
@@ -161,7 +338,9 @@
      * @hide
      */
     public void cleanup() {
-        mLeAdvertisers.clear();
+        mLegacyAdvertisers.clear();
+        mCallbackWrappers.clear();
+        mAdvertisingSets.clear();
     }
 
     // Compute the size of advertisement data or scan resp
@@ -219,142 +398,108 @@
         return array == null ? 0 : array.length;
     }
 
-    /**
-     * Bluetooth GATT interface callbacks for advertising.
-     */
-    private class AdvertiseCallbackWrapper extends IAdvertiserCallback.Stub {
-        private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
-        private final AdvertiseCallback mAdvertiseCallback;
-        private final AdvertiseData mAdvertisement;
-        private final AdvertiseData mScanResponse;
-        private final AdvertiseSettings mSettings;
-        private final IBluetoothGatt mBluetoothGatt;
-
-        // mAdvertiserId -1: not registered
-        // -2: advertise stopped or registration timeout
-        // >=0: registered and advertising started
-        private int mAdvertiserId;
-        private boolean mIsAdvertising = false;
-        private int registrationError = AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR;
-
-        public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
-                AdvertiseData advertiseData, AdvertiseData scanResponse,
-                AdvertiseSettings settings,
-                IBluetoothGatt bluetoothGatt) {
-            mAdvertiseCallback = advertiseCallback;
-            mAdvertisement = advertiseData;
-            mScanResponse = scanResponse;
-            mSettings = settings;
-            mBluetoothGatt = bluetoothGatt;
-            mAdvertiserId = -1;
-        }
-
-        public void startRegisteration() {
-            synchronized (this) {
-                if (mAdvertiserId == -2) return;
-
-                try {
-                    mBluetoothGatt.registerAdvertiser(this);
-                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
-                } catch (InterruptedException | RemoteException e) {
-                    Log.e(TAG, "Failed to start registeration", e);
-                }
-                if (mAdvertiserId >= 0 && mIsAdvertising) {
-                    mLeAdvertisers.put(mAdvertiseCallback, this);
-                } else if (mAdvertiserId < 0) {
-
-                    // Registration timeout, reset mClientIf to -2 so no subsequent operations can
-                    // proceed.
-                    if (mAdvertiserId == -1) mAdvertiserId = -2;
-                    // Post internal error if registration failed.
-                    postStartFailure(mAdvertiseCallback, registrationError);
-                } else {
-                    // Unregister application if it's already registered but advertise failed.
-                    try {
-                        mBluetoothGatt.unregisterAdvertiser(mAdvertiserId);
-                        mAdvertiserId = -2;
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "remote exception when unregistering", e);
-                    }
-                }
-            }
-        }
-
-        public void stopAdvertising() {
-            synchronized (this) {
-                try {
-                    mBluetoothGatt.stopMultiAdvertising(mAdvertiserId);
-                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
-                } catch (InterruptedException | RemoteException e) {
-                    Log.e(TAG, "Failed to stop advertising", e);
-                }
-                // Advertise callback should have been removed from LeAdvertisers when
-                // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
-                // invoked and wait timeout expires, remove callback here.
-                if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
-                    mLeAdvertisers.remove(mAdvertiseCallback);
-                }
-            }
-        }
-
-        /**
-         * Advertiser interface registered - app is ready to go
-         */
-        @Override
-        public void onAdvertiserRegistered(int status, int advertiserId) {
-            Log.d(TAG, "onAdvertiserRegistered() - status=" + status + " advertiserId=" + advertiserId);
-            synchronized (this) {
-                if (status == BluetoothGatt.GATT_SUCCESS) {
-                    try {
-                        if (mAdvertiserId == -2) {
-                            // Registration succeeds after timeout, unregister advertiser.
-                            mBluetoothGatt.unregisterAdvertiser(advertiserId);
-                        } else {
-                            mAdvertiserId = advertiserId;
-                            mBluetoothGatt.startMultiAdvertising(mAdvertiserId, mAdvertisement,
-                                    mScanResponse, mSettings);
+    IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
+        return new IAdvertisingSetCallback.Stub() {
+            public void onAdvertisingSetStarted(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                            callback.onAdvertisingSetStarted(null, status);
+                            mCallbackWrappers.remove(callback);
+                            return;
                         }
-                        return;
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "failed to start advertising", e);
-                    }
-                } else if (status == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) {
-                    registrationError = status;
-                }
-                // Registration failed.
-                mAdvertiserId = -2;
-                notifyAll();
-            }
-        }
 
-        @Override
-        public void onMultiAdvertiseCallback(int status, boolean isStart,
-                AdvertiseSettings settings) {
-            synchronized (this) {
-                if (isStart) {
-                    if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
-                        // Start success
-                        mIsAdvertising = true;
-                        postStartSuccess(mAdvertiseCallback, settings);
-                    } else {
-                        // Start failure.
-                        postStartFailure(mAdvertiseCallback, status);
+                        AdvertisingSet advertisingSet =
+                            new AdvertisingSet(advertiserId, mBluetoothManager);
+                        mAdvertisingSets.put(advertiserId, advertisingSet);
+                        callback.onAdvertisingSetStarted(advertisingSet, status);
                     }
-                } else {
-                    // unregister advertiser for stop.
-                    try {
-                        mBluetoothGatt.unregisterAdvertiser(mAdvertiserId);
-                        mAdvertiserId = -2;
-                        mIsAdvertising = false;
-                        mLeAdvertisers.remove(mAdvertiseCallback);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "remote exception when unregistering", e);
-                    }
-                }
-                notifyAll();
+                });
             }
 
-        }
+            public void onAdvertisingSetStopped(int advertiserId) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingSetStopped(advertisingSet);
+                        mAdvertisingSets.remove(advertiserId);
+                        mCallbackWrappers.remove(callback);
+                    }
+                });
+            }
+
+            public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingEnabled(advertisingSet, enabled, status);
+                    }
+                });
+            }
+
+            public void onAdvertisingDataSet(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingDataSet(advertisingSet, status);
+                    }
+                });
+            }
+
+            public void onScanResponseDataSet(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onScanResponseDataSet(advertisingSet, status);
+                    }
+                });
+            }
+
+            public void onAdvertisingParametersUpdated(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingParametersUpdated(advertisingSet, status);
+                    }
+                });
+            }
+
+            public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
+                    }
+                });
+            }
+
+            public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onPeriodicAdvertisingDataSet(advertisingSet, status);
+                    }
+                });
+            }
+
+            public void onPeriodicAdvertisingEnable(int advertiserId, boolean enable, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onPeriodicAdvertisingEnable(advertisingSet, enable, status);
+                    }
+                });
+            }
+        };
     }
 
     private void postStartFailure(final AdvertiseCallback callback, final int error) {
diff --git a/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
new file mode 100644
index 0000000..4b0a111
--- /dev/null
+++ b/core/java/android/bluetooth/le/IAdvertisingSetCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.bluetooth.le;
+
+/**
+ * Callback definitions for interacting with Advertiser
+ * @hide
+ */
+oneway interface IAdvertisingSetCallback {
+  void onAdvertisingSetStarted(in int advertiserId, in int status);
+  void onAdvertisingSetStopped(in int advertiserId);
+  void onAdvertisingEnabled(in int advertiserId, in boolean enable, in int status);
+  void onAdvertisingDataSet(in int advertiserId, in int status);
+  void onScanResponseDataSet(in int advertiserId, in int status);
+  void onAdvertisingParametersUpdated(in int advertiserId, in int status);
+  void onPeriodicAdvertisingParametersUpdated(in int advertiserId, in int status);
+  void onPeriodicAdvertisingDataSet(in int advertiserId, in int status);
+  void onPeriodicAdvertisingEnable(in int advertiserId, in boolean enable, in int status);
+}
diff --git a/core/java/android/bluetooth/le/IPeriodicAdvertisingCallback.aidl b/core/java/android/bluetooth/le/IPeriodicAdvertisingCallback.aidl
new file mode 100644
index 0000000..a76c54d
--- /dev/null
+++ b/core/java/android/bluetooth/le/IPeriodicAdvertisingCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.bluetooth.le;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.PeriodicAdvertisingReport;
+
+/**
+ * Callback definitions for interacting with Periodic Advertising
+ * @hide
+ */
+oneway interface IPeriodicAdvertisingCallback {
+
+  void onSyncEstablished(in int syncHandle, in BluetoothDevice device, in int advertisingSid,
+                         in int skip, in int timeout, in int status);
+  void onPeriodicAdvertisingReport(in PeriodicAdvertisingReport report);
+  void onSyncLost(in int syncHandle);
+}
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java b/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java
new file mode 100644
index 0000000..6616231
--- /dev/null
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.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.bluetooth.le;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * Bluetooth LE periodic advertising callbacks, used to deliver periodic
+ * advertising operation status.
+ *
+ * @see PeriodicAdvertisingManager#createSync
+ */
+public abstract class PeriodicAdvertisingCallback {
+
+    /**
+     * The requested operation was successful.
+     *
+     * @hide
+     */
+    public static final int SYNC_SUCCESS = 0;
+
+    /**
+     * Sync failed to be established because remote device did not respond.
+     */
+    public static final int SYNC_NO_RESPONSE = 1;
+
+    /**
+     *  Sync failed to be established because controller can't support more syncs.
+     */
+    public static final int SYNC_NO_RESOURCES = 2;
+
+
+    /**
+     * Callback when synchronization was established.
+     *
+     * @param syncHandle handle used to identify this synchronization.
+     * @param device remote device.
+     * @param advertisingSid synchronized advertising set id.
+     * @param skip  The number of periodic advertising packets that can be skipped
+     * after a successful receive in force. @see PeriodicAdvertisingManager#createSync
+     * @param timeout Synchronization timeout for the periodic advertising in force. One
+     * unit is 10ms. @see PeriodicAdvertisingManager#createSync
+     * @param timeout
+     * @param status operation status.
+     */
+    public void onSyncEstablished(int syncHandle, BluetoothDevice device,
+                                  int advertisingSid, int skip, int timeout,
+                                  int status) {}
+
+    /**
+     * Callback when periodic advertising report is received.
+     *
+     * @param report periodic advertising report.
+     */
+    public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {}
+
+    /**
+     * Callback when periodic advertising synchronization was lost.
+     *
+     * @param syncHandle handle used to identify this synchronization.
+     */
+    public void onSyncLost(int syncHandle) {}
+}
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
new file mode 100644
index 0000000..12c8a8c
--- /dev/null
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
@@ -0,0 +1,237 @@
+/*
+ * 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.bluetooth.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This class provides methods to perform periodic advertising related
+ * operations. An application can register for periodic advertisements using
+ * {@link PeriodicAdvertisingManager#registerSync}.
+ * <p>
+ * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
+ * instance of {@link PeriodicAdvertisingManager}.
+ * <p>
+ * <b>Note:</b> Most of the methods here require
+ * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ */
+public final class PeriodicAdvertisingManager {
+
+  private static final String TAG = "PeriodicAdvertisingManager";
+
+  private static final int SKIP_MIN = 0;
+  private static final int SKIP_MAX = 499;
+  private static final int TIMEOUT_MIN = 10;
+  private static final int TIMEOUT_MAX = 16384;
+
+  private static final int SYNC_STARTING = -1;
+
+  private final IBluetoothManager mBluetoothManager;
+  private BluetoothAdapter mBluetoothAdapter;
+
+  /* maps callback, to callback wrapper and sync handle */
+  Map<PeriodicAdvertisingCallback,
+      IPeriodicAdvertisingCallback /* callbackWrapper */> callbackWrappers;
+
+  /**
+   * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
+   *
+   * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
+   * @hide
+   */
+  public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) {
+    mBluetoothManager = bluetoothManager;
+    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+    callbackWrappers = new IdentityHashMap<>();
+  }
+
+  /**
+   * Synchronize with periodic advertising pointed to by the {@code scanResult}.
+   * The {@code scanResult} used must contain a valid advertisingSid. First
+   * call to registerSync will use the {@code skip} and {@code timeout} provided.
+   * Subsequent calls from other apps, trying to sync with same set will reuse
+   * existing sync, thus {@code skip} and {@code timeout} values will not take
+   * effect. The values in effect will be returned in
+   * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
+   *
+   * @param scanResult Scan result containing advertisingSid.
+   * @param skip The number of periodic advertising packets that can be skipped
+   * after a successful receive. Must be between 0 and 499.
+   * @param timeout Synchronization timeout for the periodic advertising. One
+   * unit is 10ms. Must be between 10 (100ms) and 16384 (163.84s).
+   * @param callback Callback used to deliver all operations status.
+   * @throws IllegalArgumentException if {@code scanResult} is null or {@code
+   * skip} is invalid or {@code timeout} is invalid or {@code callback} is null.
+   */
+  public void registerSync(ScanResult scanResult, int skip, int timeout,
+                         PeriodicAdvertisingCallback callback) {
+    registerSync(scanResult, skip, timeout, callback, null);
+  }
+
+  /**
+   * Synchronize with periodic advertising pointed to by the {@code scanResult}.
+   * The {@code scanResult} used must contain a valid advertisingSid. First
+   * call to registerSync will use the {@code skip} and {@code timeout} provided.
+   * Subsequent calls from other apps, trying to sync with same set will reuse
+   * existing sync, thus {@code skip} and {@code timeout} values will not take
+   * effect. The values in effect will be returned in
+   * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
+   *
+   * @param scanResult Scan result containing advertisingSid.
+   * @param skip The number of periodic advertising packets that can be skipped
+   * after a successful receive. Must be between 0 and 499.
+   * @param timeout Synchronization timeout for the periodic advertising. One
+   * unit is 10ms. Must be between 10 (100ms) and 16384 (163.84s).
+   * @param callback Callback used to deliver all operations status.
+   * @param handler thread upon which the callbacks will be invoked.
+   * @throws IllegalArgumentException if {@code scanResult} is null or {@code
+   * skip} is invalid or {@code timeout} is invalid or {@code callback} is null.
+   */
+  public void registerSync(ScanResult scanResult, int skip, int timeout,
+                         PeriodicAdvertisingCallback callback, Handler handler) {
+    if (callback == null) {
+      throw new IllegalArgumentException("callback can't be null");
+    }
+
+    if (scanResult == null) {
+      throw new IllegalArgumentException("scanResult can't be null");
+    }
+
+    if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) {
+      throw new IllegalArgumentException("scanResult must contain a valid sid");
+    }
+
+    if (skip < SKIP_MIN || skip > SKIP_MAX) {
+      throw new IllegalArgumentException(
+          "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
+    }
+
+    if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) {
+      throw new IllegalArgumentException(
+          "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
+    }
+
+    IBluetoothGatt gatt;
+    try {
+        gatt = mBluetoothManager.getBluetoothGatt();
+    } catch (RemoteException e) {
+        Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+        callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(),
+                                   skip, timeout,
+                                   PeriodicAdvertisingCallback.SYNC_NO_RESOURCES);
+        return;
+    }
+
+    if (handler == null)
+      handler = new Handler(Looper.getMainLooper());
+
+    IPeriodicAdvertisingCallback wrapped = wrap(callback, handler);
+    callbackWrappers.put(callback, wrapped);
+
+    try {
+      gatt.registerSync(scanResult, skip, timeout, wrapped);
+    } catch (RemoteException e) {
+      Log.e(TAG, "Failed to register sync - ", e);
+      return;
+    }
+  }
+
+  /**
+   * Cancel pending attempt to create sync, or terminate existing sync.
+   *
+   * @param callback Callback used to deliver all operations status.
+   * @throws IllegalArgumentException if {@code callback} is null, or not a properly
+   * registered callback.
+   */
+  public void unregisterSync(PeriodicAdvertisingCallback callback) {
+    if (callback == null) {
+      throw new IllegalArgumentException("callback can't be null");
+    }
+
+    IBluetoothGatt gatt;
+    try {
+        gatt = mBluetoothManager.getBluetoothGatt();
+    } catch (RemoteException e) {
+        Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+        return;
+    }
+
+    IPeriodicAdvertisingCallback wrapper = callbackWrappers.remove(callback);
+    if (wrapper == null) {
+      throw new IllegalArgumentException("callback was not properly registered");
+    }
+
+    try {
+      gatt.unregisterSync(wrapper);
+    } catch (RemoteException e) {
+        Log.e(TAG, "Failed to cancel sync creation - ", e);
+        return;
+    }
+  }
+
+  private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, Handler handler) {
+    return new IPeriodicAdvertisingCallback.Stub() {
+      public void onSyncEstablished(int syncHandle, BluetoothDevice device,
+                                    int advertisingSid, int skip, int timeout, int status) {
+
+          handler.post(new Runnable() {
+              @Override
+              public void run() {
+                  callback.onSyncEstablished(syncHandle, device, advertisingSid, skip, timeout,
+                                             status);
+
+                  if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) {
+                      // App can still unregister the sync until notified it failed. Remove callback
+                      // after app was notifed.
+                      callbackWrappers.remove(callback);
+                  }
+              }
+          });
+      }
+
+      public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
+          handler.post(new Runnable() {
+              @Override
+              public void run() {
+                callback.onPeriodicAdvertisingReport(report);
+              }
+          });
+      }
+
+      public void onSyncLost(int syncHandle) {
+          handler.post(new Runnable() {
+              @Override
+              public void run() {
+                callback.onSyncLost(syncHandle);
+                // App can still unregister the sync until notified it's lost. Remove callback after
+                // app was notifed.
+                callbackWrappers.remove(callback);
+              }
+          });
+      }
+    };
+  }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.aidl
similarity index 73%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to core/java/android/bluetooth/le/PeriodicAdvertisingParameters.aidl
index 62d5603..f4bea22 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.bluetooth.le;
 
-parcelable HomeSP;
+parcelable PeriodicAdvertisingParameters;
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
new file mode 100644
index 0000000..ebc92bd
--- /dev/null
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.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 android.bluetooth.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic
+ * advertising preferences for each Bluetooth LE advertising set. Use {@link
+ * AdvertisingSetParameters.Builder} to create an instance of this class.
+ */
+public final class PeriodicAdvertisingParameters implements Parcelable {
+
+    private static final int INTERVAL_MAX = 80;
+    private static final int INTERVAL_MIN = 65519;
+
+    private final boolean enable;
+    private final boolean includeTxPower;
+    private final int interval;
+
+    private PeriodicAdvertisingParameters(boolean enable, boolean includeTxPower, int interval) {
+        this.enable = enable;
+        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; }
+
+    /**
+     * Returns the periodic advertising interval, in 1.25ms unit.
+     * Valid values are from 80 (100ms) to 65519 (81.89875s).
+     */
+    public int getInterval() { return interval; }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(enable ? 1 : 0);
+        dest.writeInt(includeTxPower ? 1 : 0);
+        dest.writeInt(interval);
+    }
+
+    public static final Parcelable
+        .Creator<PeriodicAdvertisingParameters> CREATOR =
+        new Creator<PeriodicAdvertisingParameters>() {
+            @Override
+            public PeriodicAdvertisingParameters[] newArray(int size) {
+                return new PeriodicAdvertisingParameters[size];
+            }
+
+            @Override
+            public PeriodicAdvertisingParameters createFromParcel(Parcel in) {
+                return new PeriodicAdvertisingParameters(in);
+            }
+        };
+
+    public static final class Builder {
+        private boolean includeTxPower = false;
+        private boolean enable = false;
+        private int interval = INTERVAL_MAX;
+
+        /**
+         * Set wether 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.
+         */
+        public Builder setIncludeTxPower(boolean includeTxPower) {
+            this.includeTxPower = includeTxPower;
+            return this;
+        }
+
+        /**
+         * Set advertising interval for periodic advertising, in 1.25ms unit.
+         * Valid values are from 80 (100ms) to 65519 (81.89875s).
+         * Value from range [interval, interval+20ms] will be picked as the actual value.
+         * @throws IllegalArgumentException If the interval is invalid.
+         */
+        public Builder setInterval(int interval) {
+            if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) {
+                throw new IllegalArgumentException("Invalid interval (must be " + INTERVAL_MIN +
+                                                   "-" + INTERVAL_MAX + ")");
+            }
+            this.interval = interval;
+            return this;
+        }
+
+        /**
+         * Build the {@link AdvertisingSetParameters} object.
+         */
+        public PeriodicAdvertisingParameters build() {
+            return new PeriodicAdvertisingParameters(enable, includeTxPower, interval);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.aidl
similarity index 73%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to core/java/android/bluetooth/le/PeriodicAdvertisingReport.aidl
index 62d5603..547d0961 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.bluetooth.le;
 
-parcelable HomeSP;
+parcelable PeriodicAdvertisingReport;
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java
new file mode 100644
index 0000000..3ff4ca5
--- /dev/null
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.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 android.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * PeriodicAdvertisingReport for Bluetooth LE synchronized advertising.
+ */
+public final class PeriodicAdvertisingReport implements Parcelable {
+
+    /**
+     * The data returned is complete
+     */
+    public static final int DATA_COMPLETE = 0;
+
+    /**
+     * The data returned is incomplete. The controller was unsuccessfull to
+     * receive all chained packets, returning only partial data.
+     */
+    public static final int DATA_INCOMPLETE_TRUNCATED = 2;
+
+    private int syncHandle;
+    private int txPower;
+    private int rssi;
+    private int dataStatus;
+
+    // periodic advertising data.
+    @Nullable
+    private ScanRecord data;
+
+    // Device timestamp when the result was last seen.
+    private long timestampNanos;
+
+    /**
+     * Constructor of periodic advertising result.
+     *
+     */
+    public PeriodicAdvertisingReport(int syncHandle, int txPower, int rssi,
+                                     int dataStatus, ScanRecord data) {
+        this.syncHandle = syncHandle;
+        this.txPower = txPower;
+        this.rssi = rssi;
+        this.dataStatus = dataStatus;
+        this.data = data;
+    }
+
+    private PeriodicAdvertisingReport(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(syncHandle);
+        dest.writeLong(txPower);
+        dest.writeInt(rssi);
+        dest.writeInt(dataStatus);
+        if (data != null) {
+            dest.writeInt(1);
+            dest.writeByteArray(data.getBytes());
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    private void readFromParcel(Parcel in) {
+        syncHandle = in.readInt();
+        txPower = in.readInt();
+        rssi = in.readInt();
+        dataStatus = in.readInt();
+        if (in.readInt() == 1) {
+            data = ScanRecord.parseFromBytes(in.createByteArray());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the synchronization handle.
+     */
+    public int getSyncHandle() {
+        return syncHandle;
+    }
+
+    /**
+     * Returns the transmit power in dBm. The valid range is [-127, 126]. Value
+     * of 127 means information was not available.
+     */
+    public int getTxPower() {
+        return txPower;
+    }
+
+    /**
+     * Returns the received signal strength in dBm. The valid range is [-127, 20].
+     */
+    public int getRssi() {
+        return rssi;
+    }
+
+    /**
+     * Returns the data status. Can be one of {@link PeriodicAdvertisingReport#DATA_COMPLETE}
+     * or {@link PeriodicAdvertisingReport#DATA_INCOMPLETE_TRUNCATED}.
+     */
+    public int getDataStatus() {
+        return dataStatus;
+    }
+
+    /**
+     * Returns the data contained in this periodic advertising report.
+     */
+    @Nullable
+    public ScanRecord getData() {
+        return data;
+    }
+
+    /**
+     * Returns timestamp since boot when the scan record was observed.
+     */
+    public long getTimestampNanos() {
+        return timestampNanos;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(syncHandle, txPower, rssi, dataStatus, data, timestampNanos);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        PeriodicAdvertisingReport other = (PeriodicAdvertisingReport) obj;
+        return (syncHandle == other.syncHandle) &&
+            (txPower == other.txPower) &&
+            (rssi == other.rssi) &&
+            (dataStatus == other.dataStatus) &&
+            Objects.equals(data, other.data) &&
+            (timestampNanos == other.timestampNanos);
+    }
+
+    @Override
+    public String toString() {
+      return "PeriodicAdvertisingReport{syncHandle=" + syncHandle +
+          ", txPower=" + txPower + ", rssi=" + rssi + ", dataStatus=" + dataStatus +
+          ", data=" + Objects.toString(data) + ", timestampNanos=" + timestampNanos + '}';
+    }
+
+    public static final Parcelable.Creator<PeriodicAdvertisingReport> CREATOR = new Creator<PeriodicAdvertisingReport>() {
+            @Override
+        public PeriodicAdvertisingReport createFromParcel(Parcel source) {
+            return new PeriodicAdvertisingReport(source);
+        }
+
+            @Override
+        public PeriodicAdvertisingReport[] newArray(int size) {
+            return new PeriodicAdvertisingReport[size];
+        }
+    };
+}
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
index 2fdfe7f..745cd16 100644
--- a/core/java/android/bluetooth/le/ScanResult.java
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -27,7 +27,56 @@
  * ScanResult for Bluetooth LE scan.
  */
 public final class ScanResult implements Parcelable {
-    // Remote bluetooth device.
+
+    /**
+     * For chained advertisements, inidcates tha the data contained in this
+     * scan result is complete.
+     */
+    public static final int DATA_COMPLETE = 0x00;
+
+    /**
+     * For chained advertisements, indicates that the controller was
+     * unable to receive all chained packets and the scan result contains
+     * incomplete truncated data.
+     */
+    public static final int DATA_TRUNCATED = 0x02;
+
+    /**
+     * Indicates that the secondary physical layer was not used.
+     */
+    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;
+
+    /**
+     * Mask for checking whether event type represents legacy advertisement.
+     */
+    private static final int ET_LEGACY_MASK = 0x10;
+
+    /**
+     * Mask for checking whether event type represents connectable advertisement.
+     */
+    private static final int ET_CONNECTABLE_MASK = 0x01;
+
+    // Remote Bluetooth device.
     private BluetoothDevice mDevice;
 
     // Scan record, including advertising data and scan response data.
@@ -40,13 +89,21 @@
     // Device timestamp when the result was last seen.
     private long mTimestampNanos;
 
+    private int mEventType;
+    private int mPrimaryPhy;
+    private int mSecondaryPhy;
+    private int mAdvertisingSid;
+    private int mTxPower;
+    private int mPeriodicAdvertisingInterval;
+
     /**
-     * Constructor of scan result.
+     * Constructs a new ScanResult.
      *
-     * @param device Remote bluetooth device that is found.
+     * @param device Remote Bluetooth device found.
      * @param scanRecord Scan record including both advertising data and scan response data.
      * @param rssi Received signal strength.
-     * @param timestampNanos Device timestamp when the scan result was observed.
+     * @param timestampNanos Timestamp at which the scan result was observed.
+     * @deprecated use {@link #ScanResult(BluetoothDevice, int, int, int, int, int, int, int, ScanRecord, long)}
      */
     public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi,
             long timestampNanos) {
@@ -54,6 +111,41 @@
         mScanRecord = scanRecord;
         mRssi = rssi;
         mTimestampNanos = timestampNanos;
+        mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK;
+        mPrimaryPhy = PHY_LE_1M;
+        mSecondaryPhy = PHY_UNUSED;
+        mAdvertisingSid = SID_NOT_PRESENT;
+        mTxPower = 127;
+        mPeriodicAdvertisingInterval = 0;
+    }
+
+    /**
+     * Constructs a new ScanResult.
+     *
+     * @param device Remote Bluetooth device found.
+     * @param eventType Event type.
+     * @param primaryPhy Primary advertising phy.
+     * @param secondaryPhy Secondary advertising phy.
+     * @param advertisingSid Advertising set ID.
+     * @param txPower Transmit power.
+     * @param rssi Received signal strength.
+     * @param periodicAdvertisingInterval Periodic advertising interval.
+     * @param scanRecord Scan record including both advertising data and scan response data.
+     * @param timestampNanos Timestamp at which the scan result was observed.
+     */
+    public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy,
+                      int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval,
+                      ScanRecord scanRecord, long timestampNanos) {
+        mDevice = device;
+        mEventType = eventType;
+        mPrimaryPhy = primaryPhy;
+        mSecondaryPhy = secondaryPhy;
+        mAdvertisingSid = advertisingSid;
+        mTxPower = txPower;
+        mRssi = rssi;
+        mPeriodicAdvertisingInterval = periodicAdvertisingInterval;
+        mScanRecord = scanRecord;
+        mTimestampNanos = timestampNanos;
     }
 
     private ScanResult(Parcel in) {
@@ -76,6 +168,12 @@
         }
         dest.writeInt(mRssi);
         dest.writeLong(mTimestampNanos);
+        dest.writeInt(mEventType);
+        dest.writeInt(mPrimaryPhy);
+        dest.writeInt(mSecondaryPhy);
+        dest.writeInt(mAdvertisingSid);
+        dest.writeInt(mTxPower);
+        dest.writeInt(mPeriodicAdvertisingInterval);
     }
 
     private void readFromParcel(Parcel in) {
@@ -87,6 +185,12 @@
         }
         mRssi = in.readInt();
         mTimestampNanos = in.readLong();
+        mEventType = in.readInt();
+        mPrimaryPhy = in.readInt();
+        mSecondaryPhy = in.readInt();
+        mAdvertisingSid = in.readInt();
+        mTxPower = in.readInt();
+        mPeriodicAdvertisingInterval = in.readInt();
     }
 
     @Override
@@ -95,7 +199,7 @@
     }
 
     /**
-     * Returns the remote bluetooth device identified by the bluetooth device address.
+     * Returns the remote Bluetooth device identified by the Bluetooth device address.
      */
     public BluetoothDevice getDevice() {
         return mDevice;
@@ -110,7 +214,7 @@
     }
 
     /**
-     * Returns the received signal strength in dBm. The valid range is [-127, 127].
+     * Returns the received signal strength in dBm. The valid range is [-127, 126].
      */
     public int getRssi() {
         return mRssi;
@@ -123,9 +227,79 @@
         return mTimestampNanos;
     }
 
+    /**
+     * Returns true if this object represents legacy scan result.
+     * Legacy scan results do not contain advanced advertising information
+     * as specified in the Bluetooth Core Specification v5.
+     */
+    public boolean isLegacy() {
+        return (mEventType & ET_LEGACY_MASK) != 0;
+    }
+
+    /**
+     * Returns true if this object represents connectable scan result.
+     */
+    public boolean isConnectable() {
+        return (mEventType & ET_CONNECTABLE_MASK) != 0;
+    }
+
+    /**
+     * Returns the data status.
+     * Can be one of {@link ScanResult#DATA_COMPLETE} or
+     * {@link ScanResult#DATA_TRUNCATED}.
+     */
+    public int getDataStatus() {
+        // return bit 5 and 6
+        return (mEventType >> 5) & 0x03;
+    }
+
+    /**
+     * 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}.
+     */
+    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}
+     * or {@link ScanResult#PHY_UNUSED} - if the advertisement
+     * was not received on a secondary physical channel.
+     */
+    public int getSecondaryPhy() { return mSecondaryPhy; }
+
+    /**
+     * Returns the advertising set id.
+     * May return {@link ScanResult#SID_NOT_PRESENT} if
+     * no set id was is present.
+     */
+    public int getAdvertisingSid() { return mAdvertisingSid; }
+
+    /**
+     * Returns the transmit power in dBm.
+     * Valid range is [-127, 126]. A value of 127 indicates that the
+     * advertisement did not indicate TX power.
+     */
+    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.
+     */
+    public int getPeriodicAdvertisingInterval() {
+        return mPeriodicAdvertisingInterval;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos);
+        return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos,
+                            mEventType, mPrimaryPhy, mSecondaryPhy,
+                            mAdvertisingSid, mTxPower,
+                            mPeriodicAdvertisingInterval);
     }
 
     @Override
@@ -138,15 +312,24 @@
         }
         ScanResult other = (ScanResult) obj;
         return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) &&
-                Objects.equals(mScanRecord, other.mScanRecord)
-                && (mTimestampNanos == other.mTimestampNanos);
+            Objects.equals(mScanRecord, other.mScanRecord) &&
+            (mTimestampNanos == other.mTimestampNanos) &&
+            mEventType == other.mEventType &&
+            mPrimaryPhy == other.mPrimaryPhy &&
+            mSecondaryPhy == other.mSecondaryPhy &&
+            mAdvertisingSid == other.mAdvertisingSid &&
+            mTxPower == other.mTxPower &&
+            mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval;
     }
 
     @Override
     public String toString() {
-        return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord="
-                + Objects.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampNanos="
-                + mTimestampNanos + '}';
+      return "ScanResult{" + "device=" + mDevice + ", scanRecord=" +
+          Objects.toString(mScanRecord) + ", rssi=" + mRssi +
+          ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType +
+          ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy +
+          ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower +
+          ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}';
     }
 
     public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index d616624..69c9a8c 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -122,6 +122,24 @@
     @SystemApi
     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
+     * the 1Mbit PHY only.
+     */
+    public static final int PHY_LE_ALL_SUPPORTED = 255;
+
     // Bluetooth LE scan mode.
     private int mScanMode;
 
@@ -138,6 +156,11 @@
 
     private int mNumOfMatchesPerFilter;
 
+    // Include only legacy advertising results
+    private boolean mLegacy;
+
+    private int mPhy;
+
     public int getScanMode() {
         return mScanMode;
     }
@@ -165,6 +188,22 @@
     }
 
     /**
+     * Returns whether only legacy advertisements will be returned.
+     * Legacy advertisements include advertisements as specified
+     * by the Bluetooth core specification 4.2 and below.
+     */
+    public boolean getLegacy() {
+        return mLegacy;
+    }
+
+    /**
+     * Returns the physical layer used during a scan.
+     */
+    public int getPhy() {
+        return mPhy;
+    }
+
+    /**
      * Returns report delay timestamp based on the device clock.
      */
     public long getReportDelayMillis() {
@@ -172,13 +211,16 @@
     }
 
     private ScanSettings(int scanMode, int callbackType, int scanResultType,
-            long reportDelayMillis, int matchMode, int numOfMatchesPerFilter) {
+                         long reportDelayMillis, int matchMode,
+                         int numOfMatchesPerFilter, boolean legacy, int phy) {
         mScanMode = scanMode;
         mCallbackType = callbackType;
         mScanResultType = scanResultType;
         mReportDelayMillis = reportDelayMillis;
         mNumOfMatchesPerFilter = numOfMatchesPerFilter;
         mMatchMode = matchMode;
+        mLegacy = legacy;
+        mPhy = phy;
     }
 
     private ScanSettings(Parcel in) {
@@ -188,6 +230,8 @@
         mReportDelayMillis = in.readLong();
         mMatchMode = in.readInt();
         mNumOfMatchesPerFilter = in.readInt();
+        mLegacy = in.readInt() != 0 ? true : false;
+        mPhy = in.readInt();
     }
 
     @Override
@@ -198,6 +242,8 @@
         dest.writeLong(mReportDelayMillis);
         dest.writeInt(mMatchMode);
         dest.writeInt(mNumOfMatchesPerFilter);
+        dest.writeInt(mLegacy ? 1 : 0);
+        dest.writeInt(mPhy);
     }
 
     @Override
@@ -228,6 +274,9 @@
         private long mReportDelayMillis = 0;
         private int mMatchMode = MATCH_MODE_AGGRESSIVE;
         private int mNumOfMatchesPerFilter  = MATCH_NUM_MAX_ADVERTISEMENT;
+        private boolean mLegacy = true;
+        private int mPhy = PHY_LE_ALL_SUPPORTED;
+
         /**
          * Set scan mode for Bluetooth LE scan.
          *
@@ -341,11 +390,44 @@
         }
 
         /**
+         * Set whether only legacy advertisments should be returned in scan results.
+         * Legacy advertisements include advertisements as specified by the
+         * Bluetooth core specification 4.2 and below. This is true by default
+         * for compatibility with older apps.
+         *
+         * @param legacy true if only legacy advertisements will be returned
+         */
+        public Builder setLegacy(boolean legacy) {
+            mLegacy = legacy;
+            return this;
+        }
+
+        /**
+         * Set the Physical Layer to use during this scan.
+         * This is used only if {@link ScanSettings.Builder#setLegacy}
+         * is set to false.
+         * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}
+         * may be used to check whether LE Coded phy is supported by calling
+         * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}.
+         * 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 ScanSettings#PHY_LE_ALL_SUPPORTED}
+         */
+        public Builder setPhy(int phy) {
+            mPhy = phy;
+            return this;
+        }
+
+        /**
          * Build {@link ScanSettings}.
          */
         public ScanSettings build() {
             return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
-                    mReportDelayMillis, mMatchMode, mNumOfMatchesPerFilter);
+                                    mReportDelayMillis, mMatchMode,
+                                    mNumOfMatchesPerFilter, mLegacy, mPhy);
         }
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 02b86fe..46d3835 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2662,6 +2662,7 @@
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
+            IPSEC_SERVICE,
             //@hide: UPDATE_LOCK_SERVICE,
             //@hide: NETWORKMANAGEMENT_SERVICE,
             NETWORK_STATS_SERVICE,
@@ -2763,6 +2764,9 @@
      *  <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
@@ -3093,6 +3097,15 @@
     public static final String CONNECTIVITY_SERVICE = "connectivity";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
+     * IPSec.
+     *
+     * @see #getSystemService
+     */
+    public static final String IPSEC_SERVICE = "ipsec";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.os.IUpdateLock} for managing runtime sequences that
      * must not be interrupted by headless OTA application or similar.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c6aaa48..2ce22b4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1083,18 +1083,16 @@
      * @hide
      */
     public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
+
     /**
-     * Activity action: Activate the current SIM card.  If SIM cards do not require activation,
-     * sending this intent is a no-op.
-     * <p>Input: No data should be specified.  get*Extra may have an optional
-     * {@link #EXTRA_SIM_ACTIVATION_RESPONSE} field containing a PendingIntent through which to
-     * send the activation result.
-     * <p>Output: nothing.
-     * @hide
+     * Activity Action: Main entry point for carrier setup apps.
+     * <p>Carrier apps that provide an implementation for this action may be invoked to configure
+     * carrier service and typically require
+     * {@link android.telephony.TelephonyManager#hasCarrierPrivileges() carrier privileges} to
+     * fulfill their duties.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_SIM_ACTIVATION_REQUEST =
-            "android.intent.action.SIM_ACTIVATION_REQUEST";
+    public static final String ACTION_CARRIER_SETUP = "android.intent.action.CARRIER_SETUP";
     /**
      * Activity Action: Send a message to someone specified by the data.
      * <p>Input: {@link #getData} is URI describing the target.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index f35b13d..46b981e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -482,6 +482,7 @@
      */
     boolean performDexOpt(String packageName, boolean checkProfiles,
             int compileReason, boolean force);
+
     /**
      * Ask the package manager to perform a dex-opt with the given compiler filter.
      *
@@ -492,6 +493,16 @@
             String targetCompilerFilter, boolean force);
 
     /**
+     * Ask the package manager to perform a dex-opt with the given compiler filter on the
+     * secondary dex files belonging to the given package.
+     *
+     * Note: exposed only for the shell command to allow moving packages explicitly to a
+     *       definite state.
+     */
+    boolean performDexOptSecondary(String packageName,
+            String targetCompilerFilter, boolean force);
+
+    /**
      * Ask the package manager to dump profiles associated with a package.
      */
     void dumpProfiles(String packageName);
@@ -499,6 +510,18 @@
     void forceDexOpt(String packageName);
 
     /**
+     * Execute the background dexopt job immediately.
+     */
+    boolean runBackgroundDexoptJob();
+
+    /**
+     * Reconcile the information we have about the secondary dex files belonging to
+     * {@code packagName} and the actual dex files. For all dex files that were
+     * deleted, update the internal records and delete the generated oat files.
+     */
+    void reconcileSecondaryDexFiles(String packageName);
+
+    /**
      * Update status of external media on the package manager to scan and
      * install packages installed on the external media. Like say the
      * MountService uses this to call into the package manager to update
@@ -563,6 +586,7 @@
     void addOnPermissionsChangeListener(in IOnPermissionsChangeListener listener);
     void removeOnPermissionsChangeListener(in IOnPermissionsChangeListener listener);
     void grantDefaultPermissionsToEnabledCarrierApps(in String[] packageNames, int userId);
+    void grantDefaultPermissionsToEnabledImsServices(in String[] packageNames, int userId);
 
     boolean isPermissionRevokedByPolicy(String permission, String packageName, int userId);
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 11e19f3..57ebe52 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1810,6 +1810,20 @@
     public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device supports telephony carrier restriction mechanism.
+     *
+     * <p>Devices declaring this feature must have an implementation of the
+     * {@link android.telephony.TelephonyManager#getAllowedCarriers} and
+     * {@link android.telephony.TelephonyManager#setAllowedCarriers}.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_CARRIERLOCK =
+            "android.hardware.telephony.carrierlock";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports connecting to USB devices
      * as the USB host.
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index bb0a042..5423ca9 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -646,7 +646,7 @@
 
         // Special case where the only scene mode listed is AUTO => no scene mode
         if (sceneModes != null && sceneModes.size() == 1 &&
-                sceneModes.get(0) == Parameters.SCENE_MODE_AUTO) {
+                sceneModes.get(0).equals(Parameters.SCENE_MODE_AUTO)) {
             supportedSceneModes = null;
         }
 
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 042481f..e163365 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -46,6 +46,7 @@
 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;
@@ -1051,26 +1052,6 @@
     }
 
     /**
-     * Request that this callback be invoked at ConnectivityService's earliest
-     * convenience with the current satisfying network's LinkProperties.
-     * If no such network exists no callback invocation is performed.
-     *
-     * The callback must have been registered with #requestNetwork() or
-     * #registerDefaultNetworkCallback(); callbacks registered with
-     * registerNetworkCallback() are not specific to any particular Network so
-     * do not cause any updates.
-     *
-     * @hide
-     */
-    public void requestLinkProperties(NetworkCallback networkCallback) {
-        try {
-            mService.requestLinkProperties(networkCallback.networkRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}.  This
      * will return {@code null} if the network is unknown.
      * <p>This method requires the caller to hold the permission
@@ -1088,26 +1069,6 @@
     }
 
     /**
-     * Request that this callback be invoked at ConnectivityService's earliest
-     * convenience with the current satisfying network's NetworkCapabilities.
-     * If no such network exists no callback invocation is performed.
-     *
-     * The callback must have been registered with #requestNetwork() or
-     * #registerDefaultNetworkCallback(); callbacks registered with
-     * registerNetworkCallback() are not specific to any particular Network so
-     * do not cause any updates.
-     *
-     * @hide
-     */
-    public void requestNetworkCapabilities(NetworkCallback networkCallback) {
-        try {
-            mService.requestNetworkCapabilities(networkCallback.networkRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Gets the URL that should be used for resolving whether a captive portal is present.
      * 1. This URL should respond with a 204 response to a GET request to indicate no captive
      *    portal is present.
@@ -1219,36 +1180,27 @@
 
     private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
         if (networkType == TYPE_MOBILE) {
-            int cap = -1;
-            if ("enableMMS".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_MMS;
-            } else if ("enableSUPL".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
-            } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_DUN;
-            } else if ("enableHIPRI".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
-            } else if ("enableFOTA".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
-            } else if ("enableIMS".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_IMS;
-            } else if ("enableCBS".equals(feature)) {
-                cap = NetworkCapabilities.NET_CAPABILITY_CBS;
-            } else {
-                return null;
+            switch (feature) {
+                case "enableCBS":
+                    return networkCapabilitiesForType(TYPE_MOBILE_CBS);
+                case "enableDUN":
+                case "enableDUNAlways":
+                    return networkCapabilitiesForType(TYPE_MOBILE_DUN);
+                case "enableFOTA":
+                    return networkCapabilitiesForType(TYPE_MOBILE_FOTA);
+                case "enableHIPRI":
+                    return networkCapabilitiesForType(TYPE_MOBILE_HIPRI);
+                case "enableIMS":
+                    return networkCapabilitiesForType(TYPE_MOBILE_IMS);
+                case "enableMMS":
+                    return networkCapabilitiesForType(TYPE_MOBILE_MMS);
+                case "enableSUPL":
+                    return networkCapabilitiesForType(TYPE_MOBILE_SUPL);
+                default:
+                    return null;
             }
-            NetworkCapabilities netCap = new NetworkCapabilities();
-            netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addCapability(cap);
-            netCap.maybeMarkCapabilitiesRestricted();
-            return netCap;
-        } else if (networkType == TYPE_WIFI) {
-            if ("p2p".equals(feature)) {
-                NetworkCapabilities netCap = new NetworkCapabilities();
-                netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-                netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
-                netCap.maybeMarkCapabilitiesRestricted();
-                return netCap;
-            }
+        } else if (networkType == TYPE_WIFI && "p2p".equals(feature)) {
+            return networkCapabilitiesForType(TYPE_WIFI_P2P);
         }
         return null;
     }
@@ -1429,8 +1381,8 @@
         l.networkCapabilities = netCap;
         l.delay = delay;
         l.expireSequenceNumber = 0;
-        l.networkRequest = sendRequestForNetwork(netCap, l.networkCallback, 0,
-                REQUEST, type);
+        l.networkRequest = sendRequestForNetwork(
+                netCap, l.networkCallback, 0, REQUEST, type, getDefaultHandler());
         if (l.networkRequest == null) return null;
         sLegacyRequests.put(netCap, l);
         sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
@@ -1440,8 +1392,9 @@
     private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
         if (delay >= 0) {
             Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
-            Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
-            sCallbackHandler.sendMessageDelayed(msg, delay);
+            CallbackHandler handler = getDefaultHandler();
+            Message msg = handler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+            handler.sendMessageDelayed(msg, delay);
         }
     }
 
@@ -1456,6 +1409,59 @@
         return true;
     }
 
+    private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+    static {
+        sLegacyTypeToTransport.put(TYPE_MOBILE,       NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_CBS,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_DUN,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_FOTA,  NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_IMS,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_MMS,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_SUPL,  NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_WIFI,         NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_WIFI_P2P,     NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_BLUETOOTH,    NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        sLegacyTypeToTransport.put(TYPE_ETHERNET,     NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
+    private static final SparseIntArray sLegacyTypeToCapability = new SparseIntArray();
+    static {
+        sLegacyTypeToCapability.put(TYPE_MOBILE_CBS,  NetworkCapabilities.NET_CAPABILITY_CBS);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_DUN,  NetworkCapabilities.NET_CAPABILITY_DUN);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_FOTA, NetworkCapabilities.NET_CAPABILITY_FOTA);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_IMS,  NetworkCapabilities.NET_CAPABILITY_IMS);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_MMS,  NetworkCapabilities.NET_CAPABILITY_MMS);
+        sLegacyTypeToCapability.put(TYPE_MOBILE_SUPL, NetworkCapabilities.NET_CAPABILITY_SUPL);
+        sLegacyTypeToCapability.put(TYPE_WIFI_P2P,    NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+    }
+
+    /**
+     * Given a legacy type (TYPE_WIFI, ...) returns a NetworkCapabilities
+     * instance suitable for registering a request or callback.  Throws an
+     * IllegalArgumentException if no mapping from the legacy type to
+     * NetworkCapabilities is known.
+     *
+     * @hide
+     */
+    public static NetworkCapabilities networkCapabilitiesForType(int type) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+
+        // 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);
+        }
+        nc.addTransportType(transport);
+
+        // Map from type to capabilities.
+        nc.addCapability(sLegacyTypeToCapability.get(
+                type, NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        nc.maybeMarkCapabilitiesRestricted();
+        return nc;
+    }
+
     /** @hide */
     public static class PacketKeepaliveCallback {
         /** The requested keepalive was successfully started. */
@@ -2601,10 +2607,12 @@
         public void onLost(Network network) {}
 
         /**
-         * Called if no network is found in the given timeout time.  If no timeout is given,
-         * this will not be called. The associated {@link NetworkRequest} will have already
-         * been removed and released, as if {@link #unregisterNetworkCallback} had been called.
-         * @hide
+         * Called if no network is found in the timeout time specified in
+         * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} 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
+         * {@link #unregisterNetworkCallback(NetworkCallback)} had been called.
          */
         public void onUnavailable() {}
 
@@ -2705,6 +2713,10 @@
             super(looper);
         }
 
+        CallbackHandler(Handler handler) {
+            this(handler.getLooper());
+        }
+
         @Override
         public void handleMessage(Message message) {
             NetworkRequest request = getObject(message, NetworkRequest.class);
@@ -2814,7 +2826,7 @@
         }
     }
 
-    private CallbackHandler getHandler() {
+    private CallbackHandler getDefaultHandler() {
         synchronized (sCallbacks) {
             if (sCallbackHandler == null) {
                 sCallbackHandler = new CallbackHandler(ConnectivityThread.getInstanceLooper());
@@ -2823,19 +2835,14 @@
         }
     }
 
-    static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
-    static CallbackHandler sCallbackHandler;
+    private static final HashMap<NetworkRequest, NetworkCallback> sCallbacks = new HashMap<>();
+    private static CallbackHandler sCallbackHandler;
 
-    private final static int LISTEN  = 1;
-    private final static int REQUEST = 2;
+    private static final int LISTEN  = 1;
+    private static final int REQUEST = 2;
 
-    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
-            NetworkCallback callback, int timeoutMs, int action, int legacyType) {
-        return sendRequestForNetwork(need, callback, getHandler(), timeoutMs, action, legacyType);
-    }
-
-    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
-            NetworkCallback callback, Handler handler, int timeoutMs, int action, int legacyType) {
+    private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
+            int timeoutMs, int action, int legacyType, CallbackHandler handler) {
         if (callback == null) {
             throw new IllegalArgumentException("null NetworkCallback");
         }
@@ -2878,16 +2885,19 @@
      * @hide
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback,
-            int timeoutMs, int legacyType) {
-        sendRequestForNetwork(request.networkCapabilities, networkCallback, timeoutMs, REQUEST,
-                legacyType);
+            int timeoutMs, int legacyType, Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, timeoutMs, REQUEST, legacyType, cbHandler);
     }
 
     /**
      * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
      *
      * This {@link NetworkRequest} will live until released via
-     * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits.
+     * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
+     * version of the method which takes a timeout is
+     * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}.
      * 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.
@@ -2906,25 +2916,74 @@
      * {@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 - they
-     *                        uniquely specify 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.
+     *                        The callback is invoked on the default internal Handler.
      * @throws IllegalArgumentException if {@code request} specifies any mutable
      *         {@code NetworkCapabilities}.
      */
     public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
-        requestNetwork(request, networkCallback, 0,
-                inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
+        requestNetwork(request, networkCallback, getDefaultHandler());
     }
 
     /**
+     * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
+     *
+     * 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)}.
+     * 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.
+     * <p>It is presently unsupported to request a network with mutable
+     * {@link NetworkCapabilities} such as
+     * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+     * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+     * as these {@code NetworkCapabilities} represent states that a particular
+     * network may never attain, and whether a network will attain these states
+     * is unknown prior to bringing up the network so the framework does not
+     * know how to go about satisfing a request with these capabilities.
+     *
+     * <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 {@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.
+     * @throws IllegalArgumentException if {@code request} specifies any mutable
+     *         {@code NetworkCapabilities}.
+     */
+    public void requestNetwork(
+            NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        requestNetwork(request, networkCallback, 0, legacyType, cbHandler);
+    }
+
+    /**
+     * 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-timedout version, but if a suitable
-     * network is not found within the given time (in milliseconds) the
-     * {@link NetworkCallback#unavailable} callback is called.  The request must
-     * still be released normally by calling {@link unregisterNetworkCallback(NetworkCallback)}.
+     * 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
@@ -2936,24 +2995,93 @@
      *                        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#unavailable} is called.
-     *
-     * TODO: Make timeouts work and then unhide this method.
-     *
+     *                  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) {
-        requestNetwork(request, networkCallback, timeoutMs,
-                inferLegacyTypeForNetworkCapabilities(request.networkCapabilities));
+        if (timeoutMs <= 0) {
+            throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs);
+        }
+        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+        requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler());
     }
 
     /**
-     * The maximum number of milliseconds the framework will look for a suitable network
-     * during a timeout-equiped call to {@link requestNetwork}.
-     * {@hide}
+     * 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) - {@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 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 final static int MAX_NETWORK_REQUEST_TIMEOUT_MS = 100 * 60 * 1000;
+    public void requestNetwork(NetworkRequest request, int timeoutMs,
+            NetworkCallback networkCallback) {
+        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.
+     *
+     * This function behaves identically to the non-timedout version, 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) - {@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 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.
+     */
+    public void requestNetwork(NetworkRequest request, int timeoutMs,
+            NetworkCallback networkCallback, Handler handler) {
+        if (timeoutMs <= 0) {
+            throw new IllegalArgumentException("Non-positive timeoutMs");
+        }
+        int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities);
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler);
+    }
 
     /**
      * The lookup key for a {@link Network} object included with the intent after
@@ -3066,9 +3194,29 @@
      * @param request {@link NetworkRequest} describing this request.
      * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
      *                        networks change state.
+     *                        The callback is invoked on the default internal Handler.
      */
     public void registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback) {
-        sendRequestForNetwork(request.networkCapabilities, networkCallback, 0, LISTEN, TYPE_NONE);
+        registerNetworkCallback(request, networkCallback, getDefaultHandler());
+    }
+
+    /**
+     * Registers to receive notifications about all networks which satisfy the given
+     * {@link NetworkRequest}.  The callbacks will continue to be called until
+     * either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+     *
+     * @param request {@link NetworkRequest} describing this request.
+     * @param networkCallback The {@link NetworkCallback} that the system will call as suitable
+     *                        networks change state.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     */
+    public void registerNetworkCallback(
+            NetworkRequest request, NetworkCallback networkCallback, Handler handler) {
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        NetworkCapabilities nc = request.networkCapabilities;
+        sendRequestForNetwork(nc, networkCallback, 0, LISTEN, TYPE_NONE, cbHandler);
     }
 
     /**
@@ -3120,8 +3268,24 @@
      *
      * @param networkCallback The {@link NetworkCallback} that the system will call as the
      *                        system default network changes.
+     *                        The callback is invoked on the default internal Handler.
      */
     public void registerDefaultNetworkCallback(NetworkCallback networkCallback) {
+        registerDefaultNetworkCallback(networkCallback, getDefaultHandler());
+    }
+
+    /**
+     * Registers to receive notifications about changes in the system default network. The callbacks
+     * will continue to be called until either the application exits or
+     * {@link #unregisterNetworkCallback(NetworkCallback)} is called.
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+     *
+     * @param networkCallback The {@link NetworkCallback} that the system will call as the
+     *                        system default network changes.
+     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+     */
+    public void registerDefaultNetworkCallback(NetworkCallback networkCallback, Handler handler) {
         // This works because if the NetworkCapabilities are null,
         // ConnectivityService takes them from the default request.
         //
@@ -3129,7 +3293,8 @@
         // 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.
-        sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE);
+        CallbackHandler cbHandler = new CallbackHandler(handler);
+        sendRequestForNetwork(null, networkCallback, 0, REQUEST, TYPE_NONE, cbHandler);
     }
 
     /**
diff --git a/core/java/android/net/ConnectivityMetricsLogger.java b/core/java/android/net/ConnectivityMetricsLogger.java
index 9a2d4e0..67b6908 100644
--- a/core/java/android/net/ConnectivityMetricsLogger.java
+++ b/core/java/android/net/ConnectivityMetricsLogger.java
@@ -46,32 +46,7 @@
 
     public static final String DATA_KEY_EVENTS_COUNT = "count";
 
-    /** {@hide} */ protected IConnectivityMetricsLogger mService;
-    /** {@hide} */ protected volatile long mServiceUnblockedTimestampMillis;
-    private int mNumSkippedEvents;
-
     public ConnectivityMetricsLogger() {
-        // TODO: consider not initializing mService in constructor
-        this(IConnectivityMetricsLogger.Stub.asInterface(
-                ServiceManager.getService(CONNECTIVITY_METRICS_LOGGER_SERVICE)));
-    }
-
-    /** {@hide} */
-    @VisibleForTesting
-    public ConnectivityMetricsLogger(IConnectivityMetricsLogger service) {
-        mService = service;
-    }
-
-    /** {@hide} */
-    protected boolean checkLoggerService() {
-        if (mService != null) {
-            return true;
-        }
-        // Two threads racing here will write the same pointer because getService
-        // is idempotent once MetricsLoggerService is initialized.
-        mService = IConnectivityMetricsLogger.Stub.asInterface(
-                ServiceManager.getService(CONNECTIVITY_METRICS_LOGGER_SERVICE));
-        return mService != null;
     }
 
     /**
@@ -88,62 +63,6 @@
      * @param data is a Parcelable instance representing the event.
      */
     public void logEvent(long timestamp, int componentTag, int eventTag, Parcelable data) {
-        if (mService == null) {
-            if (DBG) {
-                Log.d(TAG, "logEvent(" + componentTag + "," + eventTag + ") Service not ready");
-            }
-            return;
-        }
-
-        if (mServiceUnblockedTimestampMillis > 0) {
-            if (System.currentTimeMillis() < mServiceUnblockedTimestampMillis) {
-                // Service is throttling events.
-                // Don't send new events because they will be dropped.
-                mNumSkippedEvents++;
-                return;
-            }
-        }
-
-        ConnectivityMetricsEvent skippedEventsEvent = null;
-        if (mNumSkippedEvents > 0) {
-            // Log number of skipped events
-            Bundle b = new Bundle();
-            b.putInt(DATA_KEY_EVENTS_COUNT, mNumSkippedEvents);
-
-            // Log the skipped event.
-            // TODO: Note that some of the clients push all states events into the server,
-            // If we lose some states logged here, we might mess up the statistics happened at the
-            // backend. One of the options is to introduce a non-skippable flag for important events
-            // that are logged.
-            skippedEventsEvent = new ConnectivityMetricsEvent(mServiceUnblockedTimestampMillis,
-                    componentTag, TAG_SKIPPED_EVENTS, b);
-
-            mServiceUnblockedTimestampMillis = 0;
-        }
-
-        ConnectivityMetricsEvent event = new ConnectivityMetricsEvent(timestamp, componentTag,
-                eventTag, data);
-
-        try {
-            long result;
-            if (skippedEventsEvent == null) {
-                result = mService.logEvent(event);
-            } else {
-                result = mService.logEvents(new ConnectivityMetricsEvent[]
-                        {skippedEventsEvent, event});
-            }
-
-            if (result == 0) {
-                mNumSkippedEvents = 0;
-            } else {
-                mNumSkippedEvents++;
-                if (result > 0) { // events are throttled
-                    mServiceUnblockedTimestampMillis = result;
-                }
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error logging event", e);
-        }
     }
 
     /**
@@ -157,33 +76,17 @@
      * @return events
      */
     public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
-        try {
-            return mService.getEvents(reference);
-        } catch (RemoteException e) {
-            Log.e(TAG, "IConnectivityMetricsLogger.getEvents", e);
-            return null;
-        }
+        return new ConnectivityMetricsEvent[0];
     }
 
     /**
      * Register PendingIntent which will be sent when new events are ready to be retrieved.
      */
     public boolean register(PendingIntent newEventsIntent) {
-        try {
-            return mService.register(newEventsIntent);
-        } catch (RemoteException e) {
-            Log.e(TAG, "IConnectivityMetricsLogger.register", e);
-            return false;
-        }
+        return false;
     }
 
     public boolean unregister(PendingIntent newEventsIntent) {
-        try {
-            mService.unregister(newEventsIntent);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "IConnectivityMetricsLogger.unregister", e);
-            return false;
-        }
+        return false;
     }
 }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 4aabda9..b123c28f 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,8 +156,6 @@
     void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
             in PendingIntent operation);
 
-    void requestLinkProperties(in NetworkRequest networkRequest);
-    void requestNetworkCapabilities(in NetworkRequest networkRequest);
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
     void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
diff --git a/core/java/android/net/INetworkScoreCache.aidl b/core/java/android/net/INetworkScoreCache.aidl
index 35601ce..1da7d67 100644
--- a/core/java/android/net/INetworkScoreCache.aidl
+++ b/core/java/android/net/INetworkScoreCache.aidl
@@ -34,7 +34,7 @@
  * the current scores for each network for debugging purposes.
  * @hide
  */
-interface INetworkScoreCache
+oneway interface INetworkScoreCache
 {
     void updateScores(in List<ScoredNetwork> networks);
 
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 9573953..82432c7 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -21,6 +21,7 @@
 import android.net.RecommendationRequest;
 import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
+import android.os.RemoteCallback;
 
 /**
  * A service for updating network scores from a network scorer application.
@@ -117,4 +118,16 @@
      *         scorer.
      */
     String getActiveScorerPackage();
+    
+    /**
+     * Request a recommendation for the best network to connect to
+     * taking into account the inputs from the {@link RecommendationRequest}.
+     *
+     * @param request a {@link RecommendationRequest} instance containing the details of the request
+     * @param remoteCallback a {@link RemoteCallback} instance to invoke when the recommendation
+     *                       is available.
+     * @throws SecurityException if the caller is not the system
+     */
+    oneway void requestRecommendationAsync(in RecommendationRequest request,
+                                           in RemoteCallback remoteCallback);
 }
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
new file mode 100644
index 0000000..da5cb37
--- /dev/null
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -0,0 +1,181 @@
+/*
+ * 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.annotation.StringDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
+ * RFC 4301.
+ */
+public final class IpSecAlgorithm implements Parcelable {
+
+    /**
+     * AES-CBC Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for this key are {128, 192, 256}.
+     */
+    public static final String ALGO_CRYPT_AES_CBC = "cbc(aes)";
+
+    /**
+     * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
+     * applications and is provided for legacy compatibility with 3gpp infrastructure.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
+     */
+    public static final String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
+
+    /**
+     * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
+     * new applications and is provided for legacy compatibility with 3gpp infrastructure.
+     *
+     * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
+     */
+    public static final String ALGO_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)";
+
+    /**
+     * 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)";
+    /**
+     * 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)";
+
+    /** @hide */
+    @StringDef({
+        ALGO_CRYPT_AES_CBC,
+        ALGO_AUTH_HMAC_MD5,
+        ALGO_AUTH_HMAC_SHA1,
+        ALGO_AUTH_HMAC_SHA256,
+        ALGO_AUTH_HMAC_SHA512
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AlgorithmName {}
+
+    private final String mName;
+    private final byte[] mKey;
+    private final int mTruncLenBits;
+
+    /**
+     * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
+     * algorithm
+     *
+     * @param algorithm type for IpSec.
+     * @param key non-null Key padded to a multiple of 8 bits.
+     */
+    public IpSecAlgorithm(String algorithm, byte[] key) {
+        this(algorithm, key, key.length * 8);
+    }
+
+    /**
+     * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
+     * algorithm
+     *
+     * @param algoName precise name of the algorithm to be used.
+     * @param key non-null Key padded to a multiple of 8 bits.
+     * @param truncLenBits the number of bits of output hash to use; only meaningful for
+     *     Authentication.
+     */
+    public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
+        if (!isTruncationLengthValid(algoName, truncLenBits)) {
+            throw new IllegalArgumentException("Unknown algorithm or invalid length");
+        }
+        mName = algoName;
+        mKey = key.clone();
+        mTruncLenBits = Math.min(truncLenBits, key.length * 8);
+    }
+
+    /** Retrieve the algorithm name */
+    public String getName() {
+        return mName;
+    }
+
+    /** Retrieve the key for this algorithm */
+    public byte[] getKey() {
+        return mKey.clone();
+    }
+
+    /**
+     * Retrieve the truncation length, in bits, for the key in this algo. By default this will be
+     * the length in bits of the key.
+     */
+    public int getTruncationLengthBits() {
+        return mTruncLenBits;
+    }
+
+    /* Parcelable Implementation */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Write to parcel */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeByteArray(mKey);
+        out.writeInt(mTruncLenBits);
+    }
+
+    /** Parcelable Creator */
+    public static final Parcelable.Creator<IpSecAlgorithm> CREATOR =
+            new Parcelable.Creator<IpSecAlgorithm>() {
+                public IpSecAlgorithm createFromParcel(Parcel in) {
+                    return new IpSecAlgorithm(in);
+                }
+
+                public IpSecAlgorithm[] newArray(int size) {
+                    return new IpSecAlgorithm[size];
+                }
+            };
+
+    private IpSecAlgorithm(Parcel in) {
+        mName = in.readString();
+        mKey = in.createByteArray();
+        mTruncLenBits = in.readInt();
+    }
+
+    private static boolean isTruncationLengthValid(String algo, int truncLenBits) {
+        switch (algo) {
+            case ALGO_AUTH_HMAC_MD5:
+                return (truncLenBits >= 96 && truncLenBits <= 128);
+            case ALGO_AUTH_HMAC_SHA1:
+                return (truncLenBits >= 96 && truncLenBits <= 160);
+            case ALGO_AUTH_HMAC_SHA256:
+                return (truncLenBits >= 96 && truncLenBits <= 256);
+            case ALGO_AUTH_HMAC_SHA384:
+                return (truncLenBits >= 192 && truncLenBits <= 384);
+            case ALGO_AUTH_HMAC_SHA512:
+                return (truncLenBits >= 256 && truncLenBits <= 512);
+            default:
+                return false;
+        }
+    }
+};
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/core/java/android/net/IpSecConfig.aidl
similarity index 74%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to core/java/android/net/IpSecConfig.aidl
index 62d5603..eaefca7 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/core/java/android/net/IpSecConfig.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.net;
 
-parcelable HomeSP;
+/** @hide */
+parcelable IpSecConfig;
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
new file mode 100644
index 0000000..b58bf42
--- /dev/null
+++ b/core/java/android/net/IpSecConfig.java
@@ -0,0 +1,197 @@
+/*
+ * 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.util.Log;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/** @hide */
+public final class IpSecConfig implements Parcelable {
+    private static final String TAG = IpSecConfig.class.getSimpleName();
+
+    //MODE_TRANSPORT or MODE_TUNNEL
+    int mode;
+
+    // For tunnel mode
+    InetAddress localAddress;
+
+    InetAddress remoteAddress;
+
+    // Limit selection by network interface
+    Network network;
+
+    public static class Flow {
+        // Minimum requirements for identifying a transform
+        // SPI identifying the IPsec flow in packet processing
+        // and a remote IP address
+        int spi;
+
+        // Encryption Algorithm
+        IpSecAlgorithm encryptionAlgo;
+
+        // Authentication Algorithm
+        IpSecAlgorithm authenticationAlgo;
+    }
+
+    Flow[] flow = new Flow[2];
+
+    // For tunnel mode IPv4 UDP Encapsulation
+    // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
+    int encapType;
+    int encapLocalPort;
+    int encapRemotePort;
+
+    // An optional protocol to match with the selector
+    int selectorProto;
+
+    // A bitmask of FEATURE_* indicating which of the fields
+    // of this class are valid.
+    long features;
+
+    // An interval, in seconds between the NattKeepalive packets
+    int nattKeepaliveInterval;
+
+    public InetAddress getLocalIp() {
+        return localAddress;
+    }
+
+    public int getSpi(int direction) {
+        return flow[direction].spi;
+    }
+
+    public InetAddress getRemoteIp() {
+        return remoteAddress;
+    }
+
+    public IpSecAlgorithm getEncryptionAlgo(int direction) {
+        return flow[direction].encryptionAlgo;
+    }
+
+    public IpSecAlgorithm getAuthenticationAlgo(int direction) {
+        return flow[direction].authenticationAlgo;
+    }
+
+    Network getNetwork() {
+        return network;
+    }
+
+    public int getEncapType() {
+        return encapType;
+    }
+
+    public int getEncapLocalPort() {
+        return encapLocalPort;
+    }
+
+    public int getEncapRemotePort() {
+        return encapRemotePort;
+    }
+
+    public int getSelectorProto() {
+        return selectorProto;
+    }
+
+    int getNattKeepaliveInterval() {
+        return nattKeepaliveInterval;
+    }
+
+    public boolean hasProperty(int featureBits) {
+        return (features & featureBits) == featureBits;
+    }
+
+    // Parcelable Methods
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(features);
+        // TODO: Use a byte array or other better method for storing IPs that can also include scope
+        out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
+        // TODO: Use a byte array or other better method for storing IPs that can also include scope
+        out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
+        out.writeParcelable(network, flags);
+        out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi);
+        out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags);
+        out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags);
+        out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi);
+        out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags);
+        out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags);
+        out.writeInt(encapType);
+        out.writeInt(encapLocalPort);
+        out.writeInt(encapRemotePort);
+        out.writeInt(selectorProto);
+    }
+
+    // Package Private: Used by the IpSecTransform.Builder;
+    // there should be no public constructor for this object
+    IpSecConfig() {
+        flow[IpSecTransform.DIRECTION_IN].spi = 0;
+        flow[IpSecTransform.DIRECTION_OUT].spi = 0;
+        nattKeepaliveInterval = 0; //FIXME constant
+    }
+
+    private static InetAddress readInetAddressFromParcel(Parcel in) {
+        String addrString = in.readString();
+        if (addrString == null) {
+            return null;
+        }
+        try {
+            return InetAddress.getByName(addrString);
+        } catch (UnknownHostException e) {
+            Log.wtf(TAG, "Invalid IpAddress " + addrString);
+            return null;
+        }
+    }
+
+    private IpSecConfig(Parcel in) {
+        features = in.readLong();
+        localAddress = readInetAddressFromParcel(in);
+        remoteAddress = readInetAddressFromParcel(in);
+        network = (Network) in.readParcelable(Network.class.getClassLoader());
+        flow[IpSecTransform.DIRECTION_IN].spi = in.readInt();
+        flow[IpSecTransform.DIRECTION_IN].encryptionAlgo =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        flow[IpSecTransform.DIRECTION_IN].authenticationAlgo =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt();
+        flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        encapType = in.readInt();
+        encapLocalPort = in.readInt();
+        encapRemotePort = in.readInt();
+        selectorProto = in.readInt();
+    }
+
+    public static final Parcelable.Creator<IpSecConfig> CREATOR =
+            new Parcelable.Creator<IpSecConfig>() {
+                public IpSecConfig createFromParcel(Parcel in) {
+                    return new IpSecConfig(in);
+                }
+
+                public IpSecConfig[] newArray(int size) {
+                    return new IpSecConfig[size];
+                }
+            };
+}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
new file mode 100644
index 0000000..2c544e9
--- /dev/null
+++ b/core/java/android/net/IpSecManager.java
@@ -0,0 +1,379 @@
+/*
+ * 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 com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.INetworkManagementService;
+import android.os.ParcelFileDescriptor;
+import android.util.AndroidException;
+import dalvik.system.CloseGuard;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * This class contains methods for managing IPsec sessions, which will perform kernel-space
+ * encryption and decryption of socket or Network traffic.
+ *
+ * <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}
+ */
+public final class IpSecManager {
+    private static final String TAG = "IpSecManager";
+
+    /**
+     * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
+     * request. If encountered, selection of a new SPI is required before a transform may be
+     * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
+     * or reserved using reserveSecurityParameterIndex.
+     */
+    public static final class SpiUnavailableException extends AndroidException {
+        private final int mSpi;
+
+        /**
+         * Construct an exception indicating that a transform with the given SPI is already in use
+         * or otherwise unavailable.
+         *
+         * @param msg Description indicating the colliding SPI
+         * @param spi the SPI that could not be used due to a collision
+         */
+        SpiUnavailableException(String msg, int spi) {
+            super(msg + "(spi: " + spi + ")");
+            mSpi = spi;
+        }
+
+        /** Retrieve the SPI that caused a collision */
+        public int getSpi() {
+            return mSpi;
+        }
+    }
+
+    /**
+     * Indicates that the requested system resource for IPsec, such as a socket or other system
+     * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
+     * type requested.
+     */
+    public static final class ResourceUnavailableException extends AndroidException {
+
+        ResourceUnavailableException(String msg) {
+            super(msg);
+        }
+    }
+
+    private final Context mContext;
+    private final INetworkManagementService mService;
+
+    public static final class SecurityParameterIndex implements AutoCloseable {
+        private final Context mContext;
+        private final InetAddress mDestinationAddress;
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+        private int mSpi;
+
+        /** Return the underlying SPI held by this object */
+        public int getSpi() {
+            return mSpi;
+        }
+
+        private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi)
+                throws ResourceUnavailableException, SpiUnavailableException {
+            mContext = context;
+            mDestinationAddress = destinationAddress;
+            mSpi = spi;
+            mCloseGuard.open("open");
+        }
+
+        /**
+         * Release an SPI that was previously reserved.
+         *
+         * <p>Release an SPI for use by other users in the system. This will fail if the SPI is
+         * currently in use by an IpSecTransform.
+         *
+         * @param destinationAddress SPIs must be unique for each combination of SPI and destination
+         *     address. Thus, the destinationAddress to which the SPI will communicate must be
+         *     supplied.
+         * @param spi the previously reserved SPI to be freed.
+         */
+        @Override
+        public void close() {
+            mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI
+            mCloseGuard.close();
+        }
+
+        @Override
+        protected void finalize() {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            close();
+        }
+    }
+
+    /**
+     * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+     *
+     * <p>No IPsec packet may contain an SPI of 0.
+     */
+    public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+    /**
+     * Reserve an SPI for traffic bound towards the specified destination address.
+     *
+     * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+     * SecurityParameterIndex#close()}.
+     *
+     * @param destinationAddress SPIs must be unique for each combination of SPI and destination
+     *     address.
+     * @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(
+            InetAddress destinationAddress, int requestedSpi)
+            throws SpiUnavailableException, ResourceUnavailableException {
+        return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi);
+    }
+
+    /**
+     * 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(Socket, IpSecTransform)};
+     *
+     * @param socket a stream socket
+     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     */
+    public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
+            throws IOException {
+        applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+    }
+
+    /**
+     * Apply an active Transport Mode IPsec Transform to a datagram 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(DatagramSocket, IpSecTransform)};
+     *
+     * @param socket a datagram socket
+     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     */
+    public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
+            throws IOException {
+        applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
+    }
+
+    /* Call down to activate a transform */
+    private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform 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).
+     * Applications should probably not use this API directly. Instead, they should use {@link
+     * VpnService} to provide VPN capability in a more generic fashion.
+     *
+     * @param net a {@link Network} that will be tunneled via IP Sec.
+     * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
+     * @hide
+     */
+    @SystemApi
+    public void applyTunnelModeTransform(Network net, IpSecTransform 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 that previously had a transform applied to it.
+     * @param transform the IPsec Transform that was previously applied to the given socket
+     */
+    public void removeTransportModeTransform(Socket socket, IpSecTransform transform) {
+        removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+    }
+
+    /**
+     * Remove a transform from a given datagram 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 that previously had a transform applied to it.
+     * @param transform the IPsec Transform that was previously applied to the given socket
+     */
+    public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) {
+        removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
+    }
+
+    /* Call down to activate a transform */
+    private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+
+    /**
+     * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
+     * cleanup if a tunneled Network experiences a change in default route. The Network will drop
+     * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
+     * lost, all traffic will drop.
+     *
+     * @param net a network that currently has transform applied to it.
+     * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
+     *     network
+     * @hide
+     */
+    @SystemApi
+    public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
+
+    /**
+     * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
+     * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
+     *
+     * <p>The socket provided by this class cannot be re-bound or closed via the inner
+     * FileDescriptor. Instead, disposing of this socket requires a call to close().
+     */
+    public static final class UdpEncapsulationSocket implements AutoCloseable {
+        private final FileDescriptor mFd;
+        private final Context mContext;
+        private final CloseGuard mCloseGuard = CloseGuard.get();
+
+        private UdpEncapsulationSocket(Context context, int port)
+                throws ResourceUnavailableException {
+            mContext = context;
+            mCloseGuard.open("constructor");
+            // TODO: go down to the kernel and get a socket on the specified
+            mFd = new FileDescriptor();
+        }
+
+        private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException {
+            mContext = context;
+            mCloseGuard.open("constructor");
+            // TODO: go get a random socket on a random port
+            mFd = new FileDescriptor();
+        }
+
+        /** Access the inner UDP Encapsulation Socket */
+        public FileDescriptor getSocket() {
+            return mFd;
+        }
+
+        /** Retrieve the port number of the inner encapsulation socket */
+        public int getPort() {
+            return 0; // TODO get the port number from the Socket;
+        }
+
+        @Override
+        /**
+         * Release the resources that have been reserved for this Socket.
+         *
+         * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
+         * system. This must be done as part of cleanup following use of a socket. Failure to do so
+         * will cause the socket to count against a total allocation limit for IpSec and eventually
+         * fail due to resource limits.
+         *
+         * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
+         */
+        public void close() {
+            // TODO: Go close the socket
+            mCloseGuard.close();
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mCloseGuard != null) {
+                mCloseGuard.warnIfOpen();
+            }
+
+            close();
+        }
+    };
+
+    /**
+     * Open a socket that is bound to a free UDP port on the system.
+     *
+     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
+     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
+     * Encapsulation port.
+     *
+     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
+     * socket port. Explicitly opening this port is only necessary if communication is desired on
+     * that port.
+     *
+     * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
+     *     method will bind to the specified port or fail. To retrieve the port number, call {@link
+     *     android.system.Os#getsockname(FileDescriptor)}.
+     * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
+     *     of the object.
+     */
+    // Returning a socket in this fashion that has been created and bound by the system
+    // is the only safe way to ensure that a socket is both accessible to the user and
+    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+    // the port, which could potentially impact the traffic of the next user who binds to that
+    // socket.
+    public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
+            throws IOException, ResourceUnavailableException {
+        // Temporary code
+        return new UdpEncapsulationSocket(mContext, port);
+    }
+
+    /**
+     * Open a socket that is bound to a port selected by the system.
+     *
+     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
+     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
+     * Encapsulation port.
+     *
+     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
+     * socket port. Explicitly opening this port is only necessary if communication is desired on
+     * that port.
+     *
+     * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
+     */
+    // Returning a socket in this fashion that has been created and bound by the system
+    // is the only safe way to ensure that a socket is both accessible to the user and
+    // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+    // the port, which could potentially impact the traffic of the next user who binds to that
+    // socket.
+    public UdpEncapsulationSocket openUdpEncapsulationSocket()
+            throws IOException, ResourceUnavailableException {
+        // Temporary code
+        return new UdpEncapsulationSocket(mContext);
+    }
+
+    /**
+     * Retrieve an instance of an IpSecManager within you application context
+     *
+     * @param context the application context for this manager
+     * @hide
+     */
+    public IpSecManager(Context context, INetworkManagementService service) {
+        mContext = checkNotNull(context, "missing context");
+        mService = checkNotNull(service, "missing service");
+    }
+}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
new file mode 100644
index 0000000..d6dd28be
--- /dev/null
+++ b/core/java/android/net/IpSecTransform.java
@@ -0,0 +1,471 @@
+/*
+ * 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.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.system.ErrnoException;
+import android.util.Log;
+import dalvik.system.CloseGuard;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+
+/**
+ * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
+ *
+ * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
+ * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
+ * transform may be disabled automatically, with likely undesirable results.
+ *
+ * <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.
+ */
+public final class IpSecTransform implements AutoCloseable {
+    private static final String TAG = "IpSecTransform";
+
+    /**
+     * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
+     * to traffic towards the host.
+     */
+    public static final int DIRECTION_IN = 0;
+
+    /**
+     * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
+     * to traffic from the host.
+     */
+    public static final int DIRECTION_OUT = 1;
+
+    /** @hide */
+    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransformDirection {}
+
+    /** @hide */
+    private static final int MODE_TUNNEL = 0;
+
+    /** @hide */
+    private static final int MODE_TRANSPORT = 1;
+
+    /** @hide */
+    public static final int ENCAP_NONE = 0;
+
+    /**
+     * IpSec traffic will be encapsulated within UDP as per <a
+     * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+     *
+     * @hide
+     */
+    public static final int ENCAP_ESPINUDP = 1;
+
+    /**
+     * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
+     * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
+     *
+     * @hide
+     */
+    public static final int ENCAP_ESPINUDP_NONIKE = 2;
+
+    /** @hide */
+    @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EncapType {}
+
+    /**
+     * Sentinel for an invalid transform (means that this transform is inactive).
+     *
+     * @hide
+     */
+    public static final int INVALID_TRANSFORM_ID = -1;
+
+    private IpSecTransform(Context context, IpSecConfig config) {
+        mContext = context;
+        mConfig = config;
+        mTransformId = INVALID_TRANSFORM_ID;
+    }
+
+    private IpSecTransform activate()
+            throws IOException, IpSecManager.ResourceUnavailableException,
+                    IpSecManager.SpiUnavailableException {
+        int transformId;
+        synchronized (this) {
+            //try {
+            transformId = INVALID_TRANSFORM_ID;
+            //} catch (RemoteException e) {
+            //    throw e.rethrowFromSystemServer();
+            //}
+
+            if (transformId < 0) {
+                throw new ErrnoException("addTransform", -transformId).rethrowAsIOException();
+            }
+
+            startKeepalive(mContext); // Will silently fail if not required
+            mTransformId = transformId;
+            Log.d(TAG, "Added Transform with Id " + transformId);
+        }
+        mCloseGuard.open("build");
+
+        return this;
+    }
+
+    /**
+     * Deactivate an IpSecTransform and free all resources for that transform that are managed by
+     * the system for this Transform.
+     *
+     * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
+     * refusing to send or receive data. This method will silently succeed if the specified
+     * transform has already been removed; thus, it is always safe to attempt cleanup when a
+     * transform is no longer needed.
+     */
+    public void close() {
+        Log.d(TAG, "Removing Transform with Id " + mTransformId);
+
+        // Always safe to attempt cleanup
+        if (mTransformId == INVALID_TRANSFORM_ID) {
+            return;
+        }
+        //try {
+        stopKeepalive();
+        //} catch (RemoteException e) {
+        //    transform.setTransformId(transformId);
+        //    throw e.rethrowFromSystemServer();
+        //} finally {
+        mTransformId = INVALID_TRANSFORM_ID;
+        //}
+        mCloseGuard.close();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        if (mCloseGuard != null) {
+            mCloseGuard.warnIfOpen();
+        }
+        close();
+    }
+
+    /* Package */
+    IpSecConfig getConfig() {
+        return mConfig;
+    }
+
+    private final IpSecConfig mConfig;
+    private int mTransformId;
+    private final Context mContext;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+    private ConnectivityManager.PacketKeepalive mKeepalive;
+    private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+    private Object mKeepaliveSyncLock = new Object();
+    private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
+            new ConnectivityManager.PacketKeepaliveCallback() {
+
+                @Override
+                public void onStarted() {
+                    synchronized (mKeepaliveSyncLock) {
+                        mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS;
+                        mKeepaliveSyncLock.notifyAll();
+                    }
+                }
+
+                @Override
+                public void onStopped() {
+                    synchronized (mKeepaliveSyncLock) {
+                        mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+                        mKeepaliveSyncLock.notifyAll();
+                    }
+                }
+
+                @Override
+                public void onError(int error) {
+                    synchronized (mKeepaliveSyncLock) {
+                        mKeepaliveStatus = error;
+                        mKeepaliveSyncLock.notifyAll();
+                    }
+                }
+            };
+
+    /* Package */
+    void startKeepalive(Context c) {
+        if (mConfig.getNattKeepaliveInterval() == 0) {
+            return;
+        }
+
+        ConnectivityManager cm =
+                (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        if (mKeepalive != null) {
+            Log.e(TAG, "Keepalive already started for this IpSecTransform.");
+            return;
+        }
+
+        synchronized (mKeepaliveSyncLock) {
+            mKeepalive =
+                    cm.startNattKeepalive(
+                            mConfig.getNetwork(),
+                            mConfig.getNattKeepaliveInterval(),
+                            mKeepaliveCallback,
+                            mConfig.getLocalIp(),
+                            mConfig.getEncapLocalPort(),
+                            mConfig.getRemoteIp());
+            try {
+                mKeepaliveSyncLock.wait(2000);
+            } catch (InterruptedException e) {
+            }
+        }
+        if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
+            throw new UnsupportedOperationException("Packet Keepalive cannot be started");
+        }
+    }
+
+    /* Package */
+    void stopKeepalive() {
+        if (mKeepalive == null) {
+            return;
+        }
+        mKeepalive.stop();
+        synchronized (mKeepaliveSyncLock) {
+            if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
+                try {
+                    mKeepaliveSyncLock.wait(2000);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /* Package */
+    void setTransformId(int transformId) {
+        mTransformId = transformId;
+    }
+
+    /* Package */
+    int getTransformId() {
+        return mTransformId;
+    }
+
+    /**
+     * Builder object to facilitate the creation of IpSecTransform objects.
+     *
+     * <p>Apply additional properties to the transform and then call a build() method to return an
+     * IpSecTransform object.
+     *
+     * @see Builder#buildTransportModeTransform(InetAddress)
+     */
+    public static class Builder {
+        private Context mContext;
+        private IpSecConfig mConfig;
+
+        /**
+         * Add an encryption algorithm to the transform for the given direction.
+         *
+         * <p>If encryption is set for a given direction without also providing an SPI for that
+         * direction, creation of an IpSecTransform will fail upon calling a build() method.
+         *
+         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
+         */
+        public IpSecTransform.Builder setEncryption(
+                @TransformDirection int direction, IpSecAlgorithm algo) {
+            mConfig.flow[direction].encryptionAlgo = algo;
+            return this;
+        }
+
+        /**
+         * Add an authentication/integrity algorithm to the transform.
+         *
+         * <p>If authentication is set for a given direction without also providing an SPI for that
+         * direction, creation of an IpSecTransform will fail upon calling a build() method.
+         *
+         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
+         */
+        public IpSecTransform.Builder setAuthentication(
+                @TransformDirection int direction, IpSecAlgorithm algo) {
+            mConfig.flow[direction].authenticationAlgo = algo;
+            return this;
+        }
+
+        /**
+         * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
+         * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
+         * given destination address.
+         *
+         * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
+         * possible. Random number generation is a reasonable approach to selecting an SPI. For
+         * outbound SPIs, they must be reserved by calling {@link
+         * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
+         * fail to build.
+         *
+         * <p>Unless an SPI is set for a given direction, traffic in that direction will be
+         * sent/received without any IPsec applied.
+         *
+         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param spi a unique 32-bit integer to identify transformed traffic
+         */
+        public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) {
+            mConfig.flow[direction].spi = spi;
+            return this;
+        }
+
+        /**
+         * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
+         * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
+         * given destination address.
+         *
+         * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
+         * possible. Random number generation is a reasonable approach to selecting an SPI. For
+         * outbound SPIs, they must be reserved by calling {@link
+         * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
+         * fail to activate.
+         *
+         * <p>Unless an SPI is set for a given direction, traffic in that direction will be
+         * sent/received without any IPsec applied.
+         *
+         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+         *     traffic
+         */
+        public IpSecTransform.Builder setSpi(
+                @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
+            mConfig.flow[direction].spi = spi.getSpi();
+            return this;
+        }
+
+        /**
+         * Specify the network on which this transform will emit its traffic; (otherwise it will
+         * emit on the default network).
+         *
+         * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
+         * tunnel mode.
+         *
+         * @hide
+         */
+        @SystemApi
+        public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
+            mConfig.network = net;
+            return this;
+        }
+
+        /**
+         * Add UDP encapsulation to an IPv4 transform
+         *
+         * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
+         * details on how UDP should be applied to IPsec.
+         *
+         * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
+         *     receiving encapsulating traffic.
+         * @param remotePort the UDP port number of the remote that will send and receive
+         *     encapsulated traffic. In the case of IKE, this is likely port 4500.
+         */
+        public IpSecTransform.Builder setIpv4Encapsulation(
+                IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+            // TODO: check encap type is valid.
+            mConfig.encapType = ENCAP_ESPINUDP;
+            mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket
+            mConfig.encapRemotePort = remotePort;
+            return this;
+        }
+
+        // TODO: Decrease the minimum keepalive to maybe 10?
+        // TODO: Probably a better exception to throw for NATTKeepalive failure
+        // TODO: Specify the needed NATT keepalive permission.
+        /**
+         * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
+         * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
+         * be activated, then the transform will fail to activate and throw an IOException.
+         *
+         * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
+         *     than 20s and no more than 3600s.
+         * @hide
+         */
+        @SystemApi
+        public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
+            mConfig.nattKeepaliveInterval = intervalSeconds;
+            return this;
+        }
+
+        /**
+         * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
+         * Some parameters have interdependencies that are checked at build time. If a well-formed
+         * transform cannot be created from the supplied parameters, this method will throw an
+         * Exception.
+         *
+         * <p>Upon a successful return from this call, the provided IpSecTransform will be active
+         * and may be applied to sockets. If too many IpSecTransform objects are active for a given
+         * user this operation will fail and throw ResourceUnavailableException. To avoid these
+         * exceptions, unused Transform objects must be cleaned up by calling {@link
+         * IpSecTransform#close()} when they are no longer needed.
+         *
+         * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
+         *     socket will cause the transform to be applied.
+         *     <p>Note that an active transform will not impact any network traffic until it has
+         *     been applied to one or more Sockets. Calling this method is a necessary precondition
+         *     for applying it to a socket, but is not sufficient to actually apply IPsec.
+         * @throws IllegalArgumentException indicating that a particular combination of transform
+         *     properties is invalid.
+         * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
+         *     may be allocated
+         * @throws SpiUnavailableException if the SPI collides with an existing transform
+         *     (unlikely).
+         * @throws ResourceUnavailableException if the current user currently has exceeded the
+         *     number of allowed active transforms.
+         */
+        public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
+                throws IpSecManager.ResourceUnavailableException,
+                        IpSecManager.SpiUnavailableException, IOException {
+            //FIXME: argument validation here
+            //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
+            mConfig.mode = MODE_TRANSPORT;
+            mConfig.remoteAddress = remoteAddress;
+            return new IpSecTransform(mContext, mConfig).activate();
+        }
+
+        /**
+         * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
+         * parameters have interdependencies that are checked at build time.
+         *
+         * @param localAddress the {@link InetAddress} that provides the local endpoint for this
+         *     IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
+         *     that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
+         * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this
+         *     IPsec tunnel.
+         * @throws IllegalArgumentException indicating that a particular combination of transform
+         *     properties is invalid.
+         * @hide
+         */
+        @SystemApi
+        public IpSecTransform buildTunnelModeTransform(
+                InetAddress localAddress, InetAddress remoteAddress) {
+            //FIXME: argument validation here
+            //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
+            mConfig.localAddress = localAddress;
+            mConfig.remoteAddress = remoteAddress;
+            mConfig.mode = MODE_TUNNEL;
+            return new IpSecTransform(mContext, mConfig);
+        }
+
+        /**
+         * Create a new IpSecTransform.Builder to construct an IpSecTransform
+         *
+         * @param context current Context
+         */
+        public Builder(Context context) {
+            mContext = context;
+            mConfig = new IpSecConfig();
+        }
+    }
+}
diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java
index 16ae867..5739c79 100644
--- a/core/java/android/net/NetworkRecommendationProvider.java
+++ b/core/java/android/net/NetworkRecommendationProvider.java
@@ -5,8 +5,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -27,8 +25,6 @@
             "android.net.extra.RECOMMENDATION_RESULT";
     /** The key into the callback Bundle where the sequence will be found. */
     public static final String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE";
-    private static final String EXTRA_RECOMMENDATION_REQUEST =
-            "android.net.extra.RECOMMENDATION_REQUEST";
     private final IBinder mService;
 
     /**
@@ -39,7 +35,7 @@
         if (handler == null) {
             throw new IllegalArgumentException("The provided handler cannot be null.");
         }
-        mService = new ServiceWrapper(new ServiceHandler(handler.getLooper()));
+        mService = new ServiceWrapper(handler);
     }
 
     /**
@@ -125,42 +121,10 @@
         }
     }
 
-    private final class ServiceHandler extends Handler {
-        static final int MSG_GET_RECOMMENDATION = 1;
-        static final int MSG_REQUEST_SCORES = 2;
-
-        ServiceHandler(Looper looper) {
-            super(looper, null /*callback*/, true /*async*/);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            final int what = msg.what;
-            switch (what) {
-                case MSG_GET_RECOMMENDATION:
-                    final IRemoteCallback callback = (IRemoteCallback) msg.obj;
-                    final int seq = msg.arg1;
-                    final RecommendationRequest request =
-                            msg.getData().getParcelable(EXTRA_RECOMMENDATION_REQUEST);
-                    final ResultCallback resultCallback = new ResultCallback(callback, seq);
-                    onRequestRecommendation(request, resultCallback);
-                    break;
-
-                case MSG_REQUEST_SCORES:
-                    final NetworkKey[] networks = (NetworkKey[]) msg.obj;
-                    onRequestScores(networks);
-                    break;
-
-                default:
-                    throw new IllegalArgumentException("Unknown message: " + what);
-            }
-        }
-    }
-
     /**
-     * A wrapper around INetworkRecommendationProvider that sends calls to the internal Handler.
+     * A wrapper around INetworkRecommendationProvider that dispatches to the provided Handler.
      */
-    private static final class ServiceWrapper extends INetworkRecommendationProvider.Stub {
+    private final class ServiceWrapper extends INetworkRecommendationProvider.Stub {
         private final Handler mHandler;
 
         ServiceWrapper(Handler handler) {
@@ -168,20 +132,26 @@
         }
 
         @Override
-        public void requestRecommendation(RecommendationRequest request, IRemoteCallback callback,
-                int sequence) throws RemoteException {
-            final Message msg = mHandler.obtainMessage(
-                    ServiceHandler.MSG_GET_RECOMMENDATION, sequence, 0 /*arg2*/, callback);
-            final Bundle data = new Bundle();
-            data.putParcelable(EXTRA_RECOMMENDATION_REQUEST, request);
-            msg.setData(data);
-            msg.sendToTarget();
+        public void requestRecommendation(final RecommendationRequest request,
+                final IRemoteCallback callback, final int sequence) throws RemoteException {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    ResultCallback resultCallback = new ResultCallback(callback, sequence);
+                    onRequestRecommendation(request, resultCallback);
+                }
+            });
         }
 
         @Override
-        public void requestScores(NetworkKey[] networks) throws RemoteException {
+        public void requestScores(final NetworkKey[] networks) throws RemoteException {
             if (networks != null && networks.length > 0) {
-                mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_SCORES, networks).sendToTarget();
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        onRequestScores(networks);
+                    }
+                });
             }
         }
     }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index ae72470..cb78009 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -178,6 +178,20 @@
         }
 
         /**
+         * Set the {@code NetworkCapabilities} for this builder instance,
+         * overriding any capabilities that had been previously set.
+         *
+         * @param nc The superseding {@code NetworkCapabilities} instance.
+         * @return The builder to facilitate chaining.
+         * @hide
+         */
+        public Builder setCapabilities(NetworkCapabilities nc) {
+            mNetworkCapabilities.clearAll();
+            mNetworkCapabilities.combineCapabilities(nc);
+            return this;
+        }
+
+        /**
          * Completely clears all the {@code NetworkCapabilities} from this builder instance,
          * removing even the capabilities that are set by default when the object is constructed.
          *
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index a6854dc..2441822 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -16,20 +16,29 @@
 
 package android.net;
 
+import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
+
 import android.Manifest;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Class that manages communication between network subsystems and a network scorer.
@@ -356,4 +365,43 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Request a recommendation for which network to connect to.
+     *
+     * <p>The callback will be run on the thread associated with provided {@link Handler}.
+     *
+     * @param request a {@link RecommendationRequest} instance containing additional
+     *                request details
+     * @param handler a {@link Handler} instance representing the thread to complete the future on.
+     *                If null the responding binder thread will be used.
+     * @return a {@link CompletableFuture} instance that will eventually receive the
+     *         {@link RecommendationResult}.
+     * @throws SecurityException
+     * @hide
+     */
+    public CompletableFuture<RecommendationResult> requestRecommendation(
+            final @NonNull RecommendationRequest request,
+            final @Nullable Handler handler) {
+        Preconditions.checkNotNull(request, "RecommendationRequest cannot be null.");
+
+        final CompletableFuture<RecommendationResult> futureResult =
+                new CompletableFuture<>();
+
+        RemoteCallback remoteCallback = new RemoteCallback(new RemoteCallback.OnResultListener() {
+            @Override
+            public void onResult(Bundle data) {
+                RecommendationResult result = data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
+                futureResult.complete(result);
+            }
+        }, handler);
+
+        try {
+            mService.requestRecommendationAsync(request, remoteCallback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        return futureResult;
+    }
 }
diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java
index d013f64..b89a245 100644
--- a/core/java/android/net/RecommendationRequest.java
+++ b/core/java/android/net/RecommendationRequest.java
@@ -34,8 +34,11 @@
 @SystemApi
 public final class RecommendationRequest implements Parcelable {
     private final ScanResult[] mScanResults;
-    private final WifiConfiguration mCurrentSelectedConfig;
-    private final NetworkCapabilities mRequiredCapabilities;
+    private final WifiConfiguration mDefaultConfig;
+    private WifiConfiguration mConnectedConfig;
+    private WifiConfiguration[] mConnectableConfigs;
+    private final int mLastSelectedNetworkId;
+    private final long mLastSelectedNetworkTimestamp;
 
     /**
      * Builder class for constructing {@link RecommendationRequest} instances.
@@ -44,26 +47,65 @@
     @SystemApi
     public static final class Builder {
         private ScanResult[] mScanResults;
-        private WifiConfiguration mCurrentConfig;
-        private NetworkCapabilities mNetworkCapabilities;
+        private WifiConfiguration mDefaultConfig;
+        private WifiConfiguration mConnectedConfig;
+        private WifiConfiguration[] mConnectableConfigs;
+        private int mLastSelectedNetworkId;
+        private long mLastSelectedTimestamp;
 
         public Builder setScanResults(ScanResult[] scanResults) {
             mScanResults = scanResults;
             return this;
         }
 
-        public Builder setCurrentRecommendedWifiConfig(WifiConfiguration config) {
-            this.mCurrentConfig = config;
+        /**
+         * @param config the {@link WifiConfiguration} to return if no recommendation is available.
+         * @return this
+         */
+        public Builder setDefaultWifiConfig(WifiConfiguration config) {
+            this.mDefaultConfig = config;
             return this;
         }
 
-        public Builder setNetworkCapabilities(NetworkCapabilities capabilities) {
-            mNetworkCapabilities = capabilities;
+        /**
+         * @param config the {@link WifiConfiguration} of the connected network at the time the
+         *               this request was made.
+         * @return this
+         */
+        public Builder setConnectedWifiConfig(WifiConfiguration config) {
+            this.mConnectedConfig = config;
             return this;
         }
 
+        /**
+         * @param connectableConfigs the set of saved {@link WifiConfiguration}s that can be
+         *                           connected to based on the current set of {@link ScanResult}s.
+         * @return this
+         */
+        public Builder setConnectableConfigs(WifiConfiguration[] connectableConfigs) {
+            this.mConnectableConfigs = connectableConfigs;
+            return this;
+        }
+
+        /**
+         * @param networkId The {@link WifiConfiguration#networkId} of the last user selected
+         *                  network.
+         * @param timestamp The {@link android.os.SystemClock#elapsedRealtime()} when the user
+         *                  selected {@code networkId}.
+         * @return this
+         */
+        public Builder setLastSelectedNetwork(int networkId, long timestamp) {
+            this.mLastSelectedNetworkId = networkId;
+            this.mLastSelectedTimestamp = timestamp;
+            return this;
+        }
+
+        /**
+         * @return a new {@link RecommendationRequest} instance
+         */
         public RecommendationRequest build() {
-            return new RecommendationRequest(mScanResults, mCurrentConfig, mNetworkCapabilities);
+            return new RecommendationRequest(mScanResults, mDefaultConfig, mConnectedConfig,
+                    mConnectableConfigs, mLastSelectedNetworkId, mLastSelectedTimestamp);
         }
     }
 
@@ -79,45 +121,103 @@
     }
 
     /**
-     * @return The best recommendation at the time this {@code RecommendationRequest} instance
-     *         was created. This may be null which indicates that no recommendation is available.
+     * @return the {@link WifiConfiguration} to return if no recommendation is available.
      */
-    public WifiConfiguration getCurrentSelectedConfig() {
-        return mCurrentSelectedConfig;
+    public WifiConfiguration getDefaultWifiConfig() {
+        return mDefaultConfig;
     }
 
     /**
-     *
-     * @return The set of {@link NetworkCapabilities} the recommendation must be constrained to.
-     *         This may be {@code null} which indicates that there are no constraints on the
-     *         capabilities of the recommended network.
+     * @return the {@link WifiConfiguration} of the connected network at the time the this request
+     *         was made.
      */
-    public NetworkCapabilities getRequiredCapabilities() {
-        return mRequiredCapabilities;
+    public WifiConfiguration getConnectedConfig() {
+        return mConnectedConfig;
+    }
+
+    /**
+     * @return the set of saved {@link WifiConfiguration}s that can be connected to based on the
+     *         current set of {@link ScanResult}s.
+     */
+    public WifiConfiguration[] getConnectableConfigs() {
+        return mConnectableConfigs;
+    }
+
+    /**
+     * @param connectedConfig the {@link WifiConfiguration} of the connected network at the time
+     *                        the this request was made.
+     */
+    public void setConnectedConfig(WifiConfiguration connectedConfig) {
+        mConnectedConfig = connectedConfig;
+    }
+
+    /**
+     * @param connectableConfigs the set of saved {@link WifiConfiguration}s that can be connected
+     *                           to based on the current set of {@link ScanResult}s.
+     */
+    public void setConnectableConfigs(WifiConfiguration[] connectableConfigs) {
+        mConnectableConfigs = connectableConfigs;
+    }
+
+    /**
+     * @return The {@link WifiConfiguration#networkId} of the last user selected network.
+     *         {@code 0} if not set.
+     */
+    public int getLastSelectedNetworkId() {
+        return mLastSelectedNetworkId;
+    }
+
+    /**
+     * @return The {@link android.os.SystemClock#elapsedRealtime()} when the user selected
+     *         {@link #getLastSelectedNetworkId()}. {@code 0} if not set.
+     */
+    public long getLastSelectedNetworkTimestamp() {
+        return mLastSelectedNetworkTimestamp;
     }
 
     @VisibleForTesting
     RecommendationRequest(ScanResult[] scanResults,
-            WifiConfiguration currentSelectedConfig,
-            NetworkCapabilities requiredCapabilities) {
+            WifiConfiguration defaultWifiConfig,
+            WifiConfiguration connectedWifiConfig,
+            WifiConfiguration[] connectableConfigs,
+            int lastSelectedNetworkId,
+            long lastSelectedNetworkTimestamp) {
         mScanResults = scanResults;
-        mCurrentSelectedConfig = currentSelectedConfig;
-        mRequiredCapabilities = requiredCapabilities;
+        mDefaultConfig = defaultWifiConfig;
+        mConnectedConfig = connectedWifiConfig;
+        mConnectableConfigs = connectableConfigs;
+        mLastSelectedNetworkId = lastSelectedNetworkId;
+        mLastSelectedNetworkTimestamp = lastSelectedNetworkTimestamp;
     }
 
     protected RecommendationRequest(Parcel in) {
         final int resultCount = in.readInt();
         if (resultCount > 0) {
             mScanResults = new ScanResult[resultCount];
+            final ClassLoader classLoader = ScanResult.class.getClassLoader();
             for (int i = 0; i < resultCount; i++) {
-                mScanResults[i] = in.readParcelable(ScanResult.class.getClassLoader());
+                mScanResults[i] = in.readParcelable(classLoader);
             }
         } else {
             mScanResults = null;
         }
 
-        mCurrentSelectedConfig = in.readParcelable(WifiConfiguration.class.getClassLoader());
-        mRequiredCapabilities = in.readParcelable(NetworkCapabilities.class.getClassLoader());
+        mDefaultConfig = in.readParcelable(WifiConfiguration.class.getClassLoader());
+        mConnectedConfig = in.readParcelable(WifiConfiguration.class.getClassLoader());
+
+        final int configCount = in.readInt();
+        if (configCount > 0) {
+            mConnectableConfigs = new WifiConfiguration[configCount];
+            final ClassLoader classLoader = WifiConfiguration.class.getClassLoader();
+            for (int i = 0; i < configCount; i++) {
+                mConnectableConfigs[i] = in.readParcelable(classLoader);
+            }
+        } else {
+            mConnectableConfigs = null;
+        }
+
+        mLastSelectedNetworkId = in.readInt();
+        mLastSelectedNetworkTimestamp = in.readLong();
     }
 
     @Override
@@ -135,8 +235,21 @@
         } else {
             dest.writeInt(0);
         }
-        dest.writeParcelable(mCurrentSelectedConfig, flags);
-        dest.writeParcelable(mRequiredCapabilities, flags);
+
+        dest.writeParcelable(mDefaultConfig, flags);
+        dest.writeParcelable(mConnectedConfig, flags);
+
+        if (mConnectableConfigs != null) {
+            dest.writeInt(mConnectableConfigs.length);
+            for (int i = 0; i < mConnectableConfigs.length; i++) {
+                dest.writeParcelable(mConnectableConfigs[i], flags);
+            }
+        } else {
+            dest.writeInt(0);
+        }
+
+        dest.writeInt(mLastSelectedNetworkId);
+        dest.writeLong(mLastSelectedNetworkTimestamp);
     }
 
     public static final Creator<RecommendationRequest> CREATOR =
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 27096b1..b56437e 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -18,6 +18,8 @@
 
 import android.os.SystemProperties;
 import android.util.Log;
+
+import com.android.internal.os.RoSystemProperties;
 import com.android.org.conscrypt.OpenSSLContextImpl;
 import com.android.org.conscrypt.OpenSSLSocketImpl;
 import com.android.org.conscrypt.SSLClientSessionCache;
@@ -221,8 +223,8 @@
     }
 
     private static boolean isSslCheckRelaxed() {
-        return "1".equals(SystemProperties.get("ro.debuggable")) &&
-            "yes".equals(SystemProperties.get("socket.relaxsslcheck"));
+        return RoSystemProperties.DEBUGGABLE &&
+            SystemProperties.getBoolean("socket.relaxsslcheck", false);
     }
 
     private synchronized SSLSocketFactory getDelegate() {
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index 83d17ba..2c9ce3f 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -805,7 +805,7 @@
 
                 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
                     throw new FormatException("expected MB flag");
-                } else if (mb && records.size() != 0 && !ignoreMbMe) {
+                } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
                     throw new FormatException("unexpected MB flag");
                 } else if (inChunk && il) {
                     throw new FormatException("unexpected IL flag in non-leading chunk");
@@ -839,6 +839,9 @@
 
                 if (cf && !inChunk) {
                     // first chunk
+                    if (typeLength == 0 && tnf != NdefRecord.TNF_UNKNOWN) {
+                        throw new FormatException("expected non-zero type length in first chunk");
+                    }
                     chunks.clear();
                     chunkTnf = tnf;
                 }
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index f6edee0..0d269af 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -245,7 +245,7 @@
          * Magic version number for a current development build, which has
          * not yet turned into an official release.
          */
-        public static final int CUR_DEVELOPMENT = 10000;
+        public static final int CUR_DEVELOPMENT = VMRuntime.SDK_VERSION_CUR_DEVELOPMENT;
 
         /**
          * October 2008: The original, first, version of Android.  Yay!
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 175d883..e05bd89 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1119,8 +1119,8 @@
      * @hide
      */
     public static void startMethodTracing(String traceName, FileDescriptor fd,
-        int bufferSize, int flags) {
-        VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0);
+        int bufferSize, int flags, boolean streamOutput) {
+        VMDebug.startMethodTracing(traceName, fd, bufferSize, flags, false, 0, streamOutput);
     }
 
     /**
@@ -2219,11 +2219,13 @@
     }
 
     /**
-     * Have the stack traces of the given native process dumped to the
-     * specified file.  Will be appended to the file.
+     * Append the stack traces of a given native process to a specified file.
+     * @param pid pid to dump.
+     * @param file path of file to append dump to.
+     * @param timeoutSecs time to wait in seconds, or 0 to wait forever.
      * @hide
      */
-    public static native void dumpNativeBacktraceToFile(int pid, String file);
+    public static native void dumpNativeBacktraceToFileTimeout(int pid, String file, int timeoutSecs);
 
     /**
      * Get description of unreachable native memory.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 4616af8..30d8e2d 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -296,11 +296,6 @@
     }
 
     /** {@hide} */
-    public static File getDataProfilesDeForeignDexDirectory(int userId) {
-        return buildPath(getDataProfilesDeDirectory(userId), "foreign-dex");
-    }
-
-    /** {@hide} */
     public static File getDataAppDirectory(String volumeUuid) {
         return new File(getDataDirectory(volumeUuid), "app");
     }
diff --git a/core/java/android/os/FactoryTest.java b/core/java/android/os/FactoryTest.java
index 7a252f9..b59227c 100644
--- a/core/java/android/os/FactoryTest.java
+++ b/core/java/android/os/FactoryTest.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import com.android.internal.os.RoSystemProperties;
+
 /**
  * Provides support for in-place factory test functions.
  *
@@ -36,7 +38,7 @@
      * or {@link #FACTORY_TEST_HIGH_LEVEL}.
      */
     public static int getMode() {
-        return SystemProperties.getInt("ro.factorytest", FACTORY_TEST_OFF);
+        return RoSystemProperties.FACTORYTEST;
     }
 
     /**
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index e025494..b09c51c 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -43,9 +43,7 @@
             int code, HwParcel request, HwParcel reply, int flags)
         throws RemoteException;
 
-    public native final void registerService(
-            ArrayList<String> interfaceChain,
-            String serviceName)
+    public native final void registerService(String serviceName)
         throws RemoteException;
 
     public static native final IHwBinder getService(
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index a265dd0..94fd5b0 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -219,6 +219,7 @@
     public native final void writeStatus(int status);
     public native final void verifySuccess();
     public native final void releaseTemporaryStorage();
+    public native final void release();
 
     public native final void send();
 
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f6e6ad6..b5ab908 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -27,6 +27,8 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import libcore.util.SneakyThrow;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -249,6 +251,7 @@
     private static final int EX_NETWORK_MAIN_THREAD = -6;
     private static final int EX_UNSUPPORTED_OPERATION = -7;
     private static final int EX_SERVICE_SPECIFIC = -8;
+    private static final int EX_PARCELABLE = -9;
     private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below
     // EX_TRANSACTION_FAILED is used exclusively in native code.
     // see libbinder's binder/Status.h
@@ -1555,7 +1558,12 @@
      */
     public final void writeException(Exception e) {
         int code = 0;
-        if (e instanceof SecurityException) {
+        if (e instanceof Parcelable
+                && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
+            // We only send Parcelable exceptions that are in the
+            // BootClassLoader to ensure that the receiver can unpack them
+            code = EX_PARCELABLE;
+        } else if (e instanceof SecurityException) {
             code = EX_SECURITY;
         } else if (e instanceof BadParcelableException) {
             code = EX_BAD_PARCELABLE;
@@ -1581,8 +1589,20 @@
             throw new RuntimeException(e);
         }
         writeString(e.getMessage());
-        if (e instanceof ServiceSpecificException) {
-            writeInt(((ServiceSpecificException)e).errorCode);
+        switch (code) {
+            case EX_SERVICE_SPECIFIC:
+                writeInt(((ServiceSpecificException) e).errorCode);
+                break;
+            case EX_PARCELABLE:
+                // Write parceled exception prefixed by length
+                final int sizePosition = dataPosition();
+                writeInt(0);
+                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                final int payloadPosition = dataPosition();
+                setDataPosition(sizePosition);
+                writeInt(payloadPosition - sizePosition);
+                setDataPosition(payloadPosition);
+                break;
         }
     }
 
@@ -1680,6 +1700,13 @@
      */
     public final void readException(int code, String msg) {
         switch (code) {
+            case EX_PARCELABLE:
+                if (readInt() > 0) {
+                    SneakyThrow.sneakyThrow(
+                            (Exception) readParcelable(Parcelable.class.getClassLoader()));
+                } else {
+                    throw new RuntimeException(msg + " [missing Parcelable]");
+                }
             case EX_SECURITY:
                 throw new SecurityException(msg);
             case EX_BAD_PARCELABLE:
diff --git a/core/java/android/os/ParcelableException.java b/core/java/android/os/ParcelableException.java
new file mode 100644
index 0000000..d84d629
--- /dev/null
+++ b/core/java/android/os/ParcelableException.java
@@ -0,0 +1,88 @@
+/*
+ * 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.io.IOException;
+
+/**
+ * Wrapper class that offers to transport typical {@link Throwable} across a
+ * {@link Binder} call. This class is typically used to transport exceptions
+ * that cannot be modified to add {@link Parcelable} behavior, such as
+ * {@link IOException}.
+ * <ul>
+ * <li>The wrapped throwable must be defined as system class (that is, it must
+ * be in the same {@link ClassLoader} as {@link Parcelable}).
+ * <li>The wrapped throwable must support the
+ * {@link Throwable#Throwable(String)} constructor.
+ * <li>The receiver side must catch any thrown {@link ParcelableException} and
+ * call {@link #maybeRethrow(Class)} for all expected exception types.
+ * </ul>
+ *
+ * @hide
+ */
+public final class ParcelableException extends RuntimeException implements Parcelable {
+    public ParcelableException(Throwable t) {
+        super(t);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends Throwable> void maybeRethrow(Class<T> clazz) throws T {
+        if (clazz.isAssignableFrom(getCause().getClass())) {
+            throw (T) getCause();
+        }
+    }
+
+    /** {@hide} */
+    public static Throwable readFromParcel(Parcel in) {
+        final String name = in.readString();
+        final String msg = in.readString();
+        try {
+            final Class<?> clazz = Class.forName(name, true, Parcelable.class.getClassLoader());
+            return (Throwable) clazz.getConstructor(String.class).newInstance(msg);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(name + ": " + msg);
+        }
+    }
+
+    /** {@hide} */
+    public static void writeToParcel(Parcel out, Throwable t) {
+        out.writeString(t.getClass().getName());
+        out.writeString(t.getMessage());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        writeToParcel(dest, getCause());
+    }
+
+    public static final Creator<ParcelableException> CREATOR = new Creator<ParcelableException>() {
+        @Override
+        public ParcelableException createFromParcel(Parcel source) {
+            return new ParcelableException(readFromParcel(source));
+        }
+
+        @Override
+        public ParcelableException[] newArray(int size) {
+            return new ParcelableException[size];
+        }
+    };
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/core/java/android/os/Seccomp.java
similarity index 69%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to core/java/android/os/Seccomp.java
index 62d5603..f14e93f 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/core/java/android/os/Seccomp.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.os;
 
-parcelable HomeSP;
+/**
+ * @hide
+ */
+public final class Seccomp {
+    public static final native void setPolicy();
+}
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index fc804e5..0b4c4c1 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -274,7 +274,7 @@
     /**
      * Implement parsing and execution of a command.  If it isn't a command you understand,
      * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
-     * User {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+     * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
      * to process additional command line arguments.  Command output can be written to
      * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
      *
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index ff69cf6..fa7aebf 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -477,7 +477,7 @@
             }
 
             /**
-             * Detect unbuffered input/output operations.
+             * Disable detection of unbuffered input/output operations.
              */
             public Builder permitUnbufferedIo() {
                 return disable(DETECT_UNBUFFERED_IO);
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index e47c238..c520917 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -16,7 +16,13 @@
 
 package android.os;
 
+import android.util.Log;
+import android.util.MutableInt;
+
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
+import java.util.HashMap;
 
 
 /**
@@ -25,13 +31,50 @@
  *
  * {@hide}
  */
-public class SystemProperties
-{
-    public static final int PROP_NAME_MAX = 31;
+public class SystemProperties {
+    private static final String TAG = "SystemProperties";
+    private static final boolean TRACK_KEY_ACCESS = false;
+
+    /**
+     * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5
+     * uses reflection to read this whenever text is selected (http://b/36095274).
+     */
+    public static final int PROP_NAME_MAX = Integer.MAX_VALUE;
+
     public static final int PROP_VALUE_MAX = 91;
 
     private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
 
+    @GuardedBy("sRoReads")
+    private static final HashMap<String, MutableInt> sRoReads;
+    static {
+        if (TRACK_KEY_ACCESS) {
+            sRoReads = new HashMap<>();
+        } else {
+            sRoReads = null;
+        }
+    }
+
+    private static void onKeyAccess(String key) {
+        if (!TRACK_KEY_ACCESS) return;
+
+        if (key != null && key.startsWith("ro.")) {
+            synchronized (sRoReads) {
+                MutableInt numReads = sRoReads.getOrDefault(key, null);
+                if (numReads == null) {
+                    numReads = new MutableInt(0);
+                    sRoReads.put(key, numReads);
+                }
+                numReads.value++;
+                if (numReads.value > 3) {
+                    Log.d(TAG, "Repeated read (count=" + numReads.value
+                            + ") of a read-only system property '" + key + "'",
+                            new Exception());
+                }
+            }
+        }
+    }
+
     private static native String native_get(String key);
     private static native String native_get(String key, String def);
     private static native int native_get_int(String key, int def);
@@ -44,24 +87,18 @@
     /**
      * Get the value for the given key.
      * @return an empty string if the key isn't found
-     * @throws IllegalArgumentException if the key exceeds 32 characters
      */
     public static String get(String key) {
-        if (key.length() > PROP_NAME_MAX) {
-            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
-        }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get(key);
     }
 
     /**
      * Get the value for the given key.
      * @return if the key isn't found, return def if it isn't null, or an empty string otherwise
-     * @throws IllegalArgumentException if the key exceeds 32 characters
      */
     public static String get(String key, String def) {
-        if (key.length() > PROP_NAME_MAX) {
-            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
-        }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get(key, def);
     }
 
@@ -71,12 +108,9 @@
      * @param def a default value to return
      * @return the key parsed as an integer, or def if the key isn't found or
      *         cannot be parsed
-     * @throws IllegalArgumentException if the key exceeds 32 characters
      */
     public static int getInt(String key, int def) {
-        if (key.length() > PROP_NAME_MAX) {
-            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
-        }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get_int(key, def);
     }
 
@@ -86,12 +120,9 @@
      * @param def a default value to return
      * @return the key parsed as a long, or def if the key isn't found or
      *         cannot be parsed
-     * @throws IllegalArgumentException if the key exceeds 32 characters
      */
     public static long getLong(String key, long def) {
-        if (key.length() > PROP_NAME_MAX) {
-            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
-        }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get_long(key, def);
     }
 
@@ -106,28 +137,22 @@
      * @param def a default value to return
      * @return the key parsed as a boolean, or def if the key isn't found or is
      *         not able to be parsed as a boolean.
-     * @throws IllegalArgumentException if the key exceeds 32 characters
      */
     public static boolean getBoolean(String key, boolean def) {
-        if (key.length() > PROP_NAME_MAX) {
-            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
-        }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get_boolean(key, def);
     }
 
     /**
      * Set the value for the given key.
-     * @throws IllegalArgumentException if the key exceeds 32 characters
      * @throws IllegalArgumentException if the value exceeds 92 characters
      */
     public static void set(String key, String val) {
-        if (key.length() > PROP_NAME_MAX) {
-            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
-        }
         if (val != null && val.length() > PROP_VALUE_MAX) {
             throw new IllegalArgumentException("val.length > " +
                 PROP_VALUE_MAX);
         }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         native_set(key, val);
     }
 
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index bf03cce..8549cff 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -26,7 +26,18 @@
 /**
  * UpdateEngine handles calls to the update engine which takes care of A/B OTA
  * updates. It wraps up the update engine Binder APIs and exposes them as
- * SystemApis, which will be called by system apps like GmsCore.
+ * SystemApis, which will be called by the system app responsible for OTAs.
+ * On a Google device, this will be GmsCore.
+ *
+ * The minimal flow is:
+ * <ol>
+ * <li>Create a new UpdateEngine instance.
+ * <li>Call {@link #bind}, optionally providing callbacks.
+ * <li>Call {@link #applyPayload}.
+ * </ol>
+ *
+ * In addition, methods are provided to {@link #cancel} or
+ * {@link #suspend}/{@link #resume} application of an update.
  *
  * The APIs defined in this class and UpdateEngineCallback class must be in
  * sync with the ones in
@@ -80,12 +91,20 @@
 
     private IUpdateEngine mUpdateEngine;
 
+    /**
+     * Creates a new instance.
+     */
     @SystemApi
     public UpdateEngine() {
         mUpdateEngine = IUpdateEngine.Stub.asInterface(
                 ServiceManager.getService(UPDATE_ENGINE_SERVICE));
     }
 
+    /**
+     * Prepares this instance for use. The callback will be notified on any
+     * status change, and when the update completes. A handler can be supplied
+     * to control which thread runs the callback, or null.
+     */
     @SystemApi
     public boolean bind(final UpdateEngineCallback callback, final Handler handler) {
         IUpdateEngineCallback updateEngineCallback = new IUpdateEngineCallback.Stub() {
@@ -125,11 +144,42 @@
         }
     }
 
+    /**
+     * Equivalent to {@code bind(callback, null)}.
+     */
     @SystemApi
     public boolean bind(final UpdateEngineCallback callback) {
         return bind(callback, null);
     }
 
+    /**
+     * Applies the payload found at the given {@code url}. For non-streaming
+     * updates, the URL can be a local file using the {@code file://} scheme.
+     *
+     * <p>The {@code offset} and {@code size} parameters specify the location
+     * of the payload within the file represented by the URL. This is useful
+     * if the downloadable package at the URL contains more than just the
+     * update_engine payload (such as extra metadata). This is true for
+     * Google's OTA system, where the URL points to a zip file in which the
+     * payload is stored uncompressed within the zip file alongside other
+     * data.
+     *
+     * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata
+     * to update_engine. In Google's implementation, this is stored as
+     * {@code payload_properties.txt} in the zip file. It's generated by the
+     * script {@code system/update_engine/scripts/brillo_update_payload}.
+     * The complete list of keys and their documentation is in
+     * {@code system/update_engine/common/constants.cc}, but an example
+     * might be:
+     * <pre>
+     * String[] pairs = {
+     *   "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=",
+     *   "FILE_SIZE=871903868",
+     *   "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=",
+     *   "METADATA_SIZE=70604"
+     * };
+     * </pre>
+     */
     @SystemApi
     public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {
         try {
@@ -139,6 +189,15 @@
         }
     }
 
+    /**
+     * Permanently cancels an in-progress update.
+     *
+     * <p>See {@link #resetStatus} to undo a finshed update (only available
+     * before the updated system has been rebooted).
+     *
+     * <p>See {@link #suspend} for a way to temporarily stop an in-progress
+     * update with the ability to resume it later.
+     */
     @SystemApi
     public void cancel() {
         try {
@@ -148,6 +207,10 @@
         }
     }
 
+    /**
+     * Suspends an in-progress update. This can be undone by calling
+     * {@link #resume}.
+     */
     @SystemApi
     public void suspend() {
         try {
@@ -157,6 +220,9 @@
         }
     }
 
+    /**
+     * Resumes a suspended update.
+     */
     @SystemApi
     public void resume() {
         try {
@@ -166,6 +232,15 @@
         }
     }
 
+    /**
+     * Resets the bootable flag on the non-current partition and all internal
+     * update_engine state. This can be used after an unwanted payload has been
+     * successfully applied and the device has not yet been rebooted to signal
+     * that we no longer want to boot into that updated system. After this call
+     * completes, update_engine will no longer report
+     * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding
+     * notification that rebooting into the new system is possible.
+     */
     @SystemApi
     public void resetStatus() {
         try {
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index b3b856f..afff60a 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -19,7 +19,8 @@
 import android.annotation.SystemApi;
 
 /**
- * Callback function for UpdateEngine.
+ * Callback function for UpdateEngine. Used to keep the caller up to date
+ * with progress, so the UI (if any) can be updated.
  *
  * The APIs defined in this class and UpdateEngine class must be in sync with
  * the ones in
@@ -31,9 +32,19 @@
 @SystemApi
 public abstract class UpdateEngineCallback {
 
+    /**
+     * Invoked when anything changes. The value of {@code status} will
+     * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
+     * and {@code percent} will be valid [TODO: in which cases?].
+     */
     @SystemApi
     public abstract void onStatusUpdate(int status, float percent);
 
+    /**
+     * Invoked when the payload has been applied, whether successfully or
+     * unsuccessfully. The value of {@code errorCode} will be one of the
+     * values from {@link UpdateEngine.ErrorCodeConstants}.
+     */
     @SystemApi
     public abstract void onPayloadApplicationComplete(int errorCode);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a4db940..ab462e4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -42,6 +42,7 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
+import com.android.internal.os.RoSystemProperties;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -761,7 +762,7 @@
      * a single owner user.  see @link {android.os.UserHandle#USER_OWNER}
      */
     public static boolean isSplitSystemUser() {
-        return SystemProperties.getBoolean("ro.fw.system_user_split", false);
+        return RoSystemProperties.FW_SYSTEM_USER_SPLIT;
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 5ac33a1..fa9f394 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -352,8 +352,8 @@
         if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
             argsForZygote.add("--enable-safemode");
         }
-        if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
-            argsForZygote.add("--enable-debugger");
+        if ((debugFlags & Zygote.DEBUG_ENABLE_JDWP) != 0) {
+            argsForZygote.add("--enable-jdwp");
         }
         if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
             argsForZygote.add("--enable-checkjni");
@@ -367,6 +367,9 @@
         if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
             argsForZygote.add("--native-debuggable");
         }
+        if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) {
+            argsForZygote.add("--java-debuggable");
+        }
         if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
             argsForZygote.add("--enable-assert");
         }
@@ -379,9 +382,6 @@
         }
         argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
 
-        //TODO optionally enable debuger
-        //argsForZygote.add("--enable-debugger");
-
         // --setgroups is a comma-separated list
         if (gids != null && gids.length > 0) {
             StringBuilder sb = new StringBuilder();
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 9252887..63b6db0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -44,6 +44,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.os.RoSystemProperties;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
@@ -1167,8 +1168,7 @@
      *         false not encrypted and not encryptable
      */
     public static boolean isEncryptable() {
-        final String state = SystemProperties.get("ro.crypto.state", "unsupported");
-        return !"unsupported".equalsIgnoreCase(state);
+        return RoSystemProperties.CRYPTO_ENCRYPTABLE;
     }
 
     /** {@hide}
@@ -1177,8 +1177,7 @@
      *         false not encrypted
      */
     public static boolean isEncrypted() {
-        final String state = SystemProperties.get("ro.crypto.state", "");
-        return "encrypted".equalsIgnoreCase(state);
+        return RoSystemProperties.CRYPTO_ENCRYPTED;
     }
 
     /** {@hide}
@@ -1190,9 +1189,7 @@
         if (!isEncrypted()) {
             return false;
         }
-
-        final String status = SystemProperties.get("ro.crypto.type", "");
-        return "file".equalsIgnoreCase(status);
+        return RoSystemProperties.CRYPTO_FILE_ENCRYPTED;
     }
 
     /** {@hide}
@@ -1204,8 +1201,7 @@
         if (!isEncrypted()) {
             return false;
         }
-        final String status = SystemProperties.get("ro.crypto.type", "");
-        return "block".equalsIgnoreCase(status);
+        return RoSystemProperties.CRYPTO_BLOCK_ENCRYPTED;
     }
 
     /** {@hide}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 1ec00db..f1d59e2 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -348,8 +348,8 @@
             if (msg.what == UPDATE_SLIDER) {
                 if (mSeekBar != null) {
                     mLastProgress = msg.arg1;
-                    mLastAudibleStreamVolume = Math.abs(msg.arg2);
-                    final boolean muted = msg.arg2 < 0;
+                    mLastAudibleStreamVolume = msg.arg2;
+                    final boolean muted = ((Boolean)msg.obj).booleanValue();
                     if (muted != mMuted) {
                         mMuted = muted;
                         if (mCallback != null) {
@@ -362,8 +362,7 @@
         }
 
         public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
-            final int arg2 = lastAudibleVolume * (mute ? -1 : 1);
-            obtainMessage(UPDATE_SLIDER, volume, arg2).sendToTarget();
+            obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3ea5dcb..38ad68d 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6416,6 +6416,12 @@
         public static final String DEVICE_PAIRED = "device_paired";
 
         /**
+         * Specifies additional package name for broadcasting the CMAS messages.
+         * @hide
+         */
+        public static final String CMAS_ADDITIONAL_BROADCAST_PKG = "cmas_additional_broadcast_pkg";
+
+        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -7672,6 +7678,36 @@
         public static final String NETWORK_RECOMMENDATIONS_ENABLED =
                 "network_recommendations_enabled";
 
+        /**
+         * Value to specify if the Wi-Fi Framework should defer to
+         * {@link com.android.server.NetworkScoreService} for evaluating saved open networks.
+         *
+         * Type: int (0 for false, 1 for true)
+         * @hide
+         */
+        @SystemApi
+        public static final String CURATE_SAVED_OPEN_NETWORKS = "curate_saved_open_networks";
+
+        /**
+         * The number of milliseconds the {@link com.android.server.NetworkScoreService}
+         * will give a recommendation request to complete before returning a default response.
+         *
+         * Type: long
+         * @hide
+         */
+        public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
+                "network_recommendation_request_timeout_ms";
+
+        /**
+         * The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in
+         * {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
+         *
+         * Type: long
+         * @hide
+         */
+        public static final String RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS =
+                "recommended_network_evaluator_cache_expiry_ms";
+
        /**
         * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
         * connectivity.
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 887f4b6..1781c2a 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -130,6 +130,11 @@
      */
     public static final String EXTRA_COMPONENT = "android.service.quicksettings.extra.COMPONENT";
 
+    /**
+     * @hide
+     */
+    public static final String EXTRA_STATE = "state";
+
     private final H mHandler = new H(Looper.getMainLooper());
 
     private boolean mListening = false;
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 356804e..80ec03e 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -189,7 +189,9 @@
         // TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
         String[] availableLanguages = {
             "as",
+            "bg",
             "bn",
+            "cu",
             "cy",
             "da",
             "de-1901", "de-1996", "de-CH-1901",
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
index f5d515d..da0b609 100644
--- a/core/java/android/util/ExceptionUtils.java
+++ b/core/java/android/util/ExceptionUtils.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.os.ParcelableException;
+
 import java.io.IOException;
 
 /**
@@ -24,19 +26,13 @@
  * @hide
  */
 public class ExceptionUtils {
-    // TODO: longer term these should be replaced with first-class
-    // Parcel.read/writeException() and AIDL support, but for now do this using
-    // a nasty hack.
-
-    private static final String PREFIX_IO = "\u2603";
-
     public static RuntimeException wrap(IOException e) {
-        throw new IllegalStateException(PREFIX_IO + e.getMessage());
+        throw new ParcelableException(e);
     }
 
     public static void maybeUnwrapIOException(RuntimeException e) throws IOException {
-        if ((e instanceof IllegalStateException) && e.getMessage().startsWith(PREFIX_IO)) {
-            throw new IOException(e.getMessage().substring(PREFIX_IO.length()));
+        if (e instanceof ParcelableException) {
+            ((ParcelableException) e).maybeRethrow(IOException.class);
         }
     }
 
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index 0a452db..86434b2 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -243,12 +243,12 @@
     public static final String GOOD_IRI_CHAR =
         "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
 
-    public static final Pattern IP_ADDRESS
-        = Pattern.compile(
+    private static final String IP_ADDRESS_STRING =
             "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
             + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
             + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
-            + "|[1-9][0-9]|[0-9]))");
+            + "|[1-9][0-9]|[0-9]))";
+    public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_STRING);
 
     /**
      * Valid UCS characters defined in RFC 3987. Excludes space characters.
@@ -298,8 +298,8 @@
 
     private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
 
-    public static final Pattern DOMAIN_NAME
-        = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")");
+    private static final String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")";
+    public static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR);
 
     private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/";
 
@@ -323,7 +323,7 @@
     public static final Pattern WEB_URL = Pattern.compile("("
             + "("
             + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?"
-            + "(?:" + DOMAIN_NAME + ")"
+            + "(?:" + DOMAIN_NAME_STR + ")"
             + "(?:" + PORT_NUMBER + ")?"
             + ")"
             + "(" + PATH_AND_QUERY + ")?"
@@ -346,14 +346,14 @@
      * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or
      * {@link #IP_ADDRESS}
      */
-    private static final Pattern STRICT_DOMAIN_NAME
-            = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")");
+    private static final String STRICT_DOMAIN_NAME = "(?:" + STRICT_HOST_NAME + "|"
+            + IP_ADDRESS_STRING + ")";
 
     /**
      * Regular expression that matches domain names without a TLD
      */
     private static final String RELAXED_DOMAIN_NAME =
-            "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")";
+            "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS_STRING + ")";
 
     /**
      * Regular expression to match strings that do not start with a supported protocol. The TLDs
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index c7b1d03..1c458ab 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -34,7 +34,6 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
 import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Predicate;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -43,6 +42,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.function.Predicate;
 
 /**
  * Class for managing accessibility interactions initiated from the system
@@ -1233,7 +1233,7 @@
         }
 
         @Override
-        public boolean apply(View view) {
+        public boolean test(View view) {
             if (view.getId() == mViewId && isShown(view)) {
                 mInfos.add(view.createAccessibilityNodeInfo());
             }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d13f6d6..f65ec94 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -103,7 +103,6 @@
 import static java.lang.Math.max;
 
 import com.android.internal.R;
-import com.android.internal.util.Predicate;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.widget.ScrollBarUtils;
 import com.google.android.collect.Lists;
@@ -126,6 +125,7 @@
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
 
 /**
  * <p>
@@ -8778,7 +8778,7 @@
                 final int id = mID;
                 return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                     @Override
-                    public boolean apply(View t) {
+                    public boolean test(View t) {
                         return t.mNextFocusForwardId == id;
                     }
                 });
@@ -19349,7 +19349,7 @@
      * @return The first view that matches the predicate or null.
      */
     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
-        if (predicate.apply(this)) {
+        if (predicate.test(this)) {
             return this;
         }
         return null;
@@ -23639,20 +23639,20 @@
         }
     }
 
-    private class MatchIdPredicate implements Predicate<View> {
+    private static class MatchIdPredicate implements Predicate<View> {
         public int mId;
 
         @Override
-        public boolean apply(View view) {
+        public boolean test(View view) {
             return (view.mID == mId);
         }
     }
 
-    private class MatchLabelForPredicate implements Predicate<View> {
+    private static class MatchLabelForPredicate implements Predicate<View> {
         private int mLabeledId;
 
         @Override
-        public boolean apply(View view) {
+        public boolean test(View view) {
             return (view.mLabelForId == mLabeledId);
         }
     }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d4b7d3b..776f119 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -52,13 +52,13 @@
 import android.view.animation.Transformation;
 
 import com.android.internal.R;
-import com.android.internal.util.Predicate;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Predicate;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
 
@@ -3994,7 +3994,7 @@
      */
     @Override
     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
-        if (predicate.apply(this)) {
+        if (predicate.test(this)) {
             return this;
         }
 
diff --git a/core/java/android/webkit/WebViewZygote.java b/core/java/android/webkit/WebViewZygote.java
index 2d6f443..f9d7332 100644
--- a/core/java/android/webkit/WebViewZygote.java
+++ b/core/java/android/webkit/WebViewZygote.java
@@ -176,7 +176,7 @@
             // paths and pass them to the zygote as strings.
             final List<String> zipPaths = new ArrayList<>(10);
             final List<String> libPaths = new ArrayList<>(10);
-            LoadedApk.makePaths(null, sPackage.applicationInfo, zipPaths, libPaths);
+            LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
             final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
             final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
                     TextUtils.join(File.pathSeparator, zipPaths);
diff --git a/core/java/android/widget/DayPickerViewPager.java b/core/java/android/widget/DayPickerViewPager.java
index 5f0ae29..07b3c95 100644
--- a/core/java/android/widget/DayPickerViewPager.java
+++ b/core/java/android/widget/DayPickerViewPager.java
@@ -25,11 +25,11 @@
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 
-import com.android.internal.util.Predicate;
 import com.android.internal.widget.PagerAdapter;
 import com.android.internal.widget.ViewPager;
 
 import java.util.ArrayList;
+import java.util.function.Predicate;
 
 /**
  * This displays a list of months in a calendar format with selectable days.
@@ -143,7 +143,7 @@
 
     @Override
     protected View findViewByPredicateTraversal(Predicate<View> predicate, View childToSkip) {
-        if (predicate.apply(this)) {
+        if (predicate.test(this)) {
             return this;
         }
 
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index b0f19d7..2e7f0fd 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -19,7 +19,6 @@
 import com.google.android.collect.Lists;
 
 import com.android.internal.R;
-import com.android.internal.util.Predicate;
 
 import android.annotation.IdRes;
 import android.annotation.NonNull;
@@ -55,6 +54,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
 
 /*
  * Implementation Notes:
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
new file mode 100644
index 0000000..80c55fb
--- /dev/null
+++ b/core/java/com/android/internal/os/RoSystemProperties.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 com.android.internal.os;
+
+import android.os.SystemProperties;
+
+/**
+ * This is a cache of various ro.* properties so that they can be read just once
+ * at class init time.
+ */
+public class RoSystemProperties {
+    public static final boolean DEBUGGABLE =
+            SystemProperties.getInt("ro.debuggable", 0) == 1;
+    public static final int FACTORYTEST =
+            SystemProperties.getInt("ro.factorytest", 0);
+
+    // ------ ro.config.* -------- //
+    public static final boolean CONFIG_LOW_RAM =
+            SystemProperties.getBoolean("ro.config.low_ram", false);
+
+    // ------ ro.fw.* ------------ //
+    public static final boolean FW_SYSTEM_USER_SPLIT =
+            SystemProperties.getBoolean("ro.fw.system_user_split", false);
+
+    // ------ ro.crypto.* -------- //
+    public static final String CRYPTO_STATE = SystemProperties.get("ro.crypto.state");
+    public static final String CRYPTO_TYPE = SystemProperties.get("ro.crypto.type");
+    // These are pseudo-properties
+    public static final boolean CRYPTO_ENCRYPTABLE =
+            !CRYPTO_STATE.isEmpty() && !"unsupported".equals(CRYPTO_STATE);
+    public static final boolean CRYPTO_ENCRYPTED =
+            "encrypted".equalsIgnoreCase(CRYPTO_STATE);
+    public static final boolean CRYPTO_FILE_ENCRYPTED =
+            "file".equalsIgnoreCase(CRYPTO_TYPE);
+    public static final boolean CRYPTO_BLOCK_ENCRYPTED =
+            "block".equalsIgnoreCase(CRYPTO_TYPE);
+}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index c851e4e..674fdea 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -43,8 +43,8 @@
  * @hide
  */
 public class RuntimeInit {
-    private final static String TAG = "AndroidRuntime";
-    private final static boolean DEBUG = false;
+    final static String TAG = "AndroidRuntime";
+    final static boolean DEBUG = false;
 
     /** true if commonInit() has been called */
     private static boolean initialized;
@@ -53,7 +53,6 @@
 
     private static volatile boolean mCrashing = false;
 
-    private static final native void nativeZygoteInit();
     private static final native void nativeFinishInit();
     private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
 
@@ -133,7 +132,7 @@
         }
     }
 
-    private static final void commonInit() {
+    protected static final void commonInit() {
         if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
 
         /*
@@ -287,50 +286,7 @@
         if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
     }
 
-    /**
-     * The main function called when started through the zygote process. This
-     * could be unified with main(), if the native code in nativeFinishInit()
-     * were rationalized with Zygote startup.<p>
-     *
-     * Current recognized args:
-     * <ul>
-     *   <li> <code> [--] &lt;start class name&gt;  &lt;args&gt;
-     * </ul>
-     *
-     * @param targetSdkVersion target SDK version
-     * @param argv arg strings
-     */
-    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
-            throws Zygote.MethodAndArgsCaller {
-        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
-
-        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
-        redirectLogStreams();
-
-        commonInit();
-        nativeZygoteInit();
-        applicationInit(targetSdkVersion, argv, classLoader);
-    }
-
-    /**
-     * The main function called when an application is started through a
-     * wrapper process.
-     *
-     * When the wrapper starts, the runtime starts {@link RuntimeInit#main}
-     * which calls {@link WrapperInit#main} which then calls this method.
-     * So we don't need to call commonInit() here.
-     *
-     * @param targetSdkVersion target SDK version
-     * @param argv arg strings
-     */
-    public static void wrapperInit(int targetSdkVersion, String[] argv)
-            throws Zygote.MethodAndArgsCaller {
-        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from wrapper");
-
-        applicationInit(targetSdkVersion, argv, null);
-    }
-
-    private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
+    protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
             throws Zygote.MethodAndArgsCaller {
         // If the application calls System.exit(), terminate the process
         // immediately without running any shutdown hooks.  It is not possible to
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 594b6ab..c329fd1 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -18,7 +18,7 @@
 
 import android.os.Process;
 import android.util.Slog;
-
+import com.android.internal.os.Zygote.MethodAndArgsCaller;
 import dalvik.system.VMRuntime;
 import java.io.DataOutputStream;
 import java.io.FileDescriptor;
@@ -80,7 +80,7 @@
             // Launch the application.
             String[] runtimeArgs = new String[args.length - 2];
             System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
-            RuntimeInit.wrapperInit(targetSdkVersion, runtimeArgs);
+            WrapperInit.wrapperInit(targetSdkVersion, runtimeArgs);
         } catch (Zygote.MethodAndArgsCaller caller) {
             caller.run();
         }
@@ -121,4 +121,39 @@
         Zygote.appendQuotedShellArgs(command, args);
         Zygote.execShell(command.toString());
     }
+
+    /**
+     * The main function called when an application is started through a
+     * wrapper process.
+     *
+     * When the wrapper starts, the runtime starts {@link RuntimeInit#main}
+     * which calls {@link main} which then calls this method.
+     * So we don't need to call commonInit() here.
+     *
+     * @param targetSdkVersion target SDK version
+     * @param argv arg strings
+     */
+    private static void wrapperInit(int targetSdkVersion, String[] argv)
+            throws Zygote.MethodAndArgsCaller {
+        if (RuntimeInit.DEBUG) {
+            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
+        }
+
+        // Check whether the first argument is a "-cp" in argv, and assume the next argument is the
+        // classpath. If found, create a PathClassLoader and use it for applicationInit.
+        ClassLoader classLoader = null;
+        if (argv != null && argv.length > 2 && argv[0].equals("-cp")) {
+            classLoader = ZygoteInit.createPathClassLoader(argv[1], targetSdkVersion);
+
+            // Install this classloader as the context classloader, too.
+            Thread.currentThread().setContextClassLoader(classLoader);
+
+            // Remove the classpath from the arguments.
+            String removedArgs[] = new String[argv.length - 2];
+            System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2);
+            argv = removedArgs;
+        }
+
+        RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+    }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e1e0a21..e065843 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -33,7 +33,7 @@
     */
 
     /** enable debugging over JDWP */
-    public static final int DEBUG_ENABLE_DEBUGGER   = 1;
+    public static final int DEBUG_ENABLE_JDWP   = 1;
     /** enable JNI checks */
     public static final int DEBUG_ENABLE_CHECKJNI   = 1 << 1;
     /** enable Java programming language "assert" statements */
@@ -46,8 +46,10 @@
     public static final int DEBUG_GENERATE_DEBUG_INFO = 1 << 5;
     /** Always use JIT-ed code. */
     public static final int DEBUG_ALWAYS_JIT = 1 << 6;
-    /** Make the code debuggable with turning off some optimizations. */
+    /** Make the code native debuggable by turning off some optimizations. */
     public static final int DEBUG_NATIVE_DEBUGGABLE = 1 << 7;
+    /** Make the code Java debuggable by turning off some optimizations. */
+    public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
@@ -98,6 +100,8 @@
           int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
           int[] fdsToIgnore, String instructionSet, String appDataDir) {
         VM_HOOKS.preFork();
+        // Resets nice priority for zygote process.
+        resetNicePriority();
         int pid = nativeForkAndSpecialize(
                   uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                   fdsToIgnore, instructionSet, appDataDir);
@@ -142,6 +146,8 @@
     public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags,
             int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
         VM_HOOKS.preFork();
+        // Resets nice priority for zygote process.
+        resetNicePriority();
         int pid = nativeForkSystemServer(
                 uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities);
         // Enable tracing as soon as we enter the system_server.
@@ -171,6 +177,14 @@
         VM_HOOKS.postForkChild(debugFlags, isSystemServer, instructionSet);
     }
 
+    /**
+     * Resets the calling thread priority to the default value (Thread.NORM_PRIORITY
+     * or nice value 0). This updates both the priority value in java.lang.Thread and
+     * the nice value (setpriority).
+     */
+    static void resetNicePriority() {
+        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
+    }
 
     /**
      * Executes "/system/bin/sh -c &lt;command&gt;" using the exec() system call.
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ec80303..527582b 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -24,6 +24,7 @@
 
 import android.net.Credentials;
 import android.net.LocalSocket;
+import android.os.FactoryTest;
 import android.os.Process;
 import android.os.SELinux;
 import android.os.SystemProperties;
@@ -335,8 +336,9 @@
         int[] gids;
 
         /**
-         * From --enable-debugger, --enable-checkjni, --enable-assert,
-         * --enable-safemode, --generate-debug-info and --enable-jni-logging.
+         * From --enable-jdwp, --enable-checkjni, --enable-assert,
+         * --enable-safemode, --generate-debug-info, --enable-jni-logging,
+         * --java-debuggable, and --native-debuggable.
          */
         int debugFlags;
 
@@ -446,8 +448,8 @@
                     targetSdkVersionSpecified = true;
                     targetSdkVersion = Integer.parseInt(
                             arg.substring(arg.indexOf('=') + 1));
-                } else if (arg.equals("--enable-debugger")) {
-                    debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+                } else if (arg.equals("--enable-jdwp")) {
+                    debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
                 } else if (arg.equals("--enable-safemode")) {
                     debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
                 } else if (arg.equals("--enable-checkjni")) {
@@ -458,6 +460,8 @@
                     debugFlags |= Zygote.DEBUG_ALWAYS_JIT;
                 } else if (arg.equals("--native-debuggable")) {
                     debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE;
+                } else if (arg.equals("--java-debuggable")) {
+                    debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                 } else if (arg.equals("--enable-jni-logging")) {
                     debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
                 } else if (arg.equals("--enable-assert")) {
@@ -642,13 +646,10 @@
             throws ZygoteSecurityException {
 
         if (peer.getUid() == Process.SYSTEM_UID) {
-            String factoryTest = SystemProperties.get("ro.factorytest");
-            boolean uidRestricted;
-
             /* In normal operation, SYSTEM_UID can only specify a restricted
              * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid.
              */
-            uidRestricted = !(factoryTest.equals("1") || factoryTest.equals("2"));
+            boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF;
 
             if (uidRestricted && args.uidSpecified && (args.uid < Process.SYSTEM_UID)) {
                 throw new ZygoteSecurityException(
@@ -672,14 +673,14 @@
      * Applies debugger system properties to the zygote arguments.
      *
      * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
-     * the debugger state is specified via the "--enable-debugger" flag
+     * the debugger state is specified via the "--enable-jdwp" flag
      * in the spawn request.
      *
      * @param args non-null; zygote spawner args
      */
     public static void applyDebuggerSystemProperty(Arguments args) {
-        if ("1".equals(SystemProperties.get("ro.debuggable"))) {
-            args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+        if (RoSystemProperties.DEBUGGABLE) {
+            args.debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
         }
     }
 
@@ -701,7 +702,7 @@
         int peerUid = peer.getUid();
 
         if (args.invokeWith != null && peerUid != 0 &&
-            (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) {
+            (args.debugFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) {
             throw new ZygoteSecurityException("Peer is permitted to specify an"
                     + "explicit invoke-with wrapper command only for debuggable"
                     + "applications.");
@@ -782,7 +783,7 @@
                     VMRuntime.getCurrentInstructionSet(),
                     pipeFd, parsedArgs.remainingArgs);
         } else {
-            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
+            ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion,
                     parsedArgs.remainingArgs, null /* classLoader */);
         }
     }
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ef5231c..e560c0c7 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -29,6 +29,7 @@
 import android.os.IInstalld;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.Seccomp;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
@@ -43,10 +44,9 @@
 import android.text.Hyphenator;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Slog;
 import android.webkit.WebViewFactory;
 import android.widget.TextView;
-
-
 import dalvik.system.DexFile;
 import dalvik.system.PathClassLoader;
 import dalvik.system.VMRuntime;
@@ -449,7 +449,8 @@
                 String[] amendedArgs = new String[args.length + 2];
                 amendedArgs[0] = "-cp";
                 amendedArgs[1] = systemServerClasspath;
-                System.arraycopy(parsedArgs.remainingArgs, 0, amendedArgs, 2, parsedArgs.remainingArgs.length);
+                System.arraycopy(args, 0, amendedArgs, 2, args.length);
+                args = amendedArgs;
             }
 
             WrapperInit.execApplication(parsedArgs.invokeWith,
@@ -458,8 +459,7 @@
         } else {
             ClassLoader cl = null;
             if (systemServerClasspath != null) {
-                cl = createSystemServerClassLoader(systemServerClasspath,
-                                                   parsedArgs.targetSdkVersion);
+                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
 
                 Thread.currentThread().setContextClassLoader(cl);
             }
@@ -467,22 +467,21 @@
             /*
              * Pass the remaining arguments to SystemServer.
              */
-            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+            ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
         }
 
         /* should never reach here */
     }
 
     /**
-     * Creates a PathClassLoader for the system server. It also creates
-     * a shared namespace associated with the classloader to let it access
-     * platform-private native libraries.
+     * Creates a PathClassLoader for the given class path that is associated with a shared
+     * namespace, i.e., this classloader can access platform-private native libraries. The
+     * classloader will use java.library.path as the native library path.
      */
-    private static PathClassLoader createSystemServerClassLoader(String systemServerClasspath,
-                                                                 int targetSdkVersion) {
+    static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
       String libraryPath = System.getProperty("java.library.path");
 
-      return PathClassLoaderFactory.createClassLoader(systemServerClasspath,
+      return PathClassLoaderFactory.createClassLoader(classPath,
                                                       libraryPath,
                                                       libraryPath,
                                                       ClassLoader.getSystemClassLoader(),
@@ -692,6 +691,9 @@
             // Zygote process unmounts root storage spaces.
             Zygote.nativeUnmountStorageOnInit();
 
+            // Set seccomp policy
+            Seccomp.setPolicy();
+
             ZygoteHooks.stopZygoteNoThreadCreation();
 
             if (startSystemServer) {
@@ -747,4 +749,33 @@
      */
     private ZygoteInit() {
     }
+
+    /**
+     * The main function called when started through the zygote process. This
+     * could be unified with main(), if the native code in nativeFinishInit()
+     * were rationalized with Zygote startup.<p>
+     *
+     * Current recognized args:
+     * <ul>
+     *   <li> <code> [--] &lt;start class name&gt;  &lt;args&gt;
+     * </ul>
+     *
+     * @param targetSdkVersion target SDK version
+     * @param argv arg strings
+     */
+    public static final void zygoteInit(int targetSdkVersion, String[] argv,
+            ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
+        if (RuntimeInit.DEBUG) {
+            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
+        RuntimeInit.redirectLogStreams();
+
+        RuntimeInit.commonInit();
+        ZygoteInit.nativeZygoteInit();
+        RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+    }
+
+    private static final native void nativeZygoteInit();
 }
diff --git a/core/java/com/android/internal/util/MessageUtils.java b/core/java/com/android/internal/util/MessageUtils.java
index 184245e..e733c30a 100644
--- a/core/java/com/android/internal/util/MessageUtils.java
+++ b/core/java/com/android/internal/util/MessageUtils.java
@@ -42,10 +42,11 @@
 
     /**
      * Finds the names of integer constants. Searches the specified {@code classes}, looking for
-     * accessible static integer fields whose names begin with one of the specified {@prefixes}.
+     * accessible static integer fields whose names begin with one of the specified
+     * {@code prefixes}.
      *
      * @param classes the classes to examine.
-     * @prefixes only consider fields names starting with one of these prefixes.
+     * @param prefixes only consider fields names starting with one of these prefixes.
      * @return a {@link SparseArray} mapping integer constants to their names.
      */
     public static SparseArray<String> findMessageNames(Class[] classes, String[] prefixes) {
@@ -122,7 +123,6 @@
      * accessible static integer values whose names begin with {@link #DEFAULT_PREFIXES}.
      *
      * @param classNames the classes to examine.
-     * @prefixes only consider fields names starting with one of these prefixes.
      * @return a {@link SparseArray} mapping integer constants to their names.
      */
     public static SparseArray<String> findMessageNames(Class[] classNames) {
diff --git a/core/java/com/android/internal/util/Predicate.java b/core/java/com/android/internal/util/Predicate.java
index bc6d6b3..1b5eaff 100644
--- a/core/java/com/android/internal/util/Predicate.java
+++ b/core/java/com/android/internal/util/Predicate.java
@@ -25,7 +25,10 @@
  * <p/>
  * Implementors of Predicate which may cause side effects upon evaluation are
  * strongly encouraged to state this fact clearly in their API documentation.
+ *
+ * @deprecated Use {@code java.util.function.Predicate} instead.
  */
+@Deprecated
 public interface Predicate<T> {
 
     boolean apply(T t);
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index be10608df..d67cef3 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -23,6 +23,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -1495,7 +1497,7 @@
     }
 
     /**
-     * @return number of log records
+     * @return the number of log records currently readable
      */
     public final int getLogRecSize() {
         // mSmHandler can be null if the state machine has quit.
@@ -1505,6 +1507,17 @@
     }
 
     /**
+     * @return the number of log records we can store
+     */
+    @VisibleForTesting
+    public final int getLogRecMaxSize() {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return 0;
+        return smh.mLogRecords.mMaxSize;
+    }
+
+    /**
      * @return the total number of records processed
      */
     public final int getLogRecCount() {
diff --git a/core/java/com/android/internal/widget/WatchHeaderListView.java b/core/java/com/android/internal/widget/WatchHeaderListView.java
index 4fd19c3..7e91537 100644
--- a/core/java/com/android/internal/widget/WatchHeaderListView.java
+++ b/core/java/com/android/internal/widget/WatchHeaderListView.java
@@ -26,8 +26,7 @@
 import android.widget.HeaderViewListAdapter;
 
 import java.util.ArrayList;
-
-import com.android.internal.util.Predicate;
+import java.util.function.Predicate;
 
 public class WatchHeaderListView extends ListView {
     private View mTopPanel;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 24c8bfb..6986732 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -84,6 +84,7 @@
     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 \
@@ -215,6 +216,11 @@
     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 \
@@ -269,6 +275,7 @@
     libhidlbase \
     libhidltransport \
     libhwbinder \
+    libvintf \
 
 LOCAL_SHARED_LIBRARIES += \
     libhwui \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 07392c4..00d9a96 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -163,6 +163,7 @@
 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_seccomp(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv *env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_Trace(JNIEnv* env);
@@ -218,7 +219,7 @@
     gCurRuntime->onStarted();
 }
 
-static void com_android_internal_os_RuntimeInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
+static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
 {
     gCurRuntime->onZygoteInit();
 }
@@ -232,19 +233,27 @@
 /*
  * JNI registration.
  */
-static const JNINativeMethod gMethods[] = {
-    { "nativeFinishInit", "()V",
-        (void*) com_android_internal_os_RuntimeInit_nativeFinishInit },
-    { "nativeZygoteInit", "()V",
-        (void*) com_android_internal_os_RuntimeInit_nativeZygoteInit },
-    { "nativeSetExitWithoutCleanup", "(Z)V",
-        (void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },
-};
 
 int register_com_android_internal_os_RuntimeInit(JNIEnv* env)
 {
+    const JNINativeMethod methods[] = {
+        { "nativeFinishInit", "()V",
+            (void*) com_android_internal_os_RuntimeInit_nativeFinishInit },
+        { "nativeSetExitWithoutCleanup", "(Z)V",
+            (void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },
+    };
     return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
-        gMethods, NELEM(gMethods));
+        methods, NELEM(methods));
+}
+
+int register_com_android_internal_os_ZygoteInit(JNIEnv* env)
+{
+    const JNINativeMethod methods[] = {
+        { "nativeZygoteInit", "()V",
+            (void*) com_android_internal_os_ZygoteInit_nativeZygoteInit },
+    };
+    return jniRegisterNativeMethods(env, "com/android/internal/os/ZygoteInit",
+        methods, NELEM(methods));
 }
 
 // ----------------------------------------------------------------------
@@ -1273,6 +1282,7 @@
 
 static const RegJNIRec gRegJNI[] = {
     REG_JNI(register_com_android_internal_os_RuntimeInit),
+    REG_JNI(register_com_android_internal_os_ZygoteInit),
     REG_JNI(register_android_os_SystemClock),
     REG_JNI(register_android_util_EventLog),
     REG_JNI(register_android_util_Log),
@@ -1366,6 +1376,7 @@
     REG_JNI(register_android_os_FileObserver),
     REG_JNI(register_android_os_MessageQueue),
     REG_JNI(register_android_os_SELinux),
+    REG_JNI(register_android_os_seccomp),
     REG_JNI(register_android_os_Trace),
     REG_JNI(register_android_os_UEventObserver),
     REG_JNI(register_android_net_LocalSocketImpl),
diff --git a/core/jni/android_hardware_Radio.cpp b/core/jni/android_hardware_Radio.cpp
index ec6471e..d2ac2cc 100644
--- a/core/jni/android_hardware_Radio.cpp
+++ b/core/jni/android_hardware_Radio.cpp
@@ -23,7 +23,7 @@
 #include "JNIHelp.h"
 #include "core_jni_helpers.h"
 #include <system/radio.h>
-#include <system/radio_metadata.h>
+#include <system/RadioMetadataWrapper.h>
 #include <radio/RadioCallback.h>
 #include <radio/Radio.h>
 #include <utils/RefBase.h>
@@ -749,7 +749,7 @@
     }
 
     struct radio_program_info nInfo;
-    radio_metadata_allocate(&nInfo.metadata, 0, 0);
+    RadioMetadataWrapper metadataWrapper(&nInfo.metadata);
     jobject jInfo = NULL;
     int jStatus;
 
@@ -767,7 +767,6 @@
     if (jInfo != NULL) {
         env->DeleteLocalRef(jInfo);
     }
-    radio_metadata_deallocate(nInfo.metadata);
     return jStatus;
 }
 
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 793d132..0c7f5a1 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -509,7 +509,7 @@
     sp<MemoryDealer> memoryDealer;
     sp<IMemory> memory;
     size_t size;
-    sound_model_handle_t handle;
+    sound_model_handle_t handle = 0;
     jobject jUuid;
     jstring jUuidString;
     const char *nUuidString;
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index cbe2bba..a758489 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -1012,9 +1012,8 @@
     ALOGD("Native heap dump complete.\n");
 }
 
-
-static void android_os_Debug_dumpNativeBacktraceToFile(JNIEnv* env, jobject clazz,
-    jint pid, jstring fileName)
+static void android_os_Debug_dumpNativeBacktraceToFileTimeout(JNIEnv* env, jobject clazz,
+    jint pid, jstring fileName, jint timeoutSecs)
 {
     if (fileName == NULL) {
         jniThrowNullPointerException(env, "file == null");
@@ -1028,17 +1027,13 @@
         env->ReleaseStringCritical(fileName, str);
     }
 
-    int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW, 0666);  /* -rw-rw-rw- */
+    int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW | O_CLOEXEC | O_APPEND, 0666);
     if (fd < 0) {
         fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno));
         return;
     }
 
-    if (lseek(fd, 0, SEEK_END) < 0) {
-        fprintf(stderr, "lseek: %s\n", strerror(errno));
-    } else {
-        dump_backtrace_to_file(pid, fd);
-    }
+    dump_backtrace_to_file_timeout(pid, fd, timeoutSecs);
 
     close(fd);
 }
@@ -1083,8 +1078,8 @@
             (void*)android_os_Debug_getProxyObjectCount },
     { "getBinderDeathObjectCount", "()I",
             (void*)android_os_Debug_getDeathObjectCount },
-    { "dumpNativeBacktraceToFile", "(ILjava/lang/String;)V",
-            (void*)android_os_Debug_dumpNativeBacktraceToFile },
+    { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)V",
+            (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
     { "getUnreachableMemory", "(IZ)Ljava/lang/String;",
             (void*)android_os_Debug_getUnreachableMemory },
 };
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index e653900..c3978e7 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -33,6 +33,7 @@
 #include <hidl/HidlTransportSupport.h>
 #include <hwbinder/ProcessState.h>
 #include <nativehelper/ScopedLocalRef.h>
+#include <vintf/parse_string.h>
 
 #include "core_jni_helpers.h"
 
@@ -48,12 +49,6 @@
 
 namespace android {
 
-static jclass gArrayListClass;
-static struct {
-    jmethodID size;
-    jmethodID get;
-} gArrayListMethods;
-
 static jclass gErrorClass;
 
 static struct fields_t {
@@ -127,18 +122,23 @@
         uint32_t flags,
         TransactCallback callback) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
+    bool isOneway = (flags & TF_ONE_WAY) != 0;
+    ScopedLocalRef<jobject> replyObj(env, nullptr);
+    sp<JHwParcel> replyContext = nullptr;
 
     ScopedLocalRef<jobject> requestObj(env, JHwParcel::NewObject(env));
     JHwParcel::GetNativeContext(env, requestObj.get())->setParcel(
             const_cast<hardware::Parcel *>(&data), false /* assumeOwnership */);
 
-    ScopedLocalRef<jobject> replyObj(env, JHwParcel::NewObject(env));
 
-    sp<JHwParcel> replyContext =
-        JHwParcel::GetNativeContext(env, replyObj.get());
+    if (!isOneway) {
+        replyObj.reset(JHwParcel::NewObject(env));
 
-    replyContext->setParcel(reply, false /* assumeOwnership */);
-    replyContext->setTransactCallback(callback);
+        replyContext = JHwParcel::GetNativeContext(env, replyObj.get());
+
+        replyContext->setParcel(reply, false /* assumeOwnership */);
+        replyContext->setTransactCallback(callback);
+    }
 
     env->CallVoidMethod(
             mObject,
@@ -166,27 +166,29 @@
 
     status_t err = OK;
 
-    if (!replyContext->wasSent()) {
-        // The implementation never finished the transaction.
-        err = UNKNOWN_ERROR;  // XXX special error code instead?
+    if (!isOneway) {
+        if (!replyContext->wasSent()) {
+            // The implementation never finished the transaction.
+            err = UNKNOWN_ERROR;  // XXX special error code instead?
 
-        reply->setDataPosition(0 /* pos */);
+            reply->setDataPosition(0 /* pos */);
+        }
+
+        // Release all temporary storage now that scatter-gather data
+        // has been consolidated, either by calling the TransactCallback,
+        // if wasSent() == true or clearing the reply parcel (setDataOffset above).
+        replyContext->getStorage()->release(env);
+
+        // We cannot permanently pass ownership of "data" and "reply" over to their
+        // Java object wrappers (we don't own them ourselves).
+        replyContext->setParcel(
+                NULL /* parcel */, false /* assumeOwnership */);
+
     }
 
-    // Release all temporary storage now that scatter-gather data
-    // has been consolidated, either by calling the TransactCallback,
-    // if wasSent() == true or clearing the reply parcel (setDataOffset above).
-    replyContext->getStorage()->release(env);
-
-    // We cannot permanently pass ownership of "data" and "reply" over to their
-    // Java object wrappers (we don't own them ourselves).
-
     JHwParcel::GetNativeContext(env, requestObj.get())->setParcel(
             NULL /* parcel */, false /* assumeOwnership */);
 
-    replyContext->setParcel(
-            NULL /* parcel */, false /* assumeOwnership */);
-
     return err;
 }
 
@@ -229,7 +231,6 @@
 static void JHwBinder_native_registerService(
         JNIEnv *env,
         jobject thiz,
-        jobject interfaceChainArrayList,
         jstring serviceNameObj) {
     if (serviceNameObj == NULL) {
         jniThrowException(env, "java/lang/NullPointerException", NULL);
@@ -241,24 +242,6 @@
         return;  // XXX exception already pending?
     }
 
-    jint numInterfaces = env->CallIntMethod(interfaceChainArrayList,
-                                            gArrayListMethods.size);
-    hidl_string *strings = new hidl_string[numInterfaces];
-
-    for (jint i = 0; i < numInterfaces; i++) {
-        jstring strObj = static_cast<jstring>(
-            env->CallObjectMethod(interfaceChainArrayList,
-                                  gArrayListMethods.get,
-                                  i)
-        );
-        const char * str = env->GetStringUTFChars(strObj, nullptr);
-        strings[i] = hidl_string(str);
-        env->ReleaseStringUTFChars(strObj, str);
-    }
-
-    hidl_vec<hidl_string> interfaceChain;
-    interfaceChain.setToExternal(strings, numInterfaces, true /* shouldOwn */);
-
     sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz);
 
     /* TODO(b/33440494) this is not right */
@@ -272,7 +255,7 @@
         return;
     }
 
-    Return<bool> ret = manager->add(interfaceChain, serviceName, base);
+    Return<bool> ret = manager->add(serviceName, base);
 
     env->ReleaseStringUTFChars(serviceNameObj, serviceName);
     serviceName = NULL;
@@ -293,6 +276,8 @@
         jstring ifaceNameObj,
         jstring serviceNameObj) {
 
+    using ::android::vintf::operator<<;
+
     if (ifaceNameObj == NULL) {
         jniThrowException(env, "java/lang/NullPointerException", NULL);
         return NULL;
@@ -302,20 +287,6 @@
         return NULL;
     }
 
-    const char *ifaceName = env->GetStringUTFChars(ifaceNameObj, NULL);
-    if (ifaceName == NULL) {
-        return NULL; // XXX exception already pending?
-    }
-    const char *serviceName = env->GetStringUTFChars(serviceNameObj, NULL);
-    if (serviceName == NULL) {
-        env->ReleaseStringUTFChars(ifaceNameObj, ifaceName);
-        return NULL; // XXX exception already pending?
-    }
-
-    LOG(INFO) << "looking for service '"
-              << serviceName
-              << "'";
-
     auto manager = hardware::defaultServiceManager();
 
     if (manager == nullptr) {
@@ -324,20 +295,50 @@
         return NULL;
     }
 
-    Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceName, serviceName);
+    const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);
+    if (ifaceNameCStr == NULL) {
+        return NULL; // XXX exception already pending?
+    }
+    std::string ifaceName(ifaceNameCStr);
+    env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);
+    ::android::hardware::hidl_string ifaceNameHStr;
+    ifaceNameHStr.setToExternal(ifaceName.c_str(), ifaceName.size());
+
+    const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);
+    if (serviceNameCStr == NULL) {
+        return NULL; // XXX exception already pending?
+    }
+    std::string serviceName(serviceNameCStr);
+    env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
+    ::android::hardware::hidl_string serviceNameHStr;
+    serviceNameHStr.setToExternal(serviceName.c_str(), serviceName.size());
+
+    LOG(INFO) << "Looking for service "
+              << ifaceName
+              << "/"
+              << serviceName;
+
+    ::android::vintf::Transport transport =
+            ::android::hardware::getTransport(ifaceName, serviceName);
+    if (   transport != ::android::vintf::Transport::EMPTY
+        && transport != ::android::vintf::Transport::HWBINDER) {
+        LOG(ERROR) << "service " << ifaceName << " declares transport method "
+                   << transport << " but framework expects "
+                   << ::android::vintf::Transport::HWBINDER;
+        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+        return NULL;
+    }
+
+    Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceNameHStr, serviceNameHStr);
 
     if (!ret.isOk()) {
         signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+        return NULL;
     }
 
     sp<hardware::IBinder> service = hardware::toBinder<
             hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase>(ret);
 
-    env->ReleaseStringUTFChars(ifaceNameObj, ifaceName);
-    ifaceName = NULL;
-    env->ReleaseStringUTFChars(serviceNameObj, serviceName);
-    serviceName = NULL;
-
     if (service == NULL) {
         signalExceptionForError(env, NAME_NOT_FOUND);
         return NULL;
@@ -357,7 +358,7 @@
         "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V",
         (void *)JHwBinder_native_transact },
 
-    { "registerService", "(Ljava/util/ArrayList;Ljava/lang/String;)V",
+    { "registerService", "(Ljava/lang/String;)V",
         (void *)JHwBinder_native_registerService },
 
     { "getService", "(Ljava/lang/String;Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;",
@@ -367,11 +368,6 @@
 namespace android {
 
 int register_android_os_HwBinder(JNIEnv *env) {
-    jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
-    gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
-    gArrayListMethods.size = GetMethodIDOrDie(env, arrayListClass, "size", "()I");
-    gArrayListMethods.get = GetMethodIDOrDie(env, arrayListClass, "get", "(I)Ljava/lang/Object;");
-
     jclass errorClass = FindClassOrDie(env, "java/lang/Error");
     gErrorClass = MakeGlobalRefOrDie(env, errorClass);
 
diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp
index b2dee06..8590ecf 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -382,7 +382,7 @@
     s = nullptr;
 
     hidl_string tmp;
-    tmp.setToExternal(static_cast<const char *>(subBlob->data()), size);
+    tmp.setToExternal(static_cast<const char *>(subBlob->data()), size - 1);
 
     sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
     blob->write(offset, &tmp, sizeof(tmp));
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 1bd2333..678041f 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -404,6 +404,11 @@
     signalExceptionForError(env, err);
 }
 
+static void JHwParcel_native_release(
+        JNIEnv *env, jobject thiz) {
+    JHwParcel::GetNativeContext(env, thiz)->setParcel(NULL, false /* assumeOwnership */);
+}
+
 static void JHwParcel_native_releaseTemporaryStorage(
         JNIEnv *env, jobject thiz) {
     JHwParcel::GetNativeContext(env, thiz)->getStorage()->release(env);
@@ -955,6 +960,10 @@
 
     { "writeBuffer", "(L" PACKAGE_PATH "/HwBlob;)V",
         (void *)JHwParcel_native_writeBuffer },
+
+    { "release", "()V",
+        (void *)JHwParcel_native_release },
+
 };
 
 namespace android {
diff --git a/core/jni/android_os_seccomp.cpp b/core/jni/android_os_seccomp.cpp
new file mode 100644
index 0000000..4502371
--- /dev/null
+++ b/core/jni/android_os_seccomp.cpp
@@ -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.
+ */
+
+#include "core_jni_helpers.h"
+#include "JniConstants.h"
+#include "utils/Log.h"
+#include <selinux/selinux.h>
+
+#include "seccomp_policy.h"
+
+static void Seccomp_setPolicy(JNIEnv* /*env*/) {
+    if (security_getenforce() == 0) {
+        ALOGI("seccomp disabled by setenforce 0");
+        return;
+    }
+
+    if (!set_seccomp_filter()) {
+        ALOGE("Failed to set seccomp policy - killing");
+        exit(1);
+    }
+}
+
+static const JNINativeMethod method_table[] = {
+    NATIVE_METHOD(Seccomp, setPolicy, "()V"),
+};
+
+namespace android {
+
+int register_android_os_seccomp(JNIEnv* env) {
+    return android::RegisterMethodsOrDie(env, "android/os/Seccomp",
+                                         method_table, NELEM(method_table));
+}
+
+}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 428159a..afd60f1 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -2161,9 +2161,9 @@
         (void*) android_content_AssetManager_readAsset },
     { "seekAsset",      "(JJI)J",
         (void*) android_content_AssetManager_seekAsset },
-    { "getAssetLength", "!(J)J",
+    { "getAssetLength", "(J)J",
         (void*) android_content_AssetManager_getAssetLength },
-    { "getAssetRemainingLength", "!(J)J",
+    { "getAssetRemainingLength", "(J)J",
         (void*) android_content_AssetManager_getAssetRemainingLength },
     { "addAssetPathNative", "(Ljava/lang/String;Z)I",
         (void*) android_content_AssetManager_addAssetPath },
@@ -2179,25 +2179,25 @@
         (void*) android_content_AssetManager_getNonSystemLocales },
     { "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
         (void*) android_content_AssetManager_getSizeConfigurations },
-    { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
+    { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
         (void*) android_content_AssetManager_setConfiguration },
-    { "getResourceIdentifier","!(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+    { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
         (void*) android_content_AssetManager_getResourceIdentifier },
-    { "getResourceName","!(I)Ljava/lang/String;",
+    { "getResourceName","(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourceName },
-    { "getResourcePackageName","!(I)Ljava/lang/String;",
+    { "getResourcePackageName","(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourcePackageName },
-    { "getResourceTypeName","!(I)Ljava/lang/String;",
+    { "getResourceTypeName","(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourceTypeName },
-    { "getResourceEntryName","!(I)Ljava/lang/String;",
+    { "getResourceEntryName","(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getResourceEntryName },
-    { "loadResourceValue","!(ISLandroid/util/TypedValue;Z)I",
+    { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadResourceValue },
-    { "loadResourceBagValue","!(IILandroid/util/TypedValue;Z)I",
+    { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadResourceBagValue },
-    { "getStringBlockCount","!()I",
+    { "getStringBlockCount","()I",
         (void*) android_content_AssetManager_getStringBlockCount },
-    { "getNativeStringBlock","!(I)J",
+    { "getNativeStringBlock","(I)J",
         (void*) android_content_AssetManager_getNativeStringBlock },
     { "getCookieName","(I)Ljava/lang/String;",
         (void*) android_content_AssetManager_getCookieName },
@@ -2215,21 +2215,21 @@
         (void*) android_content_AssetManager_copyTheme },
     { "clearTheme", "(J)V",
         (void*) android_content_AssetManager_clearTheme },
-    { "loadThemeAttributeValue", "!(JILandroid/util/TypedValue;Z)I",
+    { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
         (void*) android_content_AssetManager_loadThemeAttributeValue },
-    { "getThemeChangingConfigurations", "!(J)I",
+    { "getThemeChangingConfigurations", "(J)I",
         (void*) android_content_AssetManager_getThemeChangingConfigurations },
     { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
         (void*) android_content_AssetManager_dumpTheme },
-    { "applyStyle","!(JIIJ[I[I[I)Z",
+    { "applyStyle","(JIIJ[I[I[I)Z",
         (void*) android_content_AssetManager_applyStyle },
-    { "resolveAttrs","!(JII[I[I[I[I)Z",
+    { "resolveAttrs","(JII[I[I[I[I)Z",
         (void*) android_content_AssetManager_resolveAttrs },
-    { "retrieveAttributes","!(J[I[I[I)Z",
+    { "retrieveAttributes","(J[I[I[I)Z",
         (void*) android_content_AssetManager_retrieveAttributes },
-    { "getArraySize","!(I)I",
+    { "getArraySize","(I)I",
         (void*) android_content_AssetManager_getArraySize },
-    { "retrieveArray","!(I[I)I",
+    { "retrieveArray","(I[I)I",
         (void*) android_content_AssetManager_retrieveArray },
 
     // XML files.
@@ -2239,11 +2239,11 @@
     // Arrays.
     { "getArrayStringResource","(I)[Ljava/lang/String;",
         (void*) android_content_AssetManager_getArrayStringResource },
-    { "getArrayStringInfo","!(I)[I",
+    { "getArrayStringInfo","(I)[I",
         (void*) android_content_AssetManager_getArrayStringInfo },
-    { "getArrayIntResource","!(I)[I",
+    { "getArrayIntResource","(I)[I",
         (void*) android_content_AssetManager_getArrayIntResource },
-    { "getStyleAttributes","!(I)[I",
+    { "getStyleAttributes","(I)[I",
         (void*) android_content_AssetManager_getStyleAttributes },
 
     // Bookkeeping.
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index b57f2362..a03d3c5 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -252,25 +252,26 @@
             if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
                 // This task wants to stay at background
                 // update its cpuset so it doesn't only run on bg core(s)
-#ifdef ENABLE_CPUSETS
-                int err = set_cpuset_policy(t_pid, sp);
-                if (err != NO_ERROR) {
-                    signalExceptionForGroupError(env, -err, t_pid);
-                    break;
+                if (cpusets_enabled()) {
+                    int err = set_cpuset_policy(t_pid, sp);
+                    if (err != NO_ERROR) {
+                        signalExceptionForGroupError(env, -err, t_pid);
+                        break;
+                    }
                 }
-#endif
                 continue;
             }
         }
         int err;
-#ifdef ENABLE_CPUSETS
-        // set both cpuset and cgroup for general threads
-        err = set_cpuset_policy(t_pid, sp);
-        if (err != NO_ERROR) {
-            signalExceptionForGroupError(env, -err, t_pid);
-            break;
+
+        if (cpusets_enabled()) {
+            // set both cpuset and cgroup for general threads
+            err = set_cpuset_policy(t_pid, sp);
+            if (err != NO_ERROR) {
+                signalExceptionForGroupError(env, -err, t_pid);
+                break;
+            }
         }
-#endif
 
         err = set_sched_policy(t_pid, sp);
         if (err != NO_ERROR) {
@@ -291,7 +292,6 @@
     return (int) sp;
 }
 
-#ifdef ENABLE_CPUSETS
 /** Sample CPUset list format:
  *  0-3,4,6-8
  */
@@ -367,7 +367,6 @@
     }
     return;
 }
-#endif
 
 
 /**
@@ -376,22 +375,21 @@
  * them in the passed in cpu_set_t
  */
 void get_exclusive_cpuset_cores(SchedPolicy policy, cpu_set_t *cpu_set) {
-#ifdef ENABLE_CPUSETS
-    int i;
-    cpu_set_t tmp_set;
-    get_cpuset_cores_for_policy(policy, cpu_set);
-    for (i = 0; i < SP_CNT; i++) {
-        if ((SchedPolicy) i == policy) continue;
-        get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
-        // First get cores exclusive to one set or the other
-        CPU_XOR(&tmp_set, cpu_set, &tmp_set);
-        // Then get the ones only in cpu_set
-        CPU_AND(cpu_set, cpu_set, &tmp_set);
+    if (cpusets_enabled()) {
+        int i;
+        cpu_set_t tmp_set;
+        get_cpuset_cores_for_policy(policy, cpu_set);
+        for (i = 0; i < SP_CNT; i++) {
+            if ((SchedPolicy) i == policy) continue;
+            get_cpuset_cores_for_policy((SchedPolicy)i, &tmp_set);
+            // First get cores exclusive to one set or the other
+            CPU_XOR(&tmp_set, cpu_set, &tmp_set);
+            // Then get the ones only in cpu_set
+            CPU_AND(cpu_set, cpu_set, &tmp_set);
+        }
+    } else {
+        CPU_ZERO(cpu_set);
     }
-#else
-    (void) policy;
-    CPU_ZERO(cpu_set);
-#endif
     return;
 }
 
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index c7998a1..1c6ead0 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -111,11 +111,12 @@
 }
 
 static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
-        NativeInputChannel* nativeInputChannel) {
+        std::unique_ptr<NativeInputChannel> nativeInputChannel) {
     jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
             gInputChannelClassInfo.ctor);
     if (inputChannelObj) {
-        android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
+        android_view_InputChannel_setNativeInputChannel(env, inputChannelObj,
+                 nativeInputChannel.release());
     }
     return inputChannelObj;
 }
@@ -143,13 +144,13 @@
     }
 
     jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
-            new NativeInputChannel(serverChannel));
+            std::make_unique<NativeInputChannel>(serverChannel));
     if (env->ExceptionCheck()) {
         return NULL;
     }
 
     jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
-            new NativeInputChannel(clientChannel));
+            std::make_unique<NativeInputChannel>(clientChannel));
     if (env->ExceptionCheck()) {
         return NULL;
     }
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index a32dbad..90ad2da 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -155,24 +155,6 @@
   }
 }
 
-// Resets nice priority for zygote process. Zygote priority can be set
-// to high value during boot phase to speed it up. We want to ensure
-// zygote is running at normal priority before childs are forked from it.
-//
-// This ends up being called repeatedly before each fork(), but there's
-// no real harm in that.
-static void ResetNicePriority(JNIEnv* env) {
-  errno = 0;
-  int prio = getpriority(PRIO_PROCESS, 0);
-  if (prio == -1 && errno != 0) {
-    ALOGW("getpriority failed: %s\n", strerror(errno));
-  }
-  if (prio != 0 && setpriority(PRIO_PROCESS, 0, 0) != 0) {
-    ALOGE("setpriority(%d, 0, 0) failed: %s", PRIO_PROCESS, strerror(errno));
-    RuntimeAbort(env, __LINE__, "setpriority failed");
-  }
-}
-
 // Sets the SIGCHLD handler back to default behavior in zygote children.
 static void UnsetSigChldHandler() {
   struct sigaction sa;
@@ -253,13 +235,36 @@
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
               "your kernel is compiled with file capabilities support");
       } else {
+        ALOGE("prctl(PR_CAPBSET_DROP, %d) failed: %s", i, strerror(errno));
         RuntimeAbort(env, __LINE__, "prctl(PR_CAPBSET_DROP) failed");
       }
     }
   }
 }
 
-static void SetCapabilities(JNIEnv* env, int64_t permitted, int64_t effective) {
+static void SetInheritable(JNIEnv* env, uint64_t inheritable) {
+  __user_cap_header_struct capheader;
+  memset(&capheader, 0, sizeof(capheader));
+  capheader.version = _LINUX_CAPABILITY_VERSION_3;
+  capheader.pid = 0;
+
+  __user_cap_data_struct capdata[2];
+  if (capget(&capheader, &capdata[0]) == -1) {
+    ALOGE("capget failed: %s", strerror(errno));
+    RuntimeAbort(env, __LINE__, "capget failed");
+  }
+
+  capdata[0].inheritable = inheritable;
+  capdata[1].inheritable = inheritable >> 32;
+
+  if (capset(&capheader, &capdata[0]) == -1) {
+    ALOGE("capset(inh=%" PRIx64 ") failed: %s", inheritable, strerror(errno));
+    RuntimeAbort(env, __LINE__, "capset failed");
+  }
+}
+
+static void SetCapabilities(JNIEnv* env, uint64_t permitted, uint64_t effective,
+                            uint64_t inheritable) {
   __user_cap_header_struct capheader;
   memset(&capheader, 0, sizeof(capheader));
   capheader.version = _LINUX_CAPABILITY_VERSION_3;
@@ -271,9 +276,12 @@
   capdata[1].effective = effective >> 32;
   capdata[0].permitted = permitted;
   capdata[1].permitted = permitted >> 32;
+  capdata[0].inheritable = inheritable;
+  capdata[1].inheritable = inheritable >> 32;
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    ALOGE("capset(%" PRId64 ", %" PRId64 ") failed", permitted, effective);
+    ALOGE("capset(perm=%" PRIx64 ", eff=%" PRIx64 ", inh=%" PRIx64 ") failed: %s", permitted,
+          effective, inheritable, strerror(errno));
     RuntimeAbort(env, __LINE__, "capset failed");
   }
 }
@@ -486,8 +494,6 @@
     RuntimeAbort(env, __LINE__, "Unable to restat file descriptor table.");
   }
 
-  ResetNicePriority(env);
-
   pid_t pid = fork();
 
   if (pid == 0) {
@@ -508,6 +514,7 @@
       EnableKeepCapabilities(env);
     }
 
+    SetInheritable(env, permittedCapabilities);
     DropCapabilitiesBoundingSet(env);
 
     bool use_native_bridge = !is_system_server && (instructionSet != NULL)
@@ -580,7 +587,7 @@
         }
     }
 
-    SetCapabilities(env, permittedCapabilities, effectiveCapabilities);
+    SetCapabilities(env, permittedCapabilities, effectiveCapabilities, permittedCapabilities);
 
     SetSchedulerPolicy(env);
 
diff --git a/core/jni/com_google_android_gles_jni_GLImpl.cpp b/core/jni/com_google_android_gles_jni_GLImpl.cpp
index ad7d744..3e74d1c 100644
--- a/core/jni/com_google_android_gles_jni_GLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_GLImpl.cpp
@@ -132,6 +132,7 @@
     pointer = _env->CallStaticLongMethod(nioAccessClass,
             getBasePointerID, buffer);
     if (pointer != 0L) {
+        *offset = 0;
         *array = NULL;
         return reinterpret_cast<void *>(pointer);
     }
@@ -139,6 +140,7 @@
     *array = (jarray) _env->CallStaticObjectMethod(nioAccessClass,
             getBaseArrayID, buffer);
     if (*array == NULL) {
+        *offset = 0;
         return (void*) NULL;
     }
     *offset = _env->CallStaticIntMethod(nioAccessClass,
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 59a536b..956b724 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -26,8 +26,10 @@
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <cutils/log.h>
 
 // Static whitelist of open paths that the zygote is allowed to keep open.
 static const char* kPathWhitelist[] = {
@@ -65,9 +67,10 @@
       return true;
   }
 
-  static const std::string kFrameworksPrefix = "/system/framework/";
-  static const std::string kJarSuffix = ".jar";
-  if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) {
+  static const char* kFrameworksPrefix = "/system/framework/";
+  static const char* kJarSuffix = ".jar";
+  if (android::base::StartsWith(path, kFrameworksPrefix)
+      && android::base::EndsWith(path, kJarSuffix)) {
     return true;
   }
 
@@ -79,28 +82,31 @@
   // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
   // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
   // See AssetManager.cpp for more details on overlay-subdir.
-  static const std::string kOverlayDir = "/system/vendor/overlay/";
-  static const std::string kVendorOverlayDir = "/vendor/overlay";
-  static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/";
-  static const std::string kApkSuffix = ".apk";
+  static const char* kOverlayDir = "/system/vendor/overlay/";
+  static const char* kVendorOverlayDir = "/vendor/overlay";
+  static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/";
+  static const char* kApkSuffix = ".apk";
 
-  if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir)
-       || StartsWith(path, kVendorOverlayDir))
-      && EndsWith(path, kApkSuffix)
+  if ((android::base::StartsWith(path, kOverlayDir)
+       || android::base::StartsWith(path, kOverlaySubdir)
+       || android::base::StartsWith(path, kVendorOverlayDir))
+      && android::base::EndsWith(path, kApkSuffix)
       && path.find("/../") == std::string::npos) {
     return true;
   }
 
-  static const std::string kOverlayIdmapPrefix = "/data/resource-cache/";
-  static const std::string kOverlayIdmapSuffix = ".apk@idmap";
-  if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix)
+  static const char* kOverlayIdmapPrefix = "/data/resource-cache/";
+  static const char* kOverlayIdmapSuffix = ".apk@idmap";
+  if (android::base::StartsWith(path, kOverlayIdmapPrefix)
+      && android::base::EndsWith(path, kOverlayIdmapSuffix)
       && path.find("/../") == std::string::npos) {
     return true;
   }
 
   // All regular files that are placed under this path are whitelisted automatically.
-  static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/";
-  if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) {
+  static const char* kZygoteWhitelistPath = "/vendor/zygote_whitelist/";
+  if (android::base::StartsWith(path, kZygoteWhitelistPath)
+      && path.find("/../") == std::string::npos) {
     return true;
   }
 
@@ -111,24 +117,6 @@
     : whitelist_() {
 }
 
-// TODO: Call android::base::StartsWith instead of copying the code here.
-// static
-bool FileDescriptorWhitelist::StartsWith(const std::string& str,
-                                         const std::string& prefix) {
-  return str.compare(0, prefix.size(), prefix) == 0;
-}
-
-// TODO: Call android::base::EndsWith instead of copying the code here.
-// static
-bool FileDescriptorWhitelist::EndsWith(const std::string& str,
-                                       const std::string& suffix) {
-  if (suffix.size() > str.size()) {
-    return false;
-  }
-
-  return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
-}
-
 FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr;
 
 // static
@@ -137,7 +125,7 @@
   // This should never happen; the zygote should always have the right set
   // of permissions required to stat all its open files.
   if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
-    ALOGE("Unable to stat fd %d : %s", fd, strerror(errno));
+    PLOG(ERROR) << "Unable to stat fd " << fd;
     return NULL;
   }
 
@@ -150,7 +138,8 @@
     }
 
     if (!whitelist->IsAllowed(socket_name)) {
-      ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd);
+      LOG(ERROR) << "Socket name not whitelisted : " << socket_name
+                 << " (fd=" << fd << ")";
       return NULL;
     }
 
@@ -168,17 +157,18 @@
   // with the child process across forks but those should have been closed
   // before we got to this point.
   if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
-    ALOGE("Unsupported st_mode %d", f_stat.st_mode);
+    LOG(ERROR) << "Unsupported st_mode " << f_stat.st_mode;
     return NULL;
   }
 
   std::string file_path;
-  if (!Readlink(fd, &file_path)) {
+  const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd);
+  if (!android::base::Readlink(fd_path, &file_path)) {
     return NULL;
   }
 
   if (!whitelist->IsAllowed(file_path)) {
-    ALOGE("Not whitelisted : %s", file_path.c_str());
+    LOG(ERROR) << "Not whitelisted : " << file_path;
     return NULL;
   }
 
@@ -187,7 +177,7 @@
   // there won't be any races.
   const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
   if (fd_flags == -1) {
-    ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno));
+    PLOG(ERROR) << "Failed fcntl(" << fd << ", F_GETFD)";
     return NULL;
   }
 
@@ -205,7 +195,7 @@
   //   their presence and pass them in to open().
   int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
   if (fs_flags == -1) {
-    ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno));
+    PLOG(ERROR) << "Failed fcntl(" << fd << ", F_GETFL)";
     return NULL;
   }
 
@@ -224,6 +214,7 @@
 bool FileDescriptorInfo::Restat() const {
   struct stat f_stat;
   if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
+    PLOG(ERROR) << "Unable to restat fd " << fd;
     return false;
   }
 
@@ -241,31 +232,31 @@
   const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
 
   if (new_fd == -1) {
-    ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno));
+    PLOG(ERROR) << "Failed open(" << file_path << ", " << open_flags << ")";
     return false;
   }
 
   if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
     close(new_fd);
-    ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno));
+    PLOG(ERROR) << "Failed fcntl(" << new_fd << ", F_SETFD, " << fd_flags << ")";
     return false;
   }
 
   if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
     close(new_fd);
-    ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno));
+    PLOG(ERROR) << "Failed fcntl(" << new_fd << ", F_SETFL, " << fs_flags << ")";
     return false;
   }
 
   if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
     close(new_fd);
-    ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno));
+    PLOG(ERROR) << "Failed lseek64(" << new_fd << ", SEEK_SET)";
     return false;
   }
 
   if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
     close(new_fd);
-    ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno));
+    PLOG(ERROR) << "Failed dup2(" << fd << ", " << new_fd << ")";
     return false;
   }
 
@@ -297,27 +288,6 @@
   is_sock(false) {
 }
 
-// TODO: Call android::base::Readlink instead of copying the code here.
-// static
-bool FileDescriptorInfo::Readlink(const int fd, std::string* result) {
-  char path[64];
-  snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
-
-  // Code copied from android::base::Readlink starts here :
-
-  // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer,
-  // and truncates to whatever size you do supply, so it can't be used to query.
-  // We could call lstat first, but that would introduce a race condition that
-  // we couldn't detect.
-  // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here.
-  char buf[4096];
-  ssize_t len = readlink(path, buf, sizeof(buf));
-  if (len == -1) return false;
-
-  result->assign(buf, len);
-  return true;
-}
-
 // static
 bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
   sockaddr_storage ss;
@@ -325,12 +295,12 @@
   socklen_t addr_len = sizeof(ss);
 
   if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
-    ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno));
+    PLOG(ERROR) << "Failed getsockname(" << fd << ")";
     return false;
   }
 
   if (addr->sa_family != AF_UNIX) {
-    ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family);
+    LOG(ERROR) << "Unsupported socket (fd=" << fd << ") with family " << addr->sa_family;
     return false;
   }
 
@@ -339,13 +309,13 @@
   size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
   // This is an unnamed local socket, we do not accept it.
   if (path_len == 0) {
-    ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd);
+    LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with empty path.";
     return false;
   }
 
   // This is a local socket with an abstract address, we do not accept it.
   if (unix_addr->sun_path[0] == '\0') {
-    ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd);
+    LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with abstract address.";
     return false;
   }
 
@@ -363,17 +333,17 @@
 bool FileDescriptorInfo::DetachSocket() const {
   const int dev_null_fd = open("/dev/null", O_RDWR);
   if (dev_null_fd < 0) {
-    ALOGE("Failed to open /dev/null : %s", strerror(errno));
+    PLOG(ERROR) << "Failed to open /dev/null";
     return false;
   }
 
   if (dup2(dev_null_fd, fd) == -1) {
-    ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno));
+    PLOG(ERROR) << "Failed dup2 on socket descriptor " << fd;
     return false;
   }
 
   if (close(dev_null_fd) == -1) {
-    ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno));
+    PLOG(ERROR) << "Failed close(" << dev_null_fd << ")";
     return false;
   }
 
@@ -384,7 +354,7 @@
 FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore) {
   DIR* d = opendir(kFdPath);
   if (d == NULL) {
-    ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+    PLOG(ERROR) << "Unable to open directory " << std::string(kFdPath);
     return NULL;
   }
   int dir_fd = dirfd(d);
@@ -397,14 +367,14 @@
       continue;
     }
     if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
-      ALOGI("Ignoring open file descriptor %d", fd);
+      LOG(INFO) << "Ignoring open file descriptor " << fd;
       continue;
     }
 
     FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd);
     if (info == NULL) {
       if (closedir(d) == -1) {
-        ALOGE("Unable to close directory : %s", strerror(errno));
+        PLOG(ERROR) << "Unable to close directory";
       }
       return NULL;
     }
@@ -412,7 +382,7 @@
   }
 
   if (closedir(d) == -1) {
-    ALOGE("Unable to close directory : %s", strerror(errno));
+    PLOG(ERROR) << "Unable to close directory";
     return NULL;
   }
   return new FileDescriptorTable(open_fd_map);
@@ -424,7 +394,7 @@
   // First get the list of open descriptors.
   DIR* d = opendir(kFdPath);
   if (d == NULL) {
-    ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+    PLOG(ERROR) << "Unable to open directory " << std::string(kFdPath);
     return false;
   }
 
@@ -436,7 +406,7 @@
       continue;
     }
     if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
-      ALOGI("Ignoring open file descriptor %d", fd);
+      LOG(INFO) << "Ignoring open file descriptor " << fd;
       continue;
     }
 
@@ -444,7 +414,7 @@
   }
 
   if (closedir(d) == -1) {
-    ALOGE("Unable to close directory : %s", strerror(errno));
+    PLOG(ERROR) << "Unable to close directory";
     return false;
   }
 
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
index 03298c3..a39e387 100644
--- a/core/jni/fd_utils.h
+++ b/core/jni/fd_utils.h
@@ -59,10 +59,6 @@
  private:
   FileDescriptorWhitelist();
 
-  static bool StartsWith(const std::string& str, const std::string& prefix);
-
-  static bool EndsWith(const std::string& str, const std::string& suffix);
-
   static FileDescriptorWhitelist* instance_;
 
   std::vector<std::string> whitelist_;
@@ -99,8 +95,6 @@
   FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags,
                      int fd_flags, int fs_flags, off_t offset);
 
-  static bool Readlink(const int fd, std::string* result);
-
   // Returns the locally-bound name of the socket |fd|. Returns true
   // iff. all of the following hold :
   //
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 790b0f6..7d4f99d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1576,6 +1576,16 @@
     <permission android:name="android.permission.RECEIVE_STK_COMMANDS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by an ImsService to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature|privileged
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_IMS_SERVICE"
+        android:protectionLevel="signature|privileged" />
+
+
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
     <!-- ================================== -->
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index c4e8e9c..40c9941 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -45,7 +45,7 @@
             android:textColor="?attr/colorAccent"
             android:gravity="center_vertical"
             android:layout_alignParentTop="true"
-            android:layout_alignParentRight="true"
+            android:layout_alignParentEnd="true"
             android:singleLine="true" />
 
         <TextView
@@ -59,7 +59,7 @@
             android:paddingEnd="?attr/dialogPreferredPadding"
             android:paddingTop="8dp"
             android:layout_below="@id/profile_button"
-            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
             android:paddingBottom="8dp" />
     </RelativeLayout>
 
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index d3640e5..3ed7818 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -31,28 +31,4 @@
         <item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
     </string-array>
 
-    <string-array translatable="false" name="config_operatorConsideredNonRoaming">
-        <item>21401</item>
-        <item>21402</item>
-        <item>21403</item>
-        <item>21404</item>
-        <item>21405</item>
-        <item>21406</item>
-        <item>21407</item>
-        <item>21408</item>
-        <item>21409</item>
-        <item>21410</item>
-        <item>21411</item>
-        <item>21412</item>
-        <item>21413</item>
-        <item>21414</item>
-        <item>21415</item>
-        <item>21416</item>
-        <item>21417</item>
-        <item>21418</item>
-        <item>21419</item>
-        <item>21420</item>
-        <item>21421</item>
-    </string-array>
-
 </resources>
diff --git a/core/res/res/values-mcc214-mnc01/config.xml b/core/res/res/values-mcc214-mnc01/config.xml
index 895b770..24150a7 100644
--- a/core/res/res/values-mcc214-mnc01/config.xml
+++ b/core/res/res/values-mcc214-mnc01/config.xml
@@ -40,27 +40,4 @@
       <item>INTERNET,airtelnet.es,,,vodafone,vodafone,,,,,214,01,1,DUN</item>
     </string-array>
 
-    <string-array translatable="false" name="config_operatorConsideredNonRoaming">
-        <item>21402</item>
-        <item>21403</item>
-        <item>21404</item>
-        <item>21405</item>
-        <item>21406</item>
-        <item>21407</item>
-        <item>21408</item>
-        <item>21409</item>
-        <item>21410</item>
-        <item>21411</item>
-        <item>21412</item>
-        <item>21413</item>
-        <item>21414</item>
-        <item>21415</item>
-        <item>21416</item>
-        <item>21417</item>
-        <item>21418</item>
-        <item>21419</item>
-        <item>21420</item>
-        <item>21421</item>
-    </string-array>
-
 </resources>
diff --git a/core/res/res/values-mcc334-mnc050/config.xml b/core/res/res/values-mcc334-mnc050/config.xml
index f6777d0..616a8e8 100644
--- a/core/res/res/values-mcc334-mnc050/config.xml
+++ b/core/res/res/values-mcc334-mnc050/config.xml
@@ -40,4 +40,8 @@
       <item>Modem,modem.iusacellgsm.mx,,,iusacellgsm,iusacellgsm,,,,,334,050,1,DUN</item>
     </string-array>
 
+    <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+    <string-array translatable="false" name="config_twoDigitNumberPattern">
+        <item>"#9"</item>
+    </string-array>
 </resources>
diff --git a/core/res/res/values-mcc334-mnc090/config.xml b/core/res/res/values-mcc334-mnc090/config.xml
new file mode 100644
index 0000000..1632a42
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc090/config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+
+    <string-array translatable="false" name="config_twoDigitNumberPattern">
+        <item>"#9"</item>
+    </string-array>
+</resources>
diff --git a/core/res/res/values-mcc704-mnc01/config.xml b/core/res/res/values-mcc704-mnc01/config.xml
new file mode 100644
index 0000000..10b6470
--- /dev/null
+++ b/core/res/res/values-mcc704-mnc01/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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 my 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.
+*/
+-->
+
+<resources>
+    <!-- Do not translate. Defines the slots is Two Digit Number for dialing normally not USSD -->
+    <string-array name="config_twoDigitNumberPattern">
+        <item>"*1"</item>
+        <item>"*5"</item>
+        <item>"*9"</item>
+    </string-array>
+</resources>
diff --git a/core/res/res/values-mcc708-mnc001/config.xml b/core/res/res/values-mcc708-mnc001/config.xml
new file mode 100755
index 0000000..7b7c48d
--- /dev/null
+++ b/core/res/res/values-mcc708-mnc001/config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** 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 my 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <string-array translatable="false" name="config_twoDigitNumberPattern">
+        <item>"*1"</item>
+        <item>"*5"</item>
+    </string-array>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dbc4324..beb495de 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -236,6 +236,7 @@
         <item>"mobile,0,0,0,-1,true"</item>
         <item>"mobile_mms,2,0,2,60000,true"</item>
         <item>"mobile_supl,3,0,2,60000,true"</item>
+        <item>"mobile_dun,4,0,2,60000,true"</item>
         <item>"mobile_hipri,5,0,3,60000,true"</item>
         <item>"mobile_fota,10,0,2,60000,true"</item>
         <item>"mobile_ims,11,0,2,60000,true"</item>
@@ -461,6 +462,8 @@
     <integer translatable="false" name="config_wifi_framework_LAST_SELECTION_AWARD">480</integer>
     <integer translatable="false" name="config_wifi_framework_PASSPOINT_SECURITY_AWARD">40</integer>
     <integer translatable="false" name="config_wifi_framework_SECURITY_AWARD">80</integer>
+    <!-- Integer specifying the base interval in seconds for the exponential backoff scan for autojoin -->
+    <integer translatable="false" name="config_wifi_framework_exponential_backoff_scan_base_interval">20</integer>
     <!-- Integer parameters of the wifi to cellular handover feature
          wifi should not stick to bad networks -->
     <integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz">-82</integer>
@@ -1390,6 +1393,9 @@
     <!-- Boolean indicating if current platform supports BLE peripheral mode -->
     <bool name="config_bluetooth_le_peripheral_mode_supported">false</bool>
 
+    <!-- Boolean indicating if current platform supports HFP inband ringing -->
+    <bool name="config_bluetooth_hfp_inband_ringing_support">false</bool>
+
     <!-- Max number of scan filters supported by blutooth controller. 0 if the
          device does not support hardware scan filters-->
     <integer translatable="false" name="config_bluetooth_max_scan_filters">0</integer>
@@ -2267,6 +2273,13 @@
     <!-- Whether to use voip audio mode for ims call -->
     <bool name="config_use_voip_mode_for_ims">false</bool>
 
+    <!-- ImsService package name to bind to by default. If none is specified in an overlay, an
+         empty string is passed in -->
+    <string name="config_ims_package"/>
+
+    <!-- Flag specifying whether or not IMS will use the dynamic ImsResolver -->
+    <bool name="config_dynamic_bind_ims">false</bool>
+
     <bool name="config_networkSamplingWakesDevice">true</bool>
 
     <string-array translatable="false" name="config_cdma_home_system" />
@@ -2676,6 +2689,6 @@
     <!-- An array of packages for which notifications cannot be blocked. -->
     <string-array translatable="false" name="config_nonBlockableNotificationPackages" />
 
-    <!-- Component name of the default cell broadcast receiver -->
-    <string name="config_defaultCellBroadcastReceiverComponent" translatable="false">com.android.cellbroadcastreceiver/.PrivilegedCellBroadcastReceiver</string>
+    <!-- Package name of the default cell broadcast receiver -->
+    <string name="config_defaultCellBroadcastReceiverPkg" translatable="false">com.android.cellbroadcastreceiver</string>
 </resources>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index f07fe70..513bdd1 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -75,7 +75,7 @@
         <item>ce-RU</item> <!-- Chechen (Russia) -->
         <item>cgg-UG</item> <!-- Chiga (Uganda) -->
         <item>chr-US</item> <!-- Cherokee (United States) -->
-        <item>cs-CZ</item> <!-- Czech (Czech Republic) -->
+        <item>cs-CZ</item> <!-- Czech (Czechia) -->
         <item>cy-GB</item> <!-- Welsh (United Kingdom) -->
         <item>da-DK</item> <!-- Danish (Denmark) -->
         <item>da-GL</item> <!-- Danish (Greenland) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 426d2eb..4231698 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -101,6 +101,8 @@
     <!-- Displayed when the user dialed an MMI code whose function
          could not be performed because FDN is enabled. This will be displayed in a toast. -->
     <string name="mmiFdnError">Operation is restricted to fixed dialing numbers only.</string>
+    <!-- Displayed when a carrier does not support call forwarding queries when roaming. -->
+    <string name="mmiErrorWhileRoaming">Can not change call forwarding settings from your phone while you are roaming.</string>
 
     <!-- Displayed when a phone feature such as call barring was activated. -->
     <string name="serviceEnabled">Service was enabled.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9db131b..8f12484b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -240,6 +240,7 @@
   <java-symbol type="bool" name="config_bluetooth_address_validation" />
   <java-symbol type="bool" name="config_bluetooth_sco_off_call" />
   <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
+  <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
   <java-symbol type="bool" name="config_cellBroadcastAppLinks" />
   <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
   <java-symbol type="bool" name="config_enableAutoPowerModes" />
@@ -252,6 +253,8 @@
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
+  <java-symbol type="string" name="config_ims_package" />
+  <java-symbol type="bool" name="config_dynamic_bind_ims" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
   <java-symbol type="bool" name="config_sip_wifi_only" />
@@ -723,6 +726,7 @@
   <java-symbol type="string" name="mmiComplete" />
   <java-symbol type="string" name="mmiError" />
   <java-symbol type="string" name="mmiFdnError" />
+  <java-symbol type="string" name="mmiErrorWhileRoaming" />
   <java-symbol type="string" name="month_day_year" />
   <java-symbol type="string" name="more_item_label" />
   <java-symbol type="string" name="needPuk" />
@@ -2738,5 +2742,5 @@
 <!-- Network Recommendation -->
   <java-symbol type="array" name="config_networkRecommendationPackageNames" />
 
-  <java-symbol type="string" name="config_defaultCellBroadcastReceiverComponent" />
+  <java-symbol type="string" name="config_defaultCellBroadcastReceiverPkg" />
 </resources>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 29c6b79..1ae922a 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -79,7 +79,7 @@
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
 
-    <!-- Czech Republic: 7-8 digits, starting with 9, plus EU:
+    <!-- Czechia: 7-8 digits, starting with 9, plus EU:
          http://www.o2.cz/osobni/en/services-by-alphabet/91670-premium_sms.html -->
     <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
 
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 33a9265..d6877dd 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -21,6 +21,7 @@
 	$(call all-java-files-under, DisabledTestApp/src) \
 	$(call all-java-files-under, EnabledTestApp/src)
 
+LOCAL_DX_FLAGS := --core-library
 LOCAL_AAPT_FLAGS = -0 dat -0 gld -c fa
 LOCAL_STATIC_JAVA_LIBRARIES := \
     core-tests-support \
@@ -28,7 +29,6 @@
     frameworks-core-util-lib \
     mockwebserver \
     guava \
-    littlemock \
     android-support-test \
     mockito-target \
     espresso-core \
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index 29020ba..48f2935 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -32,10 +32,10 @@
 import android.test.InstrumentationTestCase;
 import com.android.internal.R;
 import java.util.List;
-import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.compat.ArgumentMatcher;
 
 public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
     @Mock private Context mMockContext;
@@ -218,7 +218,7 @@
         when(mMockPm.resolveService(
                 Mockito.argThat(new ArgumentMatcher<Intent>() {
                     @Override
-                    public boolean matches(Object object) {
+                    public boolean matchesObject(Object object) {
                         Intent intent = (Intent) object;
                         return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS
                                 .equals(intent.getAction())
diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
index 31560b0..bd25500 100644
--- a/core/tests/coretests/src/android/net/RecommendationRequestTest.java
+++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
@@ -3,12 +3,16 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.Parcel;
+import android.os.SystemClock;
 import android.test.AndroidTestCase;
 
 public class RecommendationRequestTest extends AndroidTestCase {
     private ScanResult[] mScanResults;
-    private WifiConfiguration mConfiguration;
-    private NetworkCapabilities mCapabilities;
+    private WifiConfiguration mDefaultConfig;
+    private WifiConfiguration mConnectedConfig;
+    private WifiConfiguration[] mConnectableConfigs;
+    private int mLastSelectedNetworkId;
+    private long mLastSelectedNetworkTimestamp;
 
     @Override
     public void setUp() throws Exception {
@@ -29,45 +33,73 @@
                 8 /*centerFreq0*/,
                 9 /*centerFreq1*/,
                 false /*is80211McRTTResponder*/);
-        mConfiguration = new WifiConfiguration();
-        mConfiguration.SSID = "RecommendationRequestTest";
-        mCapabilities = new NetworkCapabilities()
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
+        mDefaultConfig = new WifiConfiguration();
+        mDefaultConfig.SSID = "default_config";
+        mConnectedConfig = new WifiConfiguration();
+        mConnectedConfig.SSID = "connected_config";
+        mConnectableConfigs = new WifiConfiguration[] {mDefaultConfig, mConnectedConfig};
+        mLastSelectedNetworkId = 5;
+        mLastSelectedNetworkTimestamp = SystemClock.elapsedRealtime();
     }
 
     public void testParceling() throws Exception {
         RecommendationRequest request = new RecommendationRequest.Builder()
-                .setCurrentRecommendedWifiConfig(mConfiguration)
+                .setDefaultWifiConfig(mDefaultConfig)
                 .setScanResults(mScanResults)
-                .setNetworkCapabilities(mCapabilities)
+                .setConnectedWifiConfig(mConnectedConfig)
+                .setConnectableConfigs(mConnectableConfigs)
+                .setLastSelectedNetwork(mLastSelectedNetworkId, mLastSelectedNetworkTimestamp)
                 .build();
 
         RecommendationRequest parceled = passThroughParcel(request);
-        assertEquals(request.getCurrentSelectedConfig().SSID,
-                parceled.getCurrentSelectedConfig().SSID);
-        assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities());
+        assertEquals(request.getDefaultWifiConfig().SSID,
+                parceled.getDefaultWifiConfig().SSID);
+        assertEquals(request.getConnectedConfig().SSID,
+                parceled.getConnectedConfig().SSID);
         ScanResult[] parceledScanResults = parceled.getScanResults();
         assertNotNull(parceledScanResults);
         assertEquals(mScanResults.length, parceledScanResults.length);
         for (int i = 0; i < mScanResults.length; i++) {
             assertEquals(mScanResults[i].SSID, parceledScanResults[i].SSID);
         }
+        WifiConfiguration[] parceledConfigs = parceled.getConnectableConfigs();
+        for (int i = 0; i < parceledConfigs.length; i++) {
+            assertEquals(mConnectableConfigs[i].SSID, parceledConfigs[i].SSID);
+        }
+        assertEquals(mLastSelectedNetworkId, parceled.getLastSelectedNetworkId());
+        assertEquals(mLastSelectedNetworkTimestamp, parceled.getLastSelectedNetworkTimestamp());
     }
 
     public void testParceling_nullScanResults() throws Exception {
         RecommendationRequest request = new RecommendationRequest.Builder()
-                .setCurrentRecommendedWifiConfig(mConfiguration)
-                .setNetworkCapabilities(mCapabilities)
+                .setDefaultWifiConfig(mDefaultConfig)
                 .build();
 
         RecommendationRequest parceled = passThroughParcel(request);
-        assertEquals(request.getCurrentSelectedConfig().SSID,
-                parceled.getCurrentSelectedConfig().SSID);
-        assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities());
         ScanResult[] parceledScanResults = parceled.getScanResults();
         assertNull(parceledScanResults);
     }
 
+    public void testParceling_nullWifiConfigArray() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .setDefaultWifiConfig(mDefaultConfig)
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+        WifiConfiguration[] parceledConfigs = parceled.getConnectableConfigs();
+        assertNull(parceledConfigs);
+    }
+
+    public void testParceling_unsetLastSelectedNetwork() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+
+        assertEquals(0, parceled.getLastSelectedNetworkId());
+        assertEquals(0, parceled.getLastSelectedNetworkTimestamp());
+    }
+
     private RecommendationRequest passThroughParcel(RecommendationRequest request) {
         Parcel p = Parcel.obtain();
         RecommendationRequest output = null;
diff --git a/core/tests/coretests/src/android/net/SSLSessionCacheTest.java b/core/tests/coretests/src/android/net/SSLSessionCacheTest.java
index be19303..ec130e0 100644
--- a/core/tests/coretests/src/android/net/SSLSessionCacheTest.java
+++ b/core/tests/coretests/src/android/net/SSLSessionCacheTest.java
@@ -19,7 +19,7 @@
 import com.android.org.conscrypt.ClientSessionContext;
 import com.android.org.conscrypt.SSLClientSessionCache;
 
-import com.google.testing.littlemock.LittleMock;
+import org.mockito.Mockito;
 
 import junit.framework.TestCase;
 
@@ -39,25 +39,25 @@
 
     public void testInstall_compatibleContext() throws Exception {
         final SSLContext ctx = SSLContext.getDefault();
-        final SSLClientSessionCache mock = LittleMock.mock(SSLClientSessionCache.class);
+        final SSLClientSessionCache mock = Mockito.mock(SSLClientSessionCache.class);
         final ClientSessionContext clientCtx = (ClientSessionContext) ctx.getClientSessionContext();
 
         try {
             SSLSessionCache.install(new SSLSessionCache(mock), ctx);
             clientCtx.getSession("www.foogle.com", 443);
-            LittleMock.verify(mock).getSessionData(LittleMock.anyString(), LittleMock.anyInt());
+            Mockito.verify(mock).getSessionData(Mockito.anyString(), Mockito.anyInt());
         } finally {
             // Restore cacheless behaviour.
             SSLSessionCache.install(null, ctx);
             clientCtx.getSession("www.foogle.com", 443);
-            LittleMock.verifyNoMoreInteractions(mock);
+            Mockito.verifyNoMoreInteractions(mock);
         }
     }
 
     public void testInstall_incompatibleContext() {
         try {
             SSLSessionCache.install(
-                    new SSLSessionCache(LittleMock.mock(SSLClientSessionCache.class)),
+                    new SSLSessionCache(Mockito.mock(SSLClientSessionCache.class)),
                     new FakeSSLContext());
             fail();
         } catch (IllegalArgumentException expected) {}
@@ -102,7 +102,7 @@
 
         @Override
         protected SSLSessionContext engineGetClientSessionContext() {
-            return LittleMock.mock(SSLSessionContext.class);
+            return Mockito.mock(SSLSessionContext.class);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/view/PopupWindowVisibility.java b/core/tests/coretests/src/android/view/PopupWindowVisibility.java
index 7eb0468..6e11ede 100644
--- a/core/tests/coretests/src/android/view/PopupWindowVisibility.java
+++ b/core/tests/coretests/src/android/view/PopupWindowVisibility.java
@@ -82,7 +82,7 @@
         "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
         "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
         "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-        "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+        "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
         "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
         "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
         "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
index f2eba23..cdfa217 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
@@ -16,8 +16,8 @@
 
 package android.widget.focus;
 
-import static com.google.testing.littlemock.LittleMock.inOrder;
-import static com.google.testing.littlemock.LittleMock.mock;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 
 import android.os.Handler;
 import android.test.ActivityInstrumentationTestCase2;
@@ -31,7 +31,7 @@
 import android.widget.Button;
 
 import com.android.frameworks.coretests.R;
-import com.google.testing.littlemock.LittleMock.InOrder;
+import org.mockito.InOrder;
 
 /**
  * {@link RequestFocusTest} is set up to exercise cases where the views that
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
index 836ede6..14b032e 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyAndException/Android.mk
@@ -31,9 +31,28 @@
 
 LOCAL_JAVACFLAGS := -nowarn
 
+mainDexList:= \
+    $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
     -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
 
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
 
 include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+	echo "com/android/multidexlegacyandexception/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
index 2915914..208eceb 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
@@ -29,13 +29,32 @@
 
 LOCAL_DEX_PREOPT := false
 
+mainDexList:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
     -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
 
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
 
 include $(BUILD_PACKAGE)
 
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+	echo "com/android/multidexlegacytestapp/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
+
 ## The application with a full main dex
 include $(CLEAR_VARS)
 
@@ -51,9 +70,28 @@
 
 LOCAL_DEX_PREOPT := false
 
+mainDexList2:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList2)
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=multidex -D jack.preprocessor=true\
     -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
 
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
 
 include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList2): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+	echo "com/android/multidexlegacytestapp/Test.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList2)
+endif
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
index 2732372..99bcd6c 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
@@ -26,8 +26,20 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
 
+mainDexList:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.dex.output.multidex.legacy=true
 
 LOCAL_DEX_PREOPT := false
 
 include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
index b4a666f..1c7d807 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
@@ -28,9 +28,28 @@
 
 LOCAL_DEX_PREOPT := false
 
+mainDexList:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
     -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
 
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
 
 include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
index f38bd4f..b77cf31 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
@@ -28,9 +28,28 @@
 
 LOCAL_DEX_PREOPT := false
 
+mainDexList:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
     -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
 
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
 
 include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
index 5bc2c95..3631626 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
@@ -26,11 +26,31 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-multidex
 
+mainDexList:= \
+	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
+
 LOCAL_DEX_PREOPT := false
 
+LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
 LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
     -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
 
+#################################
+include $(BUILD_SYSTEM)/configure_local_jack.mk
+#################################
+
+ifdef LOCAL_JACK_ENABLED
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
+endif
 
 include $(BUILD_PACKAGE)
+
+ifndef LOCAL_JACK_ENABLED
+$(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
+	$(hide) mkdir -p $(dir $@)
+	$(MAINDEXCLASSES) $< 1>$@
+	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
+
+$(built_dex_intermediate): $(mainDexList)
+endif
+
diff --git a/core/tests/systemproperties/Android.mk b/core/tests/systemproperties/Android.mk
index e16c367..4c2e224 100644
--- a/core/tests/systemproperties/Android.mk
+++ b/core/tests/systemproperties/Android.mk
@@ -8,6 +8,7 @@
 LOCAL_SRC_FILES := \
 	$(call all-java-files-under, src)
 
+LOCAL_DX_FLAGS := --core-library
 LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCoreSystemPropertiesTests
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 0a814f3..05fec5e 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -50,5 +50,5 @@
 LOCAL_SRC_FILES := src/android/test/PerformanceTestCase.java
 LOCAL_MODULE := legacy-performance-test-hostdex
 
-include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
+include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
 endif  # HOST_OS == linux
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index a7cbf5e..bf9423c 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -167,7 +167,7 @@
 ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
     hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT
     hwui_c_includes += \
-        $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) \
+        $(call intermediates-dir-for,STATIC_LIBRARIES,TARGET,) \
         frameworks/rs/cpp \
         frameworks/rs
 endif
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
index dca78b3..8dae273 100644
--- a/libs/hwui/hwui_static_deps.mk
+++ b/libs/hwui/hwui_static_deps.mk
@@ -28,5 +28,5 @@
     libandroidfw
 
 ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
-    LOCAL_SHARED_LIBRARIES += libRS libRScpp
+    LOCAL_SHARED_LIBRARIES += libRScpp
 endif
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a734401..ce13ebc 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -685,7 +685,9 @@
     SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
     AutoMutex _lock(mSyncMutex);
     mRenderThread.queue(&syncTask);
-    mSyncCondition.wait(mSyncMutex);
+    while (!syncTask.hasRun()) {
+        mSyncCondition.wait(mSyncMutex);
+    }
     return retval;
 }
 
diff --git a/libs/hwui/renderthread/RenderTask.cpp b/libs/hwui/renderthread/RenderTask.cpp
index b14f580..928a4ef 100644
--- a/libs/hwui/renderthread/RenderTask.cpp
+++ b/libs/hwui/renderthread/RenderTask.cpp
@@ -26,6 +26,7 @@
 void SignalingRenderTask::run() {
     mTask->run();
     mLock->lock();
+    mHasRun = true;
     mSignal->signal();
     mLock->unlock();
 }
diff --git a/libs/hwui/renderthread/RenderTask.h b/libs/hwui/renderthread/RenderTask.h
index 9ea671b..a7acf91 100644
--- a/libs/hwui/renderthread/RenderTask.h
+++ b/libs/hwui/renderthread/RenderTask.h
@@ -60,13 +60,15 @@
 public:
     // Takes ownership of task, caller owns lock and signal
     SignalingRenderTask(RenderTask* task, Mutex* lock, Condition* signal)
-            : mTask(task), mLock(lock), mSignal(signal) {}
+            : mTask(task), mLock(lock), mSignal(signal), mHasRun(false) {}
     virtual void run() override;
+    bool hasRun() const { return mHasRun; }
 
 private:
     RenderTask* mTask;
     Mutex* mLock;
     Condition* mSignal;
+    bool mHasRun;
 };
 
 typedef void* (*RunnableMethod)(void* data);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 9c10c4f..f383adc 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -345,7 +345,9 @@
 
     AutoMutex _lock(mutex);
     queue(&syncTask);
-    condition.wait(mutex);
+    while (!syncTask.hasRun()) {
+        condition.wait(mutex);
+    }
 }
 
 void RenderThread::queueAtFront(RenderTask* task) {
diff --git a/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
new file mode 100644
index 0000000..a438afd
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/ShadowShaderAnimation.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 "TestSceneBase.h"
+
+class ShadowShaderAnimation;
+
+static TestScene::Registrar _ShadowShader(TestScene::Info{
+    "shadowshader",
+    "A set of overlapping shadowed areas with simple tessellation useful for"
+    " benchmarking shadow shader performance.",
+    TestScene::simpleCreateScene<ShadowShaderAnimation>
+});
+
+class ShadowShaderAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        int outset = 50;
+        for (int i = 0; i < 10; i++) {
+            sp<RenderNode> card = createCard(outset, outset,
+                    width - (outset * 2), height - (outset * 2));
+            canvas.drawRenderNode(card.get());
+            cards.push_back(card);
+        }
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 10;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(1000);
+
+            // Set 0 radius, no clipping, so shadow is easy to compute. Slightly transparent outline
+            // to signal contents aren't opaque (not necessary though, as elevation is so high, no
+            // inner content to cut out)
+            props.mutableOutline().setRoundRect(0, 0, width, height, 0, 0.99f);
+            props.mutableOutline().setShouldClip(false);
+
+            // don't draw anything to card's canvas - we just want the shadow
+        });
+    }
+};
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 6c879b9..a519f74 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -45,8 +45,8 @@
 
 import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
 
-import org.mockito.ArgumentMatcher;
 import org.mockito.ArgumentCaptor;
+import org.mockito.compat.ArgumentMatcher;
 import static org.mockito.Mockito.*;
 
 public class CameraDeviceBinderTest extends AndroidTestCase {
@@ -158,7 +158,7 @@
 
     class IsMetadataNotEmpty extends ArgumentMatcher<CameraMetadataNative> {
         @Override
-        public boolean matches(Object obj) {
+        public boolean matchesObject(Object obj) {
             return !((CameraMetadataNative) obj).isEmpty();
         }
     }
diff --git a/native/android/Android.mk b/native/android/Android.mk
index da4e4ba..1f69df1 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -1,6 +1,8 @@
 BASE_PATH := $(call my-dir)
 LOCAL_PATH:= $(call my-dir)
 
+common_cflags := -Wall -Werror -Wunused -Wunreachable-code
+
 include $(CLEAR_VARS)
 
 # our source files
@@ -42,6 +44,23 @@
 
 LOCAL_MODULE := libandroid
 
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+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/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java
index acee5dd..3831cf7 100644
--- a/obex/javax/obex/ServerSession.java
+++ b/obex/javax/obex/ServerSession.java
@@ -658,6 +658,11 @@
          */
         byte[] sendData = new byte[totalLength];
         int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport);
+        if (maxRxLength > mMaxPacketLength) {
+            if(V) Log.v(TAG,"Set maxRxLength to min of maxRxServrLen:" + maxRxLength +
+                    " and MaxNegotiated from Client: " + mMaxPacketLength);
+            maxRxLength = mMaxPacketLength;
+        }
         sendData[0] = (byte)code;
         sendData[1] = length[2];
         sendData[2] = length[3];
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index 23a8655..6394c64 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -77,8 +77,8 @@
         mCm = ConnectivityManager.from(this);
         mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
         mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
-        mUserAgent = getIntent().getParcelableExtra(
-                ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT);
+        mUserAgent =
+                getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT);
         mUrl = getUrl();
         if (mUrl == null) {
             // getUrl() failed to parse the url provided in the intent: bail out in a way that
@@ -274,8 +274,17 @@
                     if (mUserAgent != null) {
                        urlConnection.setRequestProperty("User-Agent", mUserAgent);
                     }
+                    // cannot read request header after connection
+                    String requestHeader = urlConnection.getRequestProperties().toString();
+
                     urlConnection.getInputStream();
                     httpResponseCode = urlConnection.getResponseCode();
+                    if (DBG) {
+                        Log.d(TAG, "probe at " + mUrl +
+                                " ret=" + httpResponseCode +
+                                " request=" + requestHeader +
+                                " headers=" + urlConnection.getHeaderFields());
+                    }
                 } catch (IOException e) {
                 } finally {
                     if (urlConnection != null) urlConnection.disconnect();
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 28d9e5c..8df194c 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -17,25 +17,32 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.carrierdefaultapp"
-    android:sharedUserId="android.uid.phone" >
+    package="com.android.carrierdefaultapp">
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
 
     <application android:label="@string/app_name" >
         <receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
             <intent-filter>
-                <action android:name="android.intent.action.CARRIER_SIGNAL_REDIRECTED" />
+                <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
             </intent-filter>
         </receiver>
-        <activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
-            android:excludeFromRecents="true"/>
+        <service android:name="com.android.carrierdefaultapp.ProvisionObserver"
+                 android:permission="android.permission.BIND_JOB_SERVICE"/>
+        <activity
+            android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
+            android:label="@string/action_bar_label"
+            android:theme="@style/AppTheme"
+            android:configChanges="keyboardHidden|orientation|screenSize" >
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png
new file mode 100644
index 0000000..08294ce
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png
Binary files differ
diff --git a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
index 5896757..75aa405 100644
--- a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
+++ b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
@@ -14,13 +14,12 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/glif_icon_size"
-    android:height="@dimen/glif_icon_size"
-    android:viewportWidth="48"
-    android:viewportHeight="48">
-    <path
-        android:fillColor="?android:attr/colorPrimary"
-        android:pathData="M39.98,8c0,-2.21 -1.77,-4 -3.98,-4L20,4L8,16v24c0,2.21 1.79,4 4,4h24.02c2.21,0 3.98,-1.79 3.98,-4l-0.02,-32zM18,38h-4v-4h4v4zM34,38h-4v-4h4v4zM18,30h-4v-8h4v8zM26,38h-4v-8h4v8zM26,26h-4v-4h4v4zM34,30h-4v-8h4v8z" />
-</vector>
\ No newline at end of file
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+<path
+    android:fillColor="#757575"
+    android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/>
+</vector>
diff --git a/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml
new file mode 100644
index 0000000..528576b
--- /dev/null
+++ b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml
@@ -0,0 +1,34 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.android.carrierdefaultapp.CaptivePortalLoginActivity"
+    tools:ignore="MergeRootFrame">
+    <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/url_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="20sp"
+        android:singleLine="true" />
+
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="?android:attr/progressBarStyleHorizontal" />
+
+    <WebView
+        android:id="@+id/webview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentBottom="false"
+        android:layout_alignParentRight="false" />
+
+</LinearLayout>
+</FrameLayout>
diff --git a/packages/CarrierDefaultApp/res/values/dimens.xml b/packages/CarrierDefaultApp/res/values/dimens.xml
index a3c5049..1ea8c35 100644
--- a/packages/CarrierDefaultApp/res/values/dimens.xml
+++ b/packages/CarrierDefaultApp/res/values/dimens.xml
@@ -1,3 +1,6 @@
 <resources>
     <dimen name="glif_icon_size">32dp</dimen>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml
index 838ff39..f9342b7 100644
--- a/packages/CarrierDefaultApp/res/values/strings.xml
+++ b/packages/CarrierDefaultApp/res/values/strings.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="app_name">CarrierDefaultApp</string>
-    <string name="portal_notification_id">Activate your service</string>
-    <string name="no_data_notification_id">No data service</string>
-    <string name="portal_notification_detail">Tap to activate your service</string>
-    <string name="no_data_notification_detail">No Service, please contact your service provider</string>
-    <string name="progress_dialogue_network_connection">Connecting to captive portal...</string>
-    <string name="alert_dialogue_network_timeout">Network timeout, would you like to retry?</string>
-    <string name="alert_dialogue_network_timeout_title">Network unavailable</string>
-    <string name="quit">Quit</string>
-    <string name="wait">Wait</string>
+    <string name="android_system_label">Mobile Carrier</string>
+    <string name="portal_notification_id">Mobile data has run out</string>
+    <string name="no_data_notification_id">Your mobile data has been deactivated</string>
+    <string name="portal_notification_detail">Tap to visit the %s website</string>
+    <string name="no_data_notification_detail">Please contact your service provider %s</string>
+    <string name="action_bar_label">Sign in to mobile network</string>
+    <string name="ssl_error_warning">The network you&#8217;re trying to join has security issues.</string>
+    <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string>
+    <string name="ssl_error_continue">Continue anyway via browser</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values/styles.xml b/packages/CarrierDefaultApp/res/values/styles.xml
index 3d26915..939c1aa 100644
--- a/packages/CarrierDefaultApp/res/values/styles.xml
+++ b/packages/CarrierDefaultApp/res/values/styles.xml
@@ -1,3 +1,16 @@
 <resources>
-    <style name="AlertDialog" parent="android:Theme.Material.Light.Dialog.Alert"/>
+    <style name="AppBaseTheme" parent="@android:style/Theme.Material.Settings">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+        <!-- Setting's theme's accent color makes ProgressBar useless, reset back. -->
+        <item name="android:colorAccent">@*android:color/material_deep_teal_500</item>
+    </style>
 </resources>
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java
deleted file mode 100644
index b7fde12..0000000
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.carrierdefaultapp;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.net.CaptivePortal;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
-import android.os.Bundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
-import android.text.TextUtils;
-import android.net.ICaptivePortal;
-import android.view.ContextThemeWrapper;
-import android.view.WindowManager;
-import com.android.carrierdefaultapp.R;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.ArrayUtils;
-
-import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
-
-/**
- * Activity that launches in response to the captive portal notification
- * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
- * This activity requests network connection if there is no available one, launches the
- * {@link com.android.captiveportallogin portalApp} and keeps track of the portal activation result.
- */
-public class CaptivePortalLaunchActivity extends Activity {
-    private static final String TAG = CaptivePortalLaunchActivity.class.getSimpleName();
-    private static final boolean DBG = true;
-    public static final int NETWORK_REQUEST_TIMEOUT_IN_MS = 5 * 1000;
-
-    private ConnectivityManager mCm = null;
-    private ConnectivityManager.NetworkCallback mCb = null;
-    /* Progress dialogue when request network connection for captive portal */
-    private AlertDialog mProgressDialog = null;
-    /* Alert dialogue when network request is timeout */
-    private AlertDialog mAlertDialog = null;
-
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mCm = ConnectivityManager.from(this);
-        // Check network connection before loading portal
-        Network network = getNetworkForCaptivePortal();
-        NetworkInfo nwInfo = mCm.getNetworkInfo(network);
-        if (nwInfo == null || !nwInfo.isConnected()) {
-            if (DBG) logd("Network unavailable, request restricted connection");
-            requestNetwork(getIntent());
-        } else {
-            launchCaptivePortal(getIntent(), network);
-        }
-    }
-
-    // show progress dialog during network connecting
-    private void showConnectingProgressDialog() {
-        mProgressDialog = new ProgressDialog(getApplicationContext());
-        mProgressDialog.setMessage(getString(R.string.progress_dialogue_network_connection));
-        mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
-        mProgressDialog.show();
-    }
-
-    // if network request is timeout, show alert dialog with two option: cancel & wait
-    private void showConnectionTimeoutAlertDialog() {
-        mAlertDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialog))
-                .setMessage(getString(R.string.alert_dialogue_network_timeout))
-                .setTitle(getString(R.string.alert_dialogue_network_timeout_title))
-                .setNegativeButton(getString(R.string.quit),
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                // cancel
-                                dismissDialog(mAlertDialog);
-                                finish();
-                            }
-                        })
-                .setPositiveButton(getString(R.string.wait),
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                // wait, request network again
-                                dismissDialog(mAlertDialog);
-                                requestNetwork(getIntent());
-                            }
-                        })
-                .create();
-        mAlertDialog.show();
-    }
-
-    private void requestNetwork(final Intent intent) {
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .build();
-
-        mCb = new ConnectivityManager.NetworkCallback() {
-            @Override
-            public void onAvailable(Network network) {
-                if (DBG) logd("Network available: " + network);
-                dismissDialog(mProgressDialog);
-                mCm.bindProcessToNetwork(network);
-                launchCaptivePortal(intent, network);
-            }
-
-            @Override
-            public void onUnavailable() {
-                if (DBG) logd("Network unavailable");
-                dismissDialog(mProgressDialog);
-                showConnectionTimeoutAlertDialog();
-            }
-        };
-        showConnectingProgressDialog();
-        mCm.requestNetwork(request, mCb, NETWORK_REQUEST_TIMEOUT_IN_MS);
-    }
-
-    private void releaseNetworkRequest() {
-        logd("release Network Request");
-        if (mCb != null) {
-            mCm.unregisterNetworkCallback(mCb);
-            mCb = null;
-        }
-    }
-
-    private void dismissDialog(AlertDialog dialog) {
-        if (dialog != null) {
-            dialog.dismiss();
-        }
-    }
-
-    private Network getNetworkForCaptivePortal() {
-        Network[] info = mCm.getAllNetworks();
-        if (!ArrayUtils.isEmpty(info)) {
-            for (Network nw : info) {
-                final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw);
-                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                    return nw;
-                }
-            }
-        }
-        return null;
-    }
-
-    private void launchCaptivePortal(final Intent intent, Network network) {
-        String redirectUrl = intent.getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
-                SubscriptionManager.getDefaultVoiceSubscriptionId());
-        if (TextUtils.isEmpty(redirectUrl) || !matchUrl(redirectUrl, subId)) {
-            loge("Launch portal fails due to incorrect redirection URL: " +
-                    Rlog.pii(TAG, redirectUrl));
-            return;
-        }
-        final Intent portalIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
-        portalIntent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
-        portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
-                new CaptivePortal(new ICaptivePortal.Stub() {
-                    @Override
-                    public void appResponse(int response) {
-                        logd("portal response code: " + response);
-                        releaseNetworkRequest();
-                        if (response == APP_RETURN_DISMISSED) {
-                            // Upon success http response code, trigger re-evaluation
-                            CarrierActionUtils.applyCarrierAction(
-                                    CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, intent,
-                                    getApplicationContext());
-                            CarrierActionUtils.applyCarrierAction(
-                                    CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, intent,
-                                    getApplicationContext());
-                            CarrierActionUtils.applyCarrierAction(
-                                    CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS,
-                                    intent, getApplicationContext());
-                        }
-                    }
-                }));
-        portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, redirectUrl);
-        portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK
-                        | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        if (DBG) logd("launching portal");
-        startActivity(portalIntent);
-        finish();
-    }
-
-    // match configured redirection url
-    private boolean matchUrl(String url, int subId) {
-        CarrierConfigManager configManager = getApplicationContext()
-                .getSystemService(CarrierConfigManager.class);
-        String[] redirectURLs = configManager.getConfigForSubId(subId).getStringArray(
-                CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
-        if (ArrayUtils.isEmpty(redirectURLs)) {
-            if (DBG) logd("match is unnecessary without any configured redirection url");
-            return true;
-        }
-        for (String redirectURL : redirectURLs) {
-            if (url.startsWith(redirectURL)) {
-                return true;
-            }
-        }
-        if (DBG) loge("no match found for configured redirection url");
-        return false;
-    }
-
-    private static void logd(String s) {
-        Rlog.d(TAG, s);
-    }
-
-    private static void loge(String s) {
-        Rlog.d(TAG, s);
-    }
-}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
new file mode 100644
index 0000000..ec4c00e
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -0,0 +1,433 @@
+/*
+ * 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.carrierdefaultapp;
+
+import android.app.Activity;
+import android.app.LoadedApk;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.Proxy;
+import android.net.TrafficStats;
+import android.net.Uri;
+import android.net.http.SslError;
+import android.os.Bundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.TypedValue;
+import android.webkit.SslErrorHandler;
+import android.webkit.WebChromeClient;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Random;
+
+/**
+ * Activity that launches in response to the captive portal notification
+ * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
+ * This activity requests network connection if there is no available one before loading the real
+ * portal page and apply carrier actions on the portal activation result.
+ */
+public class CaptivePortalLoginActivity extends Activity {
+    private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName();
+    private static final boolean DBG = true;
+
+    private static final int SOCKET_TIMEOUT_MS = 10 * 1000;
+    private static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000;
+
+    private URL mUrl;
+    private Network mNetwork;
+    private NetworkCallback mNetworkCallback;
+    private ConnectivityManager mCm;
+    private WebView mWebView;
+    private MyWebViewClient mWebViewClient;
+    private boolean mLaunchBrowser = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mCm = ConnectivityManager.from(this);
+        mUrl = getUrlForCaptivePortal();
+        if (mUrl == null) {
+            done(false);
+            return;
+        }
+        if (DBG) logd(String.format("onCreate for %s", mUrl.toString()));
+        setContentView(R.layout.activity_captive_portal_login);
+        getActionBar().setDisplayShowHomeEnabled(false);
+
+        mWebView = (WebView) findViewById(R.id.webview);
+        mWebView.clearCache(true);
+        WebSettings webSettings = mWebView.getSettings();
+        webSettings.setJavaScriptEnabled(true);
+        webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
+        mWebViewClient = new MyWebViewClient();
+        mWebView.setWebViewClient(mWebViewClient);
+        mWebView.setWebChromeClient(new MyWebChromeClient());
+
+        mNetwork = getNetworkForCaptivePortal();
+        if (mNetwork == null) {
+            requestNetworkForCaptivePortal();
+        } else {
+            mCm.bindProcessToNetwork(mNetwork);
+            // Start initial page load so WebView finishes loading proxy settings.
+            // Actual load of mUrl is initiated by MyWebViewClient.
+            mWebView.loadData("", "text/html", null);
+        }
+    }
+
+    @Override
+    public void onBackPressed() {
+        WebView myWebView = (WebView) findViewById(R.id.webview);
+        if (myWebView.canGoBack() && mWebViewClient.allowBack()) {
+            myWebView.goBack();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        releaseNetworkRequest();
+        if (mLaunchBrowser) {
+            // Give time for this network to become default. After 500ms just proceed.
+            for (int i = 0; i < 5; i++) {
+                // TODO: This misses when mNetwork underlies a VPN.
+                if (mNetwork.equals(mCm.getActiveNetwork())) break;
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            final String url = mUrl.toString();
+            if (DBG) logd("starting activity with intent ACTION_VIEW for " + url);
+            startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+        }
+    }
+
+    // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties.
+    private void setWebViewProxy() {
+        LoadedApk loadedApk = getApplication().mLoadedApk;
+        try {
+            Field receiversField = LoadedApk.class.getDeclaredField("mReceivers");
+            receiversField.setAccessible(true);
+            ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
+            for (Object receiverMap : receivers.values()) {
+                for (Object rec : ((ArrayMap) receiverMap).keySet()) {
+                    Class clazz = rec.getClass();
+                    if (clazz.getName().contains("ProxyChangeListener")) {
+                        Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class,
+                                Intent.class);
+                        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+                        onReceiveMethod.invoke(rec, getApplicationContext(), intent);
+                        Log.v(TAG, "Prompting WebView proxy reload.");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            loge("Exception while setting WebView proxy: " + e);
+        }
+    }
+
+    private void done(boolean success) {
+        if (DBG) logd(String.format("Result success %b for %s", success, mUrl.toString()));
+        if (success) {
+            // Trigger re-evaluation upon success http response code
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(),
+                    getApplicationContext());
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(),
+                    getApplicationContext());
+            CarrierActionUtils.applyCarrierAction(
+                    CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(),
+                    getApplicationContext());
+
+        }
+        finishAndRemoveTask();
+    }
+
+    private URL getUrlForCaptivePortal() {
+        String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+        if (url.isEmpty()) {
+            url = mCm.getCaptivePortalServerUrl();
+        }
+        final CarrierConfigManager configManager = getApplicationContext()
+                .getSystemService(CarrierConfigManager.class);
+        final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+                SubscriptionManager.getDefaultVoiceSubscriptionId());
+        final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray(
+                CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
+        if (!ArrayUtils.isEmpty(portalURLs)) {
+            for (String portalUrl : portalURLs) {
+                if (url.startsWith(portalUrl)) {
+                    break;
+                }
+            }
+            url = null;
+        }
+        try {
+            return new URL(url);
+        } catch (MalformedURLException e) {
+            loge("Invalid captive portal URL " + url);
+        }
+        return null;
+    }
+
+    private void testForCaptivePortal() {
+        new Thread(new Runnable() {
+            public void run() {
+                // Give time for captive portal to open.
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                }
+                HttpURLConnection urlConnection = null;
+                int httpResponseCode = 500;
+                TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+                try {
+                    urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl);
+                    urlConnection.setInstanceFollowRedirects(false);
+                    urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+                    urlConnection.setUseCaches(false);
+                    urlConnection.getInputStream();
+                    httpResponseCode = urlConnection.getResponseCode();
+                } catch (IOException e) {
+                } finally {
+                    if (urlConnection != null) urlConnection.disconnect();
+                }
+                if (httpResponseCode == 204) {
+                    done(true);
+                }
+            }
+        }).start();
+    }
+
+    private Network getNetworkForCaptivePortal() {
+        Network[] info = mCm.getAllNetworks();
+        if (!ArrayUtils.isEmpty(info)) {
+            for (Network nw : info) {
+                final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw);
+                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                    return nw;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void requestNetworkForCaptivePortal() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+
+        mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+            @Override
+            public void onAvailable(Network network) {
+                if (DBG) logd("Network available: " + network);
+                mCm.bindProcessToNetwork(network);
+                mNetwork = network;
+                runOnUiThreadIfNotFinishing(() -> {
+                    // Start initial page load so WebView finishes loading proxy settings.
+                    // Actual load of mUrl is initiated by MyWebViewClient.
+                    mWebView.loadData("", "text/html", null);
+                });
+            }
+
+            @Override
+            public void onUnavailable() {
+                if (DBG) logd("Network unavailable");
+                runOnUiThreadIfNotFinishing(() -> {
+                    // Instead of not loading anything in webview, simply load the page and return
+                    // HTTP error page in the absence of network connection.
+                    mWebView.loadUrl(mUrl.toString());
+                });
+            }
+        };
+        logd("request Network for captive portal");
+        mCm.requestNetwork(request, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MS);
+    }
+
+    private void releaseNetworkRequest() {
+        logd("release Network for captive portal");
+        if (mNetworkCallback != null) {
+            mCm.unregisterNetworkCallback(mNetworkCallback);
+            mNetworkCallback = null;
+            mNetwork = null;
+        }
+    }
+
+    private class MyWebViewClient extends WebViewClient {
+        private static final String INTERNAL_ASSETS = "file:///android_asset/";
+        private final String mBrowserBailOutToken = Long.toString(new Random().nextLong());
+        // How many Android device-independent-pixels per scaled-pixel
+        // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp)
+        private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
+                    getResources().getDisplayMetrics())
+                / TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
+                    getResources().getDisplayMetrics());
+        private int mPagesLoaded;
+
+        // If we haven't finished cleaning up the history, don't allow going back.
+        public boolean allowBack() {
+            return mPagesLoaded > 1;
+        }
+
+        @Override
+        public void onPageStarted(WebView view, String url, Bitmap favicon) {
+            if (url.contains(mBrowserBailOutToken)) {
+                mLaunchBrowser = true;
+                done(false);
+                return;
+            }
+            // The first page load is used only to cause the WebView to
+            // fetch the proxy settings.  Don't update the URL bar, and
+            // don't check if the captive portal is still there.
+            if (mPagesLoaded == 0) return;
+            // For internally generated pages, leave URL bar listing prior URL as this is the URL
+            // the page refers to.
+            if (!url.startsWith(INTERNAL_ASSETS)) {
+                final TextView myUrlBar = (TextView) findViewById(R.id.url_bar);
+                myUrlBar.setText(url);
+            }
+            if (mNetwork != null) {
+                testForCaptivePortal();
+            }
+        }
+
+        @Override
+        public void onPageFinished(WebView view, String url) {
+            mPagesLoaded++;
+            if (mPagesLoaded == 1) {
+                // Now that WebView has loaded at least one page we know it has read in the proxy
+                // settings.  Now prompt the WebView read the Network-specific proxy settings.
+                setWebViewProxy();
+                // Load the real page.
+                view.loadUrl(mUrl.toString());
+                return;
+            } else if (mPagesLoaded == 2) {
+                // Prevent going back to empty first page.
+                view.clearHistory();
+            }
+            if (mNetwork != null) {
+                testForCaptivePortal();
+            }
+        }
+
+        // Convert Android device-independent-pixels (dp) to HTML size.
+        private String dp(int dp) {
+            // HTML px's are scaled just like dp's, so just add "px" suffix.
+            return Integer.toString(dp) + "px";
+        }
+
+        // Convert Android scaled-pixels (sp) to HTML size.
+        private String sp(int sp) {
+            // Convert sp to dp's.
+            float dp = sp * mDpPerSp;
+            // Apply a scale factor to make things look right.
+            dp *= 1.3;
+            // Convert dp's to HTML size.
+            return dp((int) dp);
+        }
+
+        // A web page consisting of a large broken lock icon to indicate SSL failure.
+        private final String SSL_ERROR_HTML = "<html><head><style>"
+                + "body { margin-left:" + dp(48) + "; margin-right:" + dp(48) + "; "
+                + "margin-top:" + dp(96) + "; background-color:#fafafa; }"
+                + "img { width:" + dp(48) + "; height:" + dp(48) + "; }"
+                + "div.warn { font-size:" + sp(16) + "; margin-top:" + dp(16) + "; "
+                + "           opacity:0.87; line-height:1.28; }"
+                + "div.example { font-size:" + sp(14) + "; margin-top:" + dp(16) + "; "
+                + "              opacity:0.54; line-height:1.21905; }"
+                + "a { font-size:" + sp(14) + "; text-decoration:none; text-transform:uppercase; "
+                + "    margin-top:" + dp(24) + "; display:inline-block; color:#4285F4; "
+                + "    height:" + dp(48) + "; font-weight:bold; }"
+                + "</style></head><body><p><img src=quantum_ic_warning_amber_96.png><br>"
+                + "<div class=warn>%s</div>"
+                + "<div class=example>%s</div>" + "<a href=%s>%s</a></body></html>";
+
+        @Override
+        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
+            Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: "
+                    // Only show host to avoid leaking private info.
+                    + Uri.parse(error.getUrl()).getHost() + " certificate: "
+                    + error.getCertificate() + "); displaying SSL warning.");
+            final String html = String.format(SSL_ERROR_HTML, getString(R.string.ssl_error_warning),
+                    getString(R.string.ssl_error_example), mBrowserBailOutToken,
+                    getString(R.string.ssl_error_continue));
+            view.loadDataWithBaseURL(INTERNAL_ASSETS, html, "text/HTML", "UTF-8", null);
+        }
+
+        @Override
+        public boolean shouldOverrideUrlLoading(WebView view, String url) {
+            if (url.startsWith("tel:")) {
+                startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url)));
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private class MyWebChromeClient extends WebChromeClient {
+        @Override
+        public void onProgressChanged(WebView view, int newProgress) {
+            final ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
+            myProgressBar.setProgress(newProgress);
+        }
+    }
+
+    private void runOnUiThreadIfNotFinishing(Runnable r) {
+        if (!isFinishing()) {
+            runOnUiThread(r);
+        }
+    }
+
+    private static void logd(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private static void loge(String s) {
+        Rlog.d(TAG, s);
+    }
+
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index db4890f..73ff3a9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.os.Bundle;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -111,8 +112,10 @@
         logd("onShowCaptivePortalNotification");
         final NotificationManager notificationMgr = context.getSystemService(
                 NotificationManager.class);
-        Intent portalIntent = new Intent(context, CaptivePortalLaunchActivity.class);
+        Intent portalIntent = new Intent(context, CaptivePortalLoginActivity.class);
         portalIntent.putExtras(intent);
+        portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
+                | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent,
                 PendingIntent.FLAG_UPDATE_CURRENT);
         Notification notification = getNotification(context, R.string.portal_notification_id,
@@ -146,18 +149,25 @@
 
     private static Notification getNotification(Context context, int titleId, int textId,
                                          PendingIntent pendingIntent) {
-        Resources resources = context.getResources();
+        final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
+        final Resources resources = context.getResources();
+        final Bundle extras = Bundle.forPair(Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                resources.getString(R.string.android_system_label));
         Notification.Builder builder = new Notification.Builder(context)
                 .setContentTitle(resources.getString(titleId))
-                .setContentText(resources.getString(textId))
+                .setContentText(String.format(resources.getString(textId),
+                        telephonyMgr.getNetworkOperatorName()))
                 .setSmallIcon(R.drawable.ic_sim_card)
+                .setColor(context.getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
                 .setOngoing(true)
                 .setPriority(Notification.PRIORITY_HIGH)
                 .setDefaults(Notification.DEFAULT_ALL)
                 .setVisibility(Notification.VISIBILITY_PUBLIC)
                 .setLocalOnly(true)
                 .setWhen(System.currentTimeMillis())
-                .setShowWhen(false);
+                .setShowWhen(false)
+                .setExtras(extras);
 
         if (pendingIntent != null) {
             builder.setContentIntent(pendingIntent);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
index bc0fa02..3fd89d9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierDefaultBroadcastReceiver.java
@@ -28,6 +28,10 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         Log.d(TAG, "onReceive intent: " + intent.getAction());
+        if (ProvisionObserver.isDeferredForProvision(context, intent)) {
+            Log.d(TAG, "skip carrier actions during provisioning");
+            return;
+        }
         List<Integer> actionList = CustomConfigLoader.loadCarrierActionList(context, intent);
         for (int actionIdx : actionList) {
             Log.d(TAG, "apply carrier action idx: " + actionIdx);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
new file mode 100644
index 0000000..3e34f0a
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.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 com.android.carrierdefaultapp;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Service to run {@link android.app.job.JobScheduler} job.
+ * Service to monitor when there is a change to conent URI
+ * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}
+ */
+public class ProvisionObserver extends JobService {
+
+    private static final String TAG = ProvisionObserver.class.getSimpleName();
+    public static final int PROVISION_OBSERVER_REEVALUATION_JOB_ID = 1;
+    // minimum & maximum update delay TBD
+    private static final int CONTENT_UPDATE_DELAY_MS = 100;
+    private static final int CONTENT_MAX_DELAY_MS = 200;
+
+    @Override
+    public boolean onStartJob(JobParameters jobParameters) {
+        switch (jobParameters.getJobId()) {
+            case PROVISION_OBSERVER_REEVALUATION_JOB_ID:
+                if (isProvisioned(this)) {
+                    Log.d(TAG, "device provisioned, force network re-evaluation");
+                    final ConnectivityManager connMgr = ConnectivityManager.from(this);
+                    Network[] info = connMgr.getAllNetworks();
+                    for (Network nw : info) {
+                        final NetworkCapabilities nc = connMgr.getNetworkCapabilities(nw);
+                        if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                            // force connectivity re-evaluation to assume skipped carrier actions.
+                            // one of the following calls will match the last evaluation.
+                            connMgr.reportNetworkConnectivity(nw, true);
+                            connMgr.reportNetworkConnectivity(nw, false);
+                            break;
+                        }
+                    }
+                }
+            default:
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        return false;
+    }
+
+    // Returns true if the device is not provisioned yet (in setup wizard), false otherwise
+    private static boolean isProvisioned(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+    }
+
+    /**
+     * Static utility function to schedule a job to execute upon the change of content URI
+     * {@link android.provider.Settings.Global#DEVICE_PROVISIONED DEVICE_PROVISIONED}.
+     * @param context The context used to retrieve the {@link ComponentName} and system services
+     * @return true carrier actions are deferred due to phone provisioning process, false otherwise
+     */
+    public static boolean isDeferredForProvision(Context context, Intent intent) {
+        if (isProvisioned(context)) {
+            return false;
+        }
+        int jobId;
+        switch(intent.getAction()) {
+            case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+                jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
+                break;
+            default:
+                return false;
+        }
+        final JobScheduler jobScheduler =  (JobScheduler) context.getSystemService(
+                Context.JOB_SCHEDULER_SERVICE);
+        final JobInfo job = new JobInfo.Builder(jobId,
+                new ComponentName(context, ProvisionObserver.class))
+                .addTriggerContentUri(new JobInfo.TriggerContentUri(
+                        Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), 0))
+                .setTriggerContentUpdateDelay(CONTENT_UPDATE_DELAY_MS)
+                .setTriggerContentMaxDelay(CONTENT_MAX_DELAY_MS)
+                .build();
+        jobScheduler.schedule(job);
+        return true;
+    }
+}
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java
deleted file mode 100644
index 8a18d72..0000000
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.android.carrierdefaultapp;
-
-import android.annotation.TargetApi;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
-
-import com.android.internal.telephony.TelephonyIntents;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-public class LaunchCaptivePortalActivityTest extends
-        CarrierDefaultActivityTestCase<CaptivePortalLaunchActivity> {
-
-    @Mock
-    private ConnectivityManager mCm;
-    @Mock
-    private NetworkInfo mNetworkInfo;
-    @Mock
-    private Network mNetwork;
-
-    @Captor
-    private ArgumentCaptor<Integer> mInt;
-    @Captor
-    private ArgumentCaptor<NetworkRequest> mNetworkReq;
-
-    private NetworkCapabilities mNetworkCapabilities;
-
-    public LaunchCaptivePortalActivityTest() {
-        super(CaptivePortalLaunchActivity.class);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        injectSystemService(ConnectivityManager.class, mCm);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    @Override
-    protected Intent createActivityIntent() {
-        Intent intent = new Intent(getInstrumentation().getTargetContext(),
-                CaptivePortalLaunchActivity.class);
-        intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, "url");
-        return intent;
-    }
-
-    @Test
-    public void testWithoutInternetConnection() throws Throwable {
-        startActivity();
-        TestContext.waitForMs(100);
-        verify(mCm, atLeast(1)).requestNetwork(mNetworkReq.capture(), any(), mInt.capture());
-        // verify network request
-        assert(mNetworkReq.getValue().networkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET));
-        assert(mNetworkReq.getValue().networkCapabilities.hasTransport(
-                NetworkCapabilities.TRANSPORT_CELLULAR));
-        assertFalse(mNetworkReq.getValue().networkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
-        assertEquals(CaptivePortalLaunchActivity.NETWORK_REQUEST_TIMEOUT_IN_MS,
-                (int) mInt.getValue());
-        // verify captive portal app is not launched due to unavailable network
-        assertNull(getStartedActivityIntent());
-        stopActivity();
-    }
-
-    @Test
-    public void testWithInternetConnection() throws Throwable {
-        // Mock internet connection
-        mNetworkCapabilities = new NetworkCapabilities()
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        doReturn(new Network[]{mNetwork}).when(mCm).getAllNetworks();
-        doReturn(mNetworkCapabilities).when(mCm).getNetworkCapabilities(eq(mNetwork));
-        doReturn(mNetworkInfo).when(mCm).getNetworkInfo(eq(mNetwork));
-        doReturn(true).when(mNetworkInfo).isConnected();
-
-        startActivity();
-        TestContext.waitForMs(100);
-        // verify there is no network request with internet connection
-        verify(mCm, times(0)).requestNetwork(any(), any(), anyInt());
-        // verify captive portal app is launched
-        assertNotNull(getStartedActivityIntent());
-        assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN,
-                getStartedActivityIntent().getAction());
-        stopActivity();
-    }
-}
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 9d44a6d..29035ab 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -12,6 +12,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v13
 # Supplies material design components, e.g. Snackbar.
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-design
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-transition
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview
 LOCAL_STATIC_JAVA_LIBRARIES += guava
 
@@ -22,6 +23,7 @@
 LOCAL_RESOURCE_DIR += \
   frameworks/support/v7/appcompat/res \
   frameworks/support/design/res \
+  frameworks/support/transition/res \
   frameworks/support/v7/recyclerview/res
 
 # Again, required to pull in appcompat resources.  See abovementioned demo code.
@@ -29,6 +31,7 @@
   --auto-add-overlay \
   --extra-packages android.support.v7.appcompat \
   --extra-packages android.support.design \
+  --extra-packages android.support.transition \
   --extra-packages android.support.v7.recyclerview
 
 LOCAL_JACK_FLAGS := \
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
index 859763b..08b82d0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
@@ -20,33 +20,14 @@
 import android.provider.DocumentsContract.Document;
 
 import com.android.documentsui.model.DocumentInfo;
-import com.android.internal.util.Predicate;
 
-public class MimePredicate implements Predicate<DocumentInfo> {
-    private final String[] mFilters;
-
-    private static final String APK_TYPE = "application/vnd.android.package-archive";
+public class MimePredicate {
     /**
      * MIME types that are visual in nature. For example, they should always be
      * shown as thumbnails in list mode.
      */
     public static final String[] VISUAL_MIMES = new String[] { "image/*", "video/*" };
 
-    public MimePredicate(String[] filters) {
-        mFilters = filters;
-    }
-
-    @Override
-    public boolean apply(DocumentInfo doc) {
-        if (doc.isDirectory()) {
-            return true;
-        }
-        if (mimeMatches(mFilters, doc.mimeType)) {
-            return true;
-        }
-        return false;
-    }
-
     public static boolean mimeMatches(String[] filters, String[] tests) {
         if (tests == null) {
             return false;
@@ -97,10 +78,6 @@
         }
     }
 
-    public static boolean isApkType(@Nullable String mimeType) {
-        return APK_TYPE.equals(mimeType);
-    }
-
     public static boolean isDirectoryType(@Nullable String mimeType) {
         return Document.MIME_TYPE_DIR.equals(mimeType);
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index 6ef9154..6bf8cccb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -39,7 +39,6 @@
 
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
-import com.android.internal.util.Predicate;
 
 import com.google.android.collect.Sets;
 
@@ -47,6 +46,7 @@
 
 import java.io.IOException;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public class RecentsProvider extends ContentProvider {
     private static final String TAG = "RecentsProvider";
@@ -269,7 +269,7 @@
 
             purgeByAuthority(new Predicate<String>() {
                 @Override
-                public boolean apply(String authority) {
+                public boolean test(String authority) {
                     // Purge unknown authorities
                     return !knownAuth.contains(authority);
                 }
@@ -290,7 +290,7 @@
             if (!packageAuth.isEmpty()) {
                 purgeByAuthority(new Predicate<String>() {
                     @Override
-                    public boolean apply(String authority) {
+                    public boolean test(String authority) {
                         // Purge authority matches
                         return packageAuth.contains(authority);
                     }
@@ -320,7 +320,7 @@
                             cursor.getColumnIndex(RecentColumns.STACK));
                     DurableUtils.readFromArray(rawStack, stack);
 
-                    if (stack.root != null && predicate.apply(stack.root.authority)) {
+                    if (stack.root != null && predicate.test(stack.root.authority)) {
                         final String key = getCursorString(cursor, RecentColumns.KEY);
                         db.delete(TABLE_RECENT, RecentColumns.KEY + "=?", new String[] { key });
                     }
@@ -336,7 +336,7 @@
         try {
             while (cursor.moveToNext()) {
                 final String authority = getCursorString(cursor, StateColumns.AUTHORITY);
-                if (predicate.apply(authority)) {
+                if (predicate.test(authority)) {
                     db.delete(TABLE_STATE, StateColumns.AUTHORITY + "=?", new String[] {
                             authority });
                     if (DEBUG) Log.d(TAG, "Purged state for " + authority);
@@ -354,7 +354,7 @@
                             cursor.getColumnIndex(ResumeColumns.STACK));
                     DurableUtils.readFromArray(rawStack, stack);
 
-                    if (stack.root != null && predicate.apply(stack.root.authority)) {
+                    if (stack.root != null && predicate.test(stack.root.authority)) {
                         final String packageName = getCursorString(
                                 cursor, ResumeColumns.PACKAGE_NAME);
                         db.delete(TABLE_RESUME, ResumeColumns.PACKAGE_NAME + "=?",
diff --git a/packages/ExternalStorageProvider/Android.mk b/packages/ExternalStorageProvider/Android.mk
index ec6af2f..fbf3782 100644
--- a/packages/ExternalStorageProvider/Android.mk
+++ b/packages/ExternalStorageProvider/Android.mk
@@ -5,7 +5,10 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-documents-archive
+LOCAL_STATIC_JAVA_LIBRARIES :=  \
+    android-support-documents-archive \
+    android-support-annotations
+
 LOCAL_PACKAGE_NAME := ExternalStorageProvider
 LOCAL_CERTIFICATE := platform
 LOCAL_PRIVILEGED_MODULE := true
diff --git a/packages/Osu/src/com/android/MainActivity.java b/packages/Osu/src/com/android/MainActivity.java
index 7e7d49a..9bcc390 100644
--- a/packages/Osu/src/com/android/MainActivity.java
+++ b/packages/Osu/src/com/android/MainActivity.java
@@ -246,6 +246,8 @@
                 case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
                     mOsuManager.pushScanResults(wifiManager.getScanResults());
                     break;
+                // TODO(b/32883320): use updated intent.
+                /*
                 case WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION:
                     long bssid = bundle.getLong(WifiManager.EXTRA_PASSPOINT_WNM_BSSID);
                     String url = bundle.getString(WifiManager.EXTRA_PASSPOINT_WNM_URL);
@@ -282,6 +284,7 @@
                             bundle.getString(WifiManager.EXTRA_PASSPOINT_ICON_FILE),
                             bundle.getByteArray(WifiManager.EXTRA_PASSPOINT_ICON_DATA));
                     break;
+                */
                 case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION:
                     mOsuManager.networkConfigChange((WifiConfiguration)
                             intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION));
diff --git a/packages/PrintSpooler/res/layout/no_print_services_message.xml b/packages/PrintSpooler/res/layout/no_print_services_message.xml
new file mode 100644
index 0000000..7872658
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/no_print_services_message.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:orientation="horizontal"
+    android:gravity="start|center_vertical">
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="32dip">
+            <HorizontalScrollView
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content">
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textAppearance="?android:attr/textAppearanceListItem"
+                    android:text="@string/print_no_print_services"
+                    android:scrollHorizontally="true"
+                    android:singleLine="true" />
+            </HorizontalScrollView>
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java
index c06e849..72004ef 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.Loader;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.database.DataSetObserver;
 import android.net.Uri;
@@ -95,20 +96,39 @@
      */
     private RecommendedServicesAdapter mRecommendedServicesAdapter;
 
+    private static final String PKG_NAME_VENDING = "com.android.vending";
+    private boolean mHasVending;
+    private NoPrintServiceMessageAdapter mNoPrintServiceMessageAdapter;
+
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.add_printer_activity);
 
+        try {
+            getPackageManager().getPackageInfo(PKG_NAME_VENDING, 0);
+            mHasVending = true;
+        } catch (PackageManager.NameNotFoundException e) {
+            mHasVending = false;
+        }
         mEnabledServicesAdapter = new EnabledServicesAdapter();
         mDisabledServicesAdapter = new DisabledServicesAdapter();
-        mRecommendedServicesAdapter = new RecommendedServicesAdapter();
+        if (mHasVending) {
+            mRecommendedServicesAdapter = new RecommendedServicesAdapter();
+        } else {
+            mNoPrintServiceMessageAdapter = new NoPrintServiceMessageAdapter();
+        }
 
         ArrayList<ActionAdapter> adapterList = new ArrayList<>(3);
         adapterList.add(mEnabledServicesAdapter);
-        adapterList.add(mRecommendedServicesAdapter);
+        if (mHasVending) {
+            adapterList.add(mRecommendedServicesAdapter);
+        }
         adapterList.add(mDisabledServicesAdapter);
+        if (!mHasVending) {
+            adapterList.add(mNoPrintServiceMessageAdapter);
+        }
 
         setListAdapter(new CombinedAdapter(adapterList));
 
@@ -119,8 +139,10 @@
 
         getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, printServiceLoaderCallbacks);
         getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, printServiceLoaderCallbacks);
-        getLoaderManager().initLoader(LOADER_ID_RECOMMENDED_SERVICES, null,
-                new PrintServicePrintServiceRecommendationLoaderCallbacks());
+        if (mHasVending) {
+            getLoaderManager().initLoader(LOADER_ID_RECOMMENDED_SERVICES, null,
+                    new PrintServicePrintServiceRecommendationLoaderCallbacks());
+        }
         getLoaderManager().initLoader(LOADER_ID_ALL_SERVICES, null, printServiceLoaderCallbacks);
     }
 
@@ -162,7 +184,11 @@
                     mDisabledServicesAdapter.updateData(data);
                     break;
                 case LOADER_ID_ALL_SERVICES:
-                    mRecommendedServicesAdapter.updateInstalledServices(data);
+                    if (mHasVending) {
+                        mRecommendedServicesAdapter.updateInstalledServices(data);
+                    } else {
+                        mNoPrintServiceMessageAdapter.updateInstalledServices(data);
+                    }
                 default:
                     // not reached
             }
@@ -179,7 +205,11 @@
                         mDisabledServicesAdapter.updateData(null);
                         break;
                     case LOADER_ID_ALL_SERVICES:
-                        mRecommendedServicesAdapter.updateInstalledServices(null);
+                        if (mHasVending) {
+                            mRecommendedServicesAdapter.updateInstalledServices(null);
+                        } else {
+                            mNoPrintServiceMessageAdapter.updateInstalledServices(null);
+                        }
                         break;
                     default:
                         // not reached
@@ -788,4 +818,61 @@
             filterRecommendations();
         }
     }
+
+    private class NoPrintServiceMessageAdapter extends ActionAdapter {
+        private boolean mHasPrintService;
+
+        void updateInstalledServices(@Nullable List<PrintServiceInfo> services) {
+            if (services == null || services.isEmpty()) {
+                mHasPrintService = false;
+            } else {
+                mHasPrintService = true;
+            }
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mHasPrintService ? 0 : 1;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return null;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(R.layout.no_print_services_message,
+                    parent, false);
+            }
+            return convertView;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return position != 0;
+        }
+
+        @Override
+        public void performAction(@IntRange(from = 0) int position) {
+            return;
+        }
+    }
 }
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index cf0ba6c..0815534 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -24,5 +24,9 @@
 LOCAL_STATIC_JAVA_LIBRARIES += \
     android-support-annotations \
     android-support-v4 \
+    android-support-v7-appcompat \
+    android-support-v7-preference \
+    android-support-v7-recyclerview \
+    android-support-v14-preference \
     SettingsLib
 endif
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 5369f9f..1f432de 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -103,10 +103,11 @@
 
     <!-- Bluetooth settings -->
 
-    <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_titles">
         <item>Use System Selection (Default)</item>
         <item>SBC</item>
+        <item>AAC</item>
         <item>aptX</item>
         <item>aptX HD</item>
         <item>LDAC</item>
@@ -119,18 +120,20 @@
         <item>1</item>
         <item>2</item>
         <item>3</item>
+        <item>4</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_summaries" >
         <item>Use System Selection (Default)</item>
         <item>SBC</item>
+        <item>AAC</item>
         <item>aptX</item>
         <item>aptX HD</item>
         <item>LDAC</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_sample_rate_titles">
         <item>Use System Selection (Default)</item>
         <item>44.1 kHz</item>
@@ -148,7 +151,7 @@
         <item>8</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Sample Rate selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_sample_rate_summaries" >
         <item>Use System Selection (Default)</item>
         <item>44.1 kHz</item>
@@ -157,7 +160,7 @@
         <item>96.0 kHz</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
         <item>Use System Selection (Default)</item>
         <item>16 bits/sample</item>
@@ -173,7 +176,7 @@
         <item>4</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Bits Per Sample selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries" >
         <item>Use System Selection (Default)</item>
         <item>16 bits/sample</item>
@@ -181,7 +184,7 @@
         <item>32 bits/sample</item>
     </string-array>
 
-    <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40] -->
+    <!-- Titles for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50] -->
     <string-array name="bluetooth_a2dp_codec_channel_mode_titles">
         <item>Use System Selection (Default)</item>
         <item>Mono</item>
@@ -195,7 +198,7 @@
         <item>2</item>
     </string-array>
 
-    <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=40]-->
+    <!-- Summaries for Bluetooth Audio Codec Channel Mode selection preference. [CHAR LIMIT=50]-->
     <string-array name="bluetooth_a2dp_codec_channel_mode_summaries" >
         <item>Use System Selection (Default)</item>
         <item>Mono</item>
@@ -204,9 +207,9 @@
 
     <!-- Titles for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70] -->
     <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
-        <item>Optimize for Audio Quality (990kbps/909kbps)</item>
+        <item>Optimized for Audio Quality (990kbps/909kbps)</item>
         <item>Balanced Audio And Connection Quality (660kbps/606kbps)</item>
-        <item>Optimize for Connection Quality (330kbps/303kbps)</item>
+        <item>Optimized for Connection Quality (330kbps/303kbps)</item>
     </string-array>
 
     <!-- Values for Bluetooth Audio Codec LDAC Playback Quaility selection preference. -->
@@ -218,9 +221,9 @@
 
     <!-- Summaries for Bluetooth Audio Codec LDAC Playback Quality selection preference. [CHAR LIMIT=70]-->
     <string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries" >
-        <item>Optimize for Audio Quality</item>
+        <item>Optimized for Audio Quality</item>
         <item>Balanced Audio And Connection Quality</item>
-        <item>Optimize for Connection Quality</item>
+        <item>Optimized for Connection Quality</item>
     </string-array>
 
     <!-- Titles for logd limit size selection preference. [CHAR LIMIT=14] -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 3627c29..064059b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -57,6 +57,8 @@
     <string name="wifi_disabled_generic">Disabled</string>
     <!-- Status for networked disabled from a DNS or DHCP failure -->
     <string name="wifi_disabled_network_failure">IP Configuration Failure</string>
+    <!-- Status for networks disabled by the network recommendation provider -->
+    <string name="wifi_disabled_by_recommendation_provider">Not connected due to low quality network</string>
     <!-- Status for networked disabled from a wifi association failure -->
     <string name="wifi_disabled_wifi_failure">WiFi Connection Failure</string>
     <!-- Status for networks disabled from authentication failure (wrong password
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f0ec1078..a803e60 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -649,7 +649,11 @@
             }
 
             if (comparator != null) {
-                Collections.sort(filteredApps, comparator);
+                synchronized (mEntriesMap) {
+                    // Locking to ensure that the background handler does not mutate
+                    // the size of AppEntries used for ordering while sorting.
+                    Collections.sort(filteredApps, comparator);
+                }
             }
 
             synchronized (mRebuildSync) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 2683609..c4437c2 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -135,6 +135,10 @@
         mAdapter.setDiscoverableTimeout(timeout);
     }
 
+    public long getDiscoveryEndMillis() {
+        return mAdapter.getDiscoveryEndMillis();
+    }
+
     public void setName(String name) {
         mAdapter.setName(name);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 252aaab..6166cd8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -422,7 +422,8 @@
             // This is the active connection on non-passpoint network
             summary.append(getSummary(mContext, getDetailedState(),
                     mInfo != null && mInfo.isEphemeral()));
-        } else if (config != null && config.isPasspoint()) {
+        } else if (config != null && config.isPasspoint()
+                && config.getNetworkSelectionStatus().isNetworkEnabled()) {
             String format = mContext.getString(R.string.available_via_passpoint);
             summary.append(String.format(format, config.providerFriendlyName));
         } else if (config != null && config.hasNoInternetAccess()) {
@@ -445,6 +446,8 @@
                     summary.append(mContext.getString(R.string.wifi_disabled_generic));
                     break;
             }
+        } else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
+            summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
         } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
             summary.append(mContext.getString(R.string.wifi_not_in_range));
         } else { // In range, not disabled.
@@ -682,11 +685,7 @@
     }
 
     void loadConfig(WifiConfiguration config) {
-        if (config.isPasspoint())
-            ssid = config.providerFriendlyName;
-        else
-            ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
-
+        ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
         bssid = config.BSSID;
         security = getSecurity(config);
         networkId = config.networkId;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 7d279eb..3435d1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -344,29 +344,22 @@
                 }
                 AccessPoint accessPoint = getCachedOrCreate(config, cachedAccessPoints);
                 if (mLastInfo != null && mLastNetworkInfo != null) {
-                    if (config.isPasspoint() == false) {
-                        accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
-                    }
+                    accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo);
                 }
                 if (mIncludeSaved) {
-                    if (!config.isPasspoint() || mIncludePasspoints) {
-                        // If saved network not present in scan result then set its Rssi to MAX_VALUE
-                        boolean apFound = false;
-                        for (ScanResult result : results) {
-                            if (result.SSID.equals(accessPoint.getSsidStr())) {
-                                apFound = true;
-                                break;
-                            }
+                    // If saved network not present in scan result then set its Rssi to MAX_VALUE
+                    boolean apFound = false;
+                    for (ScanResult result : results) {
+                        if (result.SSID.equals(accessPoint.getSsidStr())) {
+                            apFound = true;
+                            break;
                         }
-                        if (!apFound) {
-                            accessPoint.setRssi(Integer.MAX_VALUE);
-                        }
-                        accessPoints.add(accessPoint);
                     }
-
-                    if (config.isPasspoint() == false) {
-                        apMap.put(accessPoint.getSsidStr(), accessPoint);
+                    if (!apFound) {
+                        accessPoint.setRssi(Integer.MAX_VALUE);
                     }
+                    accessPoints.add(accessPoint);
+                    apMap.put(accessPoint.getSsidStr(), accessPoint);
                 } else {
                     // If we aren't using saved networks, drop them into the cache so that
                     // we have access to their saved info.
@@ -397,20 +390,16 @@
                     }
 
                     if (result.isPasspointNetwork()) {
+                        // Retrieve a WifiConfiguration for a Passpoint provider that matches
+                        // the given ScanResult.  This is used for showing that a given AP
+                        // (ScanResult) is available via a Passpoint provider (provider friendly
+                        // name).
                         WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result);
                         if (config != null) {
                             accessPoint.update(config);
                         }
                     }
 
-                    if (mLastInfo != null && mLastInfo.getBSSID() != null
-                            && mLastInfo.getBSSID().equals(result.BSSID)
-                            && connectionConfig != null && connectionConfig.isPasspoint()) {
-                        /* This network is connected via this passpoint config */
-                        /* SSID match is not going to work for it; so update explicitly */
-                        accessPoint.update(connectionConfig);
-                    }
-
                     accessPoints.add(accessPoint);
                     apMap.put(accessPoint.getSsidStr(), accessPoint);
                 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 227d0e9..4f7a826e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -41,6 +41,7 @@
     <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+    <uses-permission android:name="android.permission.MANAGE_USB" />
     <!-- System tool permissions granted to the shell. -->
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_in.xml b/packages/SystemUI/res/drawable/stat_sys_signal_in.xml
new file mode 100644
index 0000000..7e6e09b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_in.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="17dp"
+        android:height="17dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:name="in"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M8.7,12.2l-2.0,3.5l-2.0,-3.5z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_inout.xml b/packages/SystemUI/res/drawable/stat_sys_signal_inout.xml
new file mode 100644
index 0000000..b7b6f0f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_inout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="17dp"
+        android:height="17dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:name="in"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M8.7,12.2l-2.0,3.5l-2.0,-3.5z" />
+    <path
+        android:name="out"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M0.5,15.7l2.0,-3.5l2.0,3.5z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_signal_out.xml b/packages/SystemUI/res/drawable/stat_sys_signal_out.xml
new file mode 100644
index 0000000..910c035
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_signal_out.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="17dp"
+        android:height="17dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:name="out"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M0.5,15.7l2.0,-3.5l2.0,3.5z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_in.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_in.xml
new file mode 100644
index 0000000..666127b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_in.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="18.41dp"
+        android:height="17dp"
+        android:viewportWidth="26.0"
+        android:viewportHeight="24.0">
+    <path
+        android:name="in"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M8.7,18.3l-2.0,3.5l-2.0,-3.5z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_inout.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_inout.xml
new file mode 100644
index 0000000..eeba030
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_inout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="18.41dp"
+        android:height="17dp"
+        android:viewportWidth="26.0"
+        android:viewportHeight="24.0">
+    <path
+        android:name="in"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M8.7,18.3l-2.0,3.5l-2.0,-3.5z" />
+    <path
+        android:name="out"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M0.5,21.8l2.0,-3.5l2.0,3.5z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_wifi_out.xml b/packages/SystemUI/res/drawable/stat_sys_wifi_out.xml
new file mode 100644
index 0000000..d577d1a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_wifi_out.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="18.41dp"
+        android:height="17dp"
+        android:viewportWidth="26.0"
+        android:viewportHeight="24.0">
+    <path
+        android:name="out"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M0.5,21.8l2.0,-3.5l2.0,3.5z" />
+</vector>
diff --git a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
index a726161..1059ad4 100644
--- a/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
+++ b/packages/SystemUI/res/layout-sw410dp/status_bar_alarm_group.xml
@@ -27,7 +27,7 @@
     <LinearLayout
         android:id="@+id/date_time_group"
         android:layout_width="wrap_content"
-        android:layout_height="19dp"
+        android:layout_height="wrap_content"
         android:orientation="horizontal"
         android:focusable="true" >
 
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
index a20ec8e..f597785 100644
--- a/packages/SystemUI/res/layout/mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -43,4 +43,10 @@
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
         />
+    <ImageView
+        android:id="@+id/mobile_inout"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:visibility="gone"
+        />
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index d17601c..f0bfb92 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -70,6 +70,11 @@
             android:layout_width="wrap_content"
             android:alpha="0.0"
             />
+        <ImageView
+            android:id="@+id/wifi_inout"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            />
     </FrameLayout>
     <View
         android:id="@+id/wifi_signal_spacer"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 484e008..2fd62f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -232,6 +232,8 @@
         i.setPackage(mComponent.getPackageName());
         i = resolveIntent(i);
         if (i != null) {
+            i.putExtra(TileService.EXTRA_COMPONENT, mComponent);
+            i.putExtra(TileService.EXTRA_STATE, mTile.getState());
             return i;
         }
         return new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 016c4b7..4b8d7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -37,6 +38,9 @@
 
 /** Quick settings tile: Hotspot **/
 public class HotspotTile extends QSTile<QSTile.AirplaneBooleanState> {
+    static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName(
+             "com.android.settings", "com.android.settings.TetherSettings"));
+
     private final AnimationIcon mEnable =
             new AnimationIcon(R.drawable.ic_hotspot_enable_animation,
                     R.drawable.ic_hotspot_disable);
@@ -94,7 +98,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
+        return new Intent(TETHER_SETTINGS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 74caa53..d36f572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -75,6 +75,8 @@
     private boolean mWifiVisible = false;
     private int mWifiStrengthId = 0;
     private int mLastWifiStrengthId = -1;
+    private int mWifiActivityId = 0;
+    private int mLastWifiActivityId = -1;
     private boolean mIsAirplaneMode = false;
     private int mAirplaneIconId = 0;
     private int mLastAirplaneIconId = -1;
@@ -89,6 +91,7 @@
     ViewGroup mEthernetGroup, mWifiGroup;
     View mNoSimsCombo;
     ImageView mVpn, mEthernet, mWifi, mAirplane, mNoSims, mEthernetDark, mWifiDark, mNoSimsDark;
+    ImageView mWifiActivity;
     View mWifiAirplaneSpacer;
     View mWifiSignalSpacer;
     LinearLayout mMobileSignalGroup;
@@ -180,6 +183,7 @@
         mWifiGroup      = (ViewGroup) findViewById(R.id.wifi_combo);
         mWifi           = (ImageView) findViewById(R.id.wifi_signal);
         mWifiDark       = (ImageView) findViewById(R.id.wifi_signal_dark);
+        mWifiActivity   = (ImageView) findViewById(R.id.wifi_inout);
         mAirplane       = (ImageView) findViewById(R.id.airplane);
         mNoSims         = (ImageView) findViewById(R.id.no_sims);
         mNoSimsDark     = (ImageView) findViewById(R.id.no_sims_dark);
@@ -264,6 +268,10 @@
         mWifiVisible = statusIcon.visible && !mBlockWifi;
         mWifiStrengthId = statusIcon.icon;
         mWifiDescription = statusIcon.contentDescription;
+        mWifiActivityId = activityIn && activityOut ? R.drawable.stat_sys_wifi_inout
+                : activityIn ? R.drawable.stat_sys_wifi_in
+                : activityOut ? R.drawable.stat_sys_wifi_out
+                : 0;
 
         apply();
     }
@@ -282,6 +290,10 @@
         state.mMobileDescription = statusIcon.contentDescription;
         state.mMobileTypeDescription = typeContentDescription;
         state.mIsMobileTypeIconWide = statusType != 0 && isWide;
+        state.mMobileActivityId = activityIn && activityOut ? R.drawable.stat_sys_signal_inout
+                : activityIn ? R.drawable.stat_sys_signal_in
+                : activityOut ? R.drawable.stat_sys_signal_out
+                : 0;
 
         apply();
     }
@@ -404,6 +416,10 @@
             mWifiDark.setImageDrawable(null);
             mLastWifiStrengthId = -1;
         }
+        if (mWifiActivity !=  null) {
+            mWifiActivity.setImageDrawable(null);
+            mLastWifiActivityId = -1;
+        }
 
         for (PhoneState state : mPhoneStates) {
             if (state.mMobile != null) {
@@ -420,6 +436,10 @@
                 state.mMobileType.setImageDrawable(null);
                 state.mLastMobileTypeId = -1;
             }
+            if (state.mMobileActivity != null) {
+                state.mMobileActivity.setImageDrawable(null);
+                state.mLastMobileActivityId = -1;
+            }
         }
 
         if (mAirplane != null) {
@@ -473,6 +493,12 @@
                 setIconForView(mWifiDark, mWifiStrengthId);
                 mLastWifiStrengthId = mWifiStrengthId;
             }
+            if (mWifiActivityId != mLastWifiActivityId) {
+                if (mWifiActivityId != 0) {
+                    setIconForView(mWifiActivity, mWifiActivityId);
+                }
+                mLastWifiActivityId = mWifiActivityId;
+            }
             mWifiGroup.setContentDescription(mWifiDescription);
             mWifiGroup.setVisibility(View.VISIBLE);
         } else {
@@ -484,6 +510,8 @@
                     (mWifiVisible ? "VISIBLE" : "GONE"),
                     mWifiStrengthId));
 
+        mWifiActivity.setVisibility(mWifiActivityId != 0 ? View.VISIBLE : View.GONE);
+
         boolean anyMobileVisible = false;
         int firstMobileTypeId = 0;
         for (PhoneState state : mPhoneStates) {
@@ -560,6 +588,8 @@
         applyDarkIntensity(
                 StatusBarIconController.getDarkIntensity(mTintArea, mWifi, mDarkIntensity),
                 mWifi, mWifiDark);
+        setTint(mWifiActivity,
+                StatusBarIconController.getTint(mTintArea, mWifiActivity, mIconTint));
         applyDarkIntensity(
                 StatusBarIconController.getDarkIntensity(mTintArea, mEthernet, mDarkIntensity),
                 mEthernet, mEthernetDark);
@@ -584,14 +614,16 @@
     private class PhoneState {
         private final int mSubId;
         private boolean mMobileVisible = false;
-        private int mMobileStrengthId = 0, mMobileTypeId = 0;
+        private int mMobileStrengthId = 0, mMobileTypeId = 0, mMobileActivityId = 0;
         private int mLastMobileStrengthId = -1;
         private int mLastMobileTypeId = -1;
+        private int mLastMobileActivityId = -1;
         private boolean mIsMobileTypeIconWide;
         private String mMobileDescription, mMobileTypeDescription;
 
         private ViewGroup mMobileGroup;
         private ImageView mMobile, mMobileDark, mMobileType;
+        private ImageView mMobileActivity;
 
         public PhoneState(int subId, Context context) {
             ViewGroup root = (ViewGroup) LayoutInflater.from(context)
@@ -605,6 +637,7 @@
             mMobile         = (ImageView) root.findViewById(R.id.mobile_signal);
             mMobileDark     = (ImageView) root.findViewById(R.id.mobile_signal_dark);
             mMobileType     = (ImageView) root.findViewById(R.id.mobile_type);
+            mMobileActivity = (ImageView) root.findViewById(R.id.mobile_inout);
         }
 
         public boolean apply(boolean isSecondaryIcon) {
@@ -619,6 +652,11 @@
                     mMobileType.setImageResource(mMobileTypeId);
                     mLastMobileTypeId = mMobileTypeId;
                 }
+
+                if (mLastMobileActivityId != mMobileActivityId) {
+                    mMobileActivity.setImageResource(mMobileActivityId);
+                    mLastMobileActivityId = mMobileActivityId;
+                }
                 mMobileGroup.setContentDescription(mMobileTypeDescription
                         + " " + mMobileDescription);
                 mMobileGroup.setVisibility(View.VISIBLE);
@@ -640,6 +678,7 @@
                         (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
 
             mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
+            mMobileActivity.setVisibility(mMobileActivityId != 0 ? View.VISIBLE : View.GONE);
 
             return mMobileVisible;
         }
@@ -699,6 +738,8 @@
                     StatusBarIconController.getDarkIntensity(tintArea, mMobile, darkIntensity),
                     mMobile, mMobileDark);
             setTint(mMobileType, StatusBarIconController.getTint(tintArea, mMobileType, tint));
+            setTint(mMobileActivity,
+                    StatusBarIconController.getTint(tintArea, mMobileActivity, tint));
         }
     }
 }
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 37e6a2a..9380c45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -690,6 +690,7 @@
             mDemoMode = true;
             mDemoInetCondition = mInetCondition;
             mDemoWifiState = mWifiSignalController.getState();
+            mDemoWifiState.ssid = "DemoMode";
         } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
             if (DEBUG) Log.d(TAG, "Exiting demo mode");
             mDemoMode = false;
@@ -735,6 +736,25 @@
                             : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
                     mDemoWifiState.connected = mDemoWifiState.level >= 0;
                 }
+                String activity = args.getString("activity");
+                if (activity != null) {
+                    switch (activity) {
+                        case "inout":
+                            mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_INOUT);
+                            break;
+                        case "in":
+                            mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_IN);
+                            break;
+                        case "out":
+                            mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_OUT);
+                            break;
+                        default:
+                            mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
+                            break;
+                    }
+                } else {
+                    mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
+                }
                 mDemoWifiState.enabled = show;
                 mWifiSignalController.notifyListeners();
             }
@@ -797,6 +817,26 @@
                             : Math.min(Integer.parseInt(level), icons[0].length - 1);
                     controller.getState().connected = controller.getState().level >= 0;
                 }
+                String activity = args.getString("activity");
+                if (activity != null) {
+                    controller.getState().dataConnected = true;
+                    switch (activity) {
+                        case "inout":
+                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_INOUT);
+                            break;
+                        case "in":
+                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_IN);
+                            break;
+                        case "out":
+                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_OUT);
+                            break;
+                        default:
+                            controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+                            break;
+                    }
+                } else {
+                    controller.setActivity(TelephonyManager.DATA_ACTIVITY_NONE);
+                }
                 controller.getState().enabled = show;
                 controller.notifyListeners();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 0a3197c..1b520b4 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -163,7 +163,7 @@
         intent.putExtra("sims", "1");
         intent.putExtra("nosim", "false");
         intent.putExtra("level", "4");
-        intent.putExtra("datatypel", "");
+        intent.putExtra("datatype", "lte");
         getContext().sendBroadcast(intent);
 
         // Need to send this after so that the sim controller already exists.
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index bf4d88c..51c7e55 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -18,6 +18,7 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
diff --git a/packages/WallpaperCropper/Android.mk b/packages/WallpaperCropper/Android.mk
index d8fb7a4..09b41fd 100644
--- a/packages/WallpaperCropper/Android.mk
+++ b/packages/WallpaperCropper/Android.mk
@@ -6,7 +6,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 junit
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
 
 LOCAL_PACKAGE_NAME := WallpaperCropper
 LOCAL_CERTIFICATE := platform
diff --git a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java
index 100b0b3b..f8b01cb 100644
--- a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java
+++ b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -18,7 +18,7 @@
 
 import android.graphics.Bitmap;
 
-import junit.framework.Assert;
+import com.android.gallery3d.common.Utils;
 
 // BitmapTexture is a texture whose content is specified by a fixed Bitmap.
 //
@@ -34,7 +34,7 @@
 
     public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
         super(hasBorder);
-        Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
+        Utils.assertTrue(bitmap != null && !bitmap.isRecycled());
         mContentBitmap = bitmap;
     }
 
diff --git a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java
index 16b2206..b26e9ab 100644
--- a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java
+++ b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/GLPaint.java
@@ -16,7 +16,7 @@
 
 package com.android.gallery3d.glrenderer;
 
-import junit.framework.Assert;
+import com.android.gallery3d.common.Utils;
 
 public class GLPaint {
     private float mLineWidth = 1f;
@@ -31,7 +31,7 @@
     }
 
     public void setLineWidth(float width) {
-        Assert.assertTrue(width >= 0);
+        Utils.assertTrue(width >= 0);
         mLineWidth = width;
     }
 
diff --git a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java
index f41a979..417102a 100644
--- a/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java
+++ b/packages/WallpaperCropper/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -20,7 +20,7 @@
 import android.graphics.Bitmap.Config;
 import android.opengl.GLUtils;
 
-import junit.framework.Assert;
+import com.android.gallery3d.common.Utils;
 
 import java.util.HashMap;
 
@@ -144,7 +144,7 @@
     }
 
     private void freeBitmap() {
-        Assert.assertTrue(mBitmap != null);
+        Utils.assertTrue(mBitmap != null);
         onFreeBitmap(mBitmap);
         mBitmap = null;
     }
@@ -219,7 +219,7 @@
                 int texWidth = getTextureWidth();
                 int texHeight = getTextureHeight();
 
-                Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+                Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
 
                 // Upload the bitmap to a new texture.
                 mId = canvas.getGLId().generateTexture();
diff --git a/preloaded-classes b/preloaded-classes
index 805a1c9..42f290e 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -2542,6 +2542,7 @@
 com.android.internal.os.RuntimeInit$Arguments
 com.android.internal.os.RuntimeInit$KillApplicationHandler
 com.android.internal.os.RuntimeInit$LoggingHandler
+com.android.internal.os.RoSystemProperties
 com.android.internal.os.SamplingProfilerIntegration
 com.android.internal.os.SomeArgs
 com.android.internal.os.Zygote
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 735afce..d3ed525 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2218,6 +2218,30 @@
     // CATEGORY: QUICK_SETTINGS
     QS_NFC = 497;
 
+    // ---- End N-MR2 Constants, all N-MR1 constants go above this line ----
+
+    // ACTION: A captive portal was detected during network validation
+    // CATEGORY: NOTIFICATION
+    // OS: N-MR2
+    NOTIFICATION_NETWORK_SIGN_IN = 740;
+
+    // ACTION: An unvalidated network without Internet was selected by the user
+    // CATEGORY: NOTIFICATION
+    // OS: N-MR2
+    NOTIFICATION_NETWORK_NO_INTERNET = 741;
+
+    // ACTION: A validated network failed revalidation and lost Internet access
+    // CATEGORY: NOTIFICATION
+    // OS: N-MR2
+    NOTIFICATION_NETWORK_LOST_INTERNET = 742;
+
+    // ACTION: The system default network switched to a different network
+    // CATEGORY: NOTIFICATION
+    // OS: N-MR2
+    NOTIFICATION_NETWORK_SWITCH = 743;
+
+    // ---- End O Constants, all O constants go above this line ----
+
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
   }
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
new file mode 100644
index 0000000..9aa6490
--- /dev/null
+++ b/proto/src/wifi.proto
@@ -0,0 +1,462 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package clearcut.connectivity;
+
+option java_package = "com.android.server.wifi";
+option java_outer_classname = "WifiMetricsProto";
+
+// The information about the Wifi events.
+message WifiLog {
+
+  // Session information that gets logged for every Wifi connection.
+  repeated ConnectionEvent connection_event = 1;
+
+  // Number of saved networks in the user profile.
+  optional int32 num_saved_networks = 2;
+
+  // Number of open networks in the saved networks.
+  optional int32 num_open_networks = 3;
+
+  // Number of personal networks.
+  optional int32 num_personal_networks = 4;
+
+  // Number of enterprise networks.
+  optional int32 num_enterprise_networks = 5;
+
+  // Does the user have location setting enabled.
+  optional bool is_location_enabled = 6;
+
+  // Does the user have scanning enabled.
+  optional bool is_scanning_always_enabled = 7;
+
+  // Number of times user toggled wifi using the settings menu.
+  optional int32 num_wifi_toggled_via_settings = 8;
+
+  // Number of times user toggled wifi using the airplane menu.
+  optional int32 num_wifi_toggled_via_airplane = 9;
+
+  // Number of networks added by the user.
+  optional int32 num_networks_added_by_user = 10;
+
+  // Number of networks added by applications.
+  optional int32 num_networks_added_by_apps = 11;
+
+  // Number scans that returned empty results.
+  optional int32 num_empty_scan_results = 12;
+
+  // Number scans that returned at least one result.
+  optional int32 num_non_empty_scan_results = 13;
+
+  // Number of scans that were one time.
+  optional int32 num_oneshot_scans = 14;
+
+  // Number of repeated background scans that were scheduled to the chip.
+  optional int32 num_background_scans = 15;
+
+  // Error codes that a scan can result in.
+  enum ScanReturnCode {
+
+    // Return Code is unknown.
+    SCAN_UNKNOWN = 0;
+
+    // Scan was successful.
+    SCAN_SUCCESS = 1;
+
+    // Scan was successfully started, but was interrupted.
+    SCAN_FAILURE_INTERRUPTED = 2;
+
+    //  Scan failed to start because of invalid configuration
+    //  (bad channel, etc).
+    SCAN_FAILURE_INVALID_CONFIGURATION = 3;
+
+    // Could not start a scan because wifi is disabled.
+    FAILURE_WIFI_DISABLED = 4;
+
+  }
+
+  // Mapping of error codes to the number of times that scans resulted
+  // in that error.
+  repeated ScanReturnEntry scan_return_entries = 16;
+
+  message ScanReturnEntry {
+
+     // Return code of the scan.
+     optional ScanReturnCode scan_return_code = 1;
+
+     // Number of entries that were found in the scan.
+     optional int32 scan_results_count = 2;
+  }
+
+  // State of the Wifi.
+  enum WifiState {
+
+    // State is unknown.
+    WIFI_UNKNOWN = 0;
+
+    // Wifi is disabled.
+    WIFI_DISABLED = 1;
+
+    // Wifi is enabled.
+    WIFI_DISCONNECTED = 2;
+
+    // Wifi is enabled and associated with an AP.
+    WIFI_ASSOCIATED = 3;
+  }
+
+  // Mapping of system state to the number of times that scans were requested in
+  // that state
+  repeated WifiSystemStateEntry wifi_system_state_entries = 17;
+
+  message WifiSystemStateEntry {
+
+     // Current WiFi state.
+     optional WifiState wifi_state = 1;
+
+     // Count of scans in state.
+     optional int32 wifi_state_count = 2;
+
+     // Is screen on.
+     optional bool is_screen_on = 3;
+  }
+
+  // Mapping of Error/Success codes to the number of background scans that resulted in it
+  repeated ScanReturnEntry background_scan_return_entries = 18;
+
+  // Mapping of system state to the number of times that Background scans were requested in that
+  // state
+  repeated WifiSystemStateEntry background_scan_request_state = 19;
+
+  // Total number of times the Watchdog of Last Resort triggered, resetting the wifi stack
+  optional int32 num_last_resort_watchdog_triggers = 20;
+
+  // Total number of networks over bad association threshold when watchdog triggered
+  optional int32 num_last_resort_watchdog_bad_association_networks_total = 21;
+
+  // Total number of networks over bad authentication threshold when watchdog triggered
+  optional int32 num_last_resort_watchdog_bad_authentication_networks_total = 22;
+
+  // Total number of networks over bad dhcp threshold when watchdog triggered
+  optional int32 num_last_resort_watchdog_bad_dhcp_networks_total = 23;
+
+  // Total number of networks over bad other threshold when watchdog triggered
+  optional int32 num_last_resort_watchdog_bad_other_networks_total = 24;
+
+  // Total count of networks seen when watchdog triggered
+  optional int32 num_last_resort_watchdog_available_networks_total = 25;
+
+  // Total count of triggers with atleast one bad association network
+  optional int32 num_last_resort_watchdog_triggers_with_bad_association = 26;
+
+  // Total count of triggers with atleast one bad authentication network
+  optional int32 num_last_resort_watchdog_triggers_with_bad_authentication = 27;
+
+  // Total count of triggers with atleast one bad dhcp network
+  optional int32 num_last_resort_watchdog_triggers_with_bad_dhcp = 28;
+
+  // Total count of triggers with atleast one bad other network
+  optional int32 num_last_resort_watchdog_triggers_with_bad_other = 29;
+
+  // Count of times connectivity watchdog confirmed pno is working
+  optional int32 num_connectivity_watchdog_pno_good = 30;
+
+  // Count of times connectivity watchdog found pno not working
+  optional int32 num_connectivity_watchdog_pno_bad = 31;
+
+  // Count of times connectivity watchdog confirmed background scan is working
+  optional int32 num_connectivity_watchdog_background_good = 32;
+
+  // Count of times connectivity watchdog found background scan not working
+  optional int32 num_connectivity_watchdog_background_bad = 33;
+
+  // The time duration represented by this wifi log, from start to end of capture
+  optional int32 record_duration_sec = 34;
+
+  // Counts the occurrences of each individual RSSI poll level
+  repeated RssiPollCount rssi_poll_rssi_count = 35;
+
+  // Total number of times WiFi connected immediately after a Last Resort Watchdog trigger,
+  // without new networks becoming available.
+  optional int32 num_last_resort_watchdog_successes = 36;
+
+  // Total number of saved hidden networks
+  optional int32 num_hidden_networks = 37;
+
+  // Total number of saved passpoint / hotspot 2.0 networks
+  optional int32 num_passpoint_networks = 38;
+
+  // Total number of scan results
+  optional int32 num_total_scan_results = 39;
+
+  // Total number of scan results for open networks
+  optional int32 num_open_network_scan_results = 40;
+
+  // Total number of scan results for personal networks
+  optional int32 num_personal_network_scan_results = 41;
+
+  // Total number of scan results for enterprise networks
+  optional int32 num_enterprise_network_scan_results = 42;
+
+  // Total number of scan results for hidden networks
+  optional int32 num_hidden_network_scan_results = 43;
+
+  // Total number of scan results for hotspot 2.0 r1 networks
+  optional int32 num_hotspot2_r1_network_scan_results = 44;
+
+  // Total number of scan results for hotspot 2.0 r2 networks
+  optional int32 num_hotspot2_r2_network_scan_results = 45;
+
+  // Total number of scans handled by framework (oneshot or otherwise)
+  optional int32 num_scans = 46;
+
+  // Counts the occurrences of each alert reason.
+  repeated AlertReasonCount alert_reason_count = 47;
+
+  // Counts the occurrences of each Wifi score
+  repeated WifiScoreCount wifi_score_count = 48;
+
+  // Histogram of Soft AP Durations
+  repeated SoftApDurationBucket soft_ap_duration = 49;
+
+  // Histogram of Soft AP ReturnCode
+  repeated SoftApReturnCodeCount soft_ap_return_code = 50;
+
+  // Histogram of the delta between scan result RSSI and RSSI polls
+  repeated RssiPollCount rssi_poll_delta_count = 51;
+}
+
+// Information that gets logged for every WiFi connection.
+message RouterFingerPrint {
+
+  enum RoamType {
+
+    // Type is unknown.
+    ROAM_TYPE_UNKNOWN = 0;
+
+    // No roaming - usually happens on a single band (2.4 GHz) router.
+    ROAM_TYPE_NONE = 1;
+
+    // Enterprise router.
+    ROAM_TYPE_ENTERPRISE = 2;
+
+    // DBDC => Dual Band Dual Concurrent essentially a router that
+    // supports both 2.4 GHz and 5 GHz bands.
+    ROAM_TYPE_DBDC = 3;
+  }
+
+  enum Auth {
+
+    // Auth is unknown.
+    AUTH_UNKNOWN = 0;
+
+    // No authentication.
+    AUTH_OPEN = 1;
+
+    // If the router uses a personal authentication.
+    AUTH_PERSONAL = 2;
+
+    // If the router is setup for enterprise authentication.
+    AUTH_ENTERPRISE = 3;
+  }
+
+  enum RouterTechnology {
+
+    // Router is unknown.
+    ROUTER_TECH_UNKNOWN = 0;
+
+    // Router Channel A.
+    ROUTER_TECH_A = 1;
+
+    // Router Channel B.
+    ROUTER_TECH_B = 2;
+
+    // Router Channel G.
+    ROUTER_TECH_G = 3;
+
+    // Router Channel N.
+    ROUTER_TECH_N = 4;
+
+    // Router Channel AC.
+    ROUTER_TECH_AC = 5;
+
+    // When the channel is not one of the above.
+    ROUTER_TECH_OTHER = 6;
+  }
+
+  optional RoamType roam_type = 1;
+
+  // Channel on which the connection takes place.
+  optional int32 channel_info = 2;
+
+  // DTIM setting of the router.
+  optional int32 dtim = 3;
+
+  // Authentication scheme of the router.
+  optional Auth authentication = 4;
+
+  // If the router is hidden.
+  optional bool hidden = 5;
+
+  // Channel information.
+  optional RouterTechnology router_technology = 6;
+
+  // whether ipv6 is supported.
+  optional bool supports_ipv6 = 7;
+
+  // If the router is a passpoint / hotspot 2.0 network
+  optional bool passpoint = 8;
+}
+
+message ConnectionEvent {
+
+  // Roam Type.
+  enum RoamType {
+
+    // Type is unknown.
+    ROAM_UNKNOWN = 0;
+
+    // No roaming.
+    ROAM_NONE  = 1;
+
+    // DBDC roaming.
+    ROAM_DBDC = 2;
+
+    // Enterprise roaming.
+    ROAM_ENTERPRISE = 3;
+
+    // User selected roaming.
+    ROAM_USER_SELECTED = 4;
+
+    // Unrelated.
+    ROAM_UNRELATED = 5;
+  }
+
+  // Connectivity Level Failure.
+  enum ConnectivityLevelFailure {
+
+    // Failure is unknown.
+    HLF_UNKNOWN = 0;
+
+    // No failure.
+    HLF_NONE = 1;
+
+    // DHCP failure.
+    HLF_DHCP = 2;
+
+    // No internet connection.
+    HLF_NO_INTERNET = 3;
+
+    // No internet connection.
+    HLF_UNWANTED = 4;
+  }
+
+  // Start time of the connection.
+  optional int64 start_time_millis = 1;// [(datapol.semantic_type) = ST_TIMESTAMP];
+
+  // Duration to connect.
+  optional int32 duration_taken_to_connect_millis = 2;
+
+  // Router information.
+  optional RouterFingerPrint router_fingerprint = 3;
+
+  // RSSI at the start of the connection.
+  optional int32 signal_strength = 4;
+
+  // Roam Type.
+  optional RoamType roam_type = 5;
+
+  // Result of the connection.
+  optional int32 connection_result = 6;
+
+  // Reasons for level 2 failure (needs to be coordinated with wpa-supplicant).
+  optional int32 level_2_failure_code = 7;
+
+  // Failures that happen at the connectivity layer.
+  optional ConnectivityLevelFailure connectivity_level_failure_code = 8;
+
+  // Has bug report been taken.
+  optional bool automatic_bug_report_taken = 9;
+}
+
+// Number of occurrences of a specific RSSI poll rssi value
+message RssiPollCount {
+  // RSSI
+  optional int32 rssi = 1;
+
+  // Number of RSSI polls with 'rssi'
+  optional int32 count = 2;
+}
+
+// Number of occurrences of a specific alert reason value
+message AlertReasonCount {
+  // Alert reason
+  optional int32 reason = 1;
+
+  // Number of alerts with |reason|.
+  optional int32 count = 2;
+}
+
+// Counts the number of instances of a specific Wifi Score calculated by WifiScoreReport
+message WifiScoreCount {
+  // Wifi Score
+  optional int32 score = 1;
+
+  // Number of Wifi score reports with this score
+  optional int32 count = 2;
+}
+
+// Number of occurrences of Soft AP session durations
+message SoftApDurationBucket {
+  // Bucket covers duration : [duration_sec, duration_sec + bucket_size_sec)
+  // The (inclusive) lower bound of Soft AP session duration represented by this bucket
+  optional int32 duration_sec = 1;
+
+  // The size of this bucket
+  optional int32 bucket_size_sec = 2;
+
+  // Number of soft AP session durations that fit into this bucket
+  optional int32 count = 3;
+}
+
+// Number of occurrences of a soft AP session return code
+message SoftApReturnCodeCount {
+
+  enum SoftApStartResult {
+
+    // SoftApManager return code unknown
+    SOFT_AP_RETURN_CODE_UNKNOWN = 0;
+
+    // SoftAp started successfully
+    SOFT_AP_STARTED_SUCCESSFULLY = 1;
+
+    // Catch all for failures with no specific failure reason
+    SOFT_AP_FAILED_GENERAL_ERROR = 2;
+
+    // SoftAp failed to start due to NO_CHANNEL error
+    SOFT_AP_FAILED_NO_CHANNEL = 3;
+  }
+
+  // Historical, no longer used for writing as of 01/2017.
+  optional int32 return_code = 1 [deprecated = true];
+
+  // Occurences of this soft AP return code
+  optional int32 count = 2;
+
+  // Result of attempt to start SoftAp
+  optional SoftApStartResult start_result = 3;
+}
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index 9d2f750..667bf71 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -1071,7 +1071,6 @@
             mSize += mElements[ct].mSize * mArraySizes[ct];
         }
         updateVisibleSubElements();
-        guard.open("destroy");
     }
 
     Element(long id, RenderScript rs, DataType dt, DataKind dk, boolean norm, int size) {
@@ -1091,7 +1090,6 @@
         mKind = dk;
         mNormalized = norm;
         mVectorSize = size;
-        guard.open("destroy");
     }
 
     Element(long id, RenderScript rs) {
diff --git a/rs/java/android/renderscript/Type.java b/rs/java/android/renderscript/Type.java
index 9252898..dc23785 100644
--- a/rs/java/android/renderscript/Type.java
+++ b/rs/java/android/renderscript/Type.java
@@ -227,7 +227,6 @@
 
     Type(long id, RenderScript rs) {
         super(id, rs);
-        guard.open("destroy");
     }
 
     @Override
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index bf3681b..4040db3 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -5,6 +5,7 @@
     android_renderscript_RenderScript.cpp
 
 LOCAL_SHARED_LIBRARIES := \
+    libandroid \
     libandroid_runtime \
     libandroidfw \
     libnativehelper \
@@ -19,22 +20,17 @@
 
 LOCAL_STATIC_LIBRARIES :=
 
-rs_generated_include_dir := $(call intermediates-dir-for,SHARED_LIBRARIES,libRS,,)
-
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE) \
     frameworks/rs \
     frameworks/base/core/jni \
-    frameworks/base/libs/hwui \
-    $(rs_generated_include_dir)
+    frameworks/base/libs/hwui
 
 LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
 
-LOCAL_ADDITIONAL_DEPENDENCIES := $(addprefix $(rs_generated_include_dir)/,rsgApiFuncDecl.h)
 LOCAL_MODULE:= librs_jni
-LOCAL_ADDITIONAL_DEPENDENCIES += $(rs_generated_source)
 LOCAL_MODULE_TAGS := optional
-LOCAL_REQUIRED_MODULES := libRS libRSDriver
+LOCAL_REQUIRED_MODULES := libRS
 
 include $(BUILD_SHARED_LIBRARY)
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index af370ff..b4630ef 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -34,9 +34,11 @@
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/android_util_AssetManager.h"
 #include "android/graphics/GraphicsJNI.h"
+#include "android/native_window.h"
+#include "android/native_window_jni.h"
 
-#include <rs.h>
 #include <rsEnv.h>
+#include <rsApiStubs.h>
 #include <gui/Surface.h>
 #include <gui/GLConsumer.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
@@ -1134,7 +1136,7 @@
     // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements
     assert(dataSize == 5);
 
-    uintptr_t elementData[5];
+    uint32_t elementData[5];
     rsaElementGetNativeData((RsContext)con, (RsElement)id, elementData, dataSize);
 
     for(jint i = 0; i < dataSize; i ++) {
@@ -1157,7 +1159,7 @@
 
     uintptr_t *ids = (uintptr_t*)malloc(dataSize * sizeof(uintptr_t));
     const char **names = (const char **)malloc(dataSize * sizeof(const char *));
-    uint32_t *arraySizes = (uint32_t *)malloc(dataSize * sizeof(uint32_t));
+    size_t *arraySizes = (size_t *)malloc(dataSize * sizeof(size_t));
 
     rsaElementGetSubElements((RsContext)con, (RsElement)id, ids, names, arraySizes,
                              (uint32_t)dataSize);
@@ -1264,10 +1266,10 @@
         ALOGD("nAllocationGetSurface, con(%p), a(%p)", (RsContext)con, (RsAllocation)a);
     }
 
-    IGraphicBufferProducer *v = (IGraphicBufferProducer *)rsAllocationGetSurface((RsContext)con,
-                                                                                 (RsAllocation)a);
-    sp<IGraphicBufferProducer> bp = v;
-    v->decStrong(nullptr);
+    ANativeWindow *anw = (ANativeWindow *)rsAllocationGetSurface((RsContext)con, (RsAllocation)a);
+
+    sp<Surface> surface(static_cast<Surface*>(anw));
+    sp<IGraphicBufferProducer> bp = surface->getIGraphicBufferProducer();
 
     jobject o = android_view_Surface_createFromIGraphicBufferProducer(_env, bp);
     return o;
@@ -1281,13 +1283,12 @@
               (RsAllocation)alloc, (Surface *)sur);
     }
 
-    sp<Surface> s;
+    ANativeWindow *anw = nullptr;
     if (sur != 0) {
-        s = android_view_Surface_getSurface(_env, sur);
+        anw = ANativeWindow_fromSurface(_env, sur);
     }
 
-    rsAllocationSetSurface((RsContext)con, (RsAllocation)alloc,
-                           static_cast<ANativeWindow *>(s.get()));
+    rsAllocationSetSurface((RsContext)con, (RsAllocation)alloc, anw);
 }
 
 static void
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e00178f..144d439 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -398,10 +398,12 @@
                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                     // We will update when the automation service dies.
-                    UserState userState = getCurrentUserStateLocked();
-                    if (!userState.isUiAutomationSuppressingOtherServices()) {
-                        if (readConfigurationForUserStateLocked(userState)) {
-                            onUserStateChangedLocked(userState);
+                    synchronized (mLock) {
+                        UserState userState = getCurrentUserStateLocked();
+                        if (!userState.isUiAutomationSuppressingOtherServices()) {
+                            if (readConfigurationForUserStateLocked(userState)) {
+                                onUserStateChangedLocked(userState);
+                            }
                         }
                     }
                 } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
diff --git a/services/core/Android.mk b/services/core/Android.mk
index a61743d..898eb7e 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -19,7 +19,7 @@
     system/netd/server/binder
 
 LOCAL_JAVA_LIBRARIES := services.net telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update2
+LOCAL_STATIC_JAVA_LIBRARIES := tzdata_shared2 tzdata_update2
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 
 ifneq ($(INCREMENTAL_BUILDS),)
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 4f43eac..5e9cf74 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -49,6 +49,7 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -58,6 +59,8 @@
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Slog;
 
+import com.android.server.pm.PackageManagerService;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.concurrent.ConcurrentHashMap;
@@ -78,6 +81,8 @@
     private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID="bluetooth_addr_valid";
     private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS="bluetooth_address";
     private static final String SECURE_SETTINGS_BLUETOOTH_NAME="bluetooth_name";
+    private static final String REASON_AIRPLANE_MODE = "airplane mode";
+    private static final String REASON_SYSTEM_BOOT = "system boot";
     private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
     private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save
     //Maximum msec to wait for service restart
@@ -163,7 +168,7 @@
         }
 
         public String toString() {
-            return android.text.format.DateFormat.format("MM-dd hh:mm:ss ", mTimestamp) +
+            return android.text.format.DateFormat.format("MM-dd HH:mm:ss ", mTimestamp) +
                     (mEnable ? "  Enabled " : " Disabled ") + " by " + mPackageName;
         }
 
@@ -191,19 +196,6 @@
 
     private final boolean mPermissionReviewRequired;
 
-    private void registerForAirplaneMode(IntentFilter filter) {
-        final ContentResolver resolver = mContext.getContentResolver();
-        final String airplaneModeRadios = Settings.Global.getString(resolver,
-                Settings.Global.AIRPLANE_MODE_RADIOS);
-        final String toggleableRadios = Settings.Global.getString(resolver,
-                Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
-        boolean mIsAirplaneSensitive = airplaneModeRadios == null ? true :
-                airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH);
-        if (mIsAirplaneSensitive) {
-            filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        }
-    }
-
     private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
         @Override
         public void onBluetoothStateChange(int prevState, int newState) throws RemoteException  {
@@ -217,6 +209,11 @@
         @Override
         public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
                 Bundle prevRestrictions) {
+            if (!newRestrictions.containsKey(UserManager.DISALLOW_BLUETOOTH)
+                    && !prevRestrictions.containsKey(UserManager.DISALLOW_BLUETOOTH)) {
+                // The relevant restriction has not changed - do nothing.
+                return;
+            }
             final boolean bluetoothDisallowed =
                     newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH);
             if ((mEnable || mEnableExternal) && bluetoothDisallowed) {
@@ -227,6 +224,63 @@
                   // when from system.
                 }
             }
+            updateOppLauncherComponentState(bluetoothDisallowed);
+        }
+    };
+
+    private final ContentObserver mAirplaneModeObserver = new ContentObserver(null) {
+        @Override
+        public void onChange(boolean unused) {
+            synchronized(this) {
+                if (isBluetoothPersistedStateOn()) {
+                    if (isAirplaneModeOn()) {
+                        persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
+                    } else {
+                        persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+                    }
+                }
+
+                int st = BluetoothAdapter.STATE_OFF;
+                try {
+                    mBluetoothLock.readLock().lock();
+                    if (mBluetooth != null) {
+                        st = mBluetooth.getState();
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to call getState", e);
+                    return;
+                } finally {
+                    mBluetoothLock.readLock().unlock();
+                }
+
+                Slog.d(TAG, "Airplane Mode change - current state:  " +
+                          BluetoothAdapter.nameForState(st));
+
+                if (isAirplaneModeOn()) {
+                    // Clear registered LE apps to force shut-off
+                    clearBleApps();
+
+                    // If state is BLE_ON make sure we trigger disableBLE
+                    if (st == BluetoothAdapter.STATE_BLE_ON) {
+                        try {
+                            mBluetoothLock.readLock().lock();
+                            if (mBluetooth != null) {
+                                mBluetooth.onBrEdrDown();
+                                mEnable = false;
+                                mEnableExternal = false;
+                            }
+                        } catch (RemoteException e) {
+                            Slog.e(TAG,"Unable to call onBrEdrDown", e);
+                        } finally {
+                            mBluetoothLock.readLock().unlock();
+                        }
+                    } else if (st == BluetoothAdapter.STATE_ON){
+                        sendDisableMsg(REASON_AIRPLANE_MODE);
+                    }
+                } else if (mEnableExternal) {
+                    sendEnableMsg(mQuietEnableExternal, REASON_AIRPLANE_MODE);
+                }
+            }
         }
     };
 
@@ -240,57 +294,6 @@
                 if (newName != null) {
                     storeNameAndAddress(newName, null);
                 }
-            } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
-                synchronized(mReceiver) {
-                    if (isBluetoothPersistedStateOn()) {
-                        if (isAirplaneModeOn()) {
-                            persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
-                        } else {
-                            persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
-                        }
-                    }
-
-                    int st = BluetoothAdapter.STATE_OFF;
-                    try {
-                        mBluetoothLock.readLock().lock();
-                        if (mBluetooth != null) {
-                            st = mBluetooth.getState();
-                        }
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Unable to call getState", e);
-                    } finally {
-                        mBluetoothLock.readLock().unlock();
-                    }
-                    Slog.d(TAG, "State " + BluetoothAdapter.nameForState(st));
-
-                    if (isAirplaneModeOn()) {
-                        // Clear registered LE apps to force shut-off
-                        clearBleApps();
-                        if (st == BluetoothAdapter.STATE_BLE_ON) {
-                            //if state is BLE_ON make sure you trigger disableBLE part
-                            try {
-                                mBluetoothLock.readLock().lock();
-                                if (mBluetooth != null) {
-                                    mBluetooth.onBrEdrDown();
-                                    mEnable = false;
-                                    mEnableExternal = false;
-                                }
-                            } catch (RemoteException e) {
-                                Slog.e(TAG,"Unable to call onBrEdrDown", e);
-                            } finally {
-                                mBluetoothLock.readLock().unlock();
-                            }
-                        } else if (st == BluetoothAdapter.STATE_ON){
-                            // disable without persisting the setting
-                            Slog.d(TAG, "Calling disable");
-                            sendDisableMsg("airplane mode");
-                        }
-                    } else if (mEnableExternal) {
-                        // enable without persisting the setting
-                        Slog.d(TAG, "Calling enable");
-                        sendEnableMsg(mQuietEnableExternal, "airplane mode");
-                    }
-                }
             }
         }
     };
@@ -323,7 +326,6 @@
         mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
         mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
-        registerForAirplaneMode(filter);
         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mReceiver, filter);
         loadStoredNameAndAddress();
@@ -331,6 +333,15 @@
             mEnableExternal = true;
         }
 
+        String airplaneModeRadios = Settings.Global.getString(mContentResolver,
+            Settings.Global.AIRPLANE_MODE_RADIOS);
+        if (airplaneModeRadios == null ||
+            airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) {
+            mContentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
+                true, mAirplaneModeObserver);
+        }
+
         int systemUiUid = -1;
         try {
             systemUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui",
@@ -627,6 +638,9 @@
         if (appCount == 0 && mEnable) {
             disableBleScanMode();
         }
+        if (appCount == 0 && !mEnableExternal) {
+            sendBrEdrDownCallback();
+        }
         return appCount;
     }
 
@@ -682,7 +696,14 @@
             return;
         }
 
-        if (isBleAppPresent() == false) {
+        if (isBleAppPresent()) {
+            // Need to stay at BLE ON. Disconnect all Gatt connections
+            try {
+                mBluetoothGatt.unregAll();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to disconnect all apps.", e);
+            }
+        } else {
             try {
                 mBluetoothLock.readLock().lock();
                 if (mBluetooth != null) mBluetooth.onBrEdrDown();
@@ -691,14 +712,8 @@
             } finally {
                 mBluetoothLock.readLock().unlock();
             }
-        } else {
-            // Need to stay at BLE ON. Disconnect all Gatt connections
-            try {
-                mBluetoothGatt.unregAll();
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to disconnect all apps.", e);
-            }
         }
+
     }
 
     public boolean enableNoAutoConnect(String packageName)
@@ -938,12 +953,18 @@
         UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
         userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
-        if (isBluetoothDisallowed()) {
+        final boolean isBluetoothDisallowed = isBluetoothDisallowed();
+        PackageManagerService packageManagerService =
+                (PackageManagerService) ServiceManager.getService("package");
+        if (packageManagerService != null && !packageManagerService.isOnlyCoreApps()) {
+            updateOppLauncherComponentState(isBluetoothDisallowed);
+        }
+        if (isBluetoothDisallowed) {
             return;
         }
         if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
             if (DBG) Slog.d(TAG, "Auto-enabling Bluetooth.");
-            sendEnableMsg(mQuietEnableExternal, "system boot");
+            sendEnableMsg(mQuietEnableExternal, REASON_SYSTEM_BOOT);
         } else if (!isNameAndAddressSet()) {
             if (DBG) Slog.d(TAG, "Getting adapter name and address");
             Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
@@ -1995,6 +2016,28 @@
         }
     }
 
+    /**
+     * Disables BluetoothOppLauncherActivity component, so the Bluetooth sharing option is not
+     * offered to the user if Bluetooth is disallowed. Puts the component to its default state if
+     * Bluetooth is not disallowed.
+     *
+     * @param bluetoothDisallowed whether the {@link UserManager.DISALLOW_BLUETOOTH} user
+     * restriction was set.
+     */
+    private void updateOppLauncherComponentState(boolean bluetoothDisallowed) {
+        final ComponentName oppLauncherComponent = new ComponentName("com.android.bluetooth",
+                "com.android.bluetooth.opp.BluetoothOppLauncherActivity");
+        final int newState = bluetoothDisallowed
+                ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        try {
+            mContext.getPackageManager()
+                    .setComponentEnabledSetting(oppLauncherComponent, newState, 0);
+        } catch (Exception e) {
+            // The component was not found, do nothing.
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
@@ -2018,21 +2061,32 @@
                 writer.println("  time since enabled: " + onDurationString + "\n");
             }
 
-            writer.println("Enable log:");
-            for (ActiveLog log : mActiveLogs) {
-                writer.println(log);
+            if (mActiveLogs.size() == 0) {
+                writer.println("Bluetooth never enabled!");
+            } else {
+                writer.println("Enable log:");
+                for (ActiveLog log : mActiveLogs) {
+                    writer.println("  " + log);
+                }
             }
 
-            writer.println("\n" + mBleApps.size() + " BLE Apps registered:");
+            String bleAppString = "No BLE Apps registered.";
+            if (mBleApps.size() == 1) {
+                bleAppString = "1 BLE App registered:";
+            } else if (mBleApps.size() > 1) {
+                bleAppString = mBleApps.size() + " BLE Apps registered:";
+            }
+            writer.println("\n" + bleAppString);
             for (ClientDeathRecipient app : mBleApps.values()) {
-                writer.println(app.getPackageName());
+                writer.println("  " + app.getPackageName());
             }
 
+            writer.println("");
             writer.flush();
             if (args.length == 0) {
-              // Add arg to produce output
-              args = new String[1];
-              args[0] = "--print";
+                // Add arg to produce output
+                args = new String[1];
+                args[0] = "--print";
             }
         }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b3627adc..7466f54 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -78,7 +78,7 @@
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
-import android.net.util.AvoidBadWifiTracker;
+import android.net.util.MultinetworkPolicyTracker;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -395,16 +395,6 @@
      */
     private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
 
-    /**
-     * Indicates a caller has requested to have its callback invoked with
-     * the latest LinkProperties or NetworkCapabilities.
-     *
-     * arg1 = UID of caller
-     * obj  = NetworkRequest
-     */
-    private static final int EVENT_REQUEST_LINKPROPERTIES  = 32;
-    private static final int EVENT_REQUEST_NETCAPABILITIES = 33;
-
     /** Handler thread used for both of the handlers below. */
     @VisibleForTesting
     protected final HandlerThread mHandlerThread;
@@ -499,7 +489,7 @@
     private final IpConnectivityLog mMetricsLog;
 
     @VisibleForTesting
-    final AvoidBadWifiTracker mAvoidBadWifiTracker;
+    final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
 
     /**
      * Implements support for the legacy "one network per network type" model.
@@ -690,11 +680,6 @@
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
-    @VisibleForTesting
-    protected HandlerThread createHandlerThread() {
-        return new HandlerThread("ConnectivityServiceThread");
-    }
-
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -715,7 +700,7 @@
         mDefaultMobileDataRequest = createInternetRequestForTransport(
                 NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST);
 
-        mHandlerThread = createHandlerThread();
+        mHandlerThread = new HandlerThread("ConnectivityServiceThread");
         mHandlerThread.start();
         mHandler = new InternalHandler(mHandlerThread.getLooper());
         mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper());
@@ -820,6 +805,8 @@
         intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+        mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM,
+                new IntentFilter(Intent.ACTION_USER_PRESENT), null, null);
 
         try {
             mNetd.registerObserver(mTethering);
@@ -854,9 +841,9 @@
                 LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS);
         mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
 
-        mAvoidBadWifiTracker = createAvoidBadWifiTracker(
+        mMultinetworkPolicyTracker = createMultinetworkPolicyTracker(
                 mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
-        mAvoidBadWifiTracker.start();
+        mMultinetworkPolicyTracker.start();
     }
 
     private NetworkRequest createInternetRequestForTransport(
@@ -2577,34 +2564,6 @@
         return nri;
     }
 
-    private void handleRequestCallbackUpdate(NetworkRequest request, int callingUid,
-            String description, int callbackType) {
-        final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, description);
-        if (nri == null) return;
-
-        final NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
-        // The network that is satisfying this request may have changed since
-        // the application requested the update.
-        //
-        // - If the request is no longer satisfied, don't send any updates.
-        // - If the request is satisfied by a different network, it is the
-        //   caller's responsibility to check that the Network object in the
-        //   callback matches the network that was returned in the last
-        //   onAvailable() callback for this request.
-        if (nai == null) return;
-        callCallbackForRequest(nri, nai, callbackType, 0);
-    }
-
-    private void handleRequestLinkProperties(NetworkRequest request, int callingUid) {
-        handleRequestCallbackUpdate(request, callingUid,
-                "request LinkProperties", ConnectivityManager.CALLBACK_IP_CHANGED);
-    }
-
-    private void handleRequestNetworkCapabilities(NetworkRequest request, int callingUid) {
-        handleRequestCallbackUpdate(request, callingUid,
-                "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED);
-    }
-
     private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
         if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
                 nri.request.requestId) == null) {
@@ -2798,7 +2757,7 @@
     }
 
     public boolean avoidBadWifi() {
-        return mAvoidBadWifiTracker.currentValue();
+        return mMultinetworkPolicyTracker.getAvoidBadWifi();
     }
 
     private void rematchForAvoidBadWifiUpdate() {
@@ -2811,9 +2770,9 @@
     }
 
     // TODO: Evaluate whether this is of interest to other consumers of
-    // AvoidBadWifiTracker and worth moving out of here.
+    // MultinetworkPolicyTracker and worth moving out of here.
     private void dumpAvoidBadWifiSettings(IndentingPrintWriter pw) {
-        final boolean configRestrict = mAvoidBadWifiTracker.configRestrictsAvoidBadWifi();
+        final boolean configRestrict = mMultinetworkPolicyTracker.configRestrictsAvoidBadWifi();
         if (!configRestrict) {
             pw.println("Bad Wi-Fi avoidance: unrestricted");
             return;
@@ -2823,7 +2782,7 @@
         pw.increaseIndent();
         pw.println("Config restrict:   " + configRestrict);
 
-        final String value = mAvoidBadWifiTracker.getSettingsValue();
+        final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
         String description;
         // Can't use a switch statement because strings are legal case labels, but null is not.
         if ("0".equals(value)) {
@@ -2891,7 +2850,7 @@
         if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
 
         if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
-            mAvoidBadWifiTracker.shouldNotifyWifiUnvalidated()) {
+            mMultinetworkPolicyTracker.shouldNotifyWifiUnvalidated()) {
             showValidationNotification(nai, NotificationType.LOST_INTERNET);
         }
     }
@@ -2986,12 +2945,6 @@
                     handleMobileDataAlwaysOn();
                     break;
                 }
-                case EVENT_REQUEST_LINKPROPERTIES:
-                    handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1);
-                    break;
-                case EVENT_REQUEST_NETCAPABILITIES:
-                    handleRequestNetworkCapabilities((NetworkRequest) msg.obj, msg.arg1);
-                    break;
                 // Sent by KeepaliveTracker to process an app request on the state machine thread.
                 case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
                     mKeepaliveTracker.handleStartKeepalive(msg);
@@ -3130,15 +3083,11 @@
                 Settings.Global.TETHER_SUPPORTED, defaultVal) != 0)
                 && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
         return tetherEnabledInSettings && mUserManager.isAdminUser() &&
-                ((mTethering.getTetherableUsbRegexs().length != 0 ||
-                mTethering.getTetherableWifiRegexs().length != 0 ||
-                mTethering.getTetherableBluetoothRegexs().length != 0) &&
-                mTethering.getUpstreamIfaceTypes().length != 0);
+               mTethering.hasTetherableConfiguration();
     }
 
     @Override
-    public void startTethering(int type, ResultReceiver receiver,
-            boolean showProvisioningUi) {
+    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
         ConnectivityManager.enforceTetherChangePermission(mContext);
         if (!isTetheringSupported()) {
             receiver.send(ConnectivityManager.TETHER_ERROR_UNSUPPORTED, null);
@@ -3669,7 +3618,12 @@
         // Tear down existing lockdown if profile was removed
         mLockdownEnabled = LockdownVpnTracker.isEnabled();
         if (mLockdownEnabled) {
-            final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+            byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+            if (profileTag == null) {
+                Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
+                return false;
+            }
+            String profileName = new String(profileTag);
             final VpnProfile profile = VpnProfile.decode(
                     profileName, mKeyStore.get(Credentials.VPN + profileName));
             if (profile == null) {
@@ -4008,6 +3962,16 @@
         }
     };
 
+    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Try creating lockdown tracker, since user present usually means
+            // unlocked keystore.
+            updateLockdownVpn();
+            mContext.unregisterReceiver(this);
+        }
+    };
+
     private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
             new HashMap<Messenger, NetworkFactoryInfo>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests =
@@ -4171,7 +4135,7 @@
         }
         ensureRequestableCapabilities(networkCapabilities);
 
-        if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
+        if (timeoutMs < 0) {
             throw new IllegalArgumentException("Bad timeout specified");
         }
 
@@ -4340,22 +4304,6 @@
     }
 
     @Override
-    public void requestLinkProperties(NetworkRequest networkRequest) {
-        ensureNetworkRequestHasType(networkRequest);
-        if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
-        mHandler.sendMessage(mHandler.obtainMessage(
-                EVENT_REQUEST_LINKPROPERTIES, getCallingUid(), 0, networkRequest));
-    }
-
-    @Override
-    public void requestNetworkCapabilities(NetworkRequest networkRequest) {
-        ensureNetworkRequestHasType(networkRequest);
-        if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
-        mHandler.sendMessage(mHandler.obtainMessage(
-                EVENT_REQUEST_NETCAPABILITIES, getCallingUid(), 0, networkRequest));
-    }
-
-    @Override
     public void releaseNetworkRequest(NetworkRequest networkRequest) {
         ensureNetworkRequestHasType(networkRequest);
         mHandler.sendMessage(mHandler.obtainMessage(
@@ -4847,7 +4795,7 @@
             if (!nr.isListen()) continue;
             if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
                 nai.addRequest(nr);
-                notifyNetworkCallback(nai, nri);
+                notifyNetworkAvailable(nai, nri);
             }
         }
     }
@@ -5029,7 +4977,7 @@
 
         // do this after the default net is switched, but
         // before LegacyTypeTracker sends legacy broadcasts
-        for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
+        for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
 
         // Linger any networks that are no longer needed. This should be done after sending the
         // available callback for newNetwork.
@@ -5192,7 +5140,7 @@
     }
 
     private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
-        NetworkInfo.State state = newInfo.getState();
+        final NetworkInfo.State state = newInfo.getState();
         NetworkInfo oldInfo = null;
         final int oldScore = networkAgent.getCurrentScore();
         synchronized (networkAgent) {
@@ -5319,15 +5267,27 @@
         sendUpdatedScoreToFactories(nai);
     }
 
-    // notify only this one new request of the current state
-    protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
-        int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+    // Notify only this one new request of the current state. Transfer all the
+    // current state by calling NetworkCapabilities and LinkProperties callbacks
+    // so that callers can be guaranteed to have as close to atomicity in state
+    // transfer as can be supported by this current API.
+    protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
         mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
-        if (nri.mPendingIntent == null) {
-            callCallbackForRequest(nri, nai, notifyType, 0);
-        } else {
-            sendPendingIntentForRequest(nri, nai, notifyType);
+        if (nri.mPendingIntent != null) {
+            sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
+            // Attempt no subsequent state pushes where intents are involved.
+            return;
         }
+
+        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0);
+        // Whether a network is currently suspended is also an important
+        // element of state to be transferred (it would not otherwise be
+        // delivered by any currently available mechanism).
+        if (nai.networkInfo.getState() == NetworkInfo.State.SUSPENDED) {
+            callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_SUSPENDED, 0);
+        }
+        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_CAP_CHANGED, 0);
+        callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_IP_CHANGED, 0);
     }
 
     private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
@@ -5504,6 +5464,18 @@
                 }
             }
 
+            // Turn Always-on VPN off
+            if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+                    mLockdownEnabled = false;
+                    setLockdownTracker(null);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
             // Turn VPN off
             VpnConfig vpnConfig = getVpnConfig(userId);
             if (vpnConfig != null) {
@@ -5530,8 +5502,8 @@
     }
 
     @VisibleForTesting
-    AvoidBadWifiTracker createAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
-        return new AvoidBadWifiTracker(c, h, r);
+    MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
+        return new MultinetworkPolicyTracker(c, h, r);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index e23844c..e8ecc3e 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -42,15 +42,22 @@
 import android.net.ScoredNetwork;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Pair;
 import android.util.TimedRemoteCaller;
 
 import com.android.internal.annotations.GuardedBy;
@@ -67,6 +74,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 /**
@@ -75,21 +84,24 @@
  */
 public class NetworkScoreService extends INetworkScoreService.Stub {
     private static final String TAG = "NetworkScoreService";
-    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean DBG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
 
     private final Context mContext;
     private final NetworkScorerAppManager mNetworkScorerAppManager;
-    private final RequestRecommendationCaller mRequestRecommendationCaller;
+    private final AtomicReference<RequestRecommendationCaller> mReqRecommendationCallerRef;
     @GuardedBy("mScoreCaches")
     private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
     /** Lock used to update mPackageMonitor when scorer package changes occur. */
-    private final Object mPackageMonitorLock = new Object[0];
-    private final Object mServiceConnectionLock = new Object[0];
+    private final Object mPackageMonitorLock = new Object();
+    private final Object mServiceConnectionLock = new Object();
+    private final Handler mHandler;
+    private final DispatchingContentObserver mContentObserver;
 
     @GuardedBy("mPackageMonitorLock")
     private NetworkScorerPackageMonitor mPackageMonitor;
     @GuardedBy("mServiceConnectionLock")
     private ScoringServiceConnection mServiceConnection;
+    private volatile long mRecommendationRequestTimeoutMs;
 
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -185,12 +197,25 @@
     }
 
     /**
-     * Reevaluates the service binding when the Settings toggle is changed.
+     * Dispatches observed content changes to a handler for further processing.
      */
-    private class SettingsObserver extends ContentObserver {
+    @VisibleForTesting
+    public static class DispatchingContentObserver extends ContentObserver {
+        final private Map<Uri, Integer> mUriEventMap;
+        final private Context mContext;
+        final private Handler mHandler;
 
-        public SettingsObserver() {
-            super(null /*handler*/);
+        public DispatchingContentObserver(Context context, Handler handler) {
+            super(handler);
+            mContext = context;
+            mHandler = handler;
+            mUriEventMap = new ArrayMap<>();
+        }
+
+        void observe(Uri uri, int what) {
+            mUriEventMap.put(uri, what);
+            final ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(uri, false /*notifyForDescendants*/, this);
         }
 
         @Override
@@ -201,16 +226,22 @@
         @Override
         public void onChange(boolean selfChange, Uri uri) {
             if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
-            bindToScoringServiceIfNeeded();
+            final Integer what = mUriEventMap.get(uri);
+            if (what != null) {
+                mHandler.obtainMessage(what).sendToTarget();
+            } else {
+                Log.w(TAG, "No matching event to send for URI = " + uri);
+            }
         }
     }
 
     public NetworkScoreService(Context context) {
-      this(context, new NetworkScorerAppManager(context));
+      this(context, new NetworkScorerAppManager(context), Looper.myLooper());
     }
 
     @VisibleForTesting
-    NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
+    NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager,
+            Looper looper) {
         mContext = context;
         mNetworkScorerAppManager = networkScoreAppManager;
         mScoreCaches = new ArrayMap<>();
@@ -219,16 +250,19 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
                 null /* scheduler */);
-        // TODO(jjoslin): 12/15/16 - Make timeout configurable.
-        mRequestRecommendationCaller =
-            new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+        mReqRecommendationCallerRef = new AtomicReference<>(
+                new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS));
+        mRecommendationRequestTimeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
+        mHandler = new ServiceHandler(looper);
+        mContentObserver = new DispatchingContentObserver(context, mHandler);
     }
 
     /** Called when the system is ready to run third-party code but before it actually does so. */
     void systemReady() {
         if (DBG) Log.d(TAG, "systemReady");
         registerPackageMonitorIfNeeded();
-        registerRecommendationSettingObserverIfNeeded();
+        registerRecommendationSettingsObserver();
+        refreshRecommendationRequestTimeoutMs();
     }
 
     /** Called when the system is ready for us to start third-party code. */
@@ -242,14 +276,18 @@
         bindToScoringServiceIfNeeded();
     }
 
-    private void registerRecommendationSettingObserverIfNeeded() {
+    private void registerRecommendationSettingsObserver() {
         final List<String> providerPackages =
             mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
         if (!providerPackages.isEmpty()) {
-            final ContentResolver resolver = mContext.getContentResolver();
-            final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
-            resolver.registerContentObserver(uri, false, new SettingsObserver());
+            final Uri enabledUri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
+            mContentObserver.observe(enabledUri,
+                    ServiceHandler.MSG_RECOMMENDATIONS_ENABLED_CHANGED);
         }
+
+        final Uri timeoutUri = Global.getUriFor(Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
+        mContentObserver.observe(timeoutUri,
+                ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED);
     }
 
     private void registerPackageMonitorIfNeeded() {
@@ -532,7 +570,8 @@
             final INetworkRecommendationProvider provider = getRecommendationProvider();
             if (provider != null) {
                 try {
-                    return mRequestRecommendationCaller.getRecommendationResult(provider, request);
+                    final RequestRecommendationCaller caller = mReqRecommendationCallerRef.get();
+                    return caller.getRecommendationResult(provider, request);
                 } catch (RemoteException | TimeoutException e) {
                     Log.w(TAG, "Failed to request a recommendation.", e);
                     // TODO(jjoslin): 12/15/16 - Keep track of failures.
@@ -543,9 +582,9 @@
                 Log.d(TAG, "Returning the default network recommendation.");
             }
 
-            if (request != null && request.getCurrentSelectedConfig() != null) {
+            if (request != null && request.getDefaultWifiConfig() != null) {
                 return RecommendationResult.createConnectRecommendation(
-                        request.getCurrentSelectedConfig());
+                        request.getDefaultWifiConfig());
             }
             return RecommendationResult.createDoNotConnectRecommendation();
         } finally {
@@ -553,6 +592,56 @@
         }
     }
 
+    /**
+     * Request a recommendation for the best network to connect to
+     * taking into account the inputs from the {@link RecommendationRequest}.
+     *
+     * @param request a {@link RecommendationRequest} instance containing the details of the request
+     * @param remoteCallback a {@link IRemoteCallback} instance to invoke when the recommendation
+     *                       is available.
+     * @throws SecurityException if the caller is not the system
+     */
+    @Override
+    public void requestRecommendationAsync(RecommendationRequest request,
+            RemoteCallback remoteCallback) {
+        mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
+
+        final OneTimeCallback oneTimeCallback = new OneTimeCallback(remoteCallback);
+        final Pair<RecommendationRequest, OneTimeCallback> pair =
+                Pair.create(request, oneTimeCallback);
+        final Message timeoutMsg = mHandler.obtainMessage(
+                ServiceHandler.MSG_RECOMMENDATION_REQUEST_TIMEOUT, pair);
+        final INetworkRecommendationProvider provider = getRecommendationProvider();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (provider != null) {
+                try {
+                    mHandler.sendMessageDelayed(timeoutMsg, mRecommendationRequestTimeoutMs);
+                    provider.requestRecommendation(request, new IRemoteCallback.Stub() {
+                        @Override
+                        public void sendResult(Bundle data) throws RemoteException {
+                            // Remove the timeout message
+                            mHandler.removeMessages(timeoutMsg.what, pair);
+                            oneTimeCallback.sendResult(data);
+                        }
+                    }, 0 /*sequence*/);
+                    return;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to request a recommendation.", e);
+                    // TODO(jjoslin): 12/15/16 - Keep track of failures.
+                    // Remove the timeout message
+                    mHandler.removeMessages(timeoutMsg.what, pair);
+                    // Will fall through and send back the default recommendation.
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        // Else send back the default recommendation.
+        sendDefaultRecommendationResponse(request, oneTimeCallback);
+    }
+
     @Override
     public boolean requestScores(NetworkKey[] networks) {
         mContext.enforceCallingOrSelfPermission(permission.REQUEST_NETWORK_SCORES, TAG);
@@ -651,6 +740,19 @@
         return null;
     }
 
+    @VisibleForTesting
+    public void refreshRecommendationRequestTimeoutMs() {
+        final ContentResolver cr = mContext.getContentResolver();
+        long timeoutMs = Settings.Global.getLong(cr,
+                Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L /*default*/);
+        if (timeoutMs < 0) {
+            timeoutMs = TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS;
+        }
+        if (DBG) Log.d(TAG, "Updating the recommendation request timeout to " + timeoutMs + " ms");
+        mRecommendationRequestTimeoutMs = timeoutMs;
+        mReqRecommendationCallerRef.set(new RequestRecommendationCaller(timeoutMs));
+    }
+
     private static class ScoringServiceConnection implements ServiceConnection {
         private final ComponentName mComponentName;
         private final int mScoringAppUid;
@@ -756,4 +858,83 @@
             return getResultTimed(sequence);
         }
     }
+
+    /**
+     * A wrapper around {@link RemoteCallback} that guarantees
+     * {@link RemoteCallback#sendResult(Bundle)} will be invoked at most once.
+     */
+    @VisibleForTesting
+    public static final class OneTimeCallback {
+        private final RemoteCallback mRemoteCallback;
+        private final AtomicBoolean mCallbackRun;
+
+        public OneTimeCallback(RemoteCallback remoteCallback) {
+            mRemoteCallback = remoteCallback;
+            mCallbackRun = new AtomicBoolean(false);
+        }
+
+        public void sendResult(Bundle data) {
+            if (mCallbackRun.compareAndSet(false, true)) {
+                mRemoteCallback.sendResult(data);
+            }
+        }
+    }
+
+    private static void sendDefaultRecommendationResponse(RecommendationRequest request,
+            OneTimeCallback remoteCallback) {
+        if (DBG) {
+            Log.d(TAG, "Returning the default network recommendation.");
+        }
+
+        final RecommendationResult result;
+        if (request != null && request.getDefaultWifiConfig() != null) {
+            result = RecommendationResult.createConnectRecommendation(
+                    request.getDefaultWifiConfig());
+        } else {
+            result = RecommendationResult.createDoNotConnectRecommendation();
+        }
+
+        final Bundle data = new Bundle();
+        data.putParcelable(EXTRA_RECOMMENDATION_RESULT, result);
+        remoteCallback.sendResult(data);
+    }
+
+    @VisibleForTesting
+    public final class ServiceHandler extends Handler {
+        public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT = 1;
+        public static final int MSG_RECOMMENDATIONS_ENABLED_CHANGED = 2;
+        public static final int MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED = 3;
+
+        public ServiceHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final int what = msg.what;
+            switch (what) {
+                case MSG_RECOMMENDATION_REQUEST_TIMEOUT:
+                    if (DBG) {
+                        Log.d(TAG, "Network recommendation request timed out.");
+                    }
+                    final Pair<RecommendationRequest, OneTimeCallback> pair =
+                            (Pair<RecommendationRequest, OneTimeCallback>) msg.obj;
+                    final RecommendationRequest request = pair.first;
+                    final OneTimeCallback remoteCallback = pair.second;
+                    sendDefaultRecommendationResponse(request, remoteCallback);
+                    break;
+
+                case MSG_RECOMMENDATIONS_ENABLED_CHANGED:
+                    bindToScoringServiceIfNeeded();
+                    break;
+
+                case MSG_RECOMMENDATION_REQUEST_TIMEOUT_CHANGED:
+                    refreshRecommendationRequestTimeoutMs();
+                    break;
+
+                default:
+                    Log.w(TAG,"Unknown message: " + what);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 62f4f19..82e6b42 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
@@ -142,6 +141,10 @@
 
     private ServiceState[] mServiceState;
 
+    private int[] mVoiceActivationState;
+
+    private int[] mDataActivationState;
+
     private SignalStrength[] mSignalStrength;
 
     private boolean[] mMessageWaiting;
@@ -301,6 +304,8 @@
         mDataConnectionNetworkType = new int[numPhones];
         mCallIncomingNumber = new String[numPhones];
         mServiceState = new ServiceState[numPhones];
+        mVoiceActivationState = new int[numPhones];
+        mDataActivationState = new int[numPhones];
         mSignalStrength = new SignalStrength[numPhones];
         mMessageWaiting = new boolean[numPhones];
         mDataConnectionPossible = new boolean[numPhones];
@@ -315,6 +320,8 @@
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
             mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
             mDataConnectionState[i] = TelephonyManager.DATA_UNKNOWN;
+            mVoiceActivationState[i] = TelephonyManager.SIM_ACTIVATION_STATE_UNKNOWN;
+            mDataActivationState[i] = TelephonyManager.SIM_ACTIVATION_STATE_UNKNOWN;
             mCallIncomingNumber[i] =  "";
             mServiceState[i] =  new ServiceState();
             mSignalStrength[i] =  new SignalStrength();
@@ -644,6 +651,20 @@
                             remove(r.binder);
                         }
                     }
+                    if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) !=0) {
+                        try {
+                            r.callback.onVoiceActivationStateChanged(mVoiceActivationState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) !=0) {
+                        try {
+                            r.callback.onDataActivationStateChanged(mDataActivationState[phoneId]);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
                 }
             }
         } else {
@@ -795,6 +816,67 @@
         broadcastServiceStateChanged(state, phoneId, subId);
     }
 
+    public void notifySimActivationStateChangedForPhoneId(int phoneId, int subId,
+            int activationType, int activationState) {
+        if (!checkNotifyPermission("notifySimActivationState()")){
+            return;
+        }
+        if (VDBG) {
+            log("notifySimActivationStateForPhoneId: subId=" + subId + " phoneId=" + phoneId
+                    + "type=" + activationType + " state=" + activationState);
+        }
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                switch (activationType) {
+                    case PhoneConstants.SIM_ACTIVATION_TYPE_VOICE:
+                        mVoiceActivationState[phoneId] = activationState;
+                        break;
+                    case PhoneConstants.SIM_ACTIVATION_TYPE_DATA:
+                        mDataActivationState[phoneId] = activationState;
+                        break;
+                    default:
+                        return;
+                }
+                for (Record r : mRecords) {
+                    if (VDBG) {
+                        log("notifySimActivationStateForPhoneId: r=" + r + " subId=" + subId
+                                + " phoneId=" + phoneId + "type=" + activationType
+                                + " state=" + activationState);
+                    }
+                    try {
+                        if ((activationType == PhoneConstants.SIM_ACTIVATION_TYPE_VOICE) &&
+                                r.matchPhoneStateListenerEvent(
+                                        PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) &&
+                                idMatch(r.subId, subId, phoneId)) {
+                            if (DBG) {
+                                log("notifyVoiceActivationStateForPhoneId: callback.onVASC r=" + r
+                                        + " subId=" + subId + " phoneId=" + phoneId
+                                        + " state=" + activationState);
+                            }
+                            r.callback.onVoiceActivationStateChanged(activationState);
+                        }
+                        if ((activationType == PhoneConstants.SIM_ACTIVATION_TYPE_DATA) &&
+                                r.matchPhoneStateListenerEvent(
+                                        PhoneStateListener.LISTEN_DATA_ACTIVATION_STATE) &&
+                                idMatch(r.subId, subId, phoneId)) {
+                            if (DBG) {
+                                log("notifyDataActivationStateForPhoneId: callback.onDASC r=" + r
+                                        + " subId=" + subId + " phoneId=" + phoneId
+                                        + " state=" + activationState);
+                            }
+                            r.callback.onDataActivationStateChanged(activationState);
+                        }
+                    }  catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            } else {
+                log("notifySimActivationStateForPhoneId: INVALID phoneId=" + phoneId);
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     public void notifySignalStrengthForPhoneId(int phoneId, int subId,
                 SignalStrength signalStrength) {
         if (!checkNotifyPermission("notifySignalStrength()")) {
@@ -1324,6 +1406,8 @@
                 pw.println("  mCallState=" + mCallState[i]);
                 pw.println("  mCallIncomingNumber=" + mCallIncomingNumber[i]);
                 pw.println("  mServiceState=" + mServiceState[i]);
+                pw.println("  mVoiceActivationState= " + mVoiceActivationState[i]);
+                pw.println("  mDataActivationState= " + mDataActivationState[i]);
                 pw.println("  mSignalStrength=" + mSignalStrength[i]);
                 pw.println("  mMessageWaiting=" + mMessageWaiting[i]);
                 pw.println("  mCallForwarding=" + mCallForwarding[i]);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bc03901..1feaa72 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1381,6 +1381,7 @@
     ParcelFileDescriptor mProfileFd;
     int mSamplingInterval = 0;
     boolean mAutoStopProfiler = false;
+    boolean mStreamingOutput = false;
     int mProfileType = 0;
     final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>();
     String mMemWatchDumpProcName;
@@ -3752,7 +3753,8 @@
             }
             int debugFlags = 0;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
-                debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+                debugFlags |= Zygote.DEBUG_ENABLE_JDWP;
+                debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
                 // Also turn on CheckJNI for debuggable apps. It's quite
                 // awkward to turn on otherwise.
                 debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
@@ -5355,7 +5357,8 @@
                     for (int pid : pids) {
                         if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
                         final long sime = SystemClock.elapsedRealtime();
-                        Debug.dumpNativeBacktraceToFile(pid, tracesPath);
+
+                        Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
                         if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
                                 + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
                     }
@@ -6571,12 +6574,14 @@
             ParcelFileDescriptor profileFd = null;
             int samplingInterval = 0;
             boolean profileAutoStop = false;
+            boolean profileStreamingOutput = false;
             if (mProfileApp != null && mProfileApp.equals(processName)) {
                 mProfileProc = app;
                 profileFile = mProfileFile;
                 profileFd = mProfileFd;
                 samplingInterval = mSamplingInterval;
                 profileAutoStop = mAutoStopProfiler;
+                profileStreamingOutput = mStreamingOutput;
             }
             boolean enableTrackAllocation = false;
             if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) {
@@ -6606,7 +6611,8 @@
                 profileFd = profileFd.dup();
             }
             ProfilerInfo profilerInfo = profileFile == null ? null
-                    : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
+                    : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop,
+                                       profileStreamingOutput);
             thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                     profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                     app.instrumentationUiAutomationConnection, testMode,
@@ -12039,6 +12045,7 @@
             mProfileFd = profilerInfo.profileFd;
             mSamplingInterval = profilerInfo.samplingInterval;
             mAutoStopProfiler = profilerInfo.autoStopProfiler;
+            mStreamingOutput = profilerInfo.streamingOutput;
             mProfileType = 0;
         }
     }
@@ -14920,7 +14927,7 @@
                 pw.println("  mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc);
                 pw.println("  mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd);
                 pw.println("  mSamplingInterval=" + mSamplingInterval + " mAutoStopProfiler="
-                        + mAutoStopProfiler);
+                        + mAutoStopProfiler + " mStreamingOutput=" + mStreamingOutput);
                 pw.println("  mProfileType=" + mProfileType);
             }
         }
@@ -17491,6 +17498,7 @@
                 // Not backing this app up any more; reset its OOM adjustment
                 final ProcessRecord proc = mBackupTarget.app;
                 updateOomAdjLocked(proc);
+                proc.inFullBackup = false;
 
                 // If the app crashed during backup, 'thread' will be null here
                 if (proc.thread != null) {
@@ -21438,6 +21446,7 @@
         mProfileFile = null;
         mProfileType = 0;
         mAutoStopProfiler = false;
+        mStreamingOutput = false;
         mSamplingInterval = 0;
     }
 
@@ -22269,4 +22278,29 @@
         // before the profile user is unlocked.
         return rInfo != null && rInfo.activityInfo != null;
     }
+
+    /**
+     * Attach an agent to the specified process (proces name or PID)
+     */
+    public void attachAgent(String process, String path) {
+        try {
+            synchronized (this) {
+                ProcessRecord proc = findProcessLocked(process, UserHandle.USER_SYSTEM, "attachAgent");
+                if (proc == null || proc.thread == null) {
+                    throw new IllegalArgumentException("Unknown process: " + process);
+                }
+
+                boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+                if (!isDebuggable) {
+                    if ((proc.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                        throw new SecurityException("Process not debuggable: " + proc);
+                    }
+                }
+
+                proc.thread.attachAgent(path);
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Process disappeared");
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index adf6d36..2d0ccbd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -66,6 +66,8 @@
                     return runLenientBackgroundCheck(pw);
                 case "get-uid-state":
                     return getUidState(pw);
+                case "attach-agent":
+                    return runAttachAgent(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -183,6 +185,21 @@
         return 0;
     }
 
+    int runAttachAgent(PrintWriter pw) {
+        // TODO: revisit the permissions required for attaching agents
+        mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "attach-agent");
+        String process = getNextArgRequired();
+        String agent = getNextArgRequired();
+        String opt;
+        if ((opt = getNextArg()) != null) {
+            pw.println("Error: Unknown option: " + opt);
+            return -1;
+        }
+        mInternal.attachAgent(process, agent);
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         PrintWriter pw = getOutPrintWriter();
@@ -241,6 +258,8 @@
             pw.println("    Optionally controls lenient background check mode, returns current mode.");
             pw.println("  get-uid-state <UID>");
             pw.println("    Gets the process state of an app given its <UID>.");
+            pw.println("  attach-agent <PROCESS> <FILE>");
+            pw.println("    Attach an agent to the specified <PROCESS>, which may be either a process name or a PID.");
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c6ab918..2262697 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1296,7 +1296,8 @@
                         }
 
                         profilerInfo = new ProfilerInfo(profileFile, profileFd,
-                                mService.mSamplingInterval, mService.mAutoStopProfiler);
+                                mService.mSamplingInterval, mService.mAutoStopProfiler,
+                                mService.mStreamingOutput);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java b/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
deleted file mode 100644
index 1c9feb2..0000000
--- a/services/core/java/com/android/server/connectivity/MetricsLoggerService.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.SystemService;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityMetricsEvent;
-import android.net.ConnectivityMetricsLogger;
-import android.net.IConnectivityMetricsLogger;
-import android.os.Binder;
-import android.os.Parcel;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-
-/** {@hide} */
-public class MetricsLoggerService extends SystemService {
-    private static String TAG = "ConnectivityMetricsLoggerService";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    public MetricsLoggerService(Context context) {
-        super(context);
-    }
-
-    @Override
-    public void onStart() {
-        resetThrottlingCounters(System.currentTimeMillis());
-    }
-
-    @Override
-    public void onBootPhase(int phase) {
-        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
-            if (DBG) Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
-            publishBinderService(ConnectivityMetricsLogger.CONNECTIVITY_METRICS_LOGGER_SERVICE,
-                    mBinder);
-        }
-    }
-
-    // TODO: read these constants from system property
-    private final int EVENTS_NOTIFICATION_THRESHOLD                   = 300;
-    private final int MAX_NUMBER_OF_EVENTS                            = 1000;
-    private final int THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT = 1000;
-    private final long THROTTLING_TIME_INTERVAL_MILLIS                = DateUtils.HOUR_IN_MILLIS;
-
-    private int mEventCounter = 0;
-
-    /**
-     * Reference of the last event in the list of cached events.
-     *
-     * When client of this service retrieves events by calling getEvents, it is passing
-     * ConnectivityMetricsEvent.Reference object. After getEvents returns, that object will
-     * contain this reference. The client can save it and use next time it calls getEvents.
-     * This way only new events will be returned.
-     */
-    private long mLastEventReference = 0;
-
-    private final int mThrottlingCounters[] =
-            new int[ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS];
-
-    private long mThrottlingIntervalBoundaryMillis;
-
-    private final ArrayDeque<ConnectivityMetricsEvent> mEvents = new ArrayDeque<>();
-
-    private void enforceConnectivityInternalPermission() {
-        getContext().enforceCallingOrSelfPermission(
-                android.Manifest.permission.CONNECTIVITY_INTERNAL,
-                "MetricsLoggerService");
-    }
-
-    private void enforceDumpPermission() {
-        getContext().enforceCallingOrSelfPermission(
-                android.Manifest.permission.DUMP,
-                "MetricsLoggerService");
-    }
-
-    private void resetThrottlingCounters(long currentTimeMillis) {
-        synchronized (mThrottlingCounters) {
-            for (int i = 0; i < mThrottlingCounters.length; i++) {
-                mThrottlingCounters[i] = 0;
-            }
-            mThrottlingIntervalBoundaryMillis =
-                    currentTimeMillis + THROTTLING_TIME_INTERVAL_MILLIS;
-        }
-    }
-
-    private void addEvent(ConnectivityMetricsEvent e) {
-        if (VDBG) {
-            Log.v(TAG, "writeEvent(" + e.toString() + ")");
-        }
-
-        while (mEvents.size() >= MAX_NUMBER_OF_EVENTS) {
-            mEvents.removeFirst();
-        }
-
-        mEvents.addLast(e);
-    }
-
-    @VisibleForTesting
-    final MetricsLoggerImpl mBinder = new MetricsLoggerImpl();
-
-    /**
-     * Implementation of the IConnectivityMetricsLogger interface.
-     */
-    final class MetricsLoggerImpl extends IConnectivityMetricsLogger.Stub {
-
-        private final ArrayList<PendingIntent> mPendingIntents = new ArrayList<>();
-
-        @Override
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
-                    != PackageManager.PERMISSION_GRANTED) {
-                pw.println("Permission Denial: can't dump ConnectivityMetricsLoggerService " +
-                        "from from pid=" + Binder.getCallingPid() + ", uid=" +
-                        Binder.getCallingUid());
-                return;
-            }
-
-            boolean dumpSerializedSize = false;
-            boolean dumpEvents = false;
-            boolean dumpDebugInfo = false;
-            for (String arg : args) {
-                switch (arg) {
-                    case "--debug":
-                        dumpDebugInfo = true;
-                        break;
-
-                    case "--events":
-                        dumpEvents = true;
-                        break;
-
-                    case "--size":
-                        dumpSerializedSize = true;
-                        break;
-
-                    case "--all":
-                        dumpDebugInfo = true;
-                        dumpEvents = true;
-                        dumpSerializedSize = true;
-                        break;
-                }
-            }
-
-            synchronized (mEvents) {
-                pw.println("Number of events: " + mEvents.size());
-                pw.println("Counter: " + mEventCounter);
-                if (mEvents.size() > 0) {
-                    pw.println("Time span: " +
-                            DateUtils.formatElapsedTime(
-                                    (System.currentTimeMillis() - mEvents.peekFirst().timestamp)
-                                            / 1000));
-                }
-
-                if (dumpSerializedSize) {
-                    Parcel p = Parcel.obtain();
-                    for (ConnectivityMetricsEvent e : mEvents) {
-                        p.writeParcelable(e, 0);
-                    }
-                    pw.println("Serialized data size: " + p.dataSize());
-                    p.recycle();
-                }
-
-                if (dumpEvents) {
-                    pw.println();
-                    pw.println("Events:");
-                    for (ConnectivityMetricsEvent e : mEvents) {
-                        pw.println(e.toString());
-                    }
-                }
-            }
-
-            if (dumpDebugInfo) {
-                synchronized (mThrottlingCounters) {
-                    pw.println();
-                    for (int i = 0; i < ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS; i++) {
-                        if (mThrottlingCounters[i] > 0) {
-                            pw.println("Throttling Counter #" + i + ": " + mThrottlingCounters[i]);
-                        }
-                    }
-                    pw.println("Throttling Time Remaining: " +
-                            DateUtils.formatElapsedTime(
-                                    (mThrottlingIntervalBoundaryMillis - System.currentTimeMillis())
-                                            / 1000));
-                }
-            }
-
-            synchronized (mPendingIntents) {
-                if (!mPendingIntents.isEmpty()) {
-                    pw.println();
-                    pw.println("Pending intents:");
-                    for (PendingIntent pi : mPendingIntents) {
-                        pw.println(pi.toString());
-                    }
-                }
-            }
-        }
-
-        public long logEvent(ConnectivityMetricsEvent event) {
-            ConnectivityMetricsEvent[] events = new ConnectivityMetricsEvent[]{event};
-            return logEvents(events);
-        }
-
-        /**
-         * @param events
-         *
-         * Note: All events must belong to the same component.
-         *
-         * @return 0 on success
-         *        <0 if error happened
-         *        >0 timestamp after which new events will be accepted
-         */
-        public long logEvents(ConnectivityMetricsEvent[] events) {
-            enforceConnectivityInternalPermission();
-
-            if (events == null || events.length == 0) {
-                Log.wtf(TAG, "No events passed to logEvents()");
-                return -1;
-            }
-
-            int componentTag = events[0].componentTag;
-            if (componentTag < 0 ||
-                    componentTag >= ConnectivityMetricsLogger.NUMBER_OF_COMPONENTS) {
-                Log.wtf(TAG, "Unexpected tag: " + componentTag);
-                return -1;
-            }
-
-            synchronized (mThrottlingCounters) {
-                long currentTimeMillis = System.currentTimeMillis();
-                if (currentTimeMillis > mThrottlingIntervalBoundaryMillis) {
-                    resetThrottlingCounters(currentTimeMillis);
-                }
-
-                mThrottlingCounters[componentTag] += events.length;
-
-                if (mThrottlingCounters[componentTag] >
-                        THROTTLING_MAX_NUMBER_OF_MESSAGES_PER_COMPONENT) {
-                    Log.w(TAG, "Too many events from #" + componentTag +
-                            ". Block until " + mThrottlingIntervalBoundaryMillis);
-
-                    return mThrottlingIntervalBoundaryMillis;
-                }
-            }
-
-            boolean sendPendingIntents = false;
-
-            synchronized (mEvents) {
-                for (ConnectivityMetricsEvent e : events) {
-                    if (e.componentTag != componentTag) {
-                        Log.wtf(TAG, "Unexpected tag: " + e.componentTag);
-                        return -1;
-                    }
-
-                    addEvent(e);
-                }
-
-                mLastEventReference += events.length;
-
-                mEventCounter += events.length;
-                if (mEventCounter >= EVENTS_NOTIFICATION_THRESHOLD) {
-                    mEventCounter = 0;
-                    sendPendingIntents = true;
-                }
-            }
-
-            if (sendPendingIntents) {
-                synchronized (mPendingIntents) {
-                    for (PendingIntent pi : mPendingIntents) {
-                        if (VDBG) Log.v(TAG, "Send pending intent");
-                        try {
-                            pi.send(getContext(), 0, null, null, null);
-                        } catch (PendingIntent.CanceledException e) {
-                            Log.e(TAG, "Pending intent canceled: " + pi);
-                            mPendingIntents.remove(pi);
-                        }
-                    }
-                }
-            }
-
-            return 0;
-        }
-
-        /**
-         * Retrieve events
-         *
-         * @param reference of the last event previously returned. The function will return
-         *                  events following it.
-         *                  If 0 then all events will be returned.
-         *                  After the function call it will contain reference of the
-         *                  last returned event.
-         * @return events
-         */
-        public ConnectivityMetricsEvent[] getEvents(ConnectivityMetricsEvent.Reference reference) {
-            enforceDumpPermission();
-            long ref = reference.getValue();
-            if (VDBG) Log.v(TAG, "getEvents(" + ref + ")");
-
-            ConnectivityMetricsEvent[] result;
-            synchronized (mEvents) {
-                if (ref > mLastEventReference) {
-                    Log.e(TAG, "Invalid reference");
-                    reference.setValue(mLastEventReference);
-                    return null;
-                }
-                if (ref < mLastEventReference - mEvents.size()) {
-                    ref = mLastEventReference - mEvents.size();
-                }
-
-                int numEventsToSkip =
-                        mEvents.size() // Total number of events
-                        - (int)(mLastEventReference - ref); // Number of events to return
-
-                result = new ConnectivityMetricsEvent[mEvents.size() - numEventsToSkip];
-                int i = 0;
-                for (ConnectivityMetricsEvent e : mEvents) {
-                    if (numEventsToSkip > 0) {
-                        numEventsToSkip--;
-                    } else {
-                        result[i++] = e;
-                    }
-                }
-
-                reference.setValue(mLastEventReference);
-            }
-
-            return result;
-        }
-
-        public boolean register(PendingIntent newEventsIntent) {
-            enforceDumpPermission();
-            if (VDBG) Log.v(TAG, "register(" + newEventsIntent + ")");
-
-            synchronized (mPendingIntents) {
-                if (mPendingIntents.remove(newEventsIntent)) {
-                    Log.w(TAG, "Replacing registered pending intent");
-                }
-                mPendingIntents.add(newEventsIntent);
-            }
-
-            return true;
-        }
-
-        public void unregister(PendingIntent newEventsIntent) {
-            enforceDumpPermission();
-            if (VDBG) Log.v(TAG, "unregister(" + newEventsIntent + ")");
-
-            synchronized (mPendingIntents) {
-                if (!mPendingIntents.remove(newEventsIntent)) {
-                    Log.e(TAG, "Pending intent is not registered");
-                }
-            }
-        }
-    };
-}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index 9ffe2b7..97669d2 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -789,6 +789,8 @@
             if (userAgent != null) {
                urlConnection.setRequestProperty("User-Agent", userAgent);
             }
+            // cannot read request header after connection
+            String requestHeader = urlConnection.getRequestProperties().toString();
 
             // Time how long it takes to get a response to our request
             long requestTimestamp = SystemClock.elapsedRealtime();
@@ -802,6 +804,7 @@
             validationLog(ValidationProbeEvent.getProbeName(probeType) + " " + url +
                     " time=" + (responseTimestamp - requestTimestamp) + "ms" +
                     " ret=" + httpResponseCode +
+                    " request=" + requestHeader +
                     " headers=" + urlConnection.getHeaderFields());
             // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
             // portal.  The only example of this seen so far was a captive portal.  For
@@ -809,19 +812,26 @@
             // portal.  If it is considered a captive portal, a different sign-in URL
             // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
             // proxy server.
-
-            // 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
-            // sign-in to an empty page.  Probably the result of a broken transparent proxy.
-            // See http://b/9972012.
-            if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
-                validationLog("Empty 200 response interpreted as 204 response.");
-                httpResponseCode = 204;
-            }
-
-            if (httpResponseCode == 200 && probeType == ValidationProbeEvent.PROBE_PAC) {
-                validationLog("PAC fetch 200 response interpreted as 204 response.");
-                httpResponseCode = 204;
+            if (httpResponseCode == 200) {
+                if (probeType == ValidationProbeEvent.PROBE_PAC) {
+                    validationLog("PAC fetch 200 response interpreted as 204 response.");
+                    httpResponseCode = 204;
+                } 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
+                    // sign-in to an empty page. Probably the result of a broken transparent proxy.
+                    // See http://b/9972012.
+                    validationLog(
+                        "200 response with Content-length=0 interpreted as 204 response.");
+                    httpResponseCode = 204;
+                } 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("Empty 200 response interpreted as 204 response.");
+                        httpResponseCode = 204;
+                    }
+                }
             }
         } catch (IOException e) {
             validationLog("Probably not a portal: exception " + e);
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index c6bf4c5..9ffa40b 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -27,7 +27,7 @@
 import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
-
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.R;
 
 import static android.net.NetworkCapabilities.*;
@@ -37,7 +37,8 @@
 
     public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH };
 
-    private static final String NOTIFICATION_ID = "Connectivity.Notification";
+    @VisibleForTesting
+    static final String NOTIFICATION_ID = "Connectivity.Notification";
 
     private static final String TAG = NetworkNotificationManager.class.getSimpleName();
     private static final boolean DBG = true;
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 0c80166..a53d19c 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -16,6 +16,11 @@
 
 package com.android.server.connectivity;
 
+import static android.hardware.usb.UsbManager.USB_CONNECTED;
+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 android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -71,7 +76,9 @@
 import com.android.server.connectivity.tethering.IControlsTethering;
 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.TetherInterfaceStateMachine;
+import com.android.server.connectivity.tethering.TetheringConfiguration;
 import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
 import com.android.server.net.BaseNetworkObserver;
 
@@ -95,8 +102,7 @@
  */
 public class Tethering extends BaseNetworkObserver implements IControlsTethering {
 
-    private final Context mContext;
-    private final static String TAG = "Tethering";
+    private final static String TAG = Tethering.class.getSimpleName();
     private final static boolean DBG = false;
     private final static boolean VDBG = false;
 
@@ -108,77 +114,43 @@
     private static final SparseArray<String> sMagicDecoderRing =
             MessageUtils.findMessageNames(messageClasses);
 
-    // TODO - remove both of these - should be part of interface inspection/selection stuff
-    private String[] mTetherableUsbRegexs;
-    private String[] mTetherableWifiRegexs;
-    private String[] mTetherableBluetoothRegexs;
-    private Collection<Integer> mUpstreamIfaceTypes;
+    // {@link ComponentName} of the Service used to run tether provisioning.
+    private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
+            .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
+
+    private static class TetherState {
+        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;
+        }
+    }
 
     // used to synchronize public access to members
     private final Object mPublicSync;
-
-    private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
-    private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
-    private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN);
-
-    // if we have to connect to mobile, what APN type should we use?  Calculated by examining the
-    // upstream type list and the DUN_REQUIRED secure-setting
-    private int mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_NONE;
-
+    private final Context mContext;
+    private final ArrayMap<String, TetherState> mTetherStates;
+    private final BroadcastReceiver mStateReceiver;
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
     private final INetworkPolicyManager mPolicyManager;
     private final Looper mLooper;
     private final MockableSystemProperties mSystemProperties;
-
-    private static class TetherState {
-        public final TetherInterfaceStateMachine mStateMachine;
-        public int mLastState;
-        public int mLastError;
-        public TetherState(TetherInterfaceStateMachine sm) {
-            mStateMachine = sm;
-            // Assume all state machines start out available and with no errors.
-            mLastState = IControlsTethering.STATE_AVAILABLE;
-            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
-        }
-    }
-    private final ArrayMap<String, TetherState> mTetherStates;
-
-    private final BroadcastReceiver mStateReceiver;
-
-    // {@link ComponentName} of the Service used to run tether provisioning.
-    private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
-            .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
-
-    // USB is  192.168.42.1 and 255.255.255.0
-    // Wifi is 192.168.43.1 and 255.255.255.0
-    // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
-    // with 255.255.255.0
-    // P2P is 192.168.49.1 and 255.255.255.0
-
-    private String[] mDhcpRange;
-    private static final String[] DHCP_DEFAULT_RANGE = {
-        "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
-        "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
-        "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
-        "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
-    };
-
-    private String[] mDefaultDnsServers;
-    private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
-    private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
-
     private final StateMachine mTetherMasterSM;
+    private final OffloadController mOffloadController;
     private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
-    private String mCurrentUpstreamIface;
 
+    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
-
     // True iff WiFi tethering should be started when soft AP is ready.
     private boolean mWifiTetherRequested;
 
@@ -199,6 +171,7 @@
         mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
         mTetherMasterSM.start();
 
+        mOffloadController = new OffloadController(mTetherMasterSM.getHandler());
         mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                 mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
 
@@ -208,27 +181,16 @@
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
-        mContext.registerReceiver(mStateReceiver, filter);
+        mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
 
         filter = new IntentFilter();
         filter.addAction(Intent.ACTION_MEDIA_SHARED);
         filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
         filter.addDataScheme("file");
-        mContext.registerReceiver(mStateReceiver, filter);
-
-        mDhcpRange = context.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_dhcp_range);
-        if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
-            mDhcpRange = DHCP_DEFAULT_RANGE;
-        }
+        mContext.registerReceiver(mStateReceiver, filter, null, mTetherMasterSM.getHandler());
 
         // load device config info
         updateConfiguration();
-
-        // TODO - remove and rely on real notifications of the current iface
-        mDefaultDnsServers = new String[2];
-        mDefaultDnsServers[0] = DNS_DEFAULT_SERVER1;
-        mDefaultDnsServers[1] = DNS_DEFAULT_SERVER2;
     }
 
     // We can't do this once in the Tethering() constructor and cache the value, because the
@@ -237,30 +199,8 @@
         return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
-    void updateConfiguration() {
-        String[] tetherableUsbRegexs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_usb_regexs);
-        String[] tetherableWifiRegexs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_wifi_regexs);
-        String[] tetherableBluetoothRegexs = mContext.getResources().getStringArray(
-                com.android.internal.R.array.config_tether_bluetooth_regexs);
-
-        int ifaceTypes[] = mContext.getResources().getIntArray(
-                com.android.internal.R.array.config_tether_upstream_types);
-        Collection<Integer> upstreamIfaceTypes = new ArrayList<>();
-        for (int i : ifaceTypes) {
-            upstreamIfaceTypes.add(new Integer(i));
-        }
-
-        synchronized (mPublicSync) {
-            mTetherableUsbRegexs = tetherableUsbRegexs;
-            mTetherableWifiRegexs = tetherableWifiRegexs;
-            mTetherableBluetoothRegexs = tetherableBluetoothRegexs;
-            mUpstreamIfaceTypes = upstreamIfaceTypes;
-        }
-
-        // check if the upstream type list needs to be modified due to secure-settings
-        checkDunRequired();
+    private void updateConfiguration() {
+        mConfig = new TetheringConfiguration(mContext);
     }
 
     @Override
@@ -281,7 +221,7 @@
                 }
             } else {
                 if (interfaceType == ConnectivityManager.TETHERING_BLUETOOTH) {
-                    tetherState.mStateMachine.sendMessage(
+                    tetherState.stateMachine.sendMessage(
                             TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
                     mTetherStates.remove(iface);
                 } else {
@@ -300,39 +240,14 @@
         interfaceStatusChanged(iface, up);
     }
 
-    private boolean isUsb(String iface) {
-        synchronized (mPublicSync) {
-            for (String regex : mTetherableUsbRegexs) {
-                if (iface.matches(regex)) return true;
-            }
-            return false;
-        }
-    }
-
-    private boolean isWifi(String iface) {
-        synchronized (mPublicSync) {
-            for (String regex : mTetherableWifiRegexs) {
-                if (iface.matches(regex)) return true;
-            }
-            return false;
-        }
-    }
-
-    private boolean isBluetooth(String iface) {
-        synchronized (mPublicSync) {
-            for (String regex : mTetherableBluetoothRegexs) {
-                if (iface.matches(regex)) return true;
-            }
-            return false;
-        }
-    }
-
     private int ifaceNameToType(String iface) {
-        if (isWifi(iface)) {
+        final TetheringConfiguration cfg = mConfig;
+
+        if (cfg.isWifi(iface)) {
             return ConnectivityManager.TETHERING_WIFI;
-        } else if (isUsb(iface)) {
+        } else if (cfg.isUsb(iface)) {
             return ConnectivityManager.TETHERING_USB;
-        } else if (isBluetooth(iface)) {
+        } else if (cfg.isBluetooth(iface)) {
             return ConnectivityManager.TETHERING_BLUETOOTH;
         }
         return ConnectivityManager.TETHERING_INVALID;
@@ -368,13 +283,12 @@
                 }
                 return;
             }
-            tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+            tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
             mTetherStates.remove(iface);
         }
     }
 
-    public void startTethering(int type, ResultReceiver receiver,
-            boolean showProvisioningUi) {
+    public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
         if (!isTetherProvisioningRequired()) {
             enableTetheringInternal(type, true, receiver);
             return;
@@ -606,11 +520,11 @@
             }
             // Ignore the error status of the interface.  If the interface is available,
             // the errors are referring to past tethering attempts anyway.
-            if (tetherState.mLastState != IControlsTethering.STATE_AVAILABLE) {
+            if (tetherState.lastState != IControlsTethering.STATE_AVAILABLE) {
                 Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
             }
-            tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+            tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
             return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
     }
@@ -623,11 +537,11 @@
                 Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
             }
-            if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
+            if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
                 Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
             }
-            tetherState.mStateMachine.sendMessage(
+            tetherState.stateMachine.sendMessage(
                     TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
             return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
@@ -647,7 +561,7 @@
                         ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
             }
-            return tetherState.mLastError;
+            return tetherState.lastError;
         }
     }
 
@@ -662,20 +576,22 @@
         boolean usbTethered = false;
         boolean bluetoothTethered = false;
 
+        final TetheringConfiguration cfg = mConfig;
+
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
                 TetherState tetherState = mTetherStates.valueAt(i);
                 String iface = mTetherStates.keyAt(i);
-                if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                if (tetherState.lastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                     erroredList.add(iface);
-                } else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+                } else if (tetherState.lastState == IControlsTethering.STATE_AVAILABLE) {
                     availableList.add(iface);
-                } else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
-                    if (isUsb(iface)) {
+                } else if (tetherState.lastState == IControlsTethering.STATE_TETHERED) {
+                    if (cfg.isUsb(iface)) {
                         usbTethered = true;
-                    } else if (isWifi(iface)) {
+                    } else if (cfg.isWifi(iface)) {
                         wifiTethered = true;
-                    } else if (isBluetooth(iface)) {
+                    } else if (cfg.isBluetooth(iface)) {
                         bluetoothTethered = true;
                     }
                     activeList.add(iface);
@@ -779,69 +695,84 @@
     private class StateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context content, Intent intent) {
-            String action = intent.getAction();
-            if (action == null) { return; }
+            final String action = intent.getAction();
+            if (action == null) return;
+
             if (action.equals(UsbManager.ACTION_USB_STATE)) {
-                synchronized (Tethering.this.mPublicSync) {
-                    boolean usbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
-                    mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
-                    // start tethering if we have a request pending
-                    if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
-                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
-                    }
-                    mUsbTetherRequested = false;
-                }
+                handleUsbAction(intent);
             } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
-                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
-                        ConnectivityManager.EXTRA_NETWORK_INFO);
-                if (networkInfo != null &&
-                        networkInfo.getDetailedState() != NetworkInfo.DetailedState.FAILED) {
-                    if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
-                    mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
-                }
+                handleConnectivityAction(intent);
             } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
-                synchronized (Tethering.this.mPublicSync) {
-                    int curState =  intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
-                            WifiManager.WIFI_AP_STATE_DISABLED);
-                    switch (curState) {
-                        case WifiManager.WIFI_AP_STATE_ENABLING:
-                            // We can see this state on the way to both enabled and failure states.
-                            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);
-                            }
-                            break;
-                        case WifiManager.WIFI_AP_STATE_DISABLED:
-                        case WifiManager.WIFI_AP_STATE_DISABLING:
-                        case WifiManager.WIFI_AP_STATE_FAILED:
-                        default:
-                            if (DBG) {
-                                Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
-                                    curState);
-                            }
-                            // Tell appropriate interface state machines that they should tear
-                            // themselves down.
-                            for (int i = 0; i < mTetherStates.size(); i++) {
-                                TetherInterfaceStateMachine tism =
-                                        mTetherStates.valueAt(i).mStateMachine;
-                                if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
-                                    tism.sendMessage(
-                                            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
-                                    break;  // There should be at most one of these.
-                                }
-                            }
-                            // Regardless of whether we requested this transition, the AP has gone
-                            // down.  Don't try to tether again unless we're requested to do so.
-                            mWifiTetherRequested = false;
-                            break;
-                    }
-                }
+                handleWifiApAction(intent);
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 updateConfiguration();
             }
         }
+
+        private void handleConnectivityAction(Intent intent) {
+            final NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+                    ConnectivityManager.EXTRA_NETWORK_INFO);
+            if (networkInfo == null ||
+                    networkInfo.getDetailedState() == NetworkInfo.DetailedState.FAILED) {
+                return;
+            }
+
+            if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION: " + networkInfo.toString());
+            mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
+        }
+
+        private void handleUsbAction(Intent intent) {
+            final boolean usbConnected = intent.getBooleanExtra(USB_CONNECTED, false);
+            final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
+            synchronized (Tethering.this.mPublicSync) {
+                mRndisEnabled = rndisEnabled;
+                // start tethering if we have a request pending
+                if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
+                    tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
+                }
+                mUsbTetherRequested = false;
+            }
+        }
+
+        private void handleWifiApAction(Intent intent) {
+            final int curState =  intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+            synchronized (Tethering.this.mPublicSync) {
+                switch (curState) {
+                    case WifiManager.WIFI_AP_STATE_ENABLING:
+                        // We can see this state on the way to both enabled and failure states.
+                        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);
+                        }
+                        break;
+                    case WifiManager.WIFI_AP_STATE_DISABLED:
+                    case WifiManager.WIFI_AP_STATE_DISABLING:
+                    case WifiManager.WIFI_AP_STATE_FAILED:
+                    default:
+                        if (DBG) {
+                            Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
+                                curState);
+                        }
+                        // Tell appropriate interface state machines that they should tear
+                        // themselves down.
+                        for (int i = 0; i < mTetherStates.size(); i++) {
+                            TetherInterfaceStateMachine tism =
+                                    mTetherStates.valueAt(i).stateMachine;
+                            if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+                                tism.sendMessage(
+                                        TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+                                break;  // There should be at most one of these.
+                            }
+                        }
+                        // Regardless of whether we requested this transition, the AP has gone
+                        // down.  Don't try to tether again unless we're requested to do so.
+                        mWifiTetherRequested = false;
+                    break;
+                }
+            }
+        }
     }
 
     private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
@@ -875,26 +806,38 @@
         }
     }
 
-    // TODO - return copies so people can't tamper
+    public TetheringConfiguration getTetheringConfiguration() {
+        return mConfig;
+    }
+
+    public boolean hasTetherableConfiguration() {
+        final TetheringConfiguration cfg = mConfig;
+        final boolean hasDownstreamConfiguration =
+                (cfg.tetherableUsbRegexs.length != 0) ||
+                (cfg.tetherableWifiRegexs.length != 0) ||
+                (cfg.tetherableBluetoothRegexs.length != 0);
+        final boolean hasUpstreamConfiguration = !cfg.preferredUpstreamIfaceTypes.isEmpty();
+
+        return hasDownstreamConfiguration && hasUpstreamConfiguration;
+    }
+
+    // TODO - update callers to use getTetheringConfiguration(),
+    // which has only final members.
     public String[] getTetherableUsbRegexs() {
-        return mTetherableUsbRegexs;
+        return copy(mConfig.tetherableUsbRegexs);
     }
 
     public String[] getTetherableWifiRegexs() {
-        return mTetherableWifiRegexs;
+        return copy(mConfig.tetherableWifiRegexs);
     }
 
     public String[] getTetherableBluetoothRegexs() {
-        return mTetherableBluetoothRegexs;
+        return copy(mConfig.tetherableBluetoothRegexs);
     }
 
     public int setUsbTethering(boolean enable) {
         if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
         UsbManager usbManager = mContext.getSystemService(UsbManager.class);
-        if (usbManager == null) {
-            return enable ? ConnectivityManager.TETHER_ERROR_MASTER_ERROR
-                          : ConnectivityManager.TETHER_ERROR_NO_ERROR;
-        }
 
         synchronized (mPublicSync) {
             if (enable) {
@@ -925,68 +868,13 @@
         return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
-    public int[] getUpstreamIfaceTypes() {
-        int values[];
-        synchronized (mPublicSync) {
-            updateConfiguration();  // TODO - remove?
-            values = new int[mUpstreamIfaceTypes.size()];
-            Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator();
-            for (int i=0; i < mUpstreamIfaceTypes.size(); i++) {
-                values[i] = iterator.next();
-            }
-        }
-        return values;
-    }
-
-    private void checkDunRequired() {
-        int secureSetting = 2;
-        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-        if (tm != null) {
-            secureSetting = tm.getTetherApnRequired();
-        }
-        synchronized (mPublicSync) {
-            // 2 = not set, 0 = DUN not required, 1 = DUN required
-            if (secureSetting != 2) {
-                int requiredApn = (secureSetting == 1 ?
-                        ConnectivityManager.TYPE_MOBILE_DUN :
-                        ConnectivityManager.TYPE_MOBILE_HIPRI);
-                if (requiredApn == ConnectivityManager.TYPE_MOBILE_DUN) {
-                    while (mUpstreamIfaceTypes.contains(MOBILE_TYPE)) {
-                        mUpstreamIfaceTypes.remove(MOBILE_TYPE);
-                    }
-                    while (mUpstreamIfaceTypes.contains(HIPRI_TYPE)) {
-                        mUpstreamIfaceTypes.remove(HIPRI_TYPE);
-                    }
-                    if (mUpstreamIfaceTypes.contains(DUN_TYPE) == false) {
-                        mUpstreamIfaceTypes.add(DUN_TYPE);
-                    }
-                } else {
-                    while (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
-                        mUpstreamIfaceTypes.remove(DUN_TYPE);
-                    }
-                    if (mUpstreamIfaceTypes.contains(MOBILE_TYPE) == false) {
-                        mUpstreamIfaceTypes.add(MOBILE_TYPE);
-                    }
-                    if (mUpstreamIfaceTypes.contains(HIPRI_TYPE) == false) {
-                        mUpstreamIfaceTypes.add(HIPRI_TYPE);
-                    }
-                }
-            }
-            if (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
-                mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_DUN;
-            } else {
-                mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_HIPRI;
-            }
-        }
-    }
-
     // TODO review API - maybe return ArrayList<String> here and below?
     public String[] getTetheredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
                 TetherState tetherState = mTetherStates.valueAt(i);
-                if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+                if (tetherState.lastState == IControlsTethering.STATE_TETHERED) {
                     list.add(mTetherStates.keyAt(i));
                 }
             }
@@ -999,7 +887,7 @@
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
                 TetherState tetherState = mTetherStates.valueAt(i);
-                if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+                if (tetherState.lastState == IControlsTethering.STATE_AVAILABLE) {
                     list.add(mTetherStates.keyAt(i));
                 }
             }
@@ -1008,7 +896,7 @@
     }
 
     public String[] getTetheredDhcpRanges() {
-        return mDhcpRange;
+        return mConfig.dhcpRanges;
     }
 
     public String[] getErroredIfaces() {
@@ -1016,7 +904,7 @@
         synchronized (mPublicSync) {
             for (int i = 0; i < mTetherStates.size(); i++) {
                 TetherState tetherState = mTetherStates.valueAt(i);
-                if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                if (tetherState.lastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                     list.add(mTetherStates.keyAt(i));
                 }
             }
@@ -1058,6 +946,8 @@
         // Events from NetworkCallbacks that we process on the master state
         // machine thread on behalf of the UpstreamNetworkMonitor.
         static final int EVENT_UPSTREAM_CALLBACK                = BASE_MASTER + 5;
+        // we treated the error and want now to clear it
+        static final int CMD_CLEAR_ERROR                        = BASE_MASTER + 6;
 
         private State mInitialState;
         private State mTetherModeAliveState;
@@ -1083,8 +973,6 @@
         private final ArrayList<TetherInterfaceStateMachine> mNotifyList;
         private final IPv6TetheringCoordinator mIPv6TetheringCoordinator;
 
-        private int mPreviousMobileApn = ConnectivityManager.TYPE_NONE;
-
         private static final int UPSTREAM_SETTLE_TIME_MS     = 10000;
 
         TetherMasterSM(String name, Looper looper) {
@@ -1118,46 +1006,17 @@
                 return false;
             }
 
-            protected boolean turnOnUpstreamMobileConnection(int apnType) {
-                if (apnType == ConnectivityManager.TYPE_NONE) { return false; }
-
-                if (apnType != mPreviousMobileApn) {
-                    // Unregister any previous mobile upstream callback because
-                    // this request, if any, will be different.
-                    turnOffUpstreamMobileConnection();
-                }
-
-                if (mUpstreamNetworkMonitor.mobileNetworkRequested()) {
-                    // Looks like we already filed a request for this apnType.
-                    return true;
-                }
-
-                switch (apnType) {
-                    case ConnectivityManager.TYPE_MOBILE_DUN:
-                    case ConnectivityManager.TYPE_MOBILE:
-                    case ConnectivityManager.TYPE_MOBILE_HIPRI:
-                        mPreviousMobileApn = apnType;
-                        break;
-                    default:
-                        return false;
-                }
-
-                // TODO: This should be called by the code that observes
-                // configuration changes, once the above code in this function
-                // is simplified (i.e. eradicated).
-                mUpstreamNetworkMonitor.mobileUpstreamRequiresDun(
-                        apnType == ConnectivityManager.TYPE_MOBILE_DUN);
-
+            protected void requestUpstreamMobileConnection() {
+                mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired);
                 mUpstreamNetworkMonitor.registerMobileNetworkRequest();
-                return true;
             }
 
-            protected void turnOffUpstreamMobileConnection() {
+            protected void unrequestUpstreamMobileConnection() {
                 mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
-                mPreviousMobileApn = ConnectivityManager.TYPE_NONE;
             }
 
             protected boolean turnOnMasterTetherSettings() {
+                final TetheringConfiguration cfg = mConfig;
                 try {
                     mNMService.setIpForwardingEnabled(true);
                 } catch (Exception e) {
@@ -1165,11 +1024,11 @@
                     return false;
                 }
                 try {
-                    mNMService.startTethering(mDhcpRange);
+                    mNMService.startTethering(cfg.dhcpRanges);
                 } catch (Exception e) {
                     try {
                         mNMService.stopTethering();
-                        mNMService.startTethering(mDhcpRange);
+                        mNMService.startTethering(cfg.dhcpRanges);
                     } catch (Exception ee) {
                         transitionTo(mStartTetheringErrorState);
                         return false;
@@ -1196,35 +1055,41 @@
             }
 
             protected void chooseUpstreamType(boolean tryCell) {
+                final int upstreamType = findPreferredUpstreamType(tryCell);
+                setUpstreamByType(upstreamType);
+            }
+
+            protected int findPreferredUpstreamType(boolean tryCell) {
                 final ConnectivityManager cm = getConnectivityManager();
                 int upType = ConnectivityManager.TYPE_NONE;
-                String iface = null;
 
                 updateConfiguration(); // TODO - remove?
 
-                synchronized (mPublicSync) {
-                    if (VDBG) {
-                        Log.d(TAG, "chooseUpstreamType has upstream iface types:");
-                        for (Integer netType : mUpstreamIfaceTypes) {
-                            Log.d(TAG, " " + netType);
-                        }
-                    }
-
-                    for (Integer netType : mUpstreamIfaceTypes) {
-                        NetworkInfo info = cm.getNetworkInfo(netType.intValue());
-                        // TODO: if the network is suspended we should consider
-                        // that to be the same as connected here.
-                        if ((info != null) && info.isConnected()) {
-                            upType = netType.intValue();
-                            break;
-                        }
+                final TetheringConfiguration cfg = mConfig;
+                if (VDBG) {
+                    Log.d(TAG, "chooseUpstreamType has upstream iface types:");
+                    for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
+                        Log.d(TAG, " " + netType);
                     }
                 }
 
+                for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
+                    NetworkInfo info = cm.getNetworkInfo(netType.intValue());
+                    // TODO: if the network is suspended we should consider
+                    // that to be the same as connected here.
+                    if ((info != null) && info.isConnected()) {
+                        upType = netType.intValue();
+                        break;
+                    }
+                }
+
+                final int preferredUpstreamMobileApn = cfg.isDunRequired
+                        ? ConnectivityManager.TYPE_MOBILE_DUN
+                        : ConnectivityManager.TYPE_MOBILE_HIPRI;
                 if (DBG) {
                     Log.d(TAG, "chooseUpstreamType(" + tryCell + "),"
                             + " preferredApn="
-                            + ConnectivityManager.getNetworkTypeName(mPreferredUpstreamMobileApn)
+                            + ConnectivityManager.getNetworkTypeName(preferredUpstreamMobileApn)
                             + ", got type="
                             + ConnectivityManager.getNetworkTypeName(upType));
                 }
@@ -1233,11 +1098,11 @@
                     case ConnectivityManager.TYPE_MOBILE_DUN:
                     case ConnectivityManager.TYPE_MOBILE_HIPRI:
                         // If we're on DUN, put our own grab on it.
-                        turnOnUpstreamMobileConnection(upType);
+                        requestUpstreamMobileConnection();
                         break;
                     case ConnectivityManager.TYPE_NONE:
-                        if (tryCell &&
-                                turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn)) {
+                        if (tryCell) {
+                            requestUpstreamMobileConnection();
                             // We think mobile should be coming up; don't set a retry.
                         } else {
                             sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
@@ -1250,11 +1115,17 @@
                          * If we found NONE we don't want to do this as we want any previous
                          * requests to keep trying to bring up something we can use.
                          */
-                        turnOffUpstreamMobileConnection();
+                        unrequestUpstreamMobileConnection();
                         break;
                 }
 
+                return upType;
+            }
+
+            protected void setUpstreamByType(int upType) {
+                final ConnectivityManager cm = getConnectivityManager();
                 Network network = null;
+                String iface = null;
                 if (upType != ConnectivityManager.TYPE_NONE) {
                     LinkProperties linkProperties = cm.getLinkProperties(upType);
                     if (linkProperties != null) {
@@ -1295,7 +1166,8 @@
             }
 
             protected void setDnsForwarders(final Network network, final LinkProperties lp) {
-                String[] dnsServers = mDefaultDnsServers;
+                // TODO: Set v4 and/or v6 DNS per available connectivity.
+                String[] dnsServers = mConfig.defaultIPv4DNS;
                 final Collection<InetAddress> dnses = lp.getDnsServers();
                 // TODO: Properly support the absence of DNS servers.
                 if (dnses != null && !dnses.isEmpty()) {
@@ -1327,99 +1199,132 @@
 
             protected void handleNewUpstreamNetworkState(NetworkState ns) {
                 mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
+                mOffloadController.setUpstreamLinkProperties(
+                        (ns != null) ? ns.linkProperties : null);
             }
         }
 
-        private final AtomicInteger mSimBcastGenerationNumber = new AtomicInteger(0);
-        private SimChangeBroadcastReceiver mBroadcastReceiver = null;
+        private class SimChangeListener {
+            private final Context mContext;
+            private final AtomicInteger mSimBcastGenerationNumber;
+            private BroadcastReceiver mBroadcastReceiver;
 
-        private void startListeningForSimChanges() {
-            if (DBG) Log.d(TAG, "startListeningForSimChanges");
-            if (mBroadcastReceiver == null) {
+            SimChangeListener(Context ctx) {
+                mContext = ctx;
+                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);
+                mContext.registerReceiver(mBroadcastReceiver, filter, null,
+                        mTetherMasterSM.getHandler());
             }
-        }
 
-        private void stopListeningForSimChanges() {
-            if (DBG) Log.d(TAG, "stopListeningForSimChanges");
-            if (mBroadcastReceiver != null) {
+            public void stopListening() {
+                if (DBG) Log.d(TAG, "stopListening for SIM changes");
+
+                if (mBroadcastReceiver == null) return;
+
                 mSimBcastGenerationNumber.incrementAndGet();
                 mContext.unregisterReceiver(mBroadcastReceiver);
                 mBroadcastReceiver = null;
             }
-        }
 
-        class SimChangeBroadcastReceiver extends BroadcastReceiver {
-            // used to verify this receiver is still current
-            final private int mGenerationNumber;
-
-            // we're interested in edge-triggered LOADED notifications, so
-            // ignore LOADED unless we saw an ABSENT state first
-            private boolean mSimAbsentSeen = false;
-
-            public SimChangeBroadcastReceiver(int generationNumber) {
-                super();
-                mGenerationNumber = generationNumber;
+            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;
             }
 
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (DBG) {
-                    Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
-                            ", current generationNumber=" + mSimBcastGenerationNumber.get());
-                }
-                if (mGenerationNumber != mSimBcastGenerationNumber.get()) return;
+            private boolean isSimCardAbsent(String state) {
+                return IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state);
+            }
 
-                final String state =
-                        intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+            private boolean isSimCardLoaded(String state) {
+                return IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state);
+            }
 
-                Log.d(TAG, "got Sim changed to state " + state + ", mSimAbsentSeen=" +
-                        mSimAbsentSeen);
-                if (!mSimAbsentSeen && IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state)) {
-                    mSimAbsentSeen = true;
+            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;
+
+                // we're interested in edge-triggered LOADED notifications, so
+                // ignore LOADED unless we saw an ABSENT state first
+                private boolean mSimAbsentSeen = false;
+
+                public SimChangeBroadcastReceiver(int generationNumber) {
+                    mGenerationNumber = generationNumber;
                 }
 
-                if (mSimAbsentSeen && IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state)) {
-                    mSimAbsentSeen = false;
-                    try {
-                        if (mContext.getResources().getString(com.android.internal.R.string.
-                                config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
-                            ArrayList<Integer> tethered = new ArrayList<Integer>();
-                            synchronized (mPublicSync) {
-                                for (int i = 0; i < mTetherStates.size(); i++) {
-                                    TetherState tetherState = mTetherStates.valueAt(i);
-                                    if (tetherState.mLastState !=
-                                            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));
-                                    }
+                @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 + ", mSimAbsentSeen=" +
+                            mSimAbsentSeen);
+
+                    if (isSimCardAbsent(state)) {
+                        if (!mSimAbsentSeen) mSimAbsentSeen = true;
+                        return;
+                    }
+
+                    if (isSimCardLoaded(state) && mSimAbsentSeen) {
+                        mSimAbsentSeen = 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) {
-                                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);
-                            }
-                            Log.d(TAG, "re-evaluate provisioning");
-                        } else {
-                            Log.d(TAG, "no prov-check needed for new SIM");
                         }
-                    } catch (Resources.NotFoundException e) {
-                        Log.d(TAG, "no prov-check needed for new SIM");
-                        // not defined, do nothing
+
+                        for (int tetherType : tethered) {
+                            startProvisionIntent(tetherType);
+                        }
                     }
                 }
             }
@@ -1455,24 +1360,28 @@
         }
 
         class TetherModeAliveState extends TetherMasterUtilState {
+            final SimChangeListener simChange = new SimChangeListener(mContext);
             boolean mTryCell = true;
+
             @Override
             public void enter() {
                 // TODO: examine if we should check the return value.
                 turnOnMasterTetherSettings(); // may transition us out
-                startListeningForSimChanges();
+                simChange.startListening();
                 mUpstreamNetworkMonitor.start();
+                mOffloadController.start();
 
-                mTryCell = true;  // better try something first pass or crazy tests cases will fail
-                chooseUpstreamType(mTryCell);
-                mTryCell = !mTryCell;
+                // Better try something first pass or crazy tests cases will fail.
+                chooseUpstreamType(true);
+                mTryCell = false;
             }
 
             @Override
             public void exit() {
-                turnOffUpstreamMobileConnection();
+                mOffloadController.stop();
+                unrequestUpstreamMobileConnection();
                 mUpstreamNetworkMonitor.stop();
-                stopListeningForSimChanges();
+                simChange.stopListening();
                 notifyTetheredOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
@@ -1516,10 +1425,9 @@
                         break;
                     }
                     case CMD_UPSTREAM_CHANGED:
-                        // need to try DUN immediately if Wifi goes down
-                        mTryCell = true;
-                        chooseUpstreamType(mTryCell);
-                        mTryCell = !mTryCell;
+                        // Need to try DUN immediately if Wi-Fi goes down.
+                        chooseUpstreamType(true);
+                        mTryCell = false;
                         break;
                     case CMD_RETRY_UPSTREAM:
                         chooseUpstreamType(mTryCell);
@@ -1591,6 +1499,10 @@
                         TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         who.sendMessage(mErrorNotification);
                         break;
+                    case CMD_CLEAR_ERROR:
+                        mErrorNotification = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                        transitionTo(mInitialState);
+                        break;
                     default:
                        retValue = false;
                 }
@@ -1672,9 +1584,10 @@
 
         pw.println("Tethering:");
         pw.increaseIndent();
-        pw.print("mUpstreamIfaceTypes:");
+        final TetheringConfiguration cfg = mConfig;
+        pw.print("preferredUpstreamIfaceTypes:");
         synchronized (mPublicSync) {
-            for (Integer netType : mUpstreamIfaceTypes) {
+            for (Integer netType : cfg.preferredUpstreamIfaceTypes) {
                 pw.print(" " + ConnectivityManager.getNetworkTypeName(netType));
             }
             pw.println();
@@ -1686,7 +1599,7 @@
                 final TetherState tetherState = mTetherStates.valueAt(i);
                 pw.print(iface + " - ");
 
-                switch (tetherState.mLastState) {
+                switch (tetherState.lastState) {
                     case IControlsTethering.STATE_UNAVAILABLE:
                         pw.print("UnavailableState");
                         break;
@@ -1700,7 +1613,7 @@
                         pw.print("UnknownState");
                         break;
                 }
-                pw.println(" - lastError = " + tetherState.mLastError);
+                pw.println(" - lastError = " + tetherState.lastError);
             }
             pw.decreaseIndent();
         }
@@ -1712,9 +1625,9 @@
                                            int state, int error) {
         synchronized (mPublicSync) {
             TetherState tetherState = mTetherStates.get(iface);
-            if (tetherState != null && tetherState.mStateMachine.equals(who)) {
-                tetherState.mLastState = state;
-                tetherState.mLastError = error;
+            if (tetherState != null && tetherState.stateMachine.equals(who)) {
+                tetherState.lastState = state;
+                tetherState.lastError = error;
             } else {
                 if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
             }
@@ -1734,6 +1647,12 @@
             // Not really very much we can do here.
         }
 
+        // If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
+        // Thus we give a chance for TetherMasterSM to recover to InitialState
+        // by sending CMD_CLEAR_ERROR
+        if (error == ConnectivityManager.TETHER_ERROR_MASTER_ERROR) {
+            mTetherMasterSM.sendMessage(TetherMasterSM.CMD_CLEAR_ERROR, who);
+        }
         switch (state) {
             case IControlsTethering.STATE_UNAVAILABLE:
             case IControlsTethering.STATE_AVAILABLE:
@@ -1752,6 +1671,10 @@
                 interfaceType, mNMService, mStatsService, this,
                 new IPv6TetheringInterfaceServices(iface, mNMService)));
         mTetherStates.put(iface, tetherState);
-        tetherState.mStateMachine.start();
+        tetherState.stateMachine.start();
+    }
+
+    private static String[] copy(String[] strarray) {
+        return Arrays.copyOf(strarray, strarray.length);
     }
 }
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 dec2f77..8c6430c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+
 import android.net.INetd;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -48,7 +50,6 @@
 public class IPv6TetheringInterfaceServices {
     private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName();
     private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
-    private static final int RFC7421_IP_PREFIX_LENGTH = 64;
 
     private final String mIfName;
     private final INetworkManagementService mNMService;
@@ -124,7 +125,7 @@
             params.hasDefaultRoute = v6only.hasIPv6DefaultRoute();
 
             for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
-                if (linkAddr.getPrefixLength() != RFC7421_IP_PREFIX_LENGTH) continue;
+                if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
 
                 final IpPrefix prefix = new IpPrefix(
                         linkAddr.getAddress(), linkAddr.getPrefixLength());
@@ -206,7 +207,7 @@
             for (Inet6Address dns : deprecatedDnses) {
                 final String dnsString = dns.getHostAddress();
                 try {
-                    netd.interfaceDelAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH);
+                    netd.interfaceDelAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
                 } catch (ServiceSpecificException | RemoteException e) {
                     Log.e(TAG, "Failed to remove local dns IP: " + dnsString, e);
                 }
@@ -223,7 +224,7 @@
             for (Inet6Address dns : addedDnses) {
                 final String dnsString = dns.getHostAddress();
                 try {
-                    netd.interfaceAddAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH);
+                    netd.interfaceAddAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH);
                 } catch (ServiceSpecificException | RemoteException e) {
                     Log.e(TAG, "Failed to add local dns IP: " + dnsString, e);
                     newDnses.remove(dns);
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
new file mode 100644
index 0000000..220e751
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.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.connectivity.tethering;
+
+import android.net.LinkProperties;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * A wrapper around hardware offload interface.
+ *
+ * @hide
+ */
+public class OffloadController {
+    private static final String TAG = OffloadController.class.getSimpleName();
+
+    private final Handler mHandler;
+    private LinkProperties mUpstreamLinkProperties;
+
+    public OffloadController(Handler h) {
+        mHandler = h;
+    }
+
+    public void start() {
+        // TODO: initOffload() and configure callbacks to be handled on our
+        // preferred Handler.
+        Log.d(TAG, "tethering offload not supported");
+    }
+
+    public void stop() {
+        // TODO: stopOffload().
+        mUpstreamLinkProperties = null;
+    }
+
+    public void setUpstreamLinkProperties(LinkProperties lp) {
+        // TODO: setUpstreamParameters().
+        mUpstreamLinkProperties = lp;
+    }
+}
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 37221a9..710ab33 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -167,7 +167,8 @@
     private void maybeLogMessage(State state, int what) {
         if (DBG) {
             Log.d(TAG, state.getName() + " got " +
-                    sMagicDecoderRing.get(what, Integer.toString(what)));
+                    sMagicDecoderRing.get(what, Integer.toString(what)) + ", Iface = " +
+                    mIfaceName);
         }
     }
 
@@ -250,31 +251,33 @@
         }
 
         private void cleanupUpstream() {
-            if (mMyUpstreamIfaceName != null) {
-                // note that we don't care about errors here.
-                // sometimes interfaces are gone before we get
-                // to remove their rules, which generates errors.
-                // just do the best we can.
-                try {
-                    // about to tear down NAT; gather remaining statistics
-                    mStatsService.forceUpdate();
-                } catch (Exception e) {
-                    if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
-                }
-                try {
-                    mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
-                } catch (Exception e) {
-                    if (VDBG) Log.e(
-                            TAG, "Exception in removeInterfaceForward: " + e.toString());
-                }
-                try {
-                    mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
-                } catch (Exception e) {
-                    if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
-                }
-                mMyUpstreamIfaceName = null;
+            if (mMyUpstreamIfaceName == null) return;
+
+            cleanupUpstreamInterface(mMyUpstreamIfaceName);
+            mMyUpstreamIfaceName = null;
+        }
+
+        private void cleanupUpstreamInterface(String upstreamIface) {
+            // Note that we don't care about errors here.
+            // Sometimes interfaces are gone before we get
+            // to remove their rules, which generates errors.
+            // Just do the best we can.
+            try {
+                // About to tear down NAT; gather remaining statistics.
+                mStatsService.forceUpdate();
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
             }
-            return;
+            try {
+                mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
+            }
+            try {
+                mNMService.disableNat(mIfaceName, upstreamIface);
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+            }
         }
 
         @Override
@@ -306,6 +309,7 @@
                                     newUpstreamIfaceName);
                         } catch (Exception e) {
                             Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                            cleanupUpstreamInterface(newUpstreamIfaceName);
                             mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
                             transitionTo(mInitialState);
                             return true;
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
new file mode 100644
index 0000000..14d06cc
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -0,0 +1,163 @@
+/*
+ * 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 android.content.Context;
+import android.content.res.Resources;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+
+/**
+ * A utility class to encapsulate the various tethering configuration elements.
+ *
+ * This configuration data includes elements describing upstream properties
+ * (preferred and required types of upstream connectivity as well as default
+ * DNS servers to use if none are available) and downstream properties (such
+ * as regular expressions use to match suitable downstream interfaces and the
+ * DHCPv4 ranges to use).
+ *
+ * @hide
+ */
+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;
+
+    // USB is  192.168.42.1 and 255.255.255.0
+    // Wifi is 192.168.43.1 and 255.255.255.0
+    // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+    // with 255.255.255.0
+    // P2P is 192.168.49.1 and 255.255.255.0
+    private static final String[] DHCP_DEFAULT_RANGE = {
+        "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
+        "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
+        "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
+        "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
+    };
+
+    private final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+
+    public final String[] tetherableUsbRegexs;
+    public final String[] tetherableWifiRegexs;
+    public final String[] tetherableBluetoothRegexs;
+    public final boolean isDunRequired;
+    public final Collection<Integer> preferredUpstreamIfaceTypes;
+    public final String[] dhcpRanges;
+    public final String[] defaultIPv4DNS;
+
+    public TetheringConfiguration(Context ctx) {
+        tetherableUsbRegexs = ctx.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_usb_regexs);
+        tetherableWifiRegexs = ctx.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_wifi_regexs);
+        tetherableBluetoothRegexs = ctx.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_bluetooth_regexs);
+
+        isDunRequired = checkDunRequired(ctx);
+        preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(ctx, isDunRequired);
+
+        dhcpRanges = getDhcpRanges(ctx);
+        defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+    }
+
+    public boolean isUsb(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
+    }
+
+    public boolean isWifi(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
+    }
+
+    public boolean isBluetooth(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
+    }
+
+    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 Collection<Integer> getUpstreamIfaceTypes(Context ctx, boolean requiresDun) {
+        final int ifaceTypes[] = ctx.getResources().getIntArray(
+                com.android.internal.R.array.config_tether_upstream_types);
+        final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
+        for (int i : ifaceTypes) {
+            switch (i) {
+                case TYPE_MOBILE:
+                case TYPE_MOBILE_HIPRI:
+                    if (requiresDun) continue;
+                    break;
+                case TYPE_MOBILE_DUN:
+                    if (!requiresDun) continue;
+                    break;
+            }
+            upstreamIfaceTypes.add(i);
+        }
+
+        // Fix up upstream interface types for DUN or mobile. NOTE: independent
+        // 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 (!upstreamIfaceTypes.contains(TYPE_MOBILE_DUN)) {
+                upstreamIfaceTypes.add(TYPE_MOBILE_DUN);
+            }
+        } else {
+            if (!upstreamIfaceTypes.contains(TYPE_MOBILE)) {
+                upstreamIfaceTypes.add(TYPE_MOBILE);
+            }
+            if (!upstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI)) {
+                upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
+            }
+        }
+
+        return upstreamIfaceTypes;
+    }
+
+    private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
+        for (String regex : regexs) {
+            if (iface.matches(regex)) return true;
+        }
+        return false;
+    }
+
+    private static String[] getDhcpRanges(Context ctx) {
+        final String[] fromResource = ctx.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_dhcp_range);
+        if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
+            return fromResource;
+        }
+        return copy(DHCP_DEFAULT_RANGE);
+    }
+
+    private static String[] copy(String[] strarray) {
+        return Arrays.copyOf(strarray, strarray.length);
+    }
+}
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 4c950de..6209929 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -20,6 +20,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 
 import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.LinkProperties;
@@ -40,7 +42,9 @@
  * pertaining to the current and any potential upstream network.
  *
  * Calling #start() registers two callbacks: one to track the system default
- * network and a second to specifically observe TYPE_MOBILE_DUN networks.
+ * network and a second to observe all networks.  The latter is necessary
+ * while the expression of preferred upstreams remains a list of legacy
+ * connectivity types.  In future, this can be revisited.
  *
  * The methods and data members of this class are only to be accessed and
  * modified from the tethering master state machine thread. Any other
@@ -48,6 +52,10 @@
  *
  * TODO: Move upstream selection logic here.
  *
+ * All callback methods are run on the same thread as the specified target
+ * state machine.  This class does not require locking when accessed from this
+ * thread.  Access from other threads is not advised.
+ *
  * @hide
  */
 public class UpstreamNetworkMonitor {
@@ -60,19 +68,26 @@
     public static final int EVENT_ON_LINKPROPERTIES = 3;
     public static final int EVENT_ON_LOST           = 4;
 
+    private static final int CALLBACK_LISTEN_ALL = 1;
+    private static final int CALLBACK_TRACK_DEFAULT = 2;
+    private static final int CALLBACK_MOBILE_REQUEST = 3;
+
     private final Context mContext;
     private final StateMachine mTarget;
+    private final Handler mHandler;
     private final int mWhat;
     private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
     private ConnectivityManager mCM;
+    private NetworkCallback mListenAllCallback;
     private NetworkCallback mDefaultNetworkCallback;
-    private NetworkCallback mDunTetheringCallback;
     private NetworkCallback mMobileNetworkCallback;
     private boolean mDunRequired;
+    private Network mCurrentDefault;
 
     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
         mContext = ctx;
         mTarget = tgt;
+        mHandler = mTarget.getHandler();
         mWhat = what;
     }
 
@@ -85,16 +100,13 @@
     public void start() {
         stop();
 
-        mDefaultNetworkCallback = new UpstreamNetworkCallback();
-        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
+                .clearCapabilities().build();
+        mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
+        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
 
-        final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                .build();
-        mDunTetheringCallback = new UpstreamNetworkCallback();
-        cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
+        mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_TRACK_DEFAULT);
+        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
     }
 
     public void stop() {
@@ -103,13 +115,13 @@
         releaseCallback(mDefaultNetworkCallback);
         mDefaultNetworkCallback = null;
 
-        releaseCallback(mDunTetheringCallback);
-        mDunTetheringCallback = null;
+        releaseCallback(mListenAllCallback);
+        mListenAllCallback = null;
 
         mNetworkMap.clear();
     }
 
-    public void mobileUpstreamRequiresDun(boolean dunRequired) {
+    public void updateMobileRequiresDun(boolean dunRequired) {
         final boolean valueChanged = (mDunRequired != dunRequired);
         mDunRequired = dunRequired;
         if (valueChanged && mobileNetworkRequested()) {
@@ -123,33 +135,30 @@
     }
 
     public void registerMobileNetworkRequest() {
-        if (mMobileNetworkCallback != null) return;
-
-        final NetworkRequest.Builder builder = new NetworkRequest.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        if (mDunRequired) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                   .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-        } else {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        if (mMobileNetworkCallback != null) {
+            Log.e(TAG, "registerMobileNetworkRequest() already registered");
+            return;
         }
-        final NetworkRequest mobileUpstreamRequest = builder.build();
-
-        // The existing default network and DUN callbacks will be notified.
-        // Therefore, to avoid duplicate notifications, we only register a no-op.
-        mMobileNetworkCallback = new NetworkCallback();
-
-        // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
-        // moderate callback time (once timeout callbacks are implemented). This might
-        // be useful for updating some UI. Additionally, we should definitely log a
-        // message to aid in any subsequent debugging
-        if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
 
         // The following use of the legacy type system cannot be removed until
         // after upstream selection no longer finds networks by legacy type.
-        // See also b/34364553.
-        final int apnType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
-        cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, apnType);
+        // See also http://b/34364553 .
+        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+
+        final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
+                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
+                .build();
+
+        // The existing default network and DUN callbacks will be notified.
+        // Therefore, to avoid duplicate notifications, we only register a no-op.
+        mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
+
+        // 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);
+
+        cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, legacyType, mHandler);
     }
 
     public void releaseMobileNetworkRequest() {
@@ -163,86 +172,136 @@
         return (network != null) ? mNetworkMap.get(network) : null;
     }
 
-    private void handleAvailable(Network network) {
-        if (VDBG) {
-            Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
-        }
+    private void handleAvailable(int callbackType, Network network) {
+        if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
+
         if (!mNetworkMap.containsKey(network)) {
             mNetworkMap.put(network,
                     new NetworkState(null, null, null, network, null, null));
         }
 
-        final ConnectivityManager cm = cm();
+        // Always request whatever extra information we can, in case this
+        // was already up when start() was called, in which case we would
+        // not have been notified of any information that had not changed.
+        switch (callbackType) {
+            case CALLBACK_LISTEN_ALL:
+                break;
 
-        if (mDefaultNetworkCallback != null) {
-            cm.requestNetworkCapabilities(mDefaultNetworkCallback);
-            cm.requestLinkProperties(mDefaultNetworkCallback);
+            case CALLBACK_TRACK_DEFAULT:
+                if (mDefaultNetworkCallback == null) {
+                    // The callback was unregistered in the interval between
+                    // ConnectivityService enqueueing onAvailable() and our
+                    // handling of it here on the mHandler thread.
+                    //
+                    // Clean-up of this network entry is deferred to the
+                    // handling of onLost() by other callbacks.
+                    //
+                    // These request*() calls can be deleted post oag/339444.
+                    return;
+                }
+                mCurrentDefault = network;
+                break;
+
+            case CALLBACK_MOBILE_REQUEST:
+                if (mMobileNetworkCallback == null) {
+                    // The callback was unregistered in the interval between
+                    // ConnectivityService enqueueing onAvailable() and our
+                    // handling of it here on the mHandler thread.
+                    //
+                    // Clean-up of this network entry is deferred to the
+                    // handling of onLost() by other callbacks.
+                    return;
+                }
+                break;
         }
 
-        // Requesting updates for mDunTetheringCallback is not
-        // necessary. Because it's a listen, it will already have
-        // heard all NetworkCapabilities and LinkProperties updates
-        // since UpstreamNetworkMonitor was started. Because we
-        // start UpstreamNetworkMonitor before chooseUpstreamType()
-        // is ever invoked (it can register a DUN request) this is
-        // mostly safe. However, if a DUN network is already up for
-        // some reason (unlikely, because DUN is restricted and,
-        // unless the DUN network is shared with another APN, only
-        // the system can request it and this is the only part of
-        // the system that requests it) we won't know its
-        // LinkProperties or NetworkCapabilities.
+        // Requesting updates for mListenAllCallback is not currently possible
+        // because it's a "listen". Two possible solutions to getting updates
+        // about networks without waiting for a change (which might never come)
+        // are:
+        //
+        //     [1] extend request{NetworkCapabilities,LinkProperties}() to
+        //         take a Network argument and have ConnectivityService do
+        //         what's required (if the network satisfies the request)
+        //
+        //     [2] explicitly file a NetworkRequest for each connectivity type
+        //         listed as a preferred upstream and wait for these callbacks
+        //         to be notified (requires tracking many more callbacks).
+        //
+        // Until this is addressed, networks that exist prior to the "listen"
+        // registration and which do not subsequently change will not cause
+        // us to learn their NetworkCapabilities nor their LinkProperties.
 
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_AVAILABLE, network);
     }
 
     private void handleNetCap(Network network, NetworkCapabilities newNc) {
-        if (!mNetworkMap.containsKey(network)) {
-            // Ignore updates for networks for which we have not yet
-            // received onAvailable() - which should never happen -
-            // or for which we have already received onLost().
+        final NetworkState prev = mNetworkMap.get(network);
+        if (prev == null || newNc.equals(prev.networkCapabilities)) {
+            // Ignore notifications about networks for which we have not yet
+            // received onAvailable() (should never happen) and any duplicate
+            // notifications (e.g. matching more than one of our callbacks).
             return;
         }
+
         if (VDBG) {
             Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
                     network, newNc));
         }
 
-        final NetworkState prev = mNetworkMap.get(network);
-        mNetworkMap.put(network,
-                new NetworkState(null, prev.linkProperties, newNc,
-                                 network, null, null));
+        mNetworkMap.put(network, new NetworkState(
+                null, prev.linkProperties, newNc, network, null, null));
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_CAPABILITIES, network);
     }
 
     private void handleLinkProp(Network network, LinkProperties newLp) {
-        if (!mNetworkMap.containsKey(network)) {
-            // Ignore updates for networks for which we have not yet
-            // received onAvailable() - which should never happen -
-            // or for which we have already received onLost().
+        final NetworkState prev = mNetworkMap.get(network);
+        if (prev == null || newLp.equals(prev.linkProperties)) {
+            // Ignore notifications about networks for which we have not yet
+            // received onAvailable() (should never happen) and any duplicate
+            // notifications (e.g. matching more than one of our callbacks).
             return;
         }
+
         if (VDBG) {
             Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
                     network, newLp));
         }
 
-        final NetworkState prev = mNetworkMap.get(network);
-        mNetworkMap.put(network,
-                new NetworkState(null, newLp, prev.networkCapabilities,
-                                 network, null, null));
+        mNetworkMap.put(network, new NetworkState(
+                null, newLp, prev.networkCapabilities, network, null, null));
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleLost(Network network) {
-        if (!mNetworkMap.containsKey(network)) {
-            // Ignore updates for networks for which we have not yet
-            // received onAvailable() - which should never happen -
-            // or for which we have already received onLost().
+    private void handleLost(int callbackType, Network network) {
+        if (callbackType == CALLBACK_TRACK_DEFAULT) {
+            mCurrentDefault = null;
+            // Receiving onLost() for a default network does not necessarily
+            // mean the network is gone.  We wait for a separate notification
+            // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
+            // clearing all state.
             return;
         }
-        if (VDBG) {
-            Log.d(TAG, "EVENT_ON_LOST for " + network);
+
+        if (!mNetworkMap.containsKey(network)) {
+            // Ignore loss of networks about which we had not previously
+            // learned any information or for which we have already processed
+            // an onLost() notification.
+            return;
         }
+
+        if (VDBG) Log.d(TAG, "EVENT_ON_LOST for " + network);
+
+        // TODO: If sufficient information is available to select a more
+        // preferable upstream, do so now and notify the target.  Likewise,
+        // if the current upstream network is gone, notify the target of the
+        // fact that we now have no upstream at all.
         notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
     }
 
@@ -255,28 +314,48 @@
     }
 
     /**
-     * A NetworkCallback class that relays information of interest to the
-     * tethering master state machine thread for subsequent processing.
+     * A NetworkCallback class that handles information of interest directly
+     * in the thread on which it is invoked. To avoid locking, this MUST be
+     * run on the same thread as the target state machine's handler.
      */
     private class UpstreamNetworkCallback extends NetworkCallback {
+        private final int mCallbackType;
+
+        UpstreamNetworkCallback(int callbackType) {
+            mCallbackType = callbackType;
+        }
+
         @Override
         public void onAvailable(Network network) {
-            mTarget.getHandler().post(() -> handleAvailable(network));
+            checkExpectedThread();
+            handleAvailable(mCallbackType, network);
         }
 
         @Override
         public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
-            mTarget.getHandler().post(() -> handleNetCap(network, newNc));
+            checkExpectedThread();
+            handleNetCap(network, newNc);
         }
 
         @Override
         public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
-            mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
+            checkExpectedThread();
+            handleLinkProp(network, newLp);
         }
 
+        // TODO: Handle onNetworkSuspended();
+        // TODO: Handle onNetworkResumed();
+
         @Override
         public void onLost(Network network) {
-            mTarget.getHandler().post(() -> handleLost(network));
+            checkExpectedThread();
+            handleLost(mCallbackType, network);
+        }
+
+        private void checkExpectedThread() {
+            if (Looper.myLooper() != mHandler.getLooper()) {
+                Log.wtf(TAG, "Handling callback in unexpected thread.");
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 61c2eaca..58600bb 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -285,6 +285,7 @@
                 int activeColorMode) {
             List<Integer> pendingColorModes = new ArrayList<>();
 
+            if (colorModes == null) return false;
             // Build an updated list of all existing color modes.
             boolean colorModesAdded = false;
             for (int colorMode: colorModes) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 55917fc..0c7e44e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -24,7 +24,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Predicate;
 import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
@@ -34,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.function.Predicate;
 
 /**
  * Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
@@ -72,7 +72,7 @@
     // Predicate for whether the given logical address is remote device's one or not.
     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
         @Override
-        public boolean apply(Integer address) {
+        public boolean test(Integer address) {
             return !isAllocatedLocalDeviceAddress(address);
         }
     };
@@ -80,7 +80,7 @@
     // Predicate whether the given logical address is system audio's one or not
     private final Predicate<Integer> mSystemAudioAddressPredicate = new Predicate<Integer>() {
         @Override
-        public boolean apply(Integer address) {
+        public boolean test(Integer address) {
             return HdmiUtils.getTypeFromAddress(address) == Constants.ADDR_AUDIO_SYSTEM;
         }
     };
@@ -403,7 +403,7 @@
         switch (iterationStrategy) {
             case Constants.POLL_ITERATION_IN_ORDER:
                 for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
-                    if (pickPredicate.apply(i)) {
+                    if (pickPredicate.test(i)) {
                         pollingCandidates.add(i);
                     }
                 }
@@ -411,7 +411,7 @@
             case Constants.POLL_ITERATION_REVERSE_ORDER:
             default:  // The default is reverse order.
                 for (int i = Constants.ADDR_SPECIFIC_USE; i >= Constants.ADDR_TV; --i) {
-                    if (pickPredicate.apply(i)) {
+                    if (pickPredicate.test(i)) {
                         pollingCandidates.add(i);
                     }
                 }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index ae98077..e2e834d 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -477,12 +477,6 @@
         public void onLost(Network network) {
             releaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN);
         }
-
-        @Override
-        public void onUnavailable() {
-            // timeout, it was not possible to establish the required connection
-            releaseSuplConnection(GPS_AGPS_DATA_CONN_FAILED);
-        }
     };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -902,8 +896,7 @@
         NetworkRequest request = requestBuilder.build();
         mConnMgr.requestNetwork(
                 request,
-                mSuplConnectivityCallback,
-                ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS);
+                mSuplConnectivityCallback);
     }
 
     private void handleReleaseSuplConnection(int agpsDataConnStatus) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 4658c046..060dd73 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -381,6 +381,7 @@
         mContext.unregisterReceiver(mTetherReceiver);
         mContext.unregisterReceiver(mPollReceiver);
         mContext.unregisterReceiver(mRemovedReceiver);
+        mContext.unregisterReceiver(mUserReceiver);
         mContext.unregisterReceiver(mShutdownReceiver);
 
         final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d31c7b5..22c5e85 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2078,12 +2078,14 @@
                 Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
                 return null;
             }
-            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            try {
-                writePolicyXml(baos, true /*forBackup*/);
-                return baos.toByteArray();
-            } catch (IOException e) {
-                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+            synchronized(mPolicyFile) {
+                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                try {
+                    writePolicyXml(baos, true /*forBackup*/);
+                    return baos.toByteArray();
+                } catch (IOException e) {
+                    Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+                }
             }
             return null;
         }
@@ -2101,12 +2103,14 @@
                 Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
                 return;
             }
-            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
-            try {
-                readPolicyXml(bais, true /*forRestore*/);
-                savePolicyFile();
-            } catch (NumberFormatException | XmlPullParserException | IOException e) {
-                Slog.w(TAG, "applyRestore: error reading payload", e);
+            synchronized(mPolicyFile) {
+                final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+                try {
+                    readPolicyXml(bais, true /*forRestore*/);
+                    savePolicyFile();
+                } catch (NumberFormatException | XmlPullParserException | IOException e) {
+                    Slog.w(TAG, "applyRestore: error reading payload", e);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 601a219..7aa96cf 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -30,10 +30,13 @@
 import android.os.BatteryManager;
 import android.os.Environment;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.os.storage.StorageManager;
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.server.pm.dex.DexManager;
+
 import java.io.File;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.TimeUnit;
@@ -59,21 +62,33 @@
             "android",
             BackgroundDexOptService.class.getName());
 
+    // Possible return codes of individual optimization steps.
+
+    // Optimizations finished. All packages were processed.
+    private static final int OPTIMIZE_PROCESSED = 0;
+    // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
+    private static final int OPTIMIZE_CONTINUE = 1;
+    // Optimizations should be aborted. Job scheduler requested it.
+    private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
+    // Optimizations should be aborted. No space left on device.
+    private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
+
     /**
      * Set of failed packages remembered across job runs.
      */
-    static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
+    static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
+    static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
 
     /**
      * Atomics set to true if the JobScheduler requests an abort.
      */
-    final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
-    final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+    private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
+    private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
 
     /**
      * Atomic set to true if one job should exit early because another job was started.
      */
-    final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
+    private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
 
     private final File mDataDir = Environment.getDataDirectory();
 
@@ -104,8 +119,11 @@
         // The idle maintanance job skips packages which previously failed to
         // compile. The given package has changed and may successfully compile
         // now. Remove it from the list of known failing packages.
-        synchronized (sFailedPackageNames) {
-            sFailedPackageNames.remove(packageName);
+        synchronized (sFailedPackageNamesPrimary) {
+            sFailedPackageNamesPrimary.remove(packageName);
+        }
+        synchronized (sFailedPackageNamesSecondary) {
+            sFailedPackageNamesSecondary.remove(packageName);
         }
     }
 
@@ -124,9 +142,9 @@
         return (100 * level / scale);
     }
 
-    private long getLowStorageThreshold() {
+    private long getLowStorageThreshold(Context context) {
         @SuppressWarnings("deprecation")
-        final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir);
+        final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
         if (lowThreshold == 0) {
             Log.e(TAG, "Invalid low storage threshold");
         }
@@ -155,7 +173,7 @@
         // Load low battery threshold from the system config. This is a 0-100 integer.
         final int lowBatteryThreshold = getResources().getInteger(
                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
-        final long lowThreshold = getLowStorageThreshold();
+        final long lowThreshold = getLowStorageThreshold(this);
 
         mAbortPostBootUpdate.set(false);
 
@@ -206,61 +224,123 @@
         new Thread("BackgroundDexOptService_IdleOptimization") {
             @Override
             public void run() {
-                idleOptimization(jobParams, pm, pkgs);
+                int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
+                if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                    Log.w(TAG, "Idle optimizations aborted because of space constraints.");
+                    // If we didn't abort we ran to completion (or stopped because of space).
+                    // Abandon our timeslice and do not reschedule.
+                    jobFinished(jobParams, /* reschedule */ false);
+                }
             }
         }.start();
         return true;
     }
 
-    private void idleOptimization(JobParameters jobParams, PackageManagerService pm,
-            ArraySet<String> pkgs) {
+    // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
+    private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context) {
         Log.i(TAG, "Performing idle optimizations");
         // If post-boot update is still running, request that it exits early.
         mExitPostBootUpdate.set(true);
-
         mAbortIdleOptimization.set(false);
 
-        final long lowThreshold = getLowStorageThreshold();
-        for (String pkg : pkgs) {
-            if (mAbortIdleOptimization.get()) {
-                // JobScheduler requested an early abort.
-                return;
+        long lowStorageThreshold = getLowStorageThreshold(context);
+        // Optimize primary apks.
+        int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
+                sFailedPackageNamesPrimary);
+
+        if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+            return result;
+        }
+
+        if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+            result = reconcileSecondaryDexFiles(pm.getDexManager());
+            if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+                return result;
             }
 
-            synchronized (sFailedPackageNames) {
-                if (sFailedPackageNames.contains(pkg)) {
+            result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
+                    sFailedPackageNamesSecondary);
+        }
+        return result;
+    }
+
+    private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
+            long lowStorageThreshold, boolean is_for_primary_dex,
+            ArraySet<String> failedPackageNames) {
+        for (String pkg : pkgs) {
+            int abort_code = abortIdleOptimizations(lowStorageThreshold);
+            if (abort_code != OPTIMIZE_CONTINUE) {
+                return abort_code;
+            }
+
+            synchronized (failedPackageNames) {
+                if (failedPackageNames.contains(pkg)) {
                     // Skip previously failing package
                     continue;
+                } else {
+                    // Conservatively add package to the list of failing ones in case performDexOpt
+                    // never returns.
+                    failedPackageNames.add(pkg);
                 }
             }
 
-            long usableSpace = mDataDir.getUsableSpace();
-            if (usableSpace < lowThreshold) {
-                // Rather bail than completely fill up the disk.
-                Log.w(TAG, "Aborting background dex opt job due to low storage: " +
-                        usableSpace);
-                break;
-            }
-
-            // Conservatively add package to the list of failing ones in case performDexOpt
-            // never returns.
-            synchronized (sFailedPackageNames) {
-                sFailedPackageNames.add(pkg);
-            }
             // Optimize package if needed. Note that there can be no race between
             // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
-            if (pm.performDexOpt(pkg,
-                    /* checkProfiles */ true,
-                    PackageManagerService.REASON_BACKGROUND_DEXOPT,
-                    /* force */ false)) {
+            boolean success = is_for_primary_dex
+                    ? pm.performDexOpt(pkg,
+                            /* checkProfiles */ true,
+                            PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                            /* force */ false)
+                    : pm.performDexOptSecondary(pkg,
+                            PackageManagerServiceCompilerMapping.getFullCompilerFilter(),
+                            /* force */ true);
+            if (success) {
                 // Dexopt succeeded, remove package from the list of failing ones.
-                synchronized (sFailedPackageNames) {
-                    sFailedPackageNames.remove(pkg);
+                synchronized (failedPackageNames) {
+                    failedPackageNames.remove(pkg);
                 }
             }
         }
-        // Ran to completion, so we abandon our timeslice and do not reschedule.
-        jobFinished(jobParams, /* reschedule */ false);
+        return OPTIMIZE_PROCESSED;
+    }
+
+    private int reconcileSecondaryDexFiles(DexManager dm) {
+        // TODO(calin): should we blacklist packages for which we fail to reconcile?
+        for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
+            if (mAbortIdleOptimization.get()) {
+                return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+            }
+            dm.reconcileSecondaryDexFiles(p);
+        }
+        return OPTIMIZE_PROCESSED;
+    }
+
+    // Evaluate whether or not idle optimizations should continue.
+    private int abortIdleOptimizations(long lowStorageThreshold) {
+        if (mAbortIdleOptimization.get()) {
+            // JobScheduler requested an early abort.
+            return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+        }
+        long usableSpace = mDataDir.getUsableSpace();
+        if (usableSpace < lowStorageThreshold) {
+            // Rather bail than completely fill up the disk.
+            Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
+            return OPTIMIZE_ABORT_NO_SPACE_LEFT;
+        }
+
+        return OPTIMIZE_CONTINUE;
+    }
+
+    /**
+     * Execute the idle optimizations immediately.
+     */
+    public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+        // Create a new object to make sure we don't interfere with the scheduled jobs.
+        // Note that this may still run at the same time with the job scheduled by the
+        // JobScheduler but the scheduler will not be able to cancel it.
+        BackgroundDexOptService bdos = new BackgroundDexOptService();
+        int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+        return result == OPTIMIZE_PROCESSED;
     }
 
     @Override
@@ -281,7 +361,7 @@
         }
 
         final ArraySet<String> pkgs = pm.getOptimizablePackages();
-        if (pkgs == null || pkgs.isEmpty()) {
+        if (pkgs.isEmpty()) {
             if (DEBUG_DEXOPT) {
                 Log.i(TAG, "No packages to optimize");
             }
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index a6f9243..a0fabdf 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -777,6 +777,23 @@
         }
     }
 
+    public void grantDefaultPermissionsToEnabledImsServicesLPr(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package imsServicePackage = getSystemPackageLPr(packageName);
+            if (imsServicePackage != null
+                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
+                grantRuntimePermissionsLPw(imsServicePackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissionsLPw(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
+                grantRuntimePermissionsLPw(imsServicePackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissionsLPw(imsServicePackage, CAMERA_PERMISSIONS, userId);
+            }
+        }
+    }
+
     public void grantDefaultPermissionsToDefaultBrowserLPr(String packageName, int userId) {
         Log.i(TAG, "Granting permissions to default browser for user:" + userId);
         if (packageName == null) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 04569c2..42a0bad 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -50,6 +50,14 @@
     public static final int DEXOPT_BOOTCOMPLETE   = 1 << 4;
     /** Hint that the dexopt type is profile-guided. */
     public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
+    /** The compilation is for a secondary dex file. */
+    public static final int DEXOPT_SECONDARY_DEX  = 1 << 6;
+    /** Ignore the result of dexoptNeeded and force compilation. */
+    public static final int DEXOPT_FORCE          = 1 << 7;
+    /** Indicates that the dex file passed to dexopt in on CE storage. */
+    public static final int DEXOPT_STORAGE_CE     = 1 << 8;
+    /** Indicates that the dex file passed to dexopt in on DE storage. */
+    public static final int DEXOPT_STORAGE_DE     = 1 << 9;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
@@ -425,6 +433,20 @@
         }
     }
 
+    public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
+            String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
+        for (int i = 0; i < isas.length; i++) {
+            assertValidInstructionSet(isas[i]);
+        }
+        if (!checkBeforeRemote()) return false;
+        try {
+            return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
+                    volumeUuid, flags);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     private static void assertValidInstructionSet(String instructionSet)
             throws InstallerException {
         for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 0de0c92..49d3c8b 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -287,7 +287,7 @@
         for (int i = 0; i < defMapSize; i++) {
             String alias = definedMapping.keyAt(i);
             ArraySet<PublicKey> pubKeys = definedMapping.valueAt(i);
-            if (alias != null && pubKeys != null || pubKeys.size() > 0) {
+            if (alias != null && pubKeys != null && pubKeys.size() > 0) {
                 KeySetHandle ks = addKeySetLPw(pubKeys);
                 newKeySetAliases.put(alias, ks.getId());
             }
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 60c83b4..1498693 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -213,6 +213,7 @@
         if (getAvailableSpace() > 0) {
             dexoptCommandCountExecuted++;
 
+            Log.d(TAG, "Next command: " + next);
             return next;
         } else {
             if (DEBUG_DEXOPT) {
@@ -313,7 +314,8 @@
         optimizer.performDexOpt(pkg, libraryDependencies,
                 null /* ISAs */, false /* checkProfiles */,
                 getCompilerFilterForReason(compilationReason),
-                null /* CompilerStats.PackageStats */);
+                null /* CompilerStats.PackageStats */,
+                mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName));
 
         return commands;
     }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 8e201ac..d9ea728 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
 import android.os.Environment;
 import android.os.PowerManager;
@@ -35,6 +36,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import dalvik.system.DexFile;
 
@@ -43,6 +45,10 @@
 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;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
@@ -52,13 +58,16 @@
 /**
  * Helper class for running dexopt command on packages.
  */
-class PackageDexOptimizer {
+public class PackageDexOptimizer {
     private static final String TAG = "PackageManager.DexOptimizer";
     static final String OAT_DIR_NAME = "oat";
     // TODO b/19550105 Remove error codes and use exceptions
-    static final int DEX_OPT_SKIPPED = 0;
-    static final int DEX_OPT_PERFORMED = 1;
-    static final int DEX_OPT_FAILED = -1;
+    public static final int DEX_OPT_SKIPPED = 0;
+    public static final int DEX_OPT_PERFORMED = 1;
+    public static final int DEX_OPT_FAILED = -1;
+
+    /** Special library name that skips shared libraries check during compilation. */
+    public static final String SKIP_SHARED_LIBRARY_CHECK = "&";
 
     private final Installer mInstaller;
     private final Object mInstallLock;
@@ -95,11 +104,14 @@
      */
     int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
             String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
-            CompilerStats.PackageStats packageStats) {
+            CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps) {
         if (!canOptimizePackage(pkg)) {
             return DEX_OPT_SKIPPED;
         }
         synchronized (mInstallLock) {
+            // During boot the system doesn't need to instantiate and obtain a wake lock.
+            // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+            // dexopt.
             final boolean useLock = mSystemReady;
             if (useLock) {
                 mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
@@ -107,7 +119,7 @@
             }
             try {
                 return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
-                        targetCompilationFilter, packageStats);
+                        targetCompilationFilter, packageStats, isUsedByOtherApps);
             } finally {
                 if (useLock) {
                     mDexoptWakeLock.release();
@@ -123,16 +135,19 @@
     @GuardedBy("mInstallLock")
     private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
             String[] targetInstructionSets, boolean checkForProfileUpdates,
-            String targetCompilerFilter, CompilerStats.PackageStats packageStats) {
+            String targetCompilerFilter, CompilerStats.PackageStats packageStats,
+            boolean isUsedByOtherApps) {
         final String[] instructionSets = targetInstructionSets != null ?
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
 
-        final String compilerFilter = getRealCompilerFilter(pkg, targetCompilerFilter);
+        final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
+                targetCompilerFilter, isUsedByOtherApps);
         final boolean profileUpdated = checkForProfileUpdates &&
                 isProfileUpdated(pkg, sharedGid, compilerFilter);
+
         // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
         // paths (b/34169257).
         final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
@@ -201,6 +216,79 @@
     }
 
     /**
+     * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}.
+     *
+     * @return
+     *      DEX_OPT_FAILED if there was any exception during dexopt
+     *      DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
+     * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file
+     * didn't need an update. That's because at the moment we don't get more than success/failure
+     * from installd.
+     *
+     * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than
+     * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
+     * that seems wasteful.
+     */
+    public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
+            String compilerFilter, boolean isUsedByOtherApps) {
+        synchronized (mInstallLock) {
+            // During boot the system doesn't need to instantiate and obtain a wake lock.
+            // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+            // dexopt.
+            final boolean useLock = mSystemReady;
+            if (useLock) {
+                mDexoptWakeLock.setWorkSource(new WorkSource(info.uid));
+                mDexoptWakeLock.acquire();
+            }
+            try {
+                return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
+                        isUsedByOtherApps);
+            } finally {
+                if (useLock) {
+                    mDexoptWakeLock.release();
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mInstallLock")
+    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
+            String compilerFilter, boolean isUsedByOtherApps) {
+        int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
+        // Check the app storage and add the appropriate flags.
+        if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+            dexoptFlags |= DEXOPT_STORAGE_DE;
+        } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+            dexoptFlags |= DEXOPT_STORAGE_CE;
+        } else {
+            Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+            return DEX_OPT_FAILED;
+        }
+        compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
+        Log.d(TAG, "Running dexopt on: " + path
+                + " pkg=" + info.packageName + " isa=" + isas
+                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+                + " target-filter=" + compilerFilter);
+
+        try {
+            for (String isa : isas) {
+                // Reuse the same dexopt path as for the primary apks. We don't need all the
+                // arguments as some (dexopNeeded and oatDir) will be computed by installd because
+                // system server cannot read untrusted app content.
+                // TODO(calin): maybe add a separate call.
+                mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
+                        /*oatDir*/ null, dexoptFlags,
+                        compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK);
+            }
+
+            return DEX_OPT_PERFORMED;
+        } catch (InstallerException e) {
+            Slog.w(TAG, "Failed to dexopt", e);
+            return DEX_OPT_FAILED;
+        }
+    }
+
+    /**
      * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
      * optimize or not (and in what way).
      */
@@ -246,8 +334,9 @@
      * The target filter will be updated if the package code is used by other apps
      * or if it has the safe mode flag set.
      */
-    private String getRealCompilerFilter(PackageParser.Package pkg, String targetCompilerFilter) {
-        int flags = pkg.applicationInfo.flags;
+    private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
+            boolean isUsedByOtherApps) {
+        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
@@ -259,7 +348,7 @@
             return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
         }
 
-        if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps(pkg)) {
+        if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
             // If the dex files is used by other apps, we cannot use profile-guided compilation.
             return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
         }
@@ -272,12 +361,16 @@
      * filter.
      */
     private int getDexFlags(PackageParser.Package pkg, String compilerFilter) {
-        int flags = pkg.applicationInfo.flags;
+        return getDexFlags(pkg.applicationInfo, compilerFilter);
+    }
+
+    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);
-        boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
+        boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
         int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
         int dexFlags =
                 (isPublic ? DEXOPT_PUBLIC : 0)
@@ -385,40 +478,6 @@
         mSystemReady = true;
     }
 
-    /**
-     * Returns true if the profiling data collected for the given app indicate
-     * that the apps's APK has been loaded by another app.
-     * Note that this returns false for all forward-locked apps and apps without
-     * any collected profiling data.
-     */
-    public static boolean isUsedByOtherApps(PackageParser.Package pkg) {
-        if (pkg.isForwardLocked()) {
-            // Skip the check for forward locked packages since they don't share their code.
-            return false;
-        }
-
-        for (String apkPath : pkg.getAllCodePathsExcludingResourceOnly()) {
-            try {
-                apkPath = PackageManagerServiceUtils.realpath(new File(apkPath));
-            } catch (IOException e) {
-                // Log an error but continue without it.
-                Slog.w(TAG, "Failed to get canonical path", e);
-                continue;
-            }
-            String useMarker = apkPath.replace('/', '@');
-            final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
-            for (int i = 0; i < currentUserIds.length; i++) {
-                File profileDir =
-                        Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
-                File foreignUseMark = new File(profileDir, useMarker);
-                if (foreignUseMark.exists()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     private String printDexoptFlags(int flags) {
         ArrayList<String> flagsList = new ArrayList<>();
 
@@ -437,6 +496,19 @@
         if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) {
             flagsList.add("safemode");
         }
+        if ((flags & DEXOPT_SECONDARY_DEX) == DEXOPT_SECONDARY_DEX) {
+            flagsList.add("secondary");
+        }
+        if ((flags & DEXOPT_FORCE) == DEXOPT_FORCE) {
+            flagsList.add("force");
+        }
+        if ((flags & DEXOPT_STORAGE_CE) == DEXOPT_STORAGE_CE) {
+            flagsList.add("storage_ce");
+        }
+        if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
+            flagsList.add("storage_de");
+        }
+
         return String.join(",", flagsList);
     }
 
@@ -461,5 +533,12 @@
             // TODO: The return value is wrong when patchoat is needed.
             return DexFile.DEX2OAT_FROM_SCRATCH;
         }
+
+        @Override
+        protected int adjustDexoptFlags(int flags) {
+            // Add DEXOPT_FORCE flag to signal installd that it should force compilation
+            // and discard dexoptanalyzer result.
+            return flags | DEXOPT_FORCE;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 07dbb17..42f6502 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -524,9 +524,6 @@
 
     public static final int REASON_LAST = REASON_CORE_APP;
 
-    /** Special library name that skips shared libraries check during compilation. */
-    private static final String SKIP_SHARED_LIBRARY_CHECK = "&";
-
     final ServiceThread mHandlerThread;
 
     final PackageHandler mHandler;
@@ -1787,6 +1784,18 @@
                     res.removedInfo.args.doPostDeleteLI(true);
                 }
             }
+
+            if (!isEphemeral(res.pkg)) {
+                // Notify DexManager that the package was installed for new users.
+                // The updated users should already be indexed and the package code paths
+                // should not change.
+                // Don't notify the manager for ephemeral apps as they are not expected to
+                // survive long enough to benefit of background optimizations.
+                for (int userId : firstUsers) {
+                    PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);
+                    mDexManager.notifyPackageInstalled(info, userId);
+                }
+            }
         }
 
         // If someone is watching installs - notify them
@@ -2121,7 +2130,7 @@
         mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
-        mDexManager = new DexManager();
+        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2248,7 +2257,7 @@
                                         DEXOPT_PUBLIC,
                                         getCompilerFilterForReason(REASON_SHARED_APK),
                                         StorageManager.UUID_PRIVATE_INTERNAL,
-                                        SKIP_SHARED_LIBRARY_CHECK);
+                                        PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK);
                             }
                         } catch (FileNotFoundException e) {
                             Slog.w(TAG, "Library not found: " + lib);
@@ -7489,11 +7498,46 @@
                 pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
                         false /* checkProfiles */,
                         getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY),
-                        getOrCreateCompilerPackageStats(depPackage));
+                        getOrCreateCompilerPackageStats(depPackage),
+                        mDexManager.isUsedByOtherApps(p.packageName));
             }
         }
         return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
-                targetCompilerFilter, getOrCreateCompilerPackageStats(p));
+                targetCompilerFilter, getOrCreateCompilerPackageStats(p),
+                mDexManager.isUsedByOtherApps(p.packageName));
+    }
+
+    // Performs dexopt on the used secondary dex files belonging to the given package.
+    // Returns true if all dex files were process successfully (which could mean either dexopt or
+    // skip). Returns false if any of the files caused errors.
+    @Override
+    public boolean performDexOptSecondary(String packageName, String compilerFilter,
+            boolean force) {
+        return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
+    }
+
+    /**
+     * Reconcile the information we have about the secondary dex files belonging to
+     * {@code packagName} and the actual dex files. For all dex files that were
+     * deleted, update the internal records and delete the generated oat files.
+     */
+    @Override
+    public void reconcileSecondaryDexFiles(String packageName) {
+        mDexManager.reconcileSecondaryDexFiles(packageName);
+    }
+
+    // TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject
+    // a reference there.
+    /*package*/ DexManager getDexManager() {
+        return mDexManager;
+    }
+
+    /**
+     * Execute the background dexopt job immediately.
+     */
+    @Override
+    public boolean runBackgroundDexoptJob() {
+        return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
     }
 
     Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -7703,60 +7747,9 @@
             return;
         }
         destroyAppProfilesLeafLIF(pkg);
-        destroyAppReferenceProfileLeafLIF(pkg, userId, true /* removeBaseMarker */);
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             destroyAppProfilesLeafLIF(pkg.childPackages.get(i));
-            destroyAppReferenceProfileLeafLIF(pkg.childPackages.get(i), userId,
-                    true /* removeBaseMarker */);
-        }
-    }
-
-    private void destroyAppReferenceProfileLeafLIF(PackageParser.Package pkg, int userId,
-            boolean removeBaseMarker) {
-        if (pkg.isForwardLocked()) {
-            return;
-        }
-
-        for (String path : pkg.getAllCodePathsExcludingResourceOnly()) {
-            try {
-                path = PackageManagerServiceUtils.realpath(new File(path));
-            } catch (IOException e) {
-                // TODO: Should we return early here ?
-                Slog.w(TAG, "Failed to get canonical path", e);
-                continue;
-            }
-
-            final String useMarker = path.replace('/', '@');
-            for (int realUserId : resolveUserIds(userId)) {
-                File profileDir = Environment.getDataProfilesDeForeignDexDirectory(realUserId);
-                if (removeBaseMarker) {
-                    File foreignUseMark = new File(profileDir, useMarker);
-                    if (foreignUseMark.exists()) {
-                        if (!foreignUseMark.delete()) {
-                            Slog.w(TAG, "Unable to delete foreign user mark for package: "
-                                    + pkg.packageName);
-                        }
-                    }
-                }
-
-                File[] markers = profileDir.listFiles();
-                if (markers != null) {
-                    final String searchString = "@" + pkg.packageName + "@";
-                    // We also delete all markers that contain the package name we're
-                    // uninstalling. These are associated with secondary dex-files belonging
-                    // to the package. Reconstructing the path of these dex files is messy
-                    // in general.
-                    for (File marker : markers) {
-                        if (marker.getName().indexOf(searchString) > 0) {
-                            if (!marker.delete()) {
-                                Slog.w(TAG, "Unable to delete foreign user mark for package: "
-                                    + pkg.packageName);
-                            }
-                        }
-                    }
-                }
-            }
         }
     }
 
@@ -7774,10 +7767,6 @@
             return;
         }
         clearAppProfilesLeafLIF(pkg);
-        // We don't remove the base foreign use marker when clearing profiles because
-        // we will rename it when the app is updated. Unlike the actual profile contents,
-        // the foreign use marker is good across installs.
-        destroyAppReferenceProfileLeafLIF(pkg, userId, false /* removeBaseMarker */);
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             clearAppProfilesLeafLIF(pkg.childPackages.get(i));
@@ -8657,14 +8646,6 @@
         synchronized (mPackages) {
             // We don't expect installation to fail beyond this point
 
-            if (pkgSetting.pkg != null) {
-                // Note that |user| might be null during the initial boot scan. If a codePath
-                // for an app has changed during a boot scan, it's due to an app update that's
-                // part of the system partition and marker changes must be applied to all users.
-                maybeRenameForeignDexMarkers(pkgSetting.pkg, pkg,
-                    (user != null) ? user : UserHandle.ALL);
-            }
-
             // Add the new setting to mSettings
             mSettings.insertPackageSettingLPw(pkgSetting, pkg);
             // Add the new setting to mPackages
@@ -9029,74 +9010,6 @@
         return pkg;
     }
 
-    private void maybeRenameForeignDexMarkers(PackageParser.Package existing,
-            PackageParser.Package update, UserHandle user) {
-        if (existing.applicationInfo == null || update.applicationInfo == null) {
-            // This isn't due to an app installation.
-            return;
-        }
-
-        final File oldCodePath = new File(existing.applicationInfo.getCodePath());
-        final File newCodePath = new File(update.applicationInfo.getCodePath());
-
-        // The codePath hasn't changed, so there's nothing for us to do.
-        if (Objects.equals(oldCodePath, newCodePath)) {
-            return;
-        }
-
-        File canonicalNewCodePath;
-        try {
-            canonicalNewCodePath = new File(PackageManagerServiceUtils.realpath(newCodePath));
-        } catch (IOException e) {
-            Slog.w(TAG, "Failed to get canonical path.", e);
-            return;
-        }
-
-        // This is a bit of a hack. The oldCodePath doesn't exist at this point (because
-        // we've already renamed / deleted it) so we cannot call realpath on it. Here we assume
-        // that the last component of the path (i.e, the name) doesn't need canonicalization
-        // (i.e, that it isn't ".", ".." or a symbolic link). This is a valid assumption for now
-        // but may change in the future. Hopefully this function won't exist at that point.
-        final File canonicalOldCodePath = new File(canonicalNewCodePath.getParentFile(),
-                oldCodePath.getName());
-
-        // Calculate the prefixes of the markers. These are just the paths with "/" replaced
-        // with "@".
-        String oldMarkerPrefix = canonicalOldCodePath.getAbsolutePath().replace('/', '@');
-        if (!oldMarkerPrefix.endsWith("@")) {
-            oldMarkerPrefix += "@";
-        }
-        String newMarkerPrefix = canonicalNewCodePath.getAbsolutePath().replace('/', '@');
-        if (!newMarkerPrefix.endsWith("@")) {
-            newMarkerPrefix += "@";
-        }
-
-        List<String> updatedPaths = update.getAllCodePathsExcludingResourceOnly();
-        List<String> markerSuffixes = new ArrayList<String>(updatedPaths.size());
-        for (String updatedPath : updatedPaths) {
-            String updatedPathName = new File(updatedPath).getName();
-            markerSuffixes.add(updatedPathName.replace('/', '@'));
-        }
-
-        for (int userId : resolveUserIds(user.getIdentifier())) {
-            File profileDir = Environment.getDataProfilesDeForeignDexDirectory(userId);
-
-            for (String markerSuffix : markerSuffixes) {
-                File oldForeignUseMark = new File(profileDir, oldMarkerPrefix + markerSuffix);
-                File newForeignUseMark = new File(profileDir, newMarkerPrefix + markerSuffix);
-                if (oldForeignUseMark.exists()) {
-                    try {
-                        Os.rename(oldForeignUseMark.getAbsolutePath(),
-                                newForeignUseMark.getAbsolutePath());
-                    } catch (ErrnoException e) {
-                        Slog.w(TAG, "Failed to rename foreign use marker", e);
-                        oldForeignUseMark.delete();
-                    }
-                }
-            }
-        }
-    }
-
     /**
      * Derive the ABI of a non-system package located at {@code scanFile}. This information
      * is derived purely on the basis of the contents of {@code scanFile} and
@@ -9321,9 +9234,9 @@
                         ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi;
                         Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi
                                 + " (requirer="
-                                + (requirer == null ? "null" : requirer.pkg.packageName)
+                                + (requirer != null ? requirer.pkg : "null")
                                 + ", scannedPackage="
-                                + (scannedPackage != null ? scannedPackage.packageName : "null")
+                                + (scannedPackage != null ? scannedPackage : "null")
                                 + ")");
                         try {
                             mInstaller.rmdex(ps.codePathString,
@@ -15279,7 +15192,8 @@
             mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
                     null /* instructionSets */, false /* checkProfiles */,
                     getCompilerFilterForReason(REASON_INSTALL),
-                    getOrCreateCompilerPackageStats(pkg));
+                    getOrCreateCompilerPackageStats(pkg),
+                    mDexManager.isUsedByOtherApps(pkg.packageName));
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
             // Notify BackgroundDexOptService that the package has been changed.
@@ -16578,8 +16492,6 @@
         try (PackageFreezer freezer = freezePackage(packageName, "clearApplicationProfileData")) {
             synchronized (mInstallLock) {
                 clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
-                destroyAppReferenceProfileLeafLIF(pkg, UserHandle.USER_ALL,
-                        true /* removeBaseMarker */);
             }
         }
     }
@@ -21143,6 +21055,20 @@
         }
     }
 
+    @Override
+    public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId) {
+        enforceSystemOrPhoneCaller("grantDefaultPermissionsToEnabledImsServices");
+        synchronized (mPackages) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServicesLPr(
+                        packageNames, userId);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
     private static void enforceSystemOrPhoneCaller(String tag) {
         int callingUid = Binder.getCallingUid();
         if (callingUid != Process.PHONE_UID && callingUid != Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 8a3f48efb..0634dac 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -23,7 +23,7 @@
 /**
  * Manage (retrieve) mappings from compilation reason to compilation filter.
  */
-class PackageManagerServiceCompilerMapping {
+public class PackageManagerServiceCompilerMapping {
     // Names for compilation reasons.
     static final String REASON_STRINGS[] = {
             "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "nsys-library", "shared-apk",
@@ -80,9 +80,7 @@
             try {
                 // Check that the system property name is legal.
                 String sysPropName = getSystemPropertyName(reason);
-                if (sysPropName == null ||
-                        sysPropName.isEmpty() ||
-                        sysPropName.length() > SystemProperties.PROP_NAME_MAX) {
+                if (sysPropName == null || sysPropName.isEmpty()) {
                     throw new IllegalStateException("Reason system property name \"" +
                             sysPropName +"\" for reason " + REASON_STRINGS[reason]);
                 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 45887e1..9feee8c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -133,7 +133,8 @@
                 sortTemp, packageManagerService);
 
         // Give priority to apps used by other apps.
-        applyPackageFilter((pkg) -> PackageDexOptimizer.isUsedByOtherApps(pkg), result,
+        applyPackageFilter((pkg) ->
+                packageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), result,
                 remainingPkgs, sortTemp, packageManagerService);
 
         // Filter out packages that aren't recently used, add all remaining apps.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e18d4e0..44b372b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -110,6 +110,10 @@
                     return runInstallWrite();
                 case "compile":
                     return runCompile();
+                case "reconcile-secondary-dex-files":
+                    return runreconcileSecondaryDexFiles();
+                case "bg-dexopt-job":
+                    return runDexoptJob();
                 case "dump-profiles":
                     return runDumpProfiles();
                 case "list":
@@ -281,6 +285,7 @@
         String compilerFilter = null;
         String compilationReason = null;
         String checkProfilesRaw = null;
+        boolean secondaryDex = false;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -308,6 +313,9 @@
                     clearProfileData = true;
                     compilationReason = "install";
                     break;
+                case "--secondary-dex":
+                    secondaryDex = true;
+                    break;
                 default:
                     pw.println("Error: Unknown option: " + opt);
                     return 1;
@@ -380,8 +388,11 @@
                 mInterface.clearApplicationProfileData(packageName);
             }
 
-            boolean result = mInterface.performDexOptMode(packageName,
-                    checkProfiles, targetCompilerFilter, forceCompilation);
+            boolean result = secondaryDex
+                    ? mInterface.performDexOptSecondary(packageName,
+                            targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName,
+                            checkProfiles, targetCompilerFilter, forceCompilation);
             if (!result) {
                 failedPackages.add(packageName);
             }
@@ -409,6 +420,17 @@
         }
     }
 
+    private int runreconcileSecondaryDexFiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.reconcileSecondaryDexFiles(packageName);
+        return 0;
+    }
+
+    private int runDexoptJob() throws RemoteException {
+        boolean result = mInterface.runBackgroundDexoptJob();
+        return result ? 0 : -1;
+    }
+
     private int runDumpProfiles() throws RemoteException {
         String packageName = getNextArg();
         mInterface.dumpProfiles(packageName);
@@ -1441,6 +1463,13 @@
         }
         pw.println("      --reset: restore package to its post-install state");
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
+        pw.println("      --secondary-dex: compile app secondary dex files");
+        pw.println("  bg-dexopt-job");
+        pw.println("    Execute the background optimizations immediately.");
+        pw.println("    Note that the command only runs the background optimizer logic. It may");
+        pw.println("    overlap with the actual job but the job scheduler will not be able to");
+        pw.println("    cancel it. It will also run even if the device is not in the idle");
+        pw.println("    maintenance mode.");
         pw.println("  list features");
         pw.println("    Prints all features of the system.");
         pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
@@ -1460,6 +1489,8 @@
         pw.println("      -3: filter to only show third party packages");
         pw.println("      -i: see the installer for the packages");
         pw.println("      -u: also include uninstalled packages");
+        pw.println("  reconcile-secondary-dex-files TARGET-PACKAGE");
+        pw.println("    Reconciles the package secondary dex files with the generated oat files.");
         pw.println("  list permission-groups");
         pw.println("    Prints all known permission groups.");
         pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 0fe1539..9a559ee 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -60,8 +60,8 @@
 
     /** Path to MAC permissions on system image */
     private static final File[] MAC_PERMISSIONS =
-    { new File(Environment.getRootDirectory(), "/etc/security/plat_mac_permissions.xml"),
-      new File(Environment.getRootDirectory(), "/etc/security/nonplat_mac_permissions.xml") };
+    { new File(Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"),
+      new File(Environment.getVendorDirectory(), "/etc/selinux/nonplat_mac_permissions.xml") };
 
     // Append privapp to existing seinfo label
     private static final String PRIVILEGED_APP_STR = ":privapp";
@@ -69,6 +69,9 @@
     // Append autoplay to existing seinfo label
     private static final String AUTOPLAY_APP_STR = ":autoplayapp";
 
+    // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion
+    private static final String TARGETSDKVERSION_STR = ":targetSdkVersion=";
+
     /**
      * Load the mac_permissions.xml file containing all seinfo assignments used to
      * label apps. The loaded mac_permissions.xml file is determined by the
@@ -290,6 +293,8 @@
         if (pkg.applicationInfo.isPrivilegedApp())
             pkg.applicationInfo.seinfo += PRIVILEGED_APP_STR;
 
+        pkg.applicationInfo.seinfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion;
+
         if (DEBUG_POLICY_INSTALL) {
             Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " +
                     "seinfo=" + pkg.applicationInfo.seinfo);
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 6d06838..01124e2 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,13 +16,21 @@
 
 package com.android.server.pm.dex;
 
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageParser;
-import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+import android.os.storage.StorageManager;
 
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.PackageDexOptimizer;
 import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageManagerServiceCompilerMapping;
 
 import java.io.File;
 import java.io.IOException;
@@ -32,6 +40,9 @@
 import java.util.Map;
 import java.util.Set;
 
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
 /**
  * This class keeps track of how dex files are used.
  * Every time it gets a notification about a dex file being loaded it tracks
@@ -54,15 +65,26 @@
     // encode and save the dex usage data.
     private final PackageDexUsage mPackageDexUsage;
 
+    private final IPackageManager mPackageManager;
+    private final PackageDexOptimizer mPackageDexOptimizer;
+    private final Object mInstallLock;
+    @GuardedBy("mInstallLock")
+    private final Installer mInstaller;
+
     // Possible outcomes of a dex search.
     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
     private static int DEX_SEARCH_FOUND_PRIMARY = 1;  // dex file is the primary/base apk
     private static int DEX_SEARCH_FOUND_SPLIT = 2;  // dex file is a split apk
     private static int DEX_SEARCH_FOUND_SECONDARY = 3;  // dex file is a secondary dex
 
-    public DexManager() {
+    public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
+            Installer installer, Object installLock) {
       mPackageCodeLocationsCache = new HashMap<>();
       mPackageDexUsage = new PackageDexUsage();
+      mPackageManager = pms;
+      mPackageDexOptimizer = pdo;
+      mInstaller = installer;
+      mInstallLock = installLock;
     }
 
     /**
@@ -132,8 +154,8 @@
                 // - new installed splits
                 // If we can't find the owner of the dex we simply do not track it. The impact is
                 // that the dex file will not be considered for offline optimizations.
-                // TODO(calin): add hooks for install/uninstall notifications to
-                // capture new or obsolete packages.
+                // TODO(calin): add hooks for move/uninstall notifications to
+                // capture package moves or obsolete packages.
                 if (DEBUG) {
                     Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
                 }
@@ -157,6 +179,20 @@
         }
     }
 
+    public void notifyPackageInstalled(PackageInfo info, int userId) {
+        cachePackageCodeLocation(info, userId);
+    }
+
+    private void cachePackageCodeLocation(PackageInfo info, int userId) {
+        PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName);
+        if (pcl != null) {
+            pcl.mergeAppDataDirs(info.applicationInfo, userId);
+        } else {
+            mPackageCodeLocationsCache.put(info.packageName,
+                new PackageCodeLocations(info.applicationInfo, userId));
+        }
+    }
+
     private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
         Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
         // Cache the code locations for the installed packages. This allows for
@@ -166,13 +202,8 @@
             int userId = entry.getKey();
             for (PackageInfo pi : packageInfoList) {
                 // Cache the code locations.
-                PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
-                if (pcl != null) {
-                    pcl.mergeAppDataDirs(pi.applicationInfo, userId);
-                } else {
-                    mPackageCodeLocationsCache.put(pi.packageName,
-                        new PackageCodeLocations(pi.applicationInfo, userId));
-                }
+                cachePackageCodeLocation(pi, userId);
+
                 // Cache a map from package name to the set of user ids who installed the package.
                 // We will use it to sync the data and remove obsolete entries from
                 // mPackageDexUsage.
@@ -190,11 +221,161 @@
      * Get the package dex usage for the given package name.
      * @return the package data or null if there is no data available for this package.
      */
-    public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
+    public PackageUseInfo getPackageUseInfo(String packageName) {
         return mPackageDexUsage.getPackageUseInfo(packageName);
     }
 
     /**
+     * Perform dexopt on the package {@code packageName} secondary dex files.
+     * @return true if all secondary dex files were processed successfully (compiled or skipped
+     *         because they don't need to be compiled)..
+     */
+    public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
+        // Select the dex optimizer based on the force parameter.
+        // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
+        // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
+        // passing the force flag through the multitude of layers.
+        // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+        //       allocate an object here.
+        PackageDexOptimizer pdo = force
+                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+                : mPackageDexOptimizer;
+        PackageUseInfo useInfo = getPackageUseInfo(packageName);
+        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, "No secondary dex use for package:" + packageName);
+            }
+            // Nothing to compile, return true.
+            return true;
+        }
+        boolean success = true;
+        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+            String dexPath = entry.getKey();
+            DexUseInfo dexUseInfo = entry.getValue();
+            PackageInfo pkg = null;
+            try {
+                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+                    dexUseInfo.getOwnerUserId());
+            } catch (RemoteException e) {
+                throw new AssertionError(e);
+            }
+            // It may be that the package gets uninstalled while we try to compile its
+            // secondary dex files. If that's the case, just ignore.
+            // Note that we don't break the entire loop because the package might still be
+            // installed for other users.
+            if (pkg == null) {
+                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+                        + " for user " + dexUseInfo.getOwnerUserId());
+                mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
+                continue;
+            }
+            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
+                    dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
+            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
+        }
+        return success;
+    }
+
+    /**
+     * Reconcile the information we have about the secondary dex files belonging to
+     * {@code packagName} and the actual dex files. For all dex files that were
+     * deleted, update the internal records and delete any generated oat files.
+     */
+    public void reconcileSecondaryDexFiles(String packageName) {
+        PackageUseInfo useInfo = getPackageUseInfo(packageName);
+        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, "No secondary dex use for package:" + packageName);
+            }
+            // Nothing to reconcile.
+            return;
+        }
+        Set<String> dexFilesToRemove = new HashSet<>();
+        boolean updated = false;
+        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+            String dexPath = entry.getKey();
+            DexUseInfo dexUseInfo = entry.getValue();
+            PackageInfo pkg = null;
+            try {
+                // Note that we look for the package in the PackageManager just to be able
+                // to get back the real app uid and its storage kind. These are only used
+                // to perform extra validation in installd.
+                // TODO(calin): maybe a bit overkill.
+                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+                    dexUseInfo.getOwnerUserId());
+            } catch (RemoteException ignore) {
+                // Can't happen, DexManager is local.
+            }
+            if (pkg == null) {
+                // It may be that the package was uninstalled while we process the secondary
+                // dex files.
+                Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+                        + " for user " + dexUseInfo.getOwnerUserId());
+                // Update the usage and continue, another user might still have the package.
+                updated = mPackageDexUsage.removeUserPackage(
+                        packageName, dexUseInfo.getOwnerUserId()) || updated;
+                continue;
+            }
+            ApplicationInfo info = pkg.applicationInfo;
+            int flags = 0;
+            if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+                flags |= StorageManager.FLAG_STORAGE_DE;
+            } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+                flags |= StorageManager.FLAG_STORAGE_CE;
+            } else {
+                Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+                updated = mPackageDexUsage.removeUserPackage(
+                        packageName, dexUseInfo.getOwnerUserId()) || updated;
+                continue;
+            }
+
+            boolean dexStillExists = true;
+            synchronized(mInstallLock) {
+                try {
+                    String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
+                    dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
+                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+                } catch (InstallerException e) {
+                    Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
+                            " : " + e.getMessage());
+                }
+            }
+            if (!dexStillExists) {
+                updated = mPackageDexUsage.removeDexFile(
+                        packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
+            }
+
+        }
+        if (updated) {
+            mPackageDexUsage.maybeWriteAsync();
+        }
+    }
+
+    /**
+     * Return all packages that contain records of secondary dex files.
+     */
+    public Set<String> getAllPackagesWithSecondaryDexFiles() {
+        return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
+    }
+
+    /**
+     * Return true if the profiling data collected for the given app indicate
+     * that the apps's APK has been loaded by another app.
+     * Note that this returns false for all apps without any collected profiling data.
+    */
+    public boolean isUsedByOtherApps(String packageName) {
+        PackageUseInfo useInfo = getPackageUseInfo(packageName);
+        if (useInfo == null) {
+            // No use info, means the package was not used or it was used but not by other apps.
+            // Note that right now we might prune packages which are not used by other apps.
+            // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
+            // to access the package use.
+            return false;
+        }
+        return useInfo.isUsedByOtherApps();
+    }
+
+    /**
      * Retrieves the package which owns the given dexPath.
      */
     private DexSearchResult getDexPackage(
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 10384a2..3693bce0 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -376,12 +376,86 @@
         }
     }
 
+    /**
+     * Remove all the records about package {@code packageName} belonging to user {@code userId}.
+     * @return true if the record was found and actually deleted,
+     *         false if the record doesn't exist
+     */
+    public boolean removeUserPackage(String packageName, int userId) {
+        synchronized (mPackageUseInfoMap) {
+            PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+            if (packageUseInfo == null) {
+                return false;
+            }
+            boolean updated = false;
+            Iterator<Map.Entry<String, DexUseInfo>> dIt =
+                            packageUseInfo.mDexUseInfoMap.entrySet().iterator();
+            while (dIt.hasNext()) {
+                DexUseInfo dexUseInfo = dIt.next().getValue();
+                if (dexUseInfo.mOwnerUserId == userId) {
+                    dIt.remove();
+                    updated = true;
+                }
+            }
+            return updated;
+        }
+    }
+
+    /**
+     * Remove the secondary dex file record belonging to the package {@code packageName}
+     * and user {@code userId}.
+     * @return true if the record was found and actually deleted,
+     *         false if the record doesn't exist
+     */
+    public boolean removeDexFile(String packageName, String dexFile, int userId) {
+        synchronized (mPackageUseInfoMap) {
+            PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+            if (packageUseInfo == null) {
+                return false;
+            }
+            return removeDexFile(packageUseInfo, dexFile, userId);
+        }
+    }
+
+    private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) {
+        DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile);
+        if (dexUseInfo == null) {
+            return false;
+        }
+        if (dexUseInfo.mOwnerUserId == userId) {
+            packageUseInfo.mDexUseInfoMap.remove(dexFile);
+            return true;
+        }
+        return false;
+    }
+
     public PackageUseInfo getPackageUseInfo(String packageName) {
         synchronized (mPackageUseInfoMap) {
-            return mPackageUseInfoMap.get(packageName);
+            PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName);
+            // The useInfo contains a map for secondary dex files which could be modified
+            // concurrently after this method returns and thus outside the locking we do here.
+            // (i.e. the map is updated when new class loaders are created, which can happen anytime
+            // after this method returns)
+            // Make a defensive copy to be sure we don't get concurrent modifications.
+            return useInfo == null ? null : new PackageUseInfo(useInfo);
         }
     }
 
+    /**
+     * Return all packages that contain records of secondary dex files.
+     */
+    public Set<String> getAllPackagesWithSecondaryDexFiles() {
+        Set<String> packages = new HashSet<>();
+        synchronized (mPackageUseInfoMap) {
+            for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) {
+                if (!entry.getValue().mDexUseInfoMap.isEmpty()) {
+                    packages.add(entry.getKey());
+                }
+            }
+        }
+        return packages;
+    }
+
     public void clear() {
         synchronized (mPackageUseInfoMap) {
             mPackageUseInfoMap.clear();
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
index b704eb1..3c73c88 100644
--- a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -20,7 +20,7 @@
 
 import java.io.File;
 import java.io.IOException;
-import libcore.tzdata.update2.TimeZoneBundleInstaller;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
 
 /**
  * An install receiver responsible for installing timezone data updates.
@@ -34,14 +34,14 @@
     private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
     private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
     private static final String UPDATE_VERSION_FILE_NAME = "version";
-    private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip";
+    private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_distro.zip";
 
-    private final TimeZoneBundleInstaller installer;
+    private final TimeZoneDistroInstaller installer;
 
     public TzDataInstallReceiver() {
         super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
                 UPDATE_VERSION_FILE_NAME);
-        installer = new TimeZoneBundleInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
+        installer = new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
     }
 
     @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 9e60f33..de4a55b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -61,7 +61,6 @@
 import com.android.server.camera.CameraService;
 import com.android.server.clipboard.ClipboardService;
 import com.android.server.connectivity.IpConnectivityMetrics;
-import com.android.server.connectivity.MetricsLoggerService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.display.DisplayManagerService;
 import com.android.server.display.NightDisplayService;
@@ -662,10 +661,6 @@
                 mSystemServiceManager.startService(BluetoothService.class);
             }
 
-            traceBeginAndSlog("ConnectivityMetricsLoggerService");
-            mSystemServiceManager.startService(MetricsLoggerService.class);
-            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
-
             traceBeginAndSlog("IpConnectivityMetrics");
             mSystemServiceManager.startService(IpConnectivityMetrics.class);
             Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -834,19 +829,25 @@
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
 
+                // Wifi Service must be started first for wifi-related services.
+                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+                mSystemServiceManager.startService(
+                        "com.android.server.wifi.scanner.WifiScanningService");
+
+                if (!disableRtt) {
+                    mSystemServiceManager.startService("com.android.server.wifi.RttService");
+                }
+
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_WIFI_AWARE)) {
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
                 } else {
                     Slog.i(TAG, "No Wi-Fi Aware Service (Aware support Not Present)");
                 }
-                mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
-                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
-                mSystemServiceManager.startService(
-                            "com.android.server.wifi.scanner.WifiScanningService");
 
-                if (!disableRtt) {
-                    mSystemServiceManager.startService("com.android.server.wifi.RttService");
+                if (context.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_WIFI_DIRECT)) {
+                    mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
                 }
 
                 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 83001df..0a90749 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -286,7 +286,8 @@
     }
 
     // Returns seconds since device boot.
-    private static long curTime() {
+    @VisibleForTesting
+    protected long currentTimeSeconds() {
         return SystemClock.elapsedRealtime() / DateUtils.SECOND_IN_MILLIS;
     }
 
@@ -450,7 +451,7 @@
             }
 
             mPacket = ByteBuffer.wrap(Arrays.copyOf(packet, length));
-            mLastSeen = curTime();
+            mLastSeen = currentTimeSeconds();
 
             // Sanity check packet in case a packet arrives before we attach RA filter
             // to our packet socket. b/29586253
@@ -580,7 +581,7 @@
         // How many seconds does this RA's have to live, taking into account the fact
         // that we might have seen it a while ago.
         long currentLifetime() {
-            return mMinLifetime - (curTime() - mLastSeen);
+            return mMinLifetime - (currentTimeSeconds() - mLastSeen);
         }
 
         boolean isExpired() {
@@ -946,7 +947,7 @@
             Log.e(TAG, "Failed to generate APF program.", e);
             return;
         }
-        mLastTimeInstalledProgram = curTime();
+        mLastTimeInstalledProgram = currentTimeSeconds();
         mLastInstalledProgramMinLifetime = programMinLifetime;
         mLastInstalledProgram = program;
         mNumProgramUpdates++;
@@ -965,7 +966,7 @@
      */
     private boolean shouldInstallnewProgram() {
         long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
-        return expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
+        return expiry < currentTimeSeconds() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING;
     }
 
     private void hexDump(String msg, byte[] packet, int length) {
@@ -999,7 +1000,7 @@
             if (ra.matches(packet, length)) {
                 if (VDBG) log("matched RA " + ra);
                 // Update lifetimes.
-                ra.mLastSeen = curTime();
+                ra.mLastSeen = currentTimeSeconds();
                 ra.mMinLifetime = ra.minLifetime(packet, length);
                 ra.seenCount++;
 
@@ -1128,7 +1129,7 @@
         pw.println("Program updates: " + mNumProgramUpdates);
         pw.println(String.format(
                 "Last program length %d, installed %ds ago, lifetime %ds",
-                mLastInstalledProgram.length, curTime() - mLastTimeInstalledProgram,
+                mLastInstalledProgram.length, currentTimeSeconds() - mLastTimeInstalledProgram,
                 mLastInstalledProgramMinLifetime));
 
         pw.println("RA filters:");
@@ -1137,7 +1138,7 @@
             pw.println(ra);
             pw.increaseIndent();
             pw.println(String.format(
-                    "Seen: %d, last %ds ago", ra.seenCount, curTime() - ra.mLastSeen));
+                    "Seen: %d, last %ds ago", ra.seenCount, currentTimeSeconds() - ra.mLastSeen));
             if (DBG) {
                 pw.println("Last match:");
                 pw.increaseIndent();
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index abdf683..c670782 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -16,6 +16,8 @@
 
 package android.net.ip;
 
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.WakeupMessage;
 
@@ -23,6 +25,7 @@
 import android.net.apf.ApfCapabilities;
 import android.net.apf.ApfFilter;
 import android.net.DhcpResults;
+import android.net.INetd;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -33,12 +36,15 @@
 import android.net.dhcp.DhcpClient;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
-import android.net.util.AvoidBadWifiTracker;
+import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
 import android.os.INetworkManagementService;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
+import android.system.OsConstants;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -398,7 +404,7 @@
     private final NetlinkTracker mNetlinkTracker;
     private final WakeupMessage mProvisioningTimeoutAlarm;
     private final WakeupMessage mDhcpActionTimeoutAlarm;
-    private final AvoidBadWifiTracker mAvoidBadWifiTracker;
+    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
     private final LocalLog mLocalLog;
     private final LocalLog mConnectivityPacketLog;
     private final MessageHandlingLogger mMsgStateLogger;
@@ -492,7 +498,7 @@
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mInterfaceName);
 
-        mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler(),
+        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
                 () -> { mLocalLog.log("OBSERVED AvoidBadWifi changed"); });
 
         mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
@@ -527,7 +533,7 @@
             Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
         }
 
-        mAvoidBadWifiTracker.start();
+        mMultinetworkPolicyTracker.start();
     }
 
     @Override
@@ -538,7 +544,7 @@
     // Shut down this IpManager instance altogether.
     public void shutdown() {
         stop();
-        mAvoidBadWifiTracker.shutdown();
+        mMultinetworkPolicyTracker.shutdown();
         quit();
     }
 
@@ -631,6 +637,13 @@
 
         pw.println();
         pw.println(mTag + " connectivity packet log:");
+        pw.println();
+        pw.println("Debug with python and scapy via:");
+        pw.println("shell$ python");
+        pw.println(">>> from scapy import all as scapy");
+        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+        pw.println();
+
         pw.increaseIndent();
         mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
         pw.decreaseIndent();
@@ -767,7 +780,7 @@
         // Note that we can still be disconnected by IpReachabilityMonitor
         // if the IPv6 default gateway (but not the IPv6 DNS servers; see
         // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = !mAvoidBadWifiTracker.currentValue();
+        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
 
         // Additionally:
         //
@@ -1018,16 +1031,39 @@
         return true;
     }
 
-    private boolean startIPv6() {
-        // Set privacy extensions.
+    private void enableInterfaceIPv6PrivacyExtensions() {
+        final String PREFER_TEMPADDRS = "2";
+        NetdService.run((INetd netd) -> {
+                netd.setProcSysNet(
+                        INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", PREFER_TEMPADDRS);
+            });
+    }
+
+    private void setInterfaceIPv6RaRtInfoMaxPlen(int plen) {
+        // Setting RIO max plen is best effort. Catch and ignore most exceptions.
         try {
-            mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+            NetdService.run((INetd netd) -> {
+                    netd.setProcSysNet(
+                            INetd.IPV6, INetd.CONF, mInterfaceName, "accept_ra_rt_info_max_plen",
+                            Integer.toString(plen));
+                });
+        } catch (ServiceSpecificException e) {
+            // Old kernel versions without support for RIOs do not export accept_ra_rt_info_max_plen
+            // in the /proc filesystem. If the kernel supports RIOs we should never see any other
+            // type of error.
+            if (e.errorCode != OsConstants.ENOENT) {
+                logError("unexpected error setting accept_ra_rt_info_max_plen %s", e);
+            }
+        }
+    }
+
+    private boolean startIPv6() {
+        try {
+            enableInterfaceIPv6PrivacyExtensions();
+            setInterfaceIPv6RaRtInfoMaxPlen(RFC7421_PREFIX_LENGTH);
             mNwService.enableIpv6(mInterfaceName);
-        } catch (RemoteException re) {
-            logError("Unable to change interface settings: %s", re);
-            return false;
-        } catch (IllegalStateException ie) {
-            logError("Unable to change interface settings: %s", ie);
+        } catch (IllegalStateException|RemoteException|ServiceSpecificException e) {
+            logError("Unable to change interface settings: %s", e);
             return false;
         }
 
@@ -1045,7 +1081,7 @@
                             mCallback.onReachabilityLost(logMsg);
                         }
                     },
-                    mAvoidBadWifiTracker);
+                    mMultinetworkPolicyTracker);
         } catch (IllegalArgumentException iae) {
             // Failed to start IpReachabilityMonitor. Log it and call
             // onProvisioningFailure() immediately.
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index a883e28..20eac62 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -34,7 +34,7 @@
 import android.net.netlink.StructNdaCacheInfo;
 import android.net.netlink.StructNdMsg;
 import android.net.netlink.StructNlMsgHdr;
-import android.net.util.AvoidBadWifiTracker;
+import android.net.util.MultinetworkPolicyTracker;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.system.ErrnoException;
@@ -151,7 +151,7 @@
     private final String mInterfaceName;
     private final int mInterfaceIndex;
     private final Callback mCallback;
-    private final AvoidBadWifiTracker mAvoidBadWifiTracker;
+    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
     private final NetlinkSocketObserver mNetlinkSocketObserver;
     private final Thread mObserverThread;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
@@ -226,7 +226,7 @@
     }
 
     public IpReachabilityMonitor(Context context, String ifName, Callback callback,
-            AvoidBadWifiTracker tracker) throws IllegalArgumentException {
+            MultinetworkPolicyTracker tracker) throws IllegalArgumentException {
         mInterfaceName = ifName;
         int ifIndex = -1;
         try {
@@ -238,7 +238,7 @@
         mWakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE)).newWakeLock(
                 PowerManager.PARTIAL_WAKE_LOCK, TAG + "." + mInterfaceName);
         mCallback = callback;
-        mAvoidBadWifiTracker = tracker;
+        mMultinetworkPolicyTracker = tracker;
         mNetlinkSocketObserver = new NetlinkSocketObserver();
         mObserverThread = new Thread(mNetlinkSocketObserver);
         mObserverThread.start();
@@ -379,7 +379,7 @@
     }
 
     private boolean avoidingBadLinks() {
-        return (mAvoidBadWifiTracker != null) ? mAvoidBadWifiTracker.currentValue() : true;
+        return (mMultinetworkPolicyTracker == null) || mMultinetworkPolicyTracker.getAvoidBadWifi();
     }
 
     public void probeAll() {
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java
index 699ba5b..5b068c0 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/services/net/java/android/net/util/ConnectivityPacketSummary.java
@@ -285,7 +285,10 @@
             final int ndType = asUint(mPacket.get());
             final int ndLength = asUint(mPacket.get());
             final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2;
-            if (mPacket.remaining() < ndBytes) break;
+            if (ndBytes < 0 || ndBytes > mPacket.remaining()) {
+                sj.add("<malformed>");
+                break;
+            }
             final int position = mPacket.position();
 
             switch (ndType) {
diff --git a/services/net/java/android/net/util/AvoidBadWifiTracker.java b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
similarity index 78%
rename from services/net/java/android/net/util/AvoidBadWifiTracker.java
rename to services/net/java/android/net/util/MultinetworkPolicyTracker.java
index 2abaeb1..ebd131b 100644
--- a/services/net/java/android/net/util/AvoidBadWifiTracker.java
+++ b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
@@ -42,8 +42,8 @@
  * This enables the device to switch to another form of connectivity, like
  * mobile, if it's available and working.
  *
- * The Runnable |cb|, if given, is called on the supplied Handler's thread
- * whether the computed "avoid bad wifi" value changes.
+ * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied
+ * Handler' whenever the computed "avoid bad wifi" value changes.
  *
  * Disabling this reverts the device to a level of networking sophistication
  * circa 2012-13 by disabling disparate code paths each of which contribute to
@@ -51,28 +51,30 @@
  *
  * @hide
  */
-public class AvoidBadWifiTracker {
-    private static String TAG = AvoidBadWifiTracker.class.getSimpleName();
+public class MultinetworkPolicyTracker {
+    private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
 
     private final Context mContext;
     private final Handler mHandler;
     private final Runnable mReevaluateRunnable;
-    private final Uri mUri;
+    private final Uri mAvoidBadWifiUri;
     private final ContentResolver mResolver;
     private final SettingObserver mSettingObserver;
     private final BroadcastReceiver mBroadcastReceiver;
 
     private volatile boolean mAvoidBadWifi = true;
 
-    public AvoidBadWifiTracker(Context ctx, Handler handler) {
+    public MultinetworkPolicyTracker(Context ctx, Handler handler) {
         this(ctx, handler, null);
     }
 
-    public AvoidBadWifiTracker(Context ctx, Handler handler, Runnable cb) {
+    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
         mContext = ctx;
         mHandler = handler;
-        mReevaluateRunnable = () -> { if (update() && cb != null) cb.run(); };
-        mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI);
+        mReevaluateRunnable = () -> {
+            if (updateAvoidBadWifi() && avoidBadWifiCallback != null) avoidBadWifiCallback.run();
+        };
+        mAvoidBadWifiUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI);
         mResolver = mContext.getContentResolver();
         mSettingObserver = new SettingObserver();
         mBroadcastReceiver = new BroadcastReceiver() {
@@ -82,11 +84,11 @@
             }
         };
 
-        update();
+        updateAvoidBadWifi();
     }
 
     public void start() {
-        mResolver.registerContentObserver(mUri, false, mSettingObserver);
+        mResolver.registerContentObserver(mAvoidBadWifiUri, false, mSettingObserver);
 
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -102,7 +104,7 @@
         mContext.unregisterReceiver(mBroadcastReceiver);
     }
 
-    public boolean currentValue() {
+    public boolean getAvoidBadWifi() {
         return mAvoidBadWifi;
     }
 
@@ -117,10 +119,10 @@
      * Whether we should display a notification when wifi becomes unvalidated.
      */
     public boolean shouldNotifyWifiUnvalidated() {
-        return configRestrictsAvoidBadWifi() && getSettingsValue() == null;
+        return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
     }
 
-    public String getSettingsValue() {
+    public String getAvoidBadWifiSetting() {
         return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
     }
 
@@ -129,8 +131,8 @@
         mHandler.post(mReevaluateRunnable);
     }
 
-    public boolean update() {
-        final boolean settingAvoidBadWifi = "1".equals(getSettingsValue());
+    public boolean updateAvoidBadWifi() {
+        final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
         final boolean prev = mAvoidBadWifi;
         mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
         return mAvoidBadWifi != prev;
@@ -148,7 +150,7 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            if (!mUri.equals(uri)) return;
+            if (!mAvoidBadWifiUri.equals(uri)) return;
             reevaluate();
         }
     }
diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/util/NetdService.java
index 153cb50..6e69ff5 100644
--- a/services/net/java/android/net/util/NetdService.java
+++ b/services/net/java/android/net/util/NetdService.java
@@ -17,7 +17,10 @@
 package android.net.util;
 
 import android.net.INetd;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.SystemClock;
 import android.util.Log;
 
 
@@ -27,15 +30,24 @@
 public class NetdService {
     private static final String TAG = NetdService.class.getSimpleName();
     private static final String NETD_SERVICE_NAME = "netd";
+    private static final long BASE_TIMEOUT_MS = 100;
+    private static final long MAX_TIMEOUT_MS = 1000;
+
 
     /**
+     * Return an INetd instance, or null if not available.
+     *
      * It is the caller's responsibility to check for a null return value
      * and to handle RemoteException errors from invocations on the returned
      * interface if, for example, netd dies and is restarted.
      *
+     * Returned instances of INetd should not be cached.
+     *
      * @return an INetd instance or null.
      */
     public static INetd getInstance() {
+        // NOTE: ServiceManager does no caching for the netd service,
+        // because netd is not one of the defined common services.
         final INetd netdInstance = INetd.Stub.asInterface(
                 ServiceManager.getService(NETD_SERVICE_NAME));
         if (netdInstance == null) {
@@ -43,4 +55,82 @@
         }
         return netdInstance;
     }
+
+    /**
+     * Blocks for a specified time until an INetd instance is available.
+     *
+     * It is the caller's responsibility to handle RemoteException errors
+     * from invocations on the returned interface if, for example, netd
+     * dies after this interface was returned.
+     *
+     * Returned instances of INetd should not be cached.
+     *
+     * Special values of maxTimeoutMs include: 0, meaning try to obtain an
+     * INetd instance only once, and -1 (or any value less than 0), meaning
+     * try to obtain an INetd instance indefinitely.
+     *
+     * @param maxTimeoutMs the maximum time to spend getting an INetd instance
+     * @return an INetd instance or null if no instance is available
+     * within |maxTimeoutMs| milliseconds.
+     */
+    public static INetd get(long maxTimeoutMs) {
+        if (maxTimeoutMs == 0) return getInstance();
+
+        final long stop = (maxTimeoutMs > 0)
+                ? SystemClock.elapsedRealtime() + maxTimeoutMs
+                : Long.MAX_VALUE;
+
+        long timeoutMs = 0;
+        while (true) {
+            final INetd netdInstance = getInstance();
+            if (netdInstance != null) {
+                return netdInstance;
+            }
+
+            final long remaining = stop - SystemClock.elapsedRealtime();
+            if (remaining <= 0) break;
+
+            // No netdInstance was received; sleep and retry.
+            timeoutMs = Math.min(timeoutMs + BASE_TIMEOUT_MS, MAX_TIMEOUT_MS);
+            timeoutMs = Math.min(timeoutMs, remaining);
+            try {
+                Thread.sleep(timeoutMs);
+            } catch (InterruptedException e) {}
+        }
+        return null;
+    }
+
+    /**
+     * Blocks until an INetd instance is available.
+     *
+     * It is the caller's responsibility to handle RemoteException errors
+     * from invocations on the returned interface if, for example, netd
+     * dies after this interface was returned.
+     *
+     * Returned instances of INetd should not be cached.
+     *
+     * @return an INetd instance.
+     */
+    public static INetd get() {
+        return get(-1);
+    }
+
+    public static interface NetdCommand {
+        void run(INetd netd) throws RemoteException;
+    }
+
+    /**
+     * Blocks until an INetd instance is availabe, and retries until either
+     * the command succeeds or a runtime exception is thrown.
+     */
+    public static void run(NetdCommand cmd) {
+        while (true) {
+            try {
+                cmd.run(get());
+                return;
+            } catch (RemoteException re) {
+                Log.e(TAG, "error communicating with netd: " + re);
+            }
+        }
+    }
 }
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 362f757..26f3050 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -97,6 +97,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 RFC7421_PREFIX_LENGTH = 64;
 
     /**
      * ICMPv6 constants.
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1189dae..5553fd6 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -24,6 +24,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertSame;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -60,21 +61,28 @@
 import android.net.RecommendationRequest;
 import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
+import android.net.Uri;
 import android.net.WifiKey;
 import android.net.wifi.WifiConfiguration;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import com.android.server.devicepolicy.MockUtils;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -89,6 +97,8 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for {@link NetworkScoreService}.
@@ -114,6 +124,9 @@
     private ContentResolver mContentResolver;
     private NetworkScoreService mNetworkScoreService;
     private RecommendationRequest mRecommendationRequest;
+    private RemoteCallback mRemoteCallback;
+    private OnResultListener mOnResultListener;
+    private HandlerThread mHandlerThread;
 
     @Before
     public void setUp() throws Exception {
@@ -123,12 +136,25 @@
         mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getResources()).thenReturn(mResources);
-        mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
+        mHandlerThread = new HandlerThread("NetworkScoreServiceTest");
+        mHandlerThread.start();
+        mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager,
+                mHandlerThread.getLooper());
         WifiConfiguration configuration = new WifiConfiguration();
         configuration.SSID = "NetworkScoreServiceTest_SSID";
         configuration.BSSID = "NetworkScoreServiceTest_BSSID";
         mRecommendationRequest = new RecommendationRequest.Builder()
-            .setCurrentRecommendedWifiConfig(configuration).build();
+            .setDefaultWifiConfig(configuration).build();
+        mOnResultListener = new OnResultListener();
+        mRemoteCallback = new RemoteCallback(mOnResultListener);
+        Settings.Global.putLong(mContentResolver,
+                Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, -1L);
+        mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quitSafely();
     }
 
     @Test
@@ -214,7 +240,7 @@
         final RecommendationResult result =
                 mNetworkScoreService.requestRecommendation(mRecommendationRequest);
         assertNotNull(result);
-        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
+        assertEquals(mRecommendationRequest.getDefaultWifiConfig(),
                 result.getWifiConfiguration());
     }
 
@@ -229,7 +255,7 @@
         final RecommendationResult result =
                 mNetworkScoreService.requestRecommendation(mRecommendationRequest);
         assertNotNull(result);
-        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
+        assertEquals(mRecommendationRequest.getDefaultWifiConfig(),
                 result.getWifiConfiguration());
     }
 
@@ -245,8 +271,8 @@
         final Bundle bundle = new Bundle();
         bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult);
         doAnswer(invocation -> {
-            bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class));
-            invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle);
+            bundle.putInt(EXTRA_SEQUENCE, invocation.getArgument(2));
+            invocation.<IRemoteCallback>getArgument(1).sendResult(bundle);
             return null;
         }).when(mRecommendationProvider)
                 .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
@@ -262,6 +288,116 @@
     }
 
     @Test
+    public void testRequestRecommendationAsync_noPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(permission.REQUEST_NETWORK_SCORES),
+                        anyString());
+        try {
+            mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
+                    mRemoteCallback);
+            fail("REQUEST_NETWORK_SCORES not enforced.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestRecommendationAsync_providerNotConnected() throws Exception {
+        mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
+                mRemoteCallback);
+        boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
+        assertTrue(callbackRan);
+        verifyZeroInteractions(mRecommendationProvider);
+    }
+
+    @Test
+    public void testRequestRecommendationAsync_requestTimesOut() throws Exception {
+        injectProvider();
+        Settings.Global.putLong(mContentResolver,
+                Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS, 1L);
+        mNetworkScoreService.refreshRecommendationRequestTimeoutMs();
+        mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
+                mRemoteCallback);
+        boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
+        assertTrue(callbackRan);
+        verify(mRecommendationProvider).requestRecommendation(eq(mRecommendationRequest),
+                isA(IRemoteCallback.Stub.class), anyInt());
+
+        assertTrue(mOnResultListener.receivedBundle.containsKey(EXTRA_RECOMMENDATION_RESULT));
+        RecommendationResult result =
+                mOnResultListener.receivedBundle.getParcelable(EXTRA_RECOMMENDATION_RESULT);
+        assertTrue(result.hasRecommendation());
+        assertEquals(mRecommendationRequest.getDefaultWifiConfig().SSID,
+                result.getWifiConfiguration().SSID);
+    }
+
+    @Test
+    public void testRequestRecommendationAsync_requestSucceeds() throws Exception {
+        injectProvider();
+        final Bundle bundle = new Bundle();
+        doAnswer(invocation -> {
+            invocation.<IRemoteCallback>getArgument(1).sendResult(bundle);
+            return null;
+        }).when(mRecommendationProvider)
+                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
+                        anyInt());
+
+        mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
+                mRemoteCallback);
+        boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
+        assertTrue(callbackRan);
+        // If it's not the same instance then something else ran the callback.
+        assertSame(bundle, mOnResultListener.receivedBundle);
+    }
+
+    @Test
+    public void testRequestRecommendationAsync_requestThrowsRemoteException() throws Exception {
+        injectProvider();
+        doThrow(new RemoteException()).when(mRecommendationProvider)
+                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
+                        anyInt());
+
+        mNetworkScoreService.requestRecommendationAsync(mRecommendationRequest,
+                mRemoteCallback);
+        boolean callbackRan = mOnResultListener.countDownLatch.await(3, TimeUnit.SECONDS);
+        assertTrue(callbackRan);
+    }
+
+    @Test
+    public void dispatchingContentObserver_nullUri() throws Exception {
+        NetworkScoreService.DispatchingContentObserver observer =
+                new NetworkScoreService.DispatchingContentObserver(mContext, null /*handler*/);
+
+        observer.onChange(false, null);
+        // nothing to assert or verify but since we passed in a null handler we'd see a NPE
+        // if it were interacted with.
+    }
+
+    @Test
+    public void dispatchingContentObserver_dispatchUri() throws Exception {
+        final CountDownHandler handler = new CountDownHandler(mHandlerThread.getLooper());
+        NetworkScoreService.DispatchingContentObserver observer =
+                new NetworkScoreService.DispatchingContentObserver(mContext, handler);
+        Uri uri = Uri.parse("content://settings/global/network_score_service_test");
+        int expectedWhat = 24;
+        observer.observe(uri, expectedWhat);
+
+        observer.onChange(false, uri);
+        final boolean msgHandled = handler.latch.await(3, TimeUnit.SECONDS);
+        assertTrue(msgHandled);
+        assertEquals(expectedWhat, handler.receivedWhat);
+    }
+
+    @Test
+    public void oneTimeCallback_multipleCallbacks() throws Exception {
+        NetworkScoreService.OneTimeCallback callback =
+                new NetworkScoreService.OneTimeCallback(mRemoteCallback);
+        callback.sendResult(null);
+        callback.sendResult(null);
+        assertEquals(1, mOnResultListener.resultCount);
+    }
+
+    @Test
     public void testUpdateScores_notActiveScorer() {
         bindToScorer(false /*callerIsScorer*/);
 
@@ -498,7 +634,7 @@
                 IBinder mockBinder = mock(IBinder.class);
                 when(mockBinder.queryLocalInterface(anyString()))
                         .thenReturn(mRecommendationProvider);
-                invocation.getArgumentAt(1, ServiceConnection.class)
+                invocation.<ServiceConnection>getArgument(1)
                         .onServiceConnected(componentName, mockBinder);
                 return true;
             }
@@ -515,4 +651,32 @@
                 isA(UserHandle.class))).thenReturn(true);
         mNetworkScoreService.systemRunning();
     }
+
+    private static class OnResultListener implements RemoteCallback.OnResultListener {
+        private final CountDownLatch countDownLatch = new CountDownLatch(1);
+        private int resultCount;
+        private Bundle receivedBundle;
+
+        @Override
+        public void onResult(Bundle result) {
+            countDownLatch.countDown();
+            resultCount++;
+            receivedBundle = result;
+        }
+    }
+
+    private static class CountDownHandler extends Handler {
+        CountDownLatch latch = new CountDownLatch(1);
+        int receivedWhat;
+
+        CountDownHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            latch.countDown();
+            receivedWhat = msg.what;
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 3806da6..e43786c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -29,6 +29,7 @@
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.mockito.Mockito;
+import org.mockito.hamcrest.MockitoHamcrest;
 
 public class MockUtils {
     private MockUtils() {
@@ -47,7 +48,7 @@
                 description.appendText("UserHandle: user-id= \"" + userId + "\"");
             }
         };
-        return Mockito.argThat(m);
+        return MockitoHamcrest.argThat(m);
     }
 
     public static Intent checkIntentComponent(final ComponentName component) {
@@ -63,7 +64,7 @@
                 description.appendText("Intent: component=\"" + component + "\"");
             }
         };
-        return Mockito.argThat(m);
+        return MockitoHamcrest.argThat(m);
     }
 
     public static Intent checkIntentAction(final String action) {
@@ -79,7 +80,7 @@
                 description.appendText("Intent: action=\"" + action + "\"");
             }
         };
-        return Mockito.argThat(m);
+        return MockitoHamcrest.argThat(m);
     }
 
     public static Intent checkIntent(final Intent intent) {
@@ -94,7 +95,7 @@
                 description.appendText(intent.toString());
             }
         };
-        return Mockito.argThat(m);
+        return MockitoHamcrest.argThat(m);
     }
 
     public static Bundle checkUserRestrictions(String... keys) {
@@ -111,7 +112,7 @@
                 description.appendText("User restrictions=" + getRestrictionsAsString(expected));
             }
         };
-        return Mockito.argThat(m);
+        return MockitoHamcrest.argThat(m);
     }
 
     private static String getRestrictionsAsString(Bundle b) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index b655f3a..90a2ec0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -73,8 +73,7 @@
         mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0);
         mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1);
 
-
-        mDexManager = new DexManager();
+        mDexManager = new DexManager(null, null, null, null);
 
         // Foo and Bar are available to user0.
         // Only Bar is available to user1;
@@ -204,6 +203,46 @@
         assertNull(getPackageUseInfo(mBarUser1));
     }
 
+    @Test
+    public void testNotifyPackageInstallUsedByOther() {
+        TestData newPackage = new TestData("newPackage",
+                VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0);
+
+        List<String> newSecondaries = newPackage.getSecondaryDexPaths();
+        // Before we notify about the installation of the newPackage if mFoo
+        // is trying to load something from it we should not find it.
+        notifyDexLoad(mFooUser0, newSecondaries, mUser0);
+        assertNull(getPackageUseInfo(newPackage));
+
+        // Notify about newPackage install and let mFoo load its dexes.
+        mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0);
+        notifyDexLoad(mFooUser0, newSecondaries, mUser0);
+
+        // We should get back the right info.
+        PackageUseInfo pui = getPackageUseInfo(newPackage);
+        assertNotNull(pui);
+        assertFalse(pui.isUsedByOtherApps());
+        assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
+        assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
+    }
+
+    @Test
+    public void testNotifyPackageInstallSelfUse() {
+        TestData newPackage = new TestData("newPackage",
+                VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0);
+
+        List<String> newSecondaries = newPackage.getSecondaryDexPaths();
+        // Packages should be able to find their own dex files even if the notification about
+        // their installation is delayed.
+        notifyDexLoad(newPackage, newSecondaries, mUser0);
+
+        PackageUseInfo pui = getPackageUseInfo(newPackage);
+        assertNotNull(pui);
+        assertFalse(pui.isUsedByOtherApps());
+        assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
+        assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
+    }
+
     private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
             List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
         for (String dex : secondaries) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
index 5a42841..19e0bcf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
@@ -256,6 +256,32 @@
         assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
     }
 
+    @Test
+    public void testRemoveUserPackage() {
+        // Record Bar secondaries for two different users.
+        assertTrue(record(mBarSecondary1User0));
+        assertTrue(record(mBarSecondary2User1));
+
+        // Remove user 0 files.
+        assertTrue(mPackageDexUsage.removeUserPackage(mBarSecondary1User0.mPackageName,
+                mBarSecondary1User0.mOwnerUserId));
+        // Assert that only user 1 files are there.
+        assertPackageDexUsage(null, mBarSecondary2User1);
+    }
+
+    @Test
+    public void testRemoveDexFile() {
+        // Record Bar secondaries for two different users.
+        assertTrue(record(mBarSecondary1User0));
+        assertTrue(record(mBarSecondary2User1));
+
+        // Remove mBarSecondary1User0 file.
+        assertTrue(mPackageDexUsage.removeDexFile(mBarSecondary1User0.mPackageName,
+                mBarSecondary1User0.mDexFile, mBarSecondary1User0.mOwnerUserId));
+        // Assert that only user 1 files are there.
+        assertPackageDexUsage(null, mBarSecondary2User1);
+    }
+
     private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
         String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
         boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;
diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
index 82c7bdb..1192901 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -32,14 +32,12 @@
 import android.webkit.WebViewProviderInfo;
 import android.webkit.WebViewProviderResponse;
 
-import org.hamcrest.Description;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import org.mockito.Mockito;
 import org.mockito.Matchers;
-import org.mockito.ArgumentMatcher;
+import org.mockito.compat.ArgumentMatcher;
 
 import java.util.concurrent.CountDownLatch;
 
@@ -131,14 +129,13 @@
         }
 
         @Override
-        public boolean matches(Object p) {
+        public boolean matchesObject(Object p) {
             return ((PackageInfo) p).packageName.equals(mPackageName);
         }
 
-        // Provide a more useful description in case of mismatch
         @Override
-        public void describeTo (Description description) {
-            description.appendText(String.format("PackageInfo with name '%s'", mPackageName));
+        public String toString() {
+            return String.format("PackageInfo with name '%s'", mPackageName);
         }
     }
 
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 1fe5cb7..29d58ce 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -59,6 +59,7 @@
 import org.json.JSONObject;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
+import org.mockito.hamcrest.MockitoHamcrest;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -665,7 +666,7 @@
                 d.appendText(description);
             }
         };
-        return Mockito.argThat(m);
+        return MockitoHamcrest.argThat(m);
     }
 
     public static List<ShortcutInfo> checkShortcutIds(String... ids) {
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index b3f5630..39da224 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -32,6 +32,7 @@
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+import android.os.BatteryManager;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
@@ -111,6 +112,7 @@
     private static final int MSG_USER_SWITCHED = 5;
     private static final int MSG_UPDATE_USER_RESTRICTIONS = 6;
     private static final int MSG_UPDATE_HOST_STATE = 7;
+    private static final int MSG_UPDATE_CHARGING_STATE = 9;
 
     private static final int AUDIO_MODE_SOURCE = 1;
 
@@ -150,6 +152,7 @@
     private UsbDebuggingManager mDebuggingManager;
     private final UsbAlsaManager mUsbAlsaManager;
     private Intent mBroadcastedIntent;
+    private boolean mPendingBootBroadcast;
 
     private class AdbSettingsObserver extends ContentObserver {
         public AdbSettingsObserver() {
@@ -191,6 +194,15 @@
         }
     };
 
+    private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+             int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+             boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
+             mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging);
+        }
+    };
+
     public UsbDeviceManager(Context context, UsbAlsaManager alsaManager) {
         mContext = context;
         mUsbAlsaManager = alsaManager;
@@ -215,6 +227,8 @@
         }
         mContext.registerReceiver(mHostReceiver,
                 new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));
+        mContext.registerReceiver(mChargingReceiver,
+                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
     }
 
     private UsbSettingsManager getCurrentSettings() {
@@ -329,6 +343,7 @@
         private int mUsbNotificationId;
         private boolean mAdbNotificationShown;
         private int mCurrentUser = UserHandle.USER_NULL;
+        private boolean mUsbCharging;
 
         public UsbHandler(Looper looper) {
             super(looper);
@@ -427,7 +442,10 @@
             args.argi2 = sourcePower ? 1 :0;
             args.argi3 = sinkPower ? 1 :0;
 
-            obtainMessage(MSG_UPDATE_HOST_STATE, args).sendToTarget();
+            removeMessages(MSG_UPDATE_HOST_STATE);
+            Message msg = obtainMessage(MSG_UPDATE_HOST_STATE, args);
+            // debounce rapid transitions of connect/disconnect on type-c ports
+            sendMessageDelayed(msg, UPDATE_DELAY);
         }
 
         private boolean waitForState(String state) {
@@ -725,7 +743,8 @@
                 }
                 mMidiEnabled = enabled;
             }
-            mUsbAlsaManager.setPeripheralMidiState(mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
+            mUsbAlsaManager.setPeripheralMidiState(
+                    mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
         }
 
         @Override
@@ -740,13 +759,16 @@
                     if (UsbManager.containsFunction(mCurrentFunctions,
                             UsbManager.USB_FUNCTION_ACCESSORY)) {
                         updateCurrentAccessory();
-                    } else if (!mConnected) {
-                        // restore defaults when USB is disconnected
-                        setEnabledFunctions(null, false, false);
                     }
                     if (mBootCompleted) {
+                        if (!mConnected) {
+                            // restore defaults when USB is disconnected
+                            setEnabledFunctions(null, false, false);
+                        }
                         updateUsbStateBroadcastIfNeeded(false);
                         updateUsbFunctions();
+                    } else {
+                        mPendingBootBroadcast = true;
                     }
                     break;
                 case MSG_UPDATE_HOST_STATE:
@@ -758,8 +780,14 @@
                     updateUsbNotification();
                     if (mBootCompleted) {
                         updateUsbStateBroadcastIfNeeded(false);
+                    } else {
+                        mPendingBootBroadcast = true;
                     }
                     break;
+                case MSG_UPDATE_CHARGING_STATE:
+                    mUsbCharging = (msg.arg1 == 1);
+                    updateUsbNotification();
+                    break;
                 case MSG_ENABLE_ADB:
                     setAdbEnabled(msg.arg1 == 1);
                     break;
@@ -777,6 +805,10 @@
                     break;
                 case MSG_BOOT_COMPLETED:
                     mBootCompleted = true;
+                    if (mPendingBootBroadcast) {
+                        updateUsbStateBroadcastIfNeeded(false);
+                        mPendingBootBroadcast = false;
+                    }
                     setEnabledFunctions(null, false, false);
                     if (mCurrentAccessory != null) {
                         getCurrentSettings().accessoryAttached(mCurrentAccessory);
@@ -840,7 +872,7 @@
                 }
             } else if (mSourcePower) {
                 id = com.android.internal.R.string.usb_supplying_notification_title;
-            } else if (mHostConnected && mSinkPower) {
+            } else if (mHostConnected && mSinkPower && mUsbCharging) {
                 id = com.android.internal.R.string.usb_charging_notification_title;
             }
             if (id != mUsbNotificationId) {
@@ -944,6 +976,7 @@
             pw.println("  mHostConnected: " + mHostConnected);
             pw.println("  mSourcePower: " + mSourcePower);
             pw.println("  mSinkPower: " + mSinkPower);
+            pw.println("  mUsbCharging: " + mUsbCharging);
             try {
                 pw.println("  Kernel state: "
                         + FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim());
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 58c5002..27f7172 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -16,12 +16,21 @@
 
 package android.telecom;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.ParcelFileDescriptor;
 
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
 import java.lang.String;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -312,8 +321,15 @@
          */
         public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 0x00000080;
 
+        /**
+         * Indicates that the call is from a self-managed {@link ConnectionService}.
+         * <p>
+         * See also {@link Connection#PROPERTY_SELF_MANAGED}
+         */
+        public static final int PROPERTY_SELF_MANAGED = 0x00000100;
+
         //******************************************************************************************
-        // Next PROPERTY value: 0x00000100
+        // Next PROPERTY value: 0x00000200
         //******************************************************************************************
 
         private final String mTelecomCallId;
@@ -828,6 +844,155 @@
          * @param extras Extras associated with the connection event.
          */
         public void onConnectionEvent(Call call, String event, Bundle extras) {}
+
+        /**
+         * Invoked when the RTT mode changes for this call.
+         * @param call The call whose RTT mode has changed.
+         * @param mode the new RTT mode, one of
+         * {@link RttCall#RTT_MODE_FULL}, {@link RttCall#RTT_MODE_HCO},
+         *             or {@link RttCall#RTT_MODE_VCO}
+         */
+        public void onRttModeChanged(Call call, int mode) {}
+
+        /**
+         * Invoked when the call's RTT status changes, either from off to on or from on to off.
+         * @param call The call whose RTT status has changed.
+         * @param enabled whether RTT is now enabled or disabled
+         * @param rttCall the {@link RttCall} object to use for reading and writing if RTT is now
+         *                on, null otherwise.
+         */
+        public void onRttStatusChanged(Call call, boolean enabled, RttCall rttCall) {}
+
+        /**
+         * Invoked when the remote end of the connection has requested that an RTT communication
+         * channel be opened. A response to this should be sent via {@link #respondToRttRequest}
+         * with the same ID that this method is invoked with.
+         * @param call The call which the RTT request was placed on
+         * @param id The ID of the request.
+         */
+        public void onRttRequest(Call call, int id) {}
+
+        /**
+         * Invoked when the RTT session failed to initiate for some reason, including rejection
+         * by the remote party.
+         * @param call The call which the RTT initiation failure occurred on.
+         * @param reason One of the status codes defined in
+         *               {@link android.telecom.Connection.RttModifyStatus}, with the exception of
+         *               {@link android.telecom.Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
+         */
+        public void onRttInitiationFailure(Call call, int reason) {}
+    }
+
+    /**
+     * A class that holds the state that describes the state of the RTT channel to the remote
+     * party, if it is active.
+     */
+    public static final class RttCall {
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({RTT_MODE_INVALID, RTT_MODE_FULL, RTT_MODE_HCO, RTT_MODE_VCO})
+        public @interface RttAudioMode {}
+
+        /**
+         * For metrics use. Default value in the proto.
+         * @hide
+         */
+        public static final int RTT_MODE_INVALID = 0;
+
+        /**
+         * Indicates that there should be a bidirectional audio stream between the two parties
+         * on the call.
+         */
+        public static final int RTT_MODE_FULL = 1;
+
+        /**
+         * Indicates that the local user should be able to hear the audio stream from the remote
+         * user, but not vice versa. Equivalent to muting the microphone.
+         */
+        public static final int RTT_MODE_HCO = 2;
+
+        /**
+         * Indicates that the remote user should be able to hear the audio stream from the local
+         * user, but not vice versa. Equivalent to setting the volume to zero.
+         */
+        public static final int RTT_MODE_VCO = 3;
+
+        private static final int READ_BUFFER_SIZE = 1000;
+
+        private InputStreamReader mReceiveStream;
+        private OutputStreamWriter mTransmitStream;
+        private int mRttMode;
+        private final InCallAdapter mInCallAdapter;
+        private final String mTelecomCallId;
+        private char[] mReadBuffer = new char[READ_BUFFER_SIZE];
+
+        /**
+         * @hide
+         */
+        public RttCall(String telecomCallId, InputStreamReader receiveStream,
+                OutputStreamWriter transmitStream, int mode, InCallAdapter inCallAdapter) {
+            mTelecomCallId = telecomCallId;
+            mReceiveStream = receiveStream;
+            mTransmitStream = transmitStream;
+            mRttMode = mode;
+            mInCallAdapter = inCallAdapter;
+        }
+
+        /**
+         * Returns the current RTT audio mode.
+         * @return Current RTT audio mode. One of {@link #RTT_MODE_FULL}, {@link #RTT_MODE_VCO}, or
+         * {@link #RTT_MODE_HCO}.
+         */
+        public int getRttAudioMode() {
+            return mRttMode;
+        }
+
+        /**
+         * Sets the RTT audio mode. The requested mode change will be communicated through
+         * {@link Callback#onRttModeChanged(Call, int)}.
+         * @param mode The desired RTT audio mode, one of {@link #RTT_MODE_FULL},
+         * {@link #RTT_MODE_VCO}, or {@link #RTT_MODE_HCO}.
+         */
+        public void setRttMode(@RttAudioMode int mode) {
+            mInCallAdapter.setRttMode(mTelecomCallId, mode);
+        }
+
+        /**
+         * Writes the string {@param input} into the outgoing text stream for this RTT call. Since
+         * RTT transmits text in real-time, this method should be called once for each character
+         * the user enters into the device.
+         *
+         * This method is not thread-safe -- calling it from multiple threads simultaneously may
+         * lead to interleaved text.
+         * @param input The message to send to the remote user.
+         */
+        public void write(String input) throws IOException {
+            mTransmitStream.write(input);
+            mTransmitStream.flush();
+        }
+
+        /**
+         * Reads a string from the remote user, blocking if there is no data available. Returns
+         * {@code null} if the RTT conversation has been terminated and there is no further data
+         * to read.
+         *
+         * This method is not thread-safe -- calling it from multiple threads simultaneously may
+         * lead to interleaved text.
+         * @return A string containing text sent by the remote user, or {@code null} if the
+         * conversation has been terminated or if there was an error while reading.
+         */
+        public String read() {
+            try {
+                int numRead = mReceiveStream.read(mReadBuffer, 0, READ_BUFFER_SIZE);
+                if (numRead < 0) {
+                    return null;
+                }
+                return new String(mReadBuffer, 0, numRead);
+            } catch (IOException e) {
+                Log.w(this, "Exception encountered when reading from InputStreamReader: %s", e);
+                return null;
+            }
+        }
     }
 
     /**
@@ -853,8 +1018,11 @@
     private String mParentId = null;
     private int mState;
     private List<String> mCannedTextResponses = null;
+    private String mCallingPackage;
+    private int mTargetSdkVersion;
     private String mRemainingPostDialSequence;
     private VideoCallImpl mVideoCallImpl;
+    private RttCall mRttCall;
     private Details mDetails;
     private Bundle mExtras;
 
@@ -1053,6 +1221,34 @@
     }
 
     /**
+     * Sends an RTT upgrade request to the remote end of the connection. Success is not
+     * guaranteed, and notification of success will be via the
+     * {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
+     */
+    public void sendRttRequest() {
+        mInCallAdapter.sendRttRequest(mTelecomCallId);
+    }
+
+    /**
+     * Responds to an RTT request received via the {@link Callback#onRttRequest(Call, int)} )}
+     * callback.
+     * The ID used here should be the same as the ID that was received via the callback.
+     * @param id The request ID received via {@link Callback#onRttRequest(Call, int)}
+     * @param accept {@code true} if the RTT request should be accepted, {@code false} otherwise.
+     */
+    public void respondToRttRequest(int id, boolean accept) {
+        mInCallAdapter.respondToRttRequest(mTelecomCallId, id, accept);
+    }
+
+    /**
+     * Terminate the RTT session on this call. The resulting state change will be notified via
+     * the {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback.
+     */
+    public void stopRtt() {
+        mInCallAdapter.stopRtt(mTelecomCallId);
+    }
+
+    /**
      * Adds some extras to this {@link Call}.  Existing keys are replaced and new ones are
      * added.
      * <p>
@@ -1232,6 +1428,23 @@
     }
 
     /**
+     * Returns this call's RttCall object. The {@link RttCall} instance is used to send and
+     * receive RTT text data, as well as to change the RTT mode.
+     * @return A {@link Call.RttCall}. {@code null} if there is no active RTT connection.
+     */
+    public @Nullable RttCall getRttCall() {
+        return mRttCall;
+    }
+
+    /**
+     * Returns whether this call has an active RTT connection.
+     * @return true if there is a connection, false otherwise.
+     */
+    public boolean isRttActive() {
+        return mRttCall != null;
+    }
+
+    /**
      * Registers a callback to this {@code Call}.
      *
      * @param callback A {@code Callback}.
@@ -1340,19 +1553,25 @@
     }
 
     /** {@hide} */
-    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) {
+    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage,
+         int targetSdkVersion) {
         mPhone = phone;
         mTelecomCallId = telecomCallId;
         mInCallAdapter = inCallAdapter;
         mState = STATE_NEW;
+        mCallingPackage = callingPackage;
+        mTargetSdkVersion = targetSdkVersion;
     }
 
     /** {@hide} */
-    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state) {
+    Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state,
+            String callingPackage, int targetSdkVersion) {
         mPhone = phone;
         mTelecomCallId = telecomCallId;
         mInCallAdapter = inCallAdapter;
         mState = state;
+        mCallingPackage = callingPackage;
+        mTargetSdkVersion = targetSdkVersion;
     }
 
     /** {@hide} */
@@ -1362,6 +1581,7 @@
 
     /** {@hide} */
     final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) {
+
         // First, we update the internal state as far as possible before firing any updates.
         Details details = Details.createFromParcelableCall(parcelableCall);
         boolean detailsChanged = !Objects.equals(mDetails, details);
@@ -1377,7 +1597,8 @@
             cannedTextResponsesChanged = true;
         }
 
-        VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl();
+        VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage,
+                mTargetSdkVersion);
         boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() &&
                 !Objects.equals(mVideoCallImpl, newVideoCallImpl);
         if (videoCallChanged) {
@@ -1421,6 +1642,32 @@
             fireConferenceableCallsChanged();
         }
 
+        boolean isRttChanged = false;
+        boolean rttModeChanged = false;
+        if (parcelableCall.getParcelableRttCall() != null && parcelableCall.getIsRttCallChanged()) {
+            ParcelableRttCall parcelableRttCall = parcelableCall.getParcelableRttCall();
+            InputStreamReader receiveStream = new InputStreamReader(
+                    new ParcelFileDescriptor.AutoCloseInputStream(
+                            parcelableRttCall.getReceiveStream()),
+                    StandardCharsets.UTF_8);
+            OutputStreamWriter transmitStream = new OutputStreamWriter(
+                    new ParcelFileDescriptor.AutoCloseOutputStream(
+                            parcelableRttCall.getTransmitStream()),
+                    StandardCharsets.UTF_8);
+            RttCall newRttCall = new Call.RttCall(mTelecomCallId,
+                    receiveStream, transmitStream, parcelableRttCall.getRttMode(), mInCallAdapter);
+            if (mRttCall == null) {
+                isRttChanged = true;
+            } else if (mRttCall.getRttAudioMode() != newRttCall.getRttAudioMode()) {
+                rttModeChanged = true;
+            }
+            mRttCall = newRttCall;
+        } else if (mRttCall != null && parcelableCall.getParcelableRttCall() == null
+                && parcelableCall.getIsRttCallChanged()) {
+            isRttChanged = true;
+            mRttCall = null;
+        }
+
         // Now we fire updates, ensuring that any client who listens to any of these notifications
         // gets the most up-to-date state.
 
@@ -1442,6 +1689,12 @@
         if (childrenChanged) {
             fireChildrenChanged(getChildren());
         }
+        if (isRttChanged) {
+            fireOnIsRttChanged(mRttCall != null, mRttCall);
+        }
+        if (rttModeChanged) {
+            fireOnRttModeChanged(mRttCall.getRttAudioMode());
+        }
 
         // If we have transitioned to DISCONNECTED, that means we need to notify clients and
         // remove ourselves from the Phone. Note that we do this after completing all state updates
@@ -1472,6 +1725,24 @@
         fireOnConnectionEvent(event, extras);
     }
 
+    /** {@hide} */
+    final void internalOnRttUpgradeRequest(final int requestId) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onRttRequest(call, requestId));
+        }
+    }
+
+    /** @hide */
+    final void internalOnRttInitiationFailure(int reason) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onRttInitiationFailure(call, reason));
+        }
+    }
+
     private void fireStateChanged(final int newState) {
         for (CallbackRecord<Callback> record : mCallbackRecords) {
             final Call call = this;
@@ -1640,6 +1911,32 @@
     }
 
     /**
+     * Notifies listeners of an RTT on/off change
+     *
+     * @param enabled True if RTT is now enabled, false otherwise
+     */
+    private void fireOnIsRttChanged(final boolean enabled, final RttCall rttCall) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onRttStatusChanged(call, enabled, rttCall));
+        }
+    }
+
+    /**
+     * Notifies listeners of a RTT mode change
+     *
+     * @param mode The new RTT mode
+     */
+    private void fireOnRttModeChanged(final int mode) {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onRttModeChanged(call, mode));
+        }
+    }
+
+    /**
      * Determines if two bundles are equal.
      *
      * @param bundle The original bundle.
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 560b616..833affa 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -20,20 +20,30 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.Notification;
+import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.ArraySet;
 import android.view.Surface;
 
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -384,8 +394,14 @@
      */
     public static final int PROPERTY_SELF_MANAGED = 1<<7;
 
+    /**
+     * When set, indicates that a connection has an active RTT session associated with it.
+     * @hide
+     */
+    public static final int PROPERTY_IS_RTT = 1 << 8;
+
     //**********************************************************************************************
-    // Next PROPERTY value: 1<<8
+    // Next PROPERTY value: 1<<9
     //**********************************************************************************************
 
     /**
@@ -428,6 +444,31 @@
             "android.telecom.extra.DISABLE_ADD_CALL";
 
     /**
+     * String connection extra key on a {@link Connection} or {@link Conference} which contains the
+     * original Connection ID associated with the connection.  Used in
+     * {@link RemoteConnectionService} to track the Connection ID which was originally assigned to a
+     * connection/conference added via
+     * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection)} and
+     * {@link ConnectionService#addConference(Conference)} APIs.  This is important to pass to
+     * Telecom for when it deals with RemoteConnections.  When the ConnectionManager wraps the
+     * {@link RemoteConnection} and {@link RemoteConference} and adds it to Telecom, there needs to
+     * be a way to ensure that we don't add the connection again as a duplicate.
+     * <p>
+     * For example, the TelephonyCS calls addExistingConnection for a Connection with ID
+     * {@code TelephonyCS@1}.  The ConnectionManager learns of this via
+     * {@link ConnectionService#onRemoteExistingConnectionAdded(RemoteConnection)}, and wraps this
+     * in a new {@link Connection} which it adds to Telecom via
+     * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection)}.  As part of
+     * this process, the wrapped RemoteConnection gets assigned a new ID (e.g. {@code ConnMan@1}).
+     * The TelephonyCS will ALSO try to add the existing connection to Telecom, except with the
+     * ID it originally referred to the connection as.  Thus Telecom needs to know that the
+     * Connection with ID {@code ConnMan@1} is really the same as {@code TelephonyCS@1}.
+     * @hide
+     */
+    public static final String EXTRA_ORIGINAL_CONNECTION_ID =
+            "android.telecom.extra.ORIGINAL_CONNECTION_ID";
+
+    /**
      * Connection event used to inform Telecom that it should play the on hold tone.  This is used
      * to play a tone when the peer puts the current call on hold.  Sent to Telecom via
      * {@link #sendConnectionEvent(String, Bundle)}.
@@ -728,6 +769,114 @@
         /** @hide */
         public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
         public void onAudioRouteChanged(Connection c, int audioRoute) {}
+        public void onRttInitiationSuccess(Connection c) {}
+        public void onRttInitiationFailure(Connection c, int reason) {}
+        public void onRttSessionRemotelyTerminated(Connection c) {}
+        public void onRemoteRttRequest(Connection c) {}
+    }
+
+    /**
+     * Provides methods to read and write RTT data to/from the in-call app.
+     * @hide
+     */
+    public static final class RttTextStream {
+        private static final int READ_BUFFER_SIZE = 1000;
+        private final InputStreamReader mPipeFromInCall;
+        private final OutputStreamWriter mPipeToInCall;
+        private final ParcelFileDescriptor mFdFromInCall;
+        private final ParcelFileDescriptor mFdToInCall;
+        private char[] mReadBuffer = new char[READ_BUFFER_SIZE];
+
+        /**
+         * @hide
+         */
+        public RttTextStream(ParcelFileDescriptor toInCall, ParcelFileDescriptor fromInCall) {
+            mFdFromInCall = fromInCall;
+            mFdToInCall = toInCall;
+            mPipeFromInCall = new InputStreamReader(
+                    new ParcelFileDescriptor.AutoCloseInputStream(fromInCall));
+            mPipeToInCall = new OutputStreamWriter(
+                    new ParcelFileDescriptor.AutoCloseOutputStream(toInCall));
+        }
+
+        /**
+         * Writes the string {@param input} into the text stream to the UI for this RTT call. Since
+         * RTT transmits text in real-time, this method should be called as often as text snippets
+         * are received from the remote user, even if it is only one character.
+         *
+         * This method is not thread-safe -- calling it from multiple threads simultaneously may
+         * lead to interleaved text.
+         * @param input The message to send to the in-call app.
+         */
+        public void write(String input) throws IOException {
+            mPipeToInCall.write(input);
+            mPipeToInCall.flush();
+        }
+
+
+        /**
+         * Reads a string from the in-call app, blocking if there is no data available. Returns
+         * {@code null} if the RTT conversation has been terminated and there is no further data
+         * to read.
+         *
+         * This method is not thread-safe -- calling it from multiple threads simultaneously may
+         * lead to interleaved text.
+         * @return A string containing text entered by the user, or {@code null} if the
+         * conversation has been terminated or if there was an error while reading.
+         */
+        public String read() {
+            try {
+                int numRead = mPipeFromInCall.read(mReadBuffer, 0, READ_BUFFER_SIZE);
+                if (numRead < 0) {
+                    return null;
+                }
+                return new String(mReadBuffer, 0, numRead);
+            } catch (IOException e) {
+                Log.w(this, "Exception encountered when reading from InputStreamReader: %s", e);
+                return null;
+            }
+        }
+
+        /** @hide */
+        public ParcelFileDescriptor getFdFromInCall() {
+            return mFdFromInCall;
+        }
+
+        /** @hide */
+        public ParcelFileDescriptor getFdToInCall() {
+            return mFdToInCall;
+        }
+    }
+
+    /**
+     * Provides constants to represent the results of responses to session modify requests sent via
+     * {@link Call#sendRttRequest()}
+     */
+    public static final class RttModifyStatus {
+        /**
+         * Session modify request was successful.
+         */
+        public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1;
+
+        /**
+         * Session modify request failed.
+         */
+        public static final int SESSION_MODIFY_REQUEST_FAIL = 2;
+
+        /**
+         * Session modify request ignored due to invalid parameters.
+         */
+        public static final int SESSION_MODIFY_REQUEST_INVALID = 3;
+
+        /**
+         * Session modify request timed out.
+         */
+        public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4;
+
+        /**
+         * Session modify request rejected by remote user.
+         */
+        public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5;
     }
 
     /**
@@ -774,7 +923,7 @@
         public static final int SESSION_EVENT_TX_STOP = 4;
 
         /**
-         * A camera failure has occurred for the selected camera.  The {@link InCallService} can use
+         * A camera failure has occurred for the selected camera.  The {@link VideoProvider} can use
          * this as a cue to inform the user the camera is not available.
          * @see #handleCallSessionEvent(int)
          */
@@ -782,13 +931,21 @@
 
         /**
          * Issued after {@link #SESSION_EVENT_CAMERA_FAILURE} when the camera is once again ready
-         * for operation.  The {@link InCallService} can use this as a cue to inform the user that
+         * for operation.  The {@link VideoProvider} can use this as a cue to inform the user that
          * the camera has become available again.
          * @see #handleCallSessionEvent(int)
          */
         public static final int SESSION_EVENT_CAMERA_READY = 6;
 
         /**
+         * Session event raised by Telecom when
+         * {@link android.telecom.InCallService.VideoCall#setCamera(String)} is called and the
+         * caller does not have the necessary {@link android.Manifest.permission#CAMERA} permission.
+         * @see #handleCallSessionEvent(int)
+         */
+        public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7;
+
+        /**
          * Session modify request was successful.
          * @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile)
          */
@@ -837,6 +994,8 @@
         private static final String SESSION_EVENT_TX_STOP_STR = "TX_STOP";
         private static final String SESSION_EVENT_CAMERA_FAILURE_STR = "CAMERA_FAIL";
         private static final String SESSION_EVENT_CAMERA_READY_STR = "CAMERA_READY";
+        private static final String SESSION_EVENT_CAMERA_PERMISSION_ERROR_STR =
+                "CAMERA_PERMISSION_ERROR";
         private static final String SESSION_EVENT_UNKNOWN_STR = "UNKNOWN";
 
         private VideoProvider.VideoProviderHandler mMessageHandler;
@@ -895,8 +1054,17 @@
                         break;
                     }
                     case MSG_SET_CAMERA:
-                        onSetCamera((String) msg.obj);
-                        break;
+                    {
+                        SomeArgs args = (SomeArgs) msg.obj;
+                        try {
+                            onSetCamera((String) args.arg1);
+                            onSetCamera((String) args.arg1, (String) args.arg2, args.argi1,
+                                    args.argi2, args.argi3);
+                        } finally {
+                            args.recycle();
+                        }
+                    }
+                    break;
                     case MSG_SET_PREVIEW_SURFACE:
                         onSetPreviewSurface((Surface) msg.obj);
                         break;
@@ -951,8 +1119,24 @@
                         MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
             }
 
-            public void setCamera(String cameraId) {
-                mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget();
+            public void setCamera(String cameraId, String callingPackageName,
+                                  int targetSdkVersion) {
+
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = cameraId;
+                // Propagate the calling package; originally determined in
+                // android.telecom.InCallService.VideoCall#setCamera(String) from the calling
+                // process.
+                args.arg2 = callingPackageName;
+                // Pass along the uid and pid of the calling app; this gets lost when we put the
+                // message onto the handler.  These are required for Telecom to perform a permission
+                // check to see if the calling app is able to use the camera.
+                args.argi1 = Binder.getCallingUid();
+                args.argi2 = Binder.getCallingPid();
+                // Pass along the target SDK version of the calling InCallService.  This is used to
+                // maintain backwards compatibility of the API for older callers.
+                args.argi3 = targetSdkVersion;
+                mMessageHandler.obtainMessage(MSG_SET_CAMERA, args).sendToTarget();
             }
 
             public void setPreviewSurface(Surface surface) {
@@ -1037,6 +1221,30 @@
         public abstract void onSetCamera(String cameraId);
 
         /**
+         * Sets the camera to be used for the outgoing video.
+         * <p>
+         * The {@link VideoProvider} should respond by communicating the capabilities of the chosen
+         * camera via
+         * {@link VideoProvider#changeCameraCapabilities(VideoProfile.CameraCapabilities)}.
+         * <p>
+         * This prototype is used internally to ensure that the calling package name, UID and PID
+         * are sent to Telecom so that can perform a camera permission check on the caller.
+         * <p>
+         * Sent from the {@link InCallService} via
+         * {@link InCallService.VideoCall#setCamera(String)}.
+         *
+         * @param cameraId The id of the camera (use ids as reported by
+         * {@link CameraManager#getCameraIdList()}).
+         * @param callingPackageName The AppOpps package name of the caller.
+         * @param callingUid The UID of the caller.
+         * @param callingPid The PID of the caller.
+         * @param targetSdkVersion The target SDK version of the caller.
+         * @hide
+         */
+        public void onSetCamera(String cameraId, String callingPackageName, int callingUid,
+                int callingPid, int targetSdkVersion) {}
+
+        /**
          * Sets the surface to be used for displaying a preview of what the user's camera is
          * currently capturing.  When video transmission is enabled, this is the video signal which
          * is sent to the remote device.
@@ -1222,7 +1430,8 @@
          *      {@link VideoProvider#SESSION_EVENT_TX_START},
          *      {@link VideoProvider#SESSION_EVENT_TX_STOP},
          *      {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
-         *      {@link VideoProvider#SESSION_EVENT_CAMERA_READY}.
+         *      {@link VideoProvider#SESSION_EVENT_CAMERA_READY},
+         *      {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE}.
          */
         public void handleCallSessionEvent(int event) {
             if (mVideoCallbacks != null) {
@@ -1371,6 +1580,8 @@
                     return SESSION_EVENT_TX_START_STR;
                 case SESSION_EVENT_TX_STOP:
                     return SESSION_EVENT_TX_STOP_STR;
+                case SESSION_EVENT_CAMERA_PERMISSION_ERROR:
+                    return SESSION_EVENT_CAMERA_PERMISSION_ERROR_STR;
                 default:
                     return SESSION_EVENT_UNKNOWN_STR + " " + event;
             }
@@ -2275,6 +2486,47 @@
     }
 
     /**
+     * Informs listeners that a previously requested RTT session via
+     * {@link ConnectionRequest#isRequestingRtt()} or
+     * {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)} has succeeded.
+     * @hide
+     */
+    public final void sendRttInitiationSuccess() {
+        mListeners.forEach((l) -> l.onRttInitiationSuccess(Connection.this));
+    }
+
+    /**
+     * Informs listeners that a previously requested RTT session via
+     * {@link ConnectionRequest#isRequestingRtt()} or
+     * {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)}
+     * has failed.
+     * @param reason One of the reason codes defined in {@link RttModifyStatus}, with the
+     *               exception of {@link RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
+     * @hide
+     */
+    public final void sendRttInitiationFailure(int reason) {
+        mListeners.forEach((l) -> l.onRttInitiationFailure(Connection.this, reason));
+    }
+
+    /**
+     * Informs listeners that a currently active RTT session has been terminated by the remote
+     * side of the coll.
+     * @hide
+     */
+    public final void sendRttSessionRemotelyTerminated() {
+        mListeners.forEach((l) -> l.onRttSessionRemotelyTerminated(Connection.this));
+    }
+
+    /**
+     * Informs listeners that the remote side of the call has requested an upgrade to include an
+     * RTT session in the call.
+     * @hide
+     */
+    public final void sendRemoteRttRequest() {
+        mListeners.forEach((l) -> l.onRemoteRttRequest(Connection.this));
+    }
+
+    /**
      * Notifies this Connection that the {@link #getAudioState()} property has a new value.
      *
      * @param state The new connection audio state.
@@ -2441,9 +2693,73 @@
      * regular {@link ConnectionService}, the Telecom framework will display its own incoming call
      * user interface to allow the user to choose whether to answer the new incoming call and
      * disconnect other ongoing calls, or to reject the new incoming call.
+     * <p>
+     * You should trigger the display of the incoming call user interface for your application by
+     * showing a {@link Notification} with a full-screen {@link Intent} specified.
+     * For example:
+     * <pre><code>
+     *     // Create an intent which triggers your fullscreen incoming call user interface.
+     *     Intent intent = new Intent(Intent.ACTION_MAIN, null);
+     *     intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+     *     intent.setClass(context, YourIncomingCallActivity.class);
+     *     PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, 0);
+     *
+     *     // Build the notification as an ongoing high priority item; this ensures it will show as
+     *     // a heads up notification which slides down over top of the current content.
+     *     final Notification.Builder builder = new Notification.Builder(context);
+     *     builder.setOngoing(true);
+     *     builder.setPriority(Notification.PRIORITY_HIGH);
+     *
+     *     // Set notification content intent to take user to fullscreen UI if user taps on the
+     *     // notification body.
+     *     builder.setContentIntent(pendingIntent);
+     *     // Set full screen intent to trigger display of the fullscreen UI when the notification
+     *     // manager deems it appropriate.
+     *     builder.setFullScreenIntent(pendingIntent, true);
+     *
+     *     // Setup notification content.
+     *     builder.setSmallIcon( yourIconResourceId );
+     *     builder.setContentTitle("Your notification title");
+     *     builder.setContentText("Your notification content.");
+     *
+     *     // Use builder.addAction(..) to add buttons to answer or reject the call.
+     *
+     *     NotificationManager notificationManager = mContext.getSystemService(
+     *         NotificationManager.class);
+     *     notificationManager.notify(YOUR_TAG, YOUR_ID, builder.build());
+     * </code></pre>
      */
     public void onShowIncomingCallUi() {}
 
+    /**
+     * Notifies this {@link Connection} that the user has requested an RTT session.
+     * The connection service should call {@link #sendRttInitiationSuccess} or
+     * {@link #sendRttInitiationFailure} to inform Telecom of the success or failure of the
+     * request, respectively.
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     * @hide
+     */
+    public void onStartRtt(@NonNull RttTextStream rttTextStream) {}
+
+    /**
+     * Notifies this {@link Connection} that it should terminate any existing RTT communication
+     * channel. No response to Telecom is needed for this method.
+     * @hide
+     */
+    public void onStopRtt() {}
+
+    /**
+     * Notifies this connection of a response to a previous remotely-initiated RTT upgrade
+     * request sent via {@link #sendRemoteRttRequest}. Acceptance of the request is
+     * indicated by the supplied {@link RttTextStream} being non-null, and rejection is
+     * indicated by {@code rttTextStream} being {@code null}
+     * @hide
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     */
+    public void handleRttUpgradeResponse(@Nullable RttTextStream rttTextStream) {}
+
     static String toLogSafePhoneNumber(String number) {
         // For unknown number, log empty string.
         if (number == null) {
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index 2343462..054de4c 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -19,6 +19,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 
 /**
@@ -27,13 +28,121 @@
  */
 public final class ConnectionRequest implements Parcelable {
 
-    // TODO: Token to limit recursive invocations
+    /**
+     * Builder class for {@link ConnectionRequest}
+     * @hide
+     */
+    public static final class Builder {
+        private PhoneAccountHandle mAccountHandle;
+        private Uri mAddress;
+        private Bundle mExtras;
+        private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
+        private String mTelecomCallId;
+        private boolean mShouldShowIncomingCallUi = false;
+        private ParcelFileDescriptor mRttPipeToInCall;
+        private ParcelFileDescriptor mRttPipeFromInCall;
+
+        public Builder() { }
+
+        /**
+         * Sets the phone account handle for the resulting {@link ConnectionRequest}
+         * @param accountHandle The accountHandle which should be used to place the call.
+         */
+        public Builder setAccountHandle(PhoneAccountHandle accountHandle) {
+            this.mAccountHandle = accountHandle;
+            return this;
+        }
+
+        /**
+         * Sets the address for the resulting {@link ConnectionRequest}
+         * @param address The address(e.g., phone number) to which the {@link Connection} is to
+         *                connect.
+         */
+        public Builder setAddress(Uri address) {
+            this.mAddress = address;
+            return this;
+        }
+
+        /**
+         * Sets the extras bundle for the resulting {@link ConnectionRequest}
+         * @param extras Application-specific extra data.
+         */
+        public Builder setExtras(Bundle extras) {
+            this.mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Sets the video state for the resulting {@link ConnectionRequest}
+         * @param videoState Determines the video state for the connection.
+         */
+        public Builder setVideoState(int videoState) {
+            this.mVideoState = videoState;
+            return this;
+        }
+
+        /**
+         * Sets the Telecom call ID for the resulting {@link ConnectionRequest}
+         * @param telecomCallId The telecom call ID.
+         */
+        public Builder setTelecomCallId(String telecomCallId) {
+            this.mTelecomCallId = telecomCallId;
+            return this;
+        }
+
+        /**
+         * Sets shouldShowIncomingUi for the resulting {@link ConnectionRequest}
+         * @param shouldShowIncomingCallUi For a self-managed {@link ConnectionService}, will be
+         *                                 {@code true} if the {@link ConnectionService} should show
+         *                                 its own incoming call UI for an incoming call.  When
+         *                                 {@code false}, Telecom shows the incoming call UI.
+         */
+        public Builder setShouldShowIncomingCallUi(boolean shouldShowIncomingCallUi) {
+            this.mShouldShowIncomingCallUi = shouldShowIncomingCallUi;
+            return this;
+        }
+
+        /**
+         * Sets the RTT pipe for transferring text into the {@link ConnectionService} for the
+         * resulting {@link ConnectionRequest}
+         * @param rttPipeFromInCall The data pipe to read from.
+         */
+        public Builder setRttPipeFromInCall(ParcelFileDescriptor rttPipeFromInCall) {
+            this.mRttPipeFromInCall = rttPipeFromInCall;
+            return this;
+        }
+
+        /**
+         * Sets the RTT pipe for transferring text out of {@link ConnectionService} for the
+         * resulting {@link ConnectionRequest}
+         * @param rttPipeToInCall The data pipe to write to.
+         */
+        public Builder setRttPipeToInCall(ParcelFileDescriptor rttPipeToInCall) {
+            this.mRttPipeToInCall = rttPipeToInCall;
+            return this;
+        }
+
+        public ConnectionRequest build() {
+            return new ConnectionRequest(
+                    mAccountHandle,
+                    mAddress,
+                    mExtras,
+                    mVideoState,
+                    mTelecomCallId,
+                    mShouldShowIncomingCallUi,
+                    mRttPipeFromInCall,
+                    mRttPipeToInCall);
+        }
+    }
+
     private final PhoneAccountHandle mAccountHandle;
     private final Uri mAddress;
     private final Bundle mExtras;
     private final int mVideoState;
     private final String mTelecomCallId;
     private final boolean mShouldShowIncomingCallUi;
+    private final ParcelFileDescriptor mRttPipeToInCall;
+    private final ParcelFileDescriptor mRttPipeFromInCall;
 
     /**
      * @param accountHandle The accountHandle which should be used to place the call.
@@ -44,7 +153,7 @@
             PhoneAccountHandle accountHandle,
             Uri handle,
             Bundle extras) {
-        this(accountHandle, handle, extras, VideoProfile.STATE_AUDIO_ONLY, null, false);
+        this(accountHandle, handle, extras, VideoProfile.STATE_AUDIO_ONLY, null, false, null, null);
     }
 
     /**
@@ -58,7 +167,7 @@
             Uri handle,
             Bundle extras,
             int videoState) {
-        this(accountHandle, handle, extras, videoState, null, false);
+        this(accountHandle, handle, extras, videoState, null, false, null, null);
     }
 
     /**
@@ -80,12 +189,27 @@
             int videoState,
             String telecomCallId,
             boolean shouldShowIncomingCallUi) {
+        this(accountHandle, handle, extras, videoState, telecomCallId,
+                shouldShowIncomingCallUi, null, null);
+    }
+
+    private ConnectionRequest(
+            PhoneAccountHandle accountHandle,
+            Uri handle,
+            Bundle extras,
+            int videoState,
+            String telecomCallId,
+            boolean shouldShowIncomingCallUi,
+            ParcelFileDescriptor rttPipeFromInCall,
+            ParcelFileDescriptor rttPipeToInCall) {
         mAccountHandle = accountHandle;
         mAddress = handle;
         mExtras = extras;
         mVideoState = videoState;
         mTelecomCallId = telecomCallId;
         mShouldShowIncomingCallUi = shouldShowIncomingCallUi;
+        mRttPipeFromInCall = rttPipeFromInCall;
+        mRttPipeToInCall = rttPipeToInCall;
     }
 
     private ConnectionRequest(Parcel in) {
@@ -95,6 +219,8 @@
         mVideoState = in.readInt();
         mTelecomCallId = in.readString();
         mShouldShowIncomingCallUi = in.readInt() == 1;
+        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader());
+        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader());
     }
 
     /**
@@ -149,6 +275,59 @@
         return mShouldShowIncomingCallUi;
     }
 
+    /**
+     * Gets the {@link ParcelFileDescriptor} that is used to send RTT text from the connection
+     * service to the in-call UI. In order to obtain an
+     * {@link java.io.InputStream} from this {@link ParcelFileDescriptor}, use
+     * {@link android.os.ParcelFileDescriptor.AutoCloseInputStream}.
+     * Only text data encoded using UTF-8 should be written into this {@link ParcelFileDescriptor}.
+     * @return The {@link ParcelFileDescriptor} that should be used for communication.
+     * Do not un-hide -- only for use by Telephony
+     * @hide
+     */
+    public ParcelFileDescriptor getRttPipeToInCall() {
+        return mRttPipeToInCall;
+    }
+
+    /**
+     * Gets the {@link ParcelFileDescriptor} that is used to send RTT text from the in-call UI to
+     * the connection service. In order to obtain an
+     * {@link java.io.OutputStream} from this {@link ParcelFileDescriptor}, use
+     * {@link android.os.ParcelFileDescriptor.AutoCloseOutputStream}.
+     * The contents of this {@link ParcelFileDescriptor} will consist solely of text encoded in
+     * UTF-8.
+     * @return The {@link ParcelFileDescriptor} that should be used for communication
+     * Do not un-hide -- only for use by Telephony
+     * @hide
+     */
+    public ParcelFileDescriptor getRttPipeFromInCall() {
+        return mRttPipeFromInCall;
+    }
+
+    /**
+     * Gets the {@link android.telecom.Connection.RttTextStream} object that should be used to
+     * send and receive RTT text to/from the in-call app.
+     * @return An instance of {@link android.telecom.Connection.RttTextStream}, or {@code null}
+     * if this connection request is not requesting an RTT session upon connection establishment.
+     * @hide
+     */
+    public Connection.RttTextStream getRttTextStream() {
+        if (isRequestingRtt()) {
+            return new Connection.RttTextStream(mRttPipeToInCall, mRttPipeFromInCall);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Convenience method for determining whether the ConnectionRequest is requesting an RTT session
+     * @return {@code true} if RTT is requested, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isRequestingRtt() {
+        return mRttPipeFromInCall != null && mRttPipeToInCall != null;
+    }
+
     @Override
     public String toString() {
         return String.format("ConnectionRequest %s %s",
@@ -186,5 +365,7 @@
         destination.writeInt(mVideoState);
         destination.writeString(mTelecomCallId);
         destination.writeInt(mShouldShowIncomingCallUi ? 1 : 0);
+        destination.writeParcelable(mRttPipeFromInCall, 0);
+        destination.writeParcelable(mRttPipeToInCall, 0);
     }
 }
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index ce3144b..bf8f8e4 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -26,6 +26,8 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.telecom.Logging.Session;
 
 import com.android.internal.os.SomeArgs;
@@ -98,6 +100,7 @@
     private static final String SESSION_ADD_CS_ADAPTER = "CS.aCSA";
     private static final String SESSION_REMOVE_CS_ADAPTER = "CS.rCSA";
     private static final String SESSION_CREATE_CONN = "CS.crCo";
+    private static final String SESSION_CREATE_CONN_FAILED = "CS.crCoF";
     private static final String SESSION_ABORT = "CS.ab";
     private static final String SESSION_ANSWER = "CS.an";
     private static final String SESSION_ANSWER_VIDEO = "CS.anV";
@@ -118,6 +121,9 @@
     private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
     private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
     private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
+    private static final String SESSION_START_RTT = "CS.+RTT";
+    private static final String SESSION_STOP_RTT = "CS.-RTT";
+    private static final String SESSION_RTT_UPGRADE_RESPONSE = "CS.rTRUR";
 
     private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1;
     private static final int MSG_CREATE_CONNECTION = 2;
@@ -142,6 +148,10 @@
     private static final int MSG_PULL_EXTERNAL_CALL = 22;
     private static final int MSG_SEND_CALL_EVENT = 23;
     private static final int MSG_ON_EXTRAS_CHANGED = 24;
+    private static final int MSG_CREATE_CONNECTION_FAILED = 25;
+    private static final int MSG_ON_START_RTT = 26;
+    private static final int MSG_ON_STOP_RTT = 27;
+    private static final int MSG_RTT_UPGRADE_RESPONSE = 28;
 
     private static Connection sNullConnection;
 
@@ -211,6 +221,27 @@
         }
 
         @Override
+        public void createConnectionFailed(
+                PhoneAccountHandle connectionManagerPhoneAccount,
+                String callId,
+                ConnectionRequest request,
+                boolean isIncoming,
+                Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_CREATE_CONN_FAILED);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = request;
+                args.arg3 = Log.createSubsession();
+                args.arg4 = connectionManagerPhoneAccount;
+                args.argi1 = isIncoming ? 1 : 0;
+                mHandler.obtainMessage(MSG_CREATE_CONNECTION_FAILED, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void abort(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_ABORT);
             try {
@@ -480,6 +511,53 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void startRtt(String callId, ParcelFileDescriptor fromInCall,
+                ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, SESSION_START_RTT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = new Connection.RttTextStream(toInCall, fromInCall);
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_START_RTT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, SESSION_STOP_RTT);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_ON_STOP_RTT, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall,
+                ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, SESSION_RTT_UPGRADE_RESPONSE);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                if (toInCall == null || fromInCall == null) {
+                    args.arg2 = null;
+                } else {
+                    args.arg2 = new Connection.RttTextStream(toInCall, fromInCall);
+                }
+                args.arg3 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_RTT_UPGRADE_RESPONSE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
     };
 
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -552,6 +630,39 @@
                     }
                     break;
                 }
+                case MSG_CREATE_CONNECTION_FAILED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    Log.continueSession((Session) args.arg3, SESSION_HANDLER +
+                            SESSION_CREATE_CONN_FAILED);
+                    try {
+                        final String id = (String) args.arg1;
+                        final ConnectionRequest request = (ConnectionRequest) args.arg2;
+                        final boolean isIncoming = args.argi1 == 1;
+                        final PhoneAccountHandle connectionMgrPhoneAccount =
+                                (PhoneAccountHandle) args.arg4;
+                        if (!mAreAccountsInitialized) {
+                            Log.d(this, "Enqueueing pre-init request %s", id);
+                            mPreInitializationConnectionRequests.add(
+                                    new android.telecom.Logging.Runnable(
+                                            SESSION_HANDLER + SESSION_CREATE_CONN_FAILED + ".pICR",
+                                            null /*lock*/) {
+                                        @Override
+                                        public void loggedRun() {
+                                            createConnectionFailed(connectionMgrPhoneAccount, id,
+                                                    request, isIncoming);
+                                        }
+                                    }.prepare());
+                        } else {
+                            Log.i(this, "createConnectionFailed %s", id);
+                            createConnectionFailed(connectionMgrPhoneAccount, id, request,
+                                    isIncoming);
+                        }
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 case MSG_ABORT: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     Log.continueSession((Session) args.arg2, SESSION_HANDLER + SESSION_ABORT);
@@ -798,6 +909,49 @@
                     }
                     break;
                 }
+                case MSG_ON_START_RTT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg3,
+                                SESSION_HANDLER + SESSION_START_RTT);
+                        String callId = (String) args.arg1;
+                        Connection.RttTextStream rttTextStream =
+                                (Connection.RttTextStream) args.arg2;
+                        startRtt(callId, rttTextStream);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_ON_STOP_RTT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg2,
+                                SESSION_HANDLER + SESSION_STOP_RTT);
+                        String callId = (String) args.arg1;
+                        stopRtt(callId);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
+                case MSG_RTT_UPGRADE_RESPONSE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg3,
+                                SESSION_HANDLER + SESSION_RTT_UPGRADE_RESPONSE);
+                        String callId = (String) args.arg1;
+                        Connection.RttTextStream rttTextStream =
+                                (Connection.RttTextStream) args.arg2;
+                        handleRttUpgradeResponse(callId, rttTextStream);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -1086,6 +1240,38 @@
                 mAdapter.setAudioRoute(id, audioRoute);
             }
         }
+
+        @Override
+        public void onRttInitiationSuccess(Connection c) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRttInitiationSuccess(id);
+            }
+        }
+
+        @Override
+        public void onRttInitiationFailure(Connection c, int reason) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRttInitiationFailure(id, reason);
+            }
+        }
+
+        @Override
+        public void onRttSessionRemotelyTerminated(Connection c) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRttSessionRemotelyTerminated(id);
+            }
+        }
+
+        @Override
+        public void onRemoteRttRequest(Connection c) {
+            String id = mIdByConnection.get(c);
+            if (id != null) {
+                mAdapter.onRemoteRttRequest(id);
+            }
+        }
     };
 
     /** {@inheritDoc} */
@@ -1175,6 +1361,18 @@
         }
     }
 
+    private void createConnectionFailed(final PhoneAccountHandle callManagerAccount,
+                                        final String callId, final ConnectionRequest request,
+                                        boolean isIncoming) {
+
+        Log.i(this, "createConnectionFailed %s", callId);
+        if (isIncoming) {
+            onCreateIncomingConnectionFailed(callManagerAccount, request);
+        } else {
+            onCreateOutgoingConnectionFailed(callManagerAccount, request);
+        }
+    }
+
     private void abort(String callId) {
         Log.d(this, "abort %s", callId);
         findConnectionForAction(callId, "abort").onAbort();
@@ -1369,7 +1567,6 @@
         if (connection != null) {
             connection.onCallEvent(event, extras);
         }
-
     }
 
     /**
@@ -1393,6 +1590,34 @@
         }
     }
 
+    private void startRtt(String callId, Connection.RttTextStream rttTextStream) {
+        Log.d(this, "startRtt(%s)", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream);
+        } else if (mConferenceById.containsKey(callId)) {
+            Log.w(this, "startRtt called on a conference.");
+        }
+    }
+
+    private void stopRtt(String callId) {
+        Log.d(this, "stopRtt(%s)", callId);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "stopRtt").onStopRtt();
+        } else if (mConferenceById.containsKey(callId)) {
+            Log.w(this, "stopRtt called on a conference.");
+        }
+    }
+
+    private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) {
+        Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null);
+        if (mConnectionById.containsKey(callId)) {
+            findConnectionForAction(callId, "handleRttUpgradeResponse")
+                    .handleRttUpgradeResponse(rttTextStream);
+        } else if (mConferenceById.containsKey(callId)) {
+            Log.w(this, "handleRttUpgradeResponse called on a conference.");
+        }
+    }
+
     private void onPostDialContinue(String callId, boolean proceed) {
         Log.d(this, "onPostDialContinue(%s)", callId);
         findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
@@ -1621,9 +1846,12 @@
      * <p>
      * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information.
      *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
      * @param request The incoming connection request.
      */
-    public void onCreateIncomingConnectionFailed(ConnectionRequest request) {
+    public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+                                                 ConnectionRequest request) {
     }
 
     /**
@@ -1637,9 +1865,12 @@
      * <p>
      * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information.
      *
+     * @param connectionManagerPhoneAccount See description at
+     *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
      * @param request The outgoing connection request.
      */
-    public void onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+    public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+                                                 ConnectionRequest request) {
     }
 
     /**
@@ -1756,7 +1987,13 @@
      */
     private String addExistingConnectionInternal(PhoneAccountHandle handle, Connection connection) {
         String id;
-        if (handle == null) {
+
+        if (connection.getExtras() != null && connection.getExtras()
+                .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+            id = connection.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
+            Log.d(this, "addExistingConnectionInternal - conn %s reusing original id %s",
+                    connection.getTelecomCallId(), id);
+        } else if (handle == null) {
             // If no phone account handle was provided, we cannot be sure the call ID is unique,
             // so just use a random UUID.
             id = UUID.randomUUID().toString();
@@ -1790,13 +2027,21 @@
     }
 
     private String addConferenceInternal(Conference conference) {
+        String originalId = null;
+        if (conference.getExtras() != null && conference.getExtras()
+                .containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
+            originalId = conference.getExtras().getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID);
+            Log.d(this, "addConferenceInternal: conf %s reusing original id %s",
+                    conference.getTelecomCallId(),
+                    originalId);
+        }
         if (mIdByConference.containsKey(conference)) {
             Log.w(this, "Re-adding an existing conference: %s.", conference);
         } else if (conference != null) {
             // Conferences do not (yet) have a PhoneAccountHandle associated with them, so we
             // cannot determine a ConnectionService class name to associate with the ID, so use
             // a unique UUID (for now).
-            String id = UUID.randomUUID().toString();
+            String id = originalId == null ? UUID.randomUUID().toString() : originalId;
             mConferenceById.put(id, conference);
             mIdByConference.put(conference, id);
             conference.addListener(mConferenceListener);
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 9542b73..63bdf74 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -547,4 +547,66 @@
             }
         }
     }
+
+    /**
+     * Notifies Telecom that an RTT session was successfully established.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRttInitiationSuccess(String callId) {
+        Log.v(this, "onRttInitiationSuccess: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRttInitiationSuccess(callId, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Notifies Telecom that a requested RTT session failed to be established.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRttInitiationFailure(String callId, int reason) {
+        Log.v(this, "onRttInitiationFailure: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRttInitiationFailure(callId, reason, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Notifies Telecom that an established RTT session was terminated by the remote user on
+     * the call.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRttSessionRemotelyTerminated(String callId) {
+        Log.v(this, "onRttSessionRemotelyTerminated: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRttSessionRemotelyTerminated(callId, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /**
+     * Notifies Telecom that the remote user on the call has requested an upgrade to an RTT
+     * session for this call.
+     *
+     * @param callId The unique ID of the call.
+     */
+    void onRemoteRttRequest(String callId) {
+        Log.v(this, "onRemoteRttRequest: %s", callId);
+        for (IConnectionServiceAdapter adapter : mAdapters) {
+            try {
+                adapter.onRemoteRttRequest(callId, Log.getExternalSession());
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index cc437f9..80e3c33 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -68,6 +68,10 @@
     private static final int MSG_SET_CONNECTION_PROPERTIES = 27;
     private static final int MSG_SET_PULLING = 28;
     private static final int MSG_SET_AUDIO_ROUTE = 29;
+    private static final int MSG_ON_RTT_INITIATION_SUCCESS = 30;
+    private static final int MSG_ON_RTT_INITIATION_FAILURE = 31;
+    private static final int MSG_ON_RTT_REMOTELY_TERMINATED = 32;
+    private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -300,6 +304,20 @@
                     }
                     break;
                 }
+                case MSG_ON_RTT_INITIATION_SUCCESS:
+                    mDelegate.onRttInitiationSuccess((String) msg.obj, null /*Session.Info*/);
+                    break;
+                case MSG_ON_RTT_INITIATION_FAILURE:
+                    mDelegate.onRttInitiationFailure((String) msg.obj, msg.arg1,
+                            null /*Session.Info*/);
+                    break;
+                case MSG_ON_RTT_REMOTELY_TERMINATED:
+                    mDelegate.onRttSessionRemotelyTerminated((String) msg.obj,
+                            null /*Session.Info*/);
+                    break;
+                case MSG_ON_RTT_UPGRADE_REQUEST:
+                    mDelegate.onRemoteRttRequest((String) msg.obj, null /*Session.Info*/);
+                    break;
             }
         }
     };
@@ -537,6 +555,32 @@
             args.arg3 = extras;
             mHandler.obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
         }
+
+        @Override
+        public void onRttInitiationSuccess(String connectionId, Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_INITIATION_SUCCESS, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void onRttInitiationFailure(String connectionId, int reason,
+                Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, connectionId)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onRttSessionRemotelyTerminated(String connectionId, Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_REMOTELY_TERMINATED, connectionId).sendToTarget();
+        }
+
+        @Override
+        public void onRemoteRttRequest(String connectionId, Session.Info sessionInfo)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_ON_RTT_UPGRADE_REQUEST, connectionId).sendToTarget();
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 3f270d9..9559a28 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -34,7 +34,7 @@
  * <p>
  * The adapter will stop functioning when there are no more calls.
  *
- * {@hide}
+ * @hide
  */
 public final class InCallAdapter {
     private final IInCallAdapter mAdapter;
@@ -375,4 +375,48 @@
         } catch (RemoteException ignored) {
         }
     }
+
+    /**
+     * Sends an RTT upgrade request to the remote end of the connection.
+     */
+    public void sendRttRequest(String callId) {
+        try {
+            mAdapter.sendRttRequest(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Responds to an RTT upgrade request initiated from the remote end.
+     *
+     * @param id the ID of the request as specified by Telecom
+     * @param accept Whether the request should be accepted.
+     */
+    public void respondToRttRequest(String callId, int id, boolean accept) {
+        try {
+            mAdapter.respondToRttRequest(callId, id, accept);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Instructs Telecom to shut down the RTT communication channel.
+     */
+    public void stopRtt(String callId) {
+        try {
+            mAdapter.stopRtt(callId);
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Sets the RTT audio mode.
+     * @param mode the desired RTT audio mode
+     */
+    public void setRttMode(String callId, int mode) {
+        try {
+            mAdapter.setRttMode(callId, mode);
+        } catch (RemoteException ignored) {
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 69de89d..e384d46 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -76,6 +76,8 @@
     private static final int MSG_ON_CAN_ADD_CALL_CHANGED = 7;
     private static final int MSG_SILENCE_RINGER = 8;
     private static final int MSG_ON_CONNECTION_EVENT = 9;
+    private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10;
+    private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -87,7 +89,9 @@
 
             switch (msg.what) {
                 case MSG_SET_IN_CALL_ADAPTER:
-                    mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
+                    String callingPackage = getApplicationContext().getOpPackageName();
+                    mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,
+                            getApplicationContext().getApplicationInfo().targetSdkVersion);
                     mPhone.addListener(mPhoneListener);
                     onPhoneCreated(mPhone);
                     break;
@@ -132,6 +136,18 @@
                     }
                     break;
                 }
+                case MSG_ON_RTT_UPGRADE_REQUEST: {
+                    String callId = (String) msg.obj;
+                    int requestId = msg.arg1;
+                    mPhone.internalOnRttUpgradeRequest(callId, requestId);
+                    break;
+                }
+                case MSG_ON_RTT_INITIATION_FAILURE: {
+                    String callId = (String) msg.obj;
+                    int reason = msg.arg1;
+                    mPhone.internalOnRttInitiationFailure(callId, reason);
+                    break;
+                }
                 default:
                     break;
             }
@@ -197,6 +213,16 @@
             args.arg3 = extras;
             mHandler.obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
         }
+
+        @Override
+        public void onRttUpgradeRequest(String callId, int id) {
+            mHandler.obtainMessage(MSG_ON_RTT_UPGRADE_REQUEST, id, 0, callId).sendToTarget();
+        }
+
+        @Override
+        public void onRttInitiationFailure(String callId, int reason) {
+            mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, callId).sendToTarget();
+        }
     }
 
     private Phone.Listener mPhoneListener = new Phone.Listener() {
@@ -664,7 +690,8 @@
              *      {@link Connection.VideoProvider#SESSION_EVENT_TX_START},
              *      {@link Connection.VideoProvider#SESSION_EVENT_TX_STOP},
              *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
-             *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY}.
+             *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY},
+             *      {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_PERMISSION_ERROR}.
              */
             public abstract void onCallSessionEvent(int event);
 
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index ced6627..6107895 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.telecom.Logging.EventManager;
 import android.telecom.Logging.Session;
 import android.telecom.Logging.SessionManager;
@@ -55,6 +56,7 @@
     public static boolean ERROR = isLoggable(android.util.Log.ERROR);
 
     private static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
+    private static final boolean USER_BUILD = Build.TYPE.equals("user");
 
     // Used to synchronize singleton logging lazy initialization
     private static final Object sSingletonSync = new Object();
@@ -404,7 +406,8 @@
 
     /**
      * Redact personally identifiable information for production users.
-     * If we are running in verbose mode, return the original string, otherwise
+     * If we are running in verbose mode, return the original string,
+     * and return "****" if we are running on the user build, otherwise
      * return a SHA-1 hash of the input string.
      */
     public static String pii(Object pii) {
@@ -415,6 +418,11 @@
     }
 
     private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (USER_BUILD) {
+            return "****";
+        }
+
         if (sMessageDigest != null) {
             sMessageDigest.reset();
             sMessageDigest.update(input);
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index f7a6595..85a92d1 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -50,6 +50,8 @@
     private final boolean mIsVideoCallProviderChanged;
     private final IVideoProvider mVideoCallProvider;
     private VideoCallImpl mVideoCall;
+    private final boolean mIsRttCallChanged;
+    private final ParcelableRttCall mRttCall;
     private final String mParentCallId;
     private final List<String> mChildCallIds;
     private final StatusHints mStatusHints;
@@ -75,6 +77,8 @@
             PhoneAccountHandle accountHandle,
             boolean isVideoCallProviderChanged,
             IVideoProvider videoCallProvider,
+            boolean isRttCallChanged,
+            ParcelableRttCall rttCall,
             String parentCallId,
             List<String> childCallIds,
             StatusHints statusHints,
@@ -98,6 +102,8 @@
         mAccountHandle = accountHandle;
         mIsVideoCallProviderChanged = isVideoCallProviderChanged;
         mVideoCallProvider = videoCallProvider;
+        mIsRttCallChanged = isRttCallChanged;
+        mRttCall = rttCall;
         mParentCallId = parentCallId;
         mChildCallIds = childCallIds;
         mStatusHints = statusHints;
@@ -187,13 +193,16 @@
 
     /**
      * Returns an object for remotely communicating through the video call provider's binder.
-
+     *
+     * @param callingPackageName the package name of the calling InCallService.
+     * @param targetSdkVersion the target SDK version of the calling InCallService.
      * @return The video call.
      */
-    public VideoCallImpl getVideoCallImpl() {
+    public VideoCallImpl getVideoCallImpl(String callingPackageName, int targetSdkVersion) {
         if (mVideoCall == null && mVideoCallProvider != null) {
             try {
-                mVideoCall = new VideoCallImpl(mVideoCallProvider);
+                mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName,
+                        targetSdkVersion);
             } catch (RemoteException ignored) {
                 // Ignore RemoteException.
             }
@@ -202,6 +211,18 @@
         return mVideoCall;
     }
 
+    public boolean getIsRttCallChanged() {
+        return mIsRttCallChanged;
+    }
+
+    /**
+     * RTT communication channel information
+     * @return The ParcelableRttCall
+     */
+    public ParcelableRttCall getParcelableRttCall() {
+        return mRttCall;
+    }
+
     /**
      * The conference call to which this call is conferenced. Null if not conferenced.
      */
@@ -301,6 +322,8 @@
             Bundle intentExtras = source.readBundle(classLoader);
             Bundle extras = source.readBundle(classLoader);
             int supportedAudioRoutes = source.readInt();
+            boolean isRttCallChanged = source.readByte() == 1;
+            ParcelableRttCall rttCall = source.readParcelable(classLoader);
             return new ParcelableCall(
                     id,
                     state,
@@ -318,6 +341,8 @@
                     accountHandle,
                     isVideoCallProviderChanged,
                     videoCallProvider,
+                    isRttCallChanged,
+                    rttCall,
                     parentCallId,
                     childCallIds,
                     statusHints,
@@ -366,6 +391,8 @@
         destination.writeBundle(mIntentExtras);
         destination.writeBundle(mExtras);
         destination.writeInt(mSupportedAudioRoutes);
+        destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0));
+        destination.writeParcelable(mRttCall, 0);
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/telecomm/java/android/telecom/ParcelableRttCall.aidl
similarity index 81%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to telecomm/java/android/telecom/ParcelableRttCall.aidl
index 62d5603..4480710 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/telecomm/java/android/telecom/ParcelableRttCall.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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.
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.telecom;
 
-parcelable HomeSP;
+/**
+ * {@hide}
+ */
+parcelable ParcelableRttCall;
diff --git a/telecomm/java/android/telecom/ParcelableRttCall.java b/telecomm/java/android/telecom/ParcelableRttCall.java
new file mode 100644
index 0000000..763e48b
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableRttCall.java
@@ -0,0 +1,88 @@
+/*
+ * 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.telecom;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Data container for information associated with the RTT connection on a call.
+ * @hide
+ */
+public class ParcelableRttCall implements Parcelable {
+    private final int mRttMode;
+    private final ParcelFileDescriptor mTransmitStream;
+    private final ParcelFileDescriptor mReceiveStream;
+
+    public ParcelableRttCall(
+            int rttMode,
+            ParcelFileDescriptor transmitStream,
+            ParcelFileDescriptor receiveStream) {
+        mRttMode = rttMode;
+        mTransmitStream = transmitStream;
+        mReceiveStream = receiveStream;
+    }
+
+    protected ParcelableRttCall(Parcel in) {
+        mRttMode = in.readInt();
+        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+    }
+
+    public static final Creator<ParcelableRttCall> CREATOR = new Creator<ParcelableRttCall>() {
+        @Override
+        public ParcelableRttCall createFromParcel(Parcel in) {
+            return new ParcelableRttCall(in);
+        }
+
+        @Override
+        public ParcelableRttCall[] newArray(int size) {
+            return new ParcelableRttCall[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRttMode);
+        dest.writeParcelable(mTransmitStream, flags);
+        dest.writeParcelable(mReceiveStream, flags);
+    }
+
+    public int getRttMode() {
+        return mRttMode;
+    }
+
+    public ParcelFileDescriptor getReceiveStream() {
+        return mReceiveStream;
+    }
+
+    public ParcelFileDescriptor getTransmitStream() {
+        return mTransmitStream;
+    }
+}
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index a4ef560..066f6c2 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -125,13 +125,22 @@
 
     private boolean mCanAddCall = true;
 
-    Phone(InCallAdapter adapter) {
+    private final String mCallingPackage;
+
+    /**
+     * The Target SDK version of the InCallService implementation.
+     */
+    private final int mTargetSdkVersion;
+
+    Phone(InCallAdapter adapter, String callingPackage, int targetSdkVersion) {
         mInCallAdapter = adapter;
+        mCallingPackage = callingPackage;
+        mTargetSdkVersion = targetSdkVersion;
     }
 
     final void internalAddCall(ParcelableCall parcelableCall) {
         Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
-                parcelableCall.getState());
+                parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
         mCallByTelecomCallId.put(parcelableCall.getId(), call);
         mCalls.add(call);
         checkCallTree(parcelableCall);
@@ -198,6 +207,20 @@
         }
     }
 
+    final void internalOnRttUpgradeRequest(String callId, int requestId) {
+        Call call = mCallByTelecomCallId.get(callId);
+        if (call != null) {
+            call.internalOnRttUpgradeRequest(requestId);
+        }
+    }
+
+    final void internalOnRttInitiationFailure(String callId, int reason) {
+        Call call = mCallByTelecomCallId.get(callId);
+        if (call != null) {
+            call.internalOnRttInitiationFailure(reason);
+        }
+    }
+
     /**
      * Called to destroy the phone and cleanup any lingering calls.
      */
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 845a103..3926e20 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -204,6 +204,18 @@
     public static final int CAPABILITY_SELF_MANAGED = 0x800;
 
     /**
+     * Flag indicating that this {@link PhoneAccount} is capable of making a call with an
+     * RTT (Real-time text) session.
+     * When set, Telecom will attempt to open an RTT session on outgoing calls that specify
+     * that they should be placed with an RTT session , and the in-call app will be displayed
+     * with text entry fields for RTT. Likewise, the in-call app can request that an RTT
+     * session be opened during a call if this bit is set.
+     */
+    public static final int CAPABILITY_RTT = 0x1000;
+
+    /* NEXT CAPABILITY: 0x2000 */
+
+    /**
      * URI scheme for telephone number URIs.
      */
     public static final String SCHEME_TEL = "tel";
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index 4bff688..502b7c0 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -311,6 +311,9 @@
 
     /** @hide */
     void putExtras(final Bundle extras) {
+        if (extras == null) {
+            return;
+        }
         if (mExtras == null) {
             mExtras = new Bundle();
         }
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 11842a0..57fc9ce 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -20,6 +20,7 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.hardware.camera2.CameraManager;
@@ -231,6 +232,41 @@
          * @param extras Extras associated with the event.
          */
         public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {}
+
+        /**
+         * Indicates that a RTT session was successfully established on this
+         * {@link RemoteConnection}. See {@link Connection#sendRttInitiationSuccess()}.
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onRttInitiationSuccess(RemoteConnection connection) {}
+
+        /**
+         * Indicates that a RTT session failed to be established on this
+         * {@link RemoteConnection}. See {@link Connection#sendRttInitiationFailure()}.
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         * @param reason One of the reason codes defined in {@link Connection.RttModifyStatus},
+         *               with the exception of
+         *               {@link Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}.
+         */
+        public void onRttInitiationFailure(RemoteConnection connection, int reason) {}
+
+        /**
+         * Indicates that an established RTT session was terminated remotely on this
+         * {@link RemoteConnection}. See {@link Connection#sendRttSessionRemotelyTerminated()}
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onRttSessionRemotelyTerminated(RemoteConnection connection) {}
+
+        /**
+         * Indicates that the remote user on this {@link RemoteConnection} has requested an upgrade
+         * to an RTT session. See {@link Connection#sendRemoteRttRequest()}
+         * @hide
+         * @param connection The {@code RemoteConnection} invoking this method.
+         */
+        public void onRemoteRttRequest(RemoteConnection connection) {}
     }
 
     /**
@@ -408,6 +444,10 @@
 
         private final IVideoProvider mVideoProviderBinder;
 
+        private final String mCallingPackage;
+
+        private final int mTargetSdkVersion;
+
         /**
          * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
          * load factor before resizing, 1 means we only expect a single thread to
@@ -416,8 +456,12 @@
         private final Set<Callback> mCallbacks = Collections.newSetFromMap(
                 new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
 
-        VideoProvider(IVideoProvider videoProviderBinder) {
+        VideoProvider(IVideoProvider videoProviderBinder, String callingPackage,
+                      int targetSdkVersion) {
+
             mVideoProviderBinder = videoProviderBinder;
+            mCallingPackage = callingPackage;
+            mTargetSdkVersion = targetSdkVersion;
             try {
                 mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
             } catch (RemoteException e) {
@@ -452,7 +496,7 @@
          */
         public void setCamera(String cameraId) {
             try {
-                mVideoProviderBinder.setCamera(cameraId);
+                mVideoProviderBinder.setCamera(cameraId, mCallingPackage, mTargetSdkVersion);
             } catch (RemoteException e) {
             }
         }
@@ -628,7 +672,7 @@
      * @hide
      */
     RemoteConnection(String callId, IConnectionService connectionService,
-            ParcelableConnection connection) {
+            ParcelableConnection connection, String callingPackage, int targetSdkVersion) {
         mConnectionId = callId;
         mConnectionService = connectionService;
         mConnected = true;
@@ -640,7 +684,8 @@
         mVideoState = connection.getVideoState();
         IVideoProvider videoProvider = connection.getVideoProvider();
         if (videoProvider != null) {
-            mVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
+            mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage,
+                    targetSdkVersion);
         } else {
             mVideoProvider = null;
         }
@@ -651,6 +696,14 @@
         mCallerDisplayName = connection.getCallerDisplayName();
         mCallerDisplayNamePresentation = connection.getCallerDisplayNamePresentation();
         mConference = null;
+        putExtras(connection.getExtras());
+
+        // Stash the original connection ID as it exists in the source ConnectionService.
+        // Telecom will use this to avoid adding duplicates later.
+        // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
+        Bundle newExtras = new Bundle();
+        newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+        putExtras(newExtras);
     }
 
     /**
@@ -1035,6 +1088,61 @@
     }
 
     /**
+     * Notifies this {@link RemoteConnection} that the user has requested an RTT session.
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     * @hide
+     */
+    public void startRtt(@NonNull Connection.RttTextStream rttTextStream) {
+        try {
+            if (mConnected) {
+                mConnectionService.startRtt(mConnectionId, rttTextStream.getFdFromInCall(),
+                        rttTextStream.getFdToInCall(), null /*Session.Info*/);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Notifies this {@link RemoteConnection} that it should terminate any existing RTT
+     * session. No response to Telecom is needed for this method.
+     * @hide
+     */
+    public void stopRtt() {
+        try {
+            if (mConnected) {
+                mConnectionService.stopRtt(mConnectionId, null /*Session.Info*/);
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
+     * Notifies this {@link RemoteConnection} of a response to a previous remotely-initiated RTT
+     * upgrade request sent via {@link Connection#sendRemoteRttRequest}.
+     * Acceptance of the request is indicated by the supplied {@link RttTextStream} being non-null,
+     * and rejection is indicated by {@code rttTextStream} being {@code null}
+     * @hide
+     * @param rttTextStream The object that should be used to send text to or receive text from
+     *                      the in-call app.
+     */
+    public void sendRttUpgradeResponse(@Nullable Connection.RttTextStream rttTextStream) {
+        try {
+            if (mConnected) {
+                if (rttTextStream == null) {
+                    mConnectionService.respondToRttUpgradeRequest(mConnectionId,
+                            null, null, null /*Session.Info*/);
+                } else {
+                    mConnectionService.respondToRttUpgradeRequest(mConnectionId,
+                            rttTextStream.getFdFromInCall(), rttTextStream.getFdToInCall(),
+                            null /*Session.Info*/);
+                }
+            }
+        } catch (RemoteException ignored) {
+        }
+    }
+
+    /**
      * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be
      * successfully asked to create a conference with.
      *
@@ -1350,6 +1458,9 @@
 
     /** @hide */
     void putExtras(final Bundle extras) {
+        if (extras == null) {
+            return;
+        }
         if (mExtras == null) {
             mExtras = new Bundle();
         }
@@ -1397,6 +1508,47 @@
         }
     }
 
+    /** @hide */
+    void onRttInitiationSuccess() {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRttInitiationSuccess(connection));
+        }
+    }
+
+    /** @hide */
+    void onRttInitiationFailure(int reason) {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRttInitiationFailure(connection, reason));
+        }
+    }
+
+    /** @hide */
+    void onRttSessionRemotelyTerminated() {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRttSessionRemotelyTerminated(connection));
+        }
+    }
+
+    /** @hide */
+    void onRemoteRttRequest() {
+        for (CallbackRecord record : mCallbackRecords) {
+            final RemoteConnection connection = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(
+                    () -> callback.onRemoteRttRequest(connection));
+        }
+    }
+
+    /**
     /**
      * Create a RemoteConnection represents a failure, and which will be in
      * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index fe14003..06cdd1a 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -219,18 +219,27 @@
                     conference.addConnection(c);
                 }
             }
-
             if (conference.getConnections().size() == 0) {
                 // A conference was created, but none of its connections are ones that have been
                 // created by, and therefore being tracked by, this remote connection service. It
                 // is of no interest to us.
+                Log.d(this, "addConferenceCall - skipping");
                 return;
             }
 
             conference.setState(parcel.getState());
             conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
             conference.setConnectionProperties(parcel.getConnectionProperties());
+            conference.putExtras(parcel.getExtras());
             mConferenceById.put(callId, conference);
+
+            // Stash the original connection ID as it exists in the source ConnectionService.
+            // Telecom will use this to avoid adding duplicates later.
+            // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
+            Bundle newExtras = new Bundle();
+            newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+            conference.putExtras(newExtras);
+
             conference.registerCallback(new RemoteConference.Callback() {
                 @Override
                 public void onDestroyed(RemoteConference c) {
@@ -274,9 +283,14 @@
         @Override
         public void setVideoProvider(String callId, IVideoProvider videoProvider,
                 Session.Info sessionInfo) {
+
+            String callingPackage = mOurConnectionServiceImpl.getApplicationContext()
+                    .getOpPackageName();
+            int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion;
             RemoteConnection.VideoProvider remoteVideoProvider = null;
             if (videoProvider != null) {
-                remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
+                remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider,
+                        callingPackage, targetSdkVersion);
             }
             findConnectionForAction(callId, "setVideoProvider")
                     .setVideoProvider(remoteVideoProvider);
@@ -342,11 +356,22 @@
         @Override
         public void addExistingConnection(String callId, ParcelableConnection connection,
                 Session.Info sessionInfo) {
-            // TODO: add contents of this method
-            RemoteConnection remoteConnction = new RemoteConnection(callId,
-                    mOutgoingConnectionServiceRpc, connection);
-
-            mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnction);
+            String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
+                    getOpPackageName();
+            int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
+                    .targetSdkVersion;
+            RemoteConnection remoteConnection = new RemoteConnection(callId,
+                    mOutgoingConnectionServiceRpc, connection, callingPackage,
+                    callingTargetSdkVersion);
+            mConnectionById.put(callId, remoteConnection);
+            remoteConnection.registerCallback(new RemoteConnection.Callback() {
+                @Override
+                public void onDestroyed(RemoteConnection connection) {
+                    mConnectionById.remove(callId);
+                    maybeDisconnectAdapter();
+                }
+            });
+            mOurConnectionServiceImpl.addRemoteExistingConnection(remoteConnection);
         }
 
         @Override
@@ -384,6 +409,50 @@
                         extras);
             }
         }
+
+        @Override
+        public void onRttInitiationSuccess(String callId, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRttInitiationSuccess")
+                        .onRttInitiationSuccess();
+            } else {
+                Log.w(this, "onRttInitiationSuccess called on a remote conference");
+            }
+        }
+
+        @Override
+        public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRttInitiationFailure")
+                        .onRttInitiationFailure(reason);
+            } else {
+                Log.w(this, "onRttInitiationFailure called on a remote conference");
+            }
+        }
+
+        @Override
+        public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRttSessionRemotelyTerminated")
+                        .onRttSessionRemotelyTerminated();
+            } else {
+                Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference");
+            }
+        }
+
+        @Override
+        public void onRemoteRttRequest(String callId, Session.Info sessionInfo)
+                throws RemoteException {
+            if (hasConnection(callId)) {
+                findConnectionForAction(callId, "onRemoteRttRequest")
+                        .onRemoteRttRequest();
+            } else {
+                Log.w(this, "onRemoteRttRequest called on a remote conference");
+            }
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =
@@ -429,11 +498,14 @@
             ConnectionRequest request,
             boolean isIncoming) {
         final String id = UUID.randomUUID().toString();
-        final ConnectionRequest newRequest = new ConnectionRequest(
-                request.getAccountHandle(),
-                request.getAddress(),
-                request.getExtras(),
-                request.getVideoState());
+        final ConnectionRequest newRequest = new ConnectionRequest.Builder()
+                .setAccountHandle(request.getAccountHandle())
+                .setAddress(request.getAddress())
+                .setExtras(request.getExtras())
+                .setVideoState(request.getVideoState())
+                .setRttPipeFromInCall(request.getRttPipeFromInCall())
+                .setRttPipeToInCall(request.getRttPipeToInCall())
+                .build();
         try {
             if (mConnectionById.isEmpty()) {
                 mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 00e8f9f..e21b4db 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -58,12 +58,15 @@
      * Input: get*Extra field {@link #EXTRA_PHONE_ACCOUNT_HANDLE} contains the component name of the
      * {@link android.telecom.ConnectionService} that Telecom should bind to. Telecom will then
      * ask the connection service for more information about the call prior to showing any UI.
+     *
+     * @deprecated Use {@link #addNewIncomingCall} instead.
      */
     public static final String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
 
     /**
      * Similar to {@link #ACTION_INCOMING_CALL}, but is used only by Telephony to add a new
      * sim-initiated MO call for carrier testing.
+     * @deprecated Use {@link #addNewUnknownCall} instead.
      * @hide
      */
     public static final String ACTION_NEW_UNKNOWN_CALL = "android.telecom.action.NEW_UNKNOWN_CALL";
@@ -314,6 +317,23 @@
     public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
 
     /**
+     * The number of milliseconds that Telecom should wait after disconnecting a call via the
+     * ACTION_NEW_OUTGOING_CALL broadcast, in order to wait for the app which cancelled the call
+     * to make a new one.
+     * @hide
+     */
+    public static final String EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT =
+            "android.telecom.extra.NEW_OUTGOING_CALL_CANCEL_TIMEOUT";
+
+    /**
+     * A boolean extra, which when set on the {@link Intent#ACTION_CALL} intent or on the bundle
+     * passed into {@link #placeCall(Uri, Bundle)}, indicates that the call should be initiated with
+     * an RTT session open. See {@link android.telecom.Call.RttCall} for more information on RTT.
+     */
+    public static final String EXTRA_START_CALL_WITH_RTT =
+            "android.telecom.extra.START_CALL_WITH_RTT";
+
+    /**
      * A boolean meta-data value indicating whether an {@link InCallService} implements an
      * in-call user interface. Dialer implementations (see {@link #getDefaultDialerPackage()}) which
      * would also like to replace the in-call interface should set this meta-data to {@code true} in
@@ -353,6 +373,24 @@
             "android.telecom.INCLUDE_EXTERNAL_CALLS";
 
     /**
+     * A boolean meta-data value indicating whether an {@link InCallService} wants to be informed of
+     * calls which have the {@link Call.Details#PROPERTY_SELF_MANAGED} property.  A self-managed
+     * call is one which originates from a self-managed {@link ConnectionService} which has chosen
+     * to implement its own call user interface.  An {@link InCallService} implementation which
+     * would like to be informed of external calls should set this meta-data to {@code true} in the
+     * manifest registration of their {@link InCallService}.  By default, the {@link InCallService}
+     * will NOT be informed about self-managed calls.
+     * <p>
+     * An {@link InCallService} which receives self-managed calls is free to view and control the
+     * state of calls in the self-managed {@link ConnectionService}.  An example use-case is
+     * exposing these calls to a wearable or automotive device via its companion app.
+     * <p>
+     * See also {@link Connection#PROPERTY_SELF_MANAGED}.
+     */
+    public static final String METADATA_INCLUDE_SELF_MANAGED_CALLS =
+            "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
+
+    /**
      * The dual tone multi-frequency signaling character sent to indicate the dialing system should
      * pause for a predefined period.
      */
@@ -1031,10 +1069,12 @@
 
     /**
      * Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding
-     * states).
+     * states) originating from either a manager or self-managed {@link ConnectionService}.
      * <p>
      * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
-     * </p>
+     *
+     * @return {@code true} if there is an ongoing call in either a managed or self-managed
+     *      {@link ConnectionService}, {@code false} otherwise.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public boolean isInCall() {
@@ -1049,6 +1089,31 @@
     }
 
     /**
+     * Returns whether there is an ongoing call originating from a managed
+     * {@link ConnectionService}.  An ongoing call can be in dialing, ringing, active or holding
+     * states.
+     * <p>
+     * If you also need to know if there are ongoing self-managed calls, use {@link #isInCall()}
+     * instead.
+     * <p>
+     * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
+     *
+     * @return {@code true} if there is an ongoing call in a managed {@link ConnectionService},
+     *      {@code false} otherwise.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    public boolean isInManagedCall() {
+        try {
+            if (isServiceConnected()) {
+                return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException calling isInManagedCall().", e);
+        }
+        return false;
+    }
+
+    /**
      * Returns one of the following constants that represents the current state of Telecom:
      *
      * {@link TelephonyManager#CALL_STATE_RINGING}
@@ -1059,6 +1124,9 @@
      * {@link android.Manifest.permission#READ_PHONE_STATE} permission. This is intentional, to
      * preserve the behavior of {@link TelephonyManager#getCallState()}, which also did not require
      * the permission.
+     *
+     * Takes into consideration both managed and self-managed calls.
+     *
      * @hide
      */
     @SystemApi
@@ -1076,6 +1144,7 @@
     /**
      * Returns whether there currently exists is a ringing incoming-call.
      *
+     * @return {@code true} if there is a managed or self-managed ringing call.
      * @hide
      */
     @SystemApi
@@ -1514,6 +1583,10 @@
      *      otherwise.
      */
     public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+        if (phoneAccountHandle == null) {
+            return false;
+        }
+
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index e54abee..429a434 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -43,6 +43,8 @@
     private VideoCall.Callback mCallback;
     private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN;
     private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
+    private final String mCallingPackageName;
+    private final int mTargetSdkVersion;
 
     private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
         @Override
@@ -197,12 +199,15 @@
 
     private Handler mHandler;
 
-    VideoCallImpl(IVideoProvider videoProvider) throws RemoteException {
+    VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion)
+            throws RemoteException {
         mVideoProvider = videoProvider;
         mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
 
         mBinder = new VideoCallListenerBinder();
         mVideoProvider.addVideoCallback(mBinder);
+        mCallingPackageName = callingPackageName;
+        mTargetSdkVersion = targetSdkVersion;
     }
 
     public void destroy() {
@@ -240,7 +245,8 @@
     /** {@inheritDoc} */
     public void setCamera(String cameraId) {
         try {
-            mVideoProvider.setCamera(cameraId);
+            Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName);
+            mVideoProvider.setCamera(cameraId, mCallingPackageName, mTargetSdkVersion);
         } catch (RemoteException e) {
         }
     }
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index 216603c..e0e3a08 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -235,7 +235,7 @@
         StringBuilder sb = new StringBuilder();
         sb.append("Audio");
 
-        if (isAudioOnly(videoState)) {
+        if (videoState == STATE_AUDIO_ONLY) {
             sb.append(" Only");
         } else {
             if (isTransmissionEnabled(videoState)) {
@@ -256,6 +256,9 @@
 
     /**
      * Indicates whether the video state is audio only.
+     * <p>
+     * Note: Considers only whether either both the {@link #STATE_RX_ENABLED} or
+     * {@link #STATE_TX_ENABLED} bits are off, but not {@link #STATE_PAUSED}.
      *
      * @param videoState The video state.
      * @return {@code True} if the video state is audio only, {@code false} otherwise.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 8a27675..c631d08 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.telecom;
 
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.telecom.CallAudioState;
 import android.telecom.ConnectionRequest;
 import android.telecom.Logging.Session;
@@ -46,6 +47,9 @@
             boolean isUnknown,
             in Session.Info sessionInfo);
 
+    void createConnectionFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId,
+            in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo);
+
     void abort(String callId, in Session.Info sessionInfo);
 
     void answerVideo(String callId, int videoState, in Session.Info sessionInfo);
@@ -86,4 +90,12 @@
     void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo);
 
     void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo);
+
+    void startRtt(String callId, in ParcelFileDescriptor fromInCall,
+    in ParcelFileDescriptor toInCall, in Session.Info sessionInfo);
+
+    void stopRtt(String callId, in Session.Info sessionInfo);
+
+    void respondToRttUpgradeRequest(String callId, in ParcelFileDescriptor fromInCall,
+    in ParcelFileDescriptor toInCall, in Session.Info sessionInfo);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index b58f8bc..ac9da2e 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -106,4 +106,12 @@
 
     void onConnectionEvent(String callId, String event, in Bundle extras,
     in Session.Info sessionInfo);
+
+    void onRttInitiationSuccess(String callId, in Session.Info sessionInfo);
+
+    void onRttInitiationFailure(String callId, int reason, in Session.Info sessionInfo);
+
+    void onRttSessionRemotelyTerminated(String callId, in Session.Info sessionInfo);
+
+    void onRemoteRttRequest(String callId, in Session.Info sessionInfo);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 49f9b3b..73fa29a 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -69,4 +69,12 @@
     void putExtras(String callId, in Bundle extras);
 
     void removeExtras(String callId, in List<String> keys);
+
+    void sendRttRequest(String callId);
+
+    void respondToRttRequest(String callId, int id, boolean accept);
+
+    void stopRtt(String callId);
+
+    void setRttMode(String callId, int mode);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index 3e43fe2..e8cf8e9 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -50,4 +50,8 @@
     void silenceRinger();
 
     void onConnectionEvent(String callId, String event, in Bundle extras);
+
+    void onRttUpgradeRequest(String callId, int id);
+
+    void onRttInitiationFailure(String callId, int reason);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 6ca0bc5..c044742 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -165,6 +165,11 @@
     boolean isInCall(String callingPackage);
 
     /**
+     * @see TelecomServiceImpl#isInManagedCall
+     */
+    boolean isInManagedCall(String callingPackage);
+
+    /**
      * @see TelecomServiceImpl#isRinging
      */
     boolean isRinging(String callingPackage);
@@ -259,4 +264,9 @@
      * @see TelecomServiceImpl#isOutgoingCallPermitted
      */
     boolean isOutgoingCallPermitted(in PhoneAccountHandle phoneAccountHandle);
+
+    /**
+     * @see TelecomServiceImpl#waitOnHandler
+     */
+    void waitOnHandlers();
 }
diff --git a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
index 68e5fd4..272b884 100644
--- a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
+++ b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
@@ -30,7 +30,7 @@
 
     void removeVideoCallback(IBinder videoCallbackBinder);
 
-    void setCamera(String cameraId);
+    void setCamera(String cameraId, in String mCallingPackageName, int targetSdkVersion);
 
     void setPreviewSurface(in Surface surface);
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a6da9e9..ce1c3c3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -238,6 +238,12 @@
             KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
 
     /**
+     * Override the device's configuration for the ImsService to use for this SIM card.
+     */
+    public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING =
+            "config_ims_package_override_string";
+
+    /**
      * Override the platform's notion of a network operator being considered roaming.
      * Value is string array of SIDs to be considered roaming for 3GPP2 RATs.
      */
@@ -309,6 +315,14 @@
     public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
 
     /**
+     * Flag indicating whether we should downgrade/terminate VT calls and disable VT when
+     * data enabled changed (e.g. reach data limit or turn off data).
+     * @hide
+     */
+    public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS =
+            "ignore_data_enabled_changed_for_video_calls";
+
+    /**
      * Flag specifying whether WFC over IMS should be available for carrier: independent of
      * carrier provisioning. If false: hard disabled. If true: then depends on carrier
      * provisioning, availability etc.
@@ -810,41 +824,8 @@
      public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
 
     /**
-     * A list of component name of carrier signalling receivers which are interested in intent
-     * android.intent.action.CARRIER_SIGNAL_REDIRECTED.
-     * Example:
-     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
-     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
-     * @hide
-     */
-    public static final String KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY =
-            "signal_redirection_receiver_string_array";
-
-    /**
-     * A list of component name of carrier signalling receivers which are interested in intent
-     * android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED.
-     * Example:
-     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
-     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
-     * @hide
-     */
-    public static final String KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY =
-            "signal_dcfailure_receiver_string_array";
-
-    /**
-     * A list of component name of carrier signalling receivers which are interested in intent
-     * android.intent.action.CARRIER_SIGNAL_PCO_VALUE.
-     * Example:
-     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameA</item>
-     * <item>com.google.android.carrierPackageName/.CarrierSignalReceiverNameB</item>
-     * @hide
-     */
-    public static final String KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY =
-            "signal_pco_receiver_string_array";
-
-    /**
      * Defines carrier-specific actions which act upon
-     * android.intent.action.CARRIER_SIGNAL_REDIRECTED, used for customization of the
+     * com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, used for customization of the
      * default carrier app
      * Format: "CARRIER_ACTION_IDX, ..."
      * Where {@code CARRIER_ACTION_IDX} is an integer defined in
@@ -859,7 +840,7 @@
 
     /**
      * Defines carrier-specific actions which act upon
-     * android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+     * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
      * and configured signal args:
      * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType},
      * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode}
@@ -895,6 +876,42 @@
             "carrier_default_redirection_url_string_array";
 
     /**
+     * Each config includes the componentName of the carrier app, followed by a list of interesting
+     * signals(declared in the manifest) which could wake up the app.
+     * @see com.android.internal.telephony.TelephonyIntents
+     * Example:
+     * <item>com.google.android.carrierAPK/.CarrierSignalReceiverA:
+     * com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED,
+     * com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE
+     * </item>
+     * <item>com.google.android.carrierAPK/.CarrierSignalReceiverB:
+     * com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE
+     * </item>
+     * @hide
+     */
+    public static final String KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY =
+            "carrier_app_wake_signal_config";
+
+    /**
+     * Each config includes the componentName of the carrier app, followed by a list of interesting
+     * signals for the app during run-time. The list of signals(intents) are targeting on run-time
+     * broadcast receivers only, aiming to avoid unnecessary wake-ups and should not be declared in
+     * the app's manifest.
+     * @see com.android.internal.telephony.TelephonyIntents
+     * Example:
+     * <item>com.google.android.carrierAPK/.CarrierSignalReceiverA:
+     * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
+     * com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE
+     * </item>
+     * <item>com.google.android.carrierAPK/.CarrierSignalReceiverB:
+     * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+     * </item>
+     * @hide
+     */
+    public static final String KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY =
+            "carrier_app_no_wake_signal_config";
+
+    /**
      * Determines whether the carrier supports making non-emergency phone calls while the phone is
      * in emergency callback mode.  Default value is {@code true}, meaning that non-emergency calls
      * are allowed in emergency callback mode.
@@ -1055,6 +1072,9 @@
      * and {@code NEW_CODE} is the new {@code ImsReasonInfo#CODE_*} which this combination of
      * original code and message shall be remapped to.
      *
+     * Note: If {@code *} is specified for the original code, any ImsReasonInfo with the matching
+     * {@code MESSAGE} will be remapped to {@code NEW_CODE}.
+     *
      * Example: "501|call completion elsewhere|1014"
      * When the {@link ImsReasonInfo#getCode()} is {@link ImsReasonInfo#CODE_USER_TERMINATED} and
      * the {@link ImsReasonInfo#getExtraMessage()} is {@code "call completion elsewhere"},
@@ -1107,6 +1127,38 @@
     public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL =
             "editable_wfc_roaming_mode_bool";
 
+    /**
+     * Indicates whether the carrier supports 3gpp call forwarding MMI codes while roaming. If
+     * false, the user will be notified that call forwarding is not available when the MMI code
+     * fails.
+     */
+    public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL =
+            "support_3gpp_call_forwarding_while_roaming_bool";
+
+    /**
+     * When {@code true}, the user will be notified when they attempt to place an international call
+     * when the call is placed using wifi calling.
+     * @hide
+     */
+    public static final String KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL =
+            "notify_international_call_on_wfc_bool";
+
+    /**
+     * Offset to be reduced from rsrp threshold while calculating signal strength level.
+     * @hide
+     */
+    public static final String KEY_LTE_EARFCNS_RSRP_BOOST_INT = "lte_earfcns_rsrp_boost_int";
+
+    /**
+     * List of EARFCN (E-UTRA Absolute Radio Frequency Channel Number,
+     * Reference: 3GPP TS 36.104 5.4.3) inclusive ranges on which lte_rsrp_boost_int
+     * will be applied. Format of the String array is expected to be {"erafcn1_start-earfcn1_end",
+     * "earfcn2_start-earfcn2_end" ... }
+     * @hide
+     */
+    public static final String KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY =
+            "boosted_lte_earfcns_string_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1124,6 +1176,7 @@
         sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
         sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
+        sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, false);
         sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
@@ -1212,6 +1265,7 @@
                 });
         sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null);
+        sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
         sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null);
@@ -1276,10 +1330,13 @@
         sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
 
         // Carrier Signalling Receivers
-        sDefaults.putStringArray(KEY_SIGNAL_REDIRECTION_RECEIVER_STRING_ARRAY, null);
-        sDefaults.putStringArray(KEY_SIGNAL_DCFAILURE_RECEIVER_STRING_ARRAY, null);
-        sDefaults.putStringArray(KEY_SIGNAL_PCO_RECEIVER_STRING_ARRAY, null);
         sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, "");
+        sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
+                new String[]{
+                        "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" +
+                                "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED"
+                });
+        sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
 
         // Default carrier app configurations
         sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY,
@@ -1307,6 +1364,10 @@
         sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false);
         sDefaults.putStringArray(KEY_FILTERED_CNAP_NAMES_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false);
+        sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+        sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
+        sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
+        sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ClientRequestStats.aidl b/telephony/java/android/telephony/ClientRequestStats.aidl
new file mode 100644
index 0000000..206ee70
--- /dev/null
+++ b/telephony/java/android/telephony/ClientRequestStats.aidl
@@ -0,0 +1,22 @@
+/*
+** 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;
+
+/**
+ * @hide
+ */
+parcelable  ClientRequestStats;
diff --git a/telephony/java/android/telephony/ClientRequestStats.java b/telephony/java/android/telephony/ClientRequestStats.java
new file mode 100644
index 0000000..381c847
--- /dev/null
+++ b/telephony/java/android/telephony/ClientRequestStats.java
@@ -0,0 +1,175 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.TelephonyHistogram;
+import android.util.SparseArray;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parcelable class to store Client request statistics information.
+ *
+ * @hide
+ */
+public final class ClientRequestStats implements Parcelable {
+    public static final Parcelable.Creator<ClientRequestStats> CREATOR =
+            new Parcelable.Creator<ClientRequestStats>() {
+
+                public ClientRequestStats createFromParcel(Parcel in) {
+                    return new ClientRequestStats(in);
+                }
+
+                public ClientRequestStats[] newArray(int size) {
+                    return new ClientRequestStats[size];
+                }
+            };
+    private static final int REQUEST_HISTOGRAM_BUCKET_COUNT = 5;
+    private String mCallingPackage;
+    /* completed requests wake lock time in milli seconds */
+    private long mCompletedRequestsWakelockTime = 0;
+    private long mCompletedRequestsCount = 0;
+    private long mPendingRequestsWakelockTime = 0;
+    private long mPendingRequestsCount = 0;
+    private SparseArray<TelephonyHistogram> mRequestHistograms =
+            new SparseArray<TelephonyHistogram>();
+
+    public ClientRequestStats(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public ClientRequestStats() {
+    }
+
+    public ClientRequestStats(ClientRequestStats clientRequestStats) {
+        mCallingPackage = clientRequestStats.getCallingPackage();
+        mCompletedRequestsCount = clientRequestStats.getCompletedRequestsCount();
+        mCompletedRequestsWakelockTime = clientRequestStats.getCompletedRequestsWakelockTime();
+        mPendingRequestsCount = clientRequestStats.getPendingRequestsCount();
+        mPendingRequestsWakelockTime = clientRequestStats.getPendingRequestsWakelockTime();
+
+        List<TelephonyHistogram> list = clientRequestStats.getRequestHistograms();
+        for (TelephonyHistogram entry : list) {
+            mRequestHistograms.put(entry.getId(), entry);
+        }
+    }
+
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    public void setCallingPackage(String mCallingPackage) {
+        this.mCallingPackage = mCallingPackage;
+    }
+
+    public long getCompletedRequestsWakelockTime() {
+        return mCompletedRequestsWakelockTime;
+    }
+
+    public void addCompletedWakelockTime(long completedRequestsWakelockTime) {
+        this.mCompletedRequestsWakelockTime += completedRequestsWakelockTime;
+    }
+
+    public long getPendingRequestsWakelockTime() {
+        return mPendingRequestsWakelockTime;
+    }
+
+    public void setPendingRequestsWakelockTime(long pendingRequestsWakelockTime) {
+        this.mPendingRequestsWakelockTime = pendingRequestsWakelockTime;
+    }
+
+    public long getCompletedRequestsCount() {
+        return mCompletedRequestsCount;
+    }
+
+    public void incrementCompletedRequestsCount() {
+        this.mCompletedRequestsCount++;
+    }
+
+    public long getPendingRequestsCount() {
+        return mPendingRequestsCount;
+    }
+
+    public void setPendingRequestsCount(long pendingRequestsCount) {
+        this.mPendingRequestsCount = pendingRequestsCount;
+    }
+
+    public List<TelephonyHistogram> getRequestHistograms() {
+        List<TelephonyHistogram> list;
+        synchronized (mRequestHistograms) {
+            list = new ArrayList<>(mRequestHistograms.size());
+            for (int i = 0; i < mRequestHistograms.size(); i++) {
+                TelephonyHistogram entry = new TelephonyHistogram(mRequestHistograms.valueAt(i));
+                list.add(entry);
+            }
+        }
+        return list;
+    }
+
+    public void updateRequestHistograms(int requestId, int time) {
+        synchronized (mRequestHistograms) {
+            TelephonyHistogram entry = mRequestHistograms.get(requestId);
+            if (entry == null) {
+                entry = new TelephonyHistogram(TelephonyHistogram.TELEPHONY_CATEGORY_RIL,
+                        requestId, REQUEST_HISTOGRAM_BUCKET_COUNT);
+                mRequestHistograms.put(requestId, entry);
+            }
+            entry.addTimeTaken(time);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ClientRequestStats{" +
+                "mCallingPackage='" + mCallingPackage + '\'' +
+                ", mCompletedRequestsWakelockTime=" + mCompletedRequestsWakelockTime +
+                ", mCompletedRequestsCount=" + mCompletedRequestsCount +
+                ", mPendingRequestsWakelockTime=" + mPendingRequestsWakelockTime +
+                ", mPendingRequestsCount=" + mPendingRequestsCount +
+                '}';
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public void readFromParcel(Parcel in) {
+        mCallingPackage = in.readString();
+        mCompletedRequestsWakelockTime = in.readLong();
+        mCompletedRequestsCount = in.readLong();
+        mPendingRequestsWakelockTime = in.readLong();
+        mPendingRequestsCount = in.readLong();
+        ArrayList<TelephonyHistogram> requestHistograms = new ArrayList<TelephonyHistogram>();
+        in.readTypedList(requestHistograms, TelephonyHistogram.CREATOR);
+        for (TelephonyHistogram h : requestHistograms) {
+            mRequestHistograms.put(h.getId(), h);
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mCallingPackage);
+        dest.writeLong(mCompletedRequestsWakelockTime);
+        dest.writeLong(mCompletedRequestsCount);
+        dest.writeLong(mPendingRequestsWakelockTime);
+        dest.writeLong(mPendingRequestsCount);
+        dest.writeTypedList(getRequestHistograms());
+    }
+}
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 811c996..6a081d0 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -240,23 +240,19 @@
      */
     public static final int IMEI_NOT_ACCEPTED = 57;
 
+    /**
+     * A call over WIFI was disconnected because the WIFI signal was lost or became too degraded to
+     * continue the call.
+     */
+    public static final int WIFI_LOST = 59;
+
     //*********************************************************************************************
     // When adding a disconnect type:
-    // 1) Please assign the new type the next id value below.
-    // 2) Increment the next id value below to a new value.
-    // 3) Update MAXIMUM_VALID_VALUE to the new disconnect type.
-    // 4) Update toString() with the newly added disconnect type.
-    // 5) Update android.telecom.DisconnectCauseUtil with any mappings to a telecom.DisconnectCause.
+    // 1) Update toString() with the newly added disconnect type.
+    // 2) Update android.telecom.DisconnectCauseUtil with any mappings to a telecom.DisconnectCause.
     //
-    // NextId: 58
     //*********************************************************************************************
 
-    /** Smallest valid value for call disconnect codes. */
-    public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
-
-    /** Largest valid value for call disconnect codes. */
-    public static final int MAXIMUM_VALID_VALUE = IMEI_NOT_ACCEPTED;
-
     /** Private constructor to avoid class instantiation. */
     private DisconnectCause() {
         // Do nothing.
@@ -379,6 +375,8 @@
             return "DIALED_ON_WRONG_SLOT";
         case IMEI_NOT_ACCEPTED:
             return "IMEI_NOT_ACCEPTED";
+        case WIFI_LOST:
+            return "WIFI_LOST";
         default:
             return "INVALID: " + cause;
         }
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 152b868..bbd4018 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1139,6 +1139,8 @@
 
     private static final String KOREA_ISO_COUNTRY_CODE = "KR";
 
+    private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
+
     /**
      * Breaks the given number down and formats it according to the rules
      * for the country the number is from.
@@ -1437,6 +1439,30 @@
     }
 
     /**
+     * Determines if a {@param phoneNumber} is international if dialed from
+     * {@param defaultCountryIso}.
+     *
+     * @param phoneNumber The phone number.
+     * @param defaultCountryIso The current country ISO.
+     * @return {@code true} if the number is international, {@code false} otherwise.
+     * @hide
+     */
+    public static boolean isInternationalNumber(String phoneNumber, String defaultCountryIso) {
+        // If it starts with # or * its not international.
+        if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
+            return false;
+        }
+
+        PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+        try {
+            PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
+            return pn.getCountryCode() != util.getCountryCodeForRegion(defaultCountryIso);
+        } catch (NumberParseException e) {
+            return false;
+        }
+    }
+
+    /**
      * Format a phone number.
      * <p>
      * If the given number doesn't have the country code, the phone will be
@@ -1459,15 +1485,25 @@
         String result = null;
         try {
             PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
-            /**
-             * Need to reformat any local Korean phone numbers (when the user is in Korea) with
-             * country code to corresponding national format which would replace the leading
-             * +82 with 0.
-             */
-            if (KOREA_ISO_COUNTRY_CODE.equals(defaultCountryIso) &&
+
+            if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
                     (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) &&
                     (pn.getCountryCodeSource() ==
                             PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) {
+                /**
+                 * Need to reformat any local Korean phone numbers (when the user is in Korea) with
+                 * country code to corresponding national format which would replace the leading
+                 * +82 with 0.
+                 */
+                result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
+            } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) &&
+                    pn.getCountryCode() == util.getCountryCodeForRegion(JAPAN_ISO_COUNTRY_CODE) &&
+                    (pn.getCountryCodeSource() ==
+                            PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) {
+                /**
+                 * Need to reformat Japanese phone numbers (when user is in Japan) with the national
+                 * dialing format.
+                 */
                 result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL);
             } else {
                 result = util.formatInOriginalFormat(pn, defaultCountryIso);
@@ -1583,7 +1619,7 @@
     //
     // Australia: Short codes are six or eight digits in length, starting with the prefix "19"
     //            followed by an additional four or six digits and two.
-    // Czech Republic: Codes are seven digits in length for MO and five (not billed) or
+    // Czechia: Codes are seven digits in length for MO and five (not billed) or
     //            eight (billed) for MT direction
     //
     // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 32f487b..afff6d5 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -20,17 +20,9 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.telephony.SubscriptionManager;
-import android.telephony.CellLocation;
-import android.telephony.CellInfo;
-import android.telephony.VoLteServiceState;
-import android.telephony.Rlog;
-import android.telephony.ServiceState;
-import android.telephony.SignalStrength;
-import android.telephony.PreciseCallState;
-import android.telephony.PreciseDataConnectionState;
 
 import com.android.internal.telephony.IPhoneStateListener;
+
 import java.util.List;
 import java.lang.ref.WeakReference;
 
@@ -216,7 +208,9 @@
      *
      * @see #onOemHookRawEvent
      * @hide
+     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
      */
+    @Deprecated
     public static final int LISTEN_OEM_HOOK_RAW_EVENT                       = 0x00008000;
 
     /**
@@ -228,6 +222,38 @@
      */
     public static final int LISTEN_CARRIER_NETWORK_CHANGE                   = 0x00010000;
 
+    /**
+     *  Listen for changes to the sim voice activation state
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
+     *  {@more}
+     *  Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been
+     *  fully activated
+     *
+     *  @see #onVoiceActivationStateChanged
+     *  @hide
+     */
+    public static final int LISTEN_VOICE_ACTIVATION_STATE                   = 0x00020000;
+
+    /**
+     *  Listen for changes to the sim data activation state
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED
+     *  @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN
+     *  {@more}
+     *  Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been
+     *  fully activated
+     *
+     *  @see #onDataActivationStateChanged
+     *  @hide
+     */
+    public static final int LISTEN_DATA_ACTIVATION_STATE                   = 0x00040000;
+
      /*
      * Subscription used to listen to the phone state changes
      * @hide
@@ -327,6 +353,12 @@
                     case LISTEN_VOLTE_STATE:
                         PhoneStateListener.this.onVoLteServiceStateChanged((VoLteServiceState)msg.obj);
                         break;
+                    case LISTEN_VOICE_ACTIVATION_STATE:
+                        PhoneStateListener.this.onVoiceActivationStateChanged((int)msg.obj);
+                        break;
+                    case LISTEN_DATA_ACTIVATION_STATE:
+                        PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
+                        break;
                     case LISTEN_OEM_HOOK_RAW_EVENT:
                         PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
                         break;
@@ -506,6 +538,24 @@
     }
 
     /**
+     * Callback invoked when the SIM voice activation state has changed
+     * @param state is the current SIM voice activation state
+     * @hide
+     */
+    public void onVoiceActivationStateChanged(int state) {
+
+    }
+
+    /**
+     * Callback invoked when the SIM data activation state has changed
+     * @param state is the current SIM data activation state
+     * @hide
+     */
+    public void onDataActivationStateChanged(int state) {
+
+    }
+
+    /**
      * Callback invoked when OEM hook raw event is received. Requires
      * the READ_PRIVILEGED_PHONE_STATE permission.
      * @param rawData is the byte array of the OEM hook raw data.
@@ -619,6 +669,14 @@
             send(LISTEN_VOLTE_STATE, 0, 0, lteState);
         }
 
+        public void onVoiceActivationStateChanged(int activationState) {
+            send(LISTEN_VOICE_ACTIVATION_STATE, 0, 0, activationState);
+        }
+
+        public void onDataActivationStateChanged(int activationState) {
+            send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
+        }
+
         public void onOemHookRawEvent(byte[] rawData) {
             send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
         }
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 54ab19d..2516d51 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -25,75 +25,480 @@
 public class PreciseDisconnectCause {
 
     /** The disconnect cause is not valid (Not received a disconnect cause)*/
-    public static final int NOT_VALID                      = -1;
+    public static final int NOT_VALID                                        = -1;
     /** No disconnect cause provided. Generally a local disconnect or an incoming missed call */
-    public static final int NO_DISCONNECT_CAUSE_AVAILABLE  = 0;
+    public static final int NO_DISCONNECT_CAUSE_AVAILABLE                    = 0;
     /**
      * The destination cannot be reached because the number, although valid,
      * is not currently assigned
      */
-    public static final int UNOBTAINABLE_NUMBER            = 1;
+    public static final int UNOBTAINABLE_NUMBER                              = 1;
+    /** The user cannot be reached because the network through which the call has been
+     *  routed does not serve the destination desired
+     */
+    public static final int NO_ROUTE_TO_DESTINATION                          = 3;
+    /** The channel most recently identified is not acceptable to the sending entity for
+     *  use in this call
+     */
+    public static final int CHANNEL_UNACCEPTABLE                             = 6;
+    /** The MS has tried to access a service that the MS's network operator or service
+     *  provider is not prepared to allow
+     */
+    public static final int OPERATOR_DETERMINED_BARRING                      = 8;
     /** One of the users involved in the call has requested that the call is cleared */
-    public static final int NORMAL                         = 16;
+    public static final int NORMAL                                           = 16;
     /** The called user is unable to accept another call */
-    public static final int BUSY                           = 17;
+    public static final int BUSY                                             = 17;
+    /** The user does not respond to a call establishment message with either an alerting
+     *  or connect indication within the prescribed period of time allocated
+     */
+    public static final int NO_USER_RESPONDING                               = 18;
+    /** The user has provided an alerting indication but has not provided a connect
+     *  indication within a prescribed period of time
+     */
+    public static final int NO_ANSWER_FROM_USER                              = 19;
+    /** The equipment sending this cause does not wish to accept this call */
+    public static final int CALL_REJECTED                                    = 21;
     /** The called number is no longer assigned */
-    public static final int NUMBER_CHANGED                 = 22;
+    public static final int NUMBER_CHANGED                                   = 22;
+    /** This cause is returned to the network when a mobile station clears an active
+     *  call which is being pre-empted by another call with higher precedence
+     */
+    public static final int PREEMPTION                                       = 25;
+    /** The destination indicated by the mobile station cannot be reached because
+     *  the interface to the destination is not functioning correctly
+     */
+    public static final int DESTINATION_OUT_OF_ORDER                         = 27;
+    /** The called party number is not a valid format or is not complete */
+    public static final int INVALID_NUMBER_FORMAT                            = 28;
+    /** The facility requested by user can not be provided by the network */
+    public static final int FACILITY_REJECTED                                = 29;
     /** Provided in response to a STATUS ENQUIRY message */
-    public static final int STATUS_ENQUIRY                 = 30;
+    public static final int STATUS_ENQUIRY                                   = 30;
     /** Reports a normal disconnect only when no other normal cause applies */
-    public static final int NORMAL_UNSPECIFIED             = 31;
+    public static final int NORMAL_UNSPECIFIED                               = 31;
     /** There is no channel presently available to handle the call */
-    public static final int NO_CIRCUIT_AVAIL               = 34;
+    public static final int NO_CIRCUIT_AVAIL                                 = 34;
+    /** The network is not functioning correctly and that the condition is likely
+     *  to last a relatively long period of time
+     */
+    public static final int NETWORK_OUT_OF_ORDER                             = 38;
     /**
      * The network is not functioning correctly and the condition is not likely to last
      * a long period of time
      */
-    public static final int TEMPORARY_FAILURE              = 41;
+    public static final int TEMPORARY_FAILURE                                = 41;
     /** The switching equipment is experiencing a period of high traffic */
-    public static final int SWITCHING_CONGESTION           = 42;
+    public static final int SWITCHING_CONGESTION                             = 42;
+    /** The network could not deliver access information to the remote user as requested */
+    public static final int ACCESS_INFORMATION_DISCARDED                     = 43;
     /** The channel cannot be provided */
-    public static final int CHANNEL_NOT_AVAIL              = 44;
+    public static final int CHANNEL_NOT_AVAIL                                = 44;
+    /** This cause is used to report a resource unavailable event only when no other
+     *  cause in the resource unavailable class applies
+     */
+    public static final int RESOURCES_UNAVAILABLE_OR_UNSPECIFIED             = 44;
     /** The requested quality of service (ITU-T X.213) cannot be provided */
-    public static final int QOS_NOT_AVAIL                  = 49;
+    public static final int QOS_NOT_AVAIL                                    = 49;
+    /** The facility could not be provided by the network because the user has no
+     *  complete subscription
+     */
+    public static final int REQUESTED_FACILITY_NOT_SUBSCRIBED                = 50;
+    /** Incoming calls are not allowed within this CUG */
+    public static final int INCOMING_CALLS_BARRED_WITHIN_CUG                 = 55;
+    /** The mobile station is not authorized to use bearer capability requested */
+    public static final int BEARER_CAPABILITY_NOT_AUTHORIZED                 = 57;
     /** The requested bearer capability is not available at this time */
-    public static final int BEARER_NOT_AVAIL               = 58;
+    public static final int BEARER_NOT_AVAIL                                 = 58;
+    /** The service option is not availble at this time */
+    public static final int SERVICE_OPTION_NOT_AVAILABLE                     = 63;
+    /** The equipment sending this cause does not support the bearer capability requested */
+    public static final int BEARER_SERVICE_NOT_IMPLEMENTED                   = 65;
     /** The call clearing is due to ACM being greater than or equal to ACMmax */
-    public static final int ACM_LIMIT_EXCEEDED             = 68;
+    public static final int ACM_LIMIT_EXCEEDED                               = 68;
+    /** The equipment sending this cause does not support the requested facility */
+    public static final int REQUESTED_FACILITY_NOT_IMPLEMENTED               = 69;
+    /** The equipment sending this cause only supports the restricted version of
+     *  the requested bearer capability
+     */
+    public static final int ONLY_DIGITAL_INFORMATION_BEARER_AVAILABLE        = 70;
+    /** The service requested is not implemented at network */
+    public static final int SERVICE_OR_OPTION_NOT_IMPLEMENTED                = 79;
+    /** The equipment sending this cause has received a message with a transaction identifier
+     *  which is not currently in use on the MS-network interface
+     */
+    public static final int INVALID_TRANSACTION_IDENTIFIER                   = 81;
+    /** The called user for the incoming CUG call is not a member of the specified CUG */
+    public static final int USER_NOT_MEMBER_OF_CUG                           = 87;
+    /** The equipment sending this cause has received a request which can't be accomodated */
+    public static final int INCOMPATIBLE_DESTINATION                         = 88;
+    /** This cause is used to report receipt of a message with semantically incorrect contents */
+    public static final int SEMANTICALLY_INCORRECT_MESSAGE                   = 95;
+    /** The equipment sending this cause has received a message with a non-semantical
+     *  mandatory IE error
+     */
+    public static final int INVALID_MANDATORY_INFORMATION                    = 96;
+    /** This is sent in response to a message which is not defined, or defined but not
+     *  implemented by the equipment sending this cause
+     */
+    public static final int MESSAGE_TYPE_NON_IMPLEMENTED                     = 97;
+    /** The equipment sending this cause has received a message not compatible with the
+     *  protocol state
+     */
+    public static final int MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE  = 98;
+    /** The equipment sending this cause has received a message which includes information
+     *  elements not recognized because its identifier is not defined or it is defined but not
+     *  implemented by the equipment sending the cause
+     */
+    public static final int INFORMATION_ELEMENT_NON_EXISTENT                 = 99;
+    /** The equipment sending this cause has received a message with conditional IE errors */
+    public static final int CONDITIONAL_IE_ERROR                             = 100;
+    /** The message has been received which is incompatible with the protocol state */
+    public static final int MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE       = 101;
+    /** The  procedure has been initiated by the expiry of a timer in association with
+     *  3GPP TS 24.008 error handling procedures
+     */
+    public static final int RECOVERY_ON_TIMER_EXPIRED                        = 102;
+    /** This protocol error event is reported only when no other cause in the protocol
+     *  error class applies
+     */
+    public static final int PROTOCOL_ERROR_UNSPECIFIED                       = 111;
+    /** interworking with a network which does not provide causes for actions it takes
+     *  thus, the precise cause for a message which is being sent cannot be ascertained
+     */
+    public static final int INTERWORKING_UNSPECIFIED                         = 127;
     /** The call is restricted */
-    public static final int CALL_BARRED                    = 240;
+    public static final int CALL_BARRED                                      = 240;
     /** The call is blocked by the Fixed Dialing Number list */
-    public static final int FDN_BLOCKED                    = 241;
+    public static final int FDN_BLOCKED                                      = 241;
     /** The given IMSI is not known at the VLR */
     /** TS 24.008 cause 4 */
-    public static final int IMSI_UNKNOWN_IN_VLR            = 242;
+    public static final int IMSI_UNKNOWN_IN_VLR                              = 242;
     /**
      * The network does not accept emergency call establishment using an IMEI or not accept attach
      * procedure for emergency services using an IMEI
      */
-    public static final int IMEI_NOT_ACCEPTED              = 243;
+    public static final int IMEI_NOT_ACCEPTED                                = 243;
+    /** The call cannot be established because RADIO is OFF */
+    public static final int RADIO_OFF                                        = 247;
+    /** The call cannot be established because of no cell coverage */
+    public static final int OUT_OF_SRV                                       = 248;
+    /** The call cannot be established because of no valid SIM */
+    public static final int NO_VALID_SIM                                     = 249;
+    /** The call is dropped or failed internally by modem */
+    public static final int RADIO_INTERNAL_ERROR                             = 250;
+    /** Call failed because of UE timer expired while waiting for a response from network */
+    public static final int NETWORK_RESP_TIMEOUT                             = 251;
+    /** Call failed because of a network reject */
+    public static final int NETWORK_REJECT                                   = 252;
+    /** Call failed because of radio access failure. ex. RACH failure */
+    public static final int RADIO_ACCESS_FAILURE                             = 253;
+    /** Call failed/dropped because of a RLF */
+    public static final int RADIO_LINK_FAILURE                               = 254;
+    /** Call failed/dropped because of radio link lost */
+    public static final int RADIO_LINK_LOST                                  = 255;
+    /** Call failed because of a radio uplink issue */
+    public static final int RADIO_UPLINK_FAILURE                             = 256;
+    /** Call failed because of a RRC connection setup failure */
+    public static final int RADIO_SETUP_FAILURE                              = 257;
+    /** Call failed/dropped because of RRC connection release from NW */
+    public static final int RADIO_RELEASE_NORMAL                             = 258;
+    /** Call failed/dropped because of RRC abnormally released by modem/network */
+    public static final int RADIO_RELEASE_ABNORMAL                           = 259;
+    /** Call setup failed because of access class barring */
+    public static final int ACCESS_CLASS_BLOCKED                             = 260;
+    /** Call failed/dropped because of a network detach */
+    public static final int NETWORK_DETACH                                   = 261;
+
     /** MS is locked until next power cycle */
-    public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE  = 1000;
+    public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE                    = 1000;
     /** Drop call*/
-    public static final int CDMA_DROP                      = 1001;
+    public static final int CDMA_DROP                                        = 1001;
     /** INTERCEPT order received, MS state idle entered */
-    public static final int CDMA_INTERCEPT                 = 1002;
+    public static final int CDMA_INTERCEPT                                   = 1002;
     /** MS has been redirected, call is cancelled */
-    public static final int CDMA_REORDER                   = 1003;
+    public static final int CDMA_REORDER                                     = 1003;
     /** Service option rejection */
-    public static final int CDMA_SO_REJECT                 = 1004;
+    public static final int CDMA_SO_REJECT                                   = 1004;
     /** Requested service is rejected, retry delay is set */
-    public static final int CDMA_RETRY_ORDER               = 1005;
+    public static final int CDMA_RETRY_ORDER                                 = 1005;
     /** Unable to obtain access to the CDMA system */
-    public static final int CDMA_ACCESS_FAILURE            = 1006;
+    public static final int CDMA_ACCESS_FAILURE                              = 1006;
     /** Not a preempted call */
-    public static final int CDMA_PREEMPTED                 = 1007;
+    public static final int CDMA_PREEMPTED                                   = 1007;
     /** Not an emergency call */
-    public static final int CDMA_NOT_EMERGENCY             = 1008;
+    public static final int CDMA_NOT_EMERGENCY                               = 1008;
     /** Access Blocked by CDMA network */
-    public static final int CDMA_ACCESS_BLOCKED            = 1009;
+    public static final int CDMA_ACCESS_BLOCKED                              = 1009;
+
+    /** Mapped from ImsReasonInfo */
+    /* The passed argument is an invalid */
+    public static final int LOCAL_ILLEGAL_ARGUMENT                           = 1200;
+    // The operation is invoked in invalid call state
+    public static final int LOCAL_ILLEGAL_STATE                              = 1201;
+    // IMS service internal error
+    public static final int LOCAL_INTERNAL_ERROR                             = 1202;
+    // IMS service goes down (service connection is lost)
+    public static final int LOCAL_IMS_SERVICE_DOWN                           = 1203;
+    // No pending incoming call exists
+    public static final int LOCAL_NO_PENDING_CALL                            = 1204;
+    // Service unavailable; by power off
+    public static final int LOCAL_POWER_OFF                                  = 1205;
+    // Service unavailable; by low battery
+    public static final int LOCAL_LOW_BATTERY                                = 1206;
+    // Service unavailable; by out of service (data service state)
+    public static final int LOCAL_NETWORK_NO_SERVICE                         = 1207;
+    /* Service unavailable; by no LTE coverage
+     * (VoLTE is not supported even though IMS is registered)
+     */
+    public static final int LOCAL_NETWORK_NO_LTE_COVERAGE                    = 1208;
+    /** Service unavailable; by located in roaming area */
+    public static final int LOCAL_NETWORK_ROAMING                            = 1209;
+    /** Service unavailable; by IP changed */
+    public static final int LOCAL_NETWORK_IP_CHANGED                         = 1210;
+    /** Service unavailable; other */
+    public static final int LOCAL_SERVICE_UNAVAILABLE                        = 1211;
+    /* Service unavailable; IMS connection is lost (IMS is not registered) */
+    public static final int LOCAL_NOT_REGISTERED                             = 1212;
+    /** Max call exceeded */
+    public static final int LOCAL_MAX_CALL_EXCEEDED                          = 1213;
+    /** Call decline */
+    public static final int LOCAL_CALL_DECLINE                               = 1214;
+    /** SRVCC is in progress */
+    public static final int LOCAL_CALL_VCC_ON_PROGRESSING                    = 1215;
+    /** Resource reservation is failed (QoS precondition) */
+    public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED           = 1216;
+    /** Retry CS call; VoLTE service can't be provided by the network or remote end
+     *  Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
+     */
+    public static final int LOCAL_CALL_CS_RETRY_REQUIRED                     = 1217;
+    /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */
+    public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED                  = 1218;
+    /** IMS call is already terminated (in TERMINATED state) */
+    public static final int LOCAL_CALL_TERMINATED                            = 1219;
+    /** Handover not feasible */
+    public static final int LOCAL_HO_NOT_FEASIBLE                            = 1220;
+
+    /** 1xx waiting timer is expired after sending INVITE request (MO only) */
+    public static final int TIMEOUT_1XX_WAITING                              = 1221;
+    /** User no answer during call setup operation (MO/MT)
+     *  MO : 200 OK to INVITE request is not received,
+     *  MT : No action from user after alerting the call
+     */
+    public static final int TIMEOUT_NO_ANSWER                                = 1222;
+    /** User no answer during call update operation (MO/MT)
+     *  MO : 200 OK to re-INVITE request is not received,
+     *  MT : No action from user after alerting the call
+     */
+    public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE                    = 1223;
+
+    /**
+     * STATUSCODE (SIP response code) (IMS -> Telephony)
+     */
+    /** SIP request is redirected */
+    public static final int SIP_REDIRECTED                                   = 1300;
+    /** 4xx responses */
+    /** 400 : Bad Request */
+    public static final int SIP_BAD_REQUEST                                  = 1310;
+    /** 403 : Forbidden */
+    public static final int SIP_FORBIDDEN                                    = 1311;
+    /** 404 : Not Found */
+    public static final int SIP_NOT_FOUND                                    = 1312;
+    /** 415 : Unsupported Media Type
+     *  416 : Unsupported URI Scheme
+     *  420 : Bad Extension
+     */
+    public static final int SIP_NOT_SUPPORTED                                = 1313;
+    /** 408 : Request Timeout */
+    public static final int SIP_REQUEST_TIMEOUT                              = 1314;
+    /** 480 : Temporarily Unavailable */
+    public static final int SIP_TEMPRARILY_UNAVAILABLE                       = 1315;
+    /** 484 : Address Incomplete */
+    public static final int SIP_BAD_ADDRESS                                  = 1316;
+    /** 486 : Busy Here
+     *  600 : Busy Everywhere
+     */
+    public static final int SIP_BUSY                                         = 1317;
+    /** 487 : Request Terminated */
+    public static final int SIP_REQUEST_CANCELLED                            = 1318;
+    /** 406 : Not Acceptable
+     *  488 : Not Acceptable Here
+     *  606 : Not Acceptable
+     */
+    public static final int SIP_NOT_ACCEPTABLE                               = 1319;
+    /** 410 : Gone
+     *  604 : Does Not Exist Anywhere
+     */
+    public static final int SIP_NOT_REACHABLE                                = 1320;
+    /** Others */
+    public static final int SIP_CLIENT_ERROR                                 = 1321;
+    /** 5xx responses
+     *  501 : Server Internal Error
+     */
+    public static final int SIP_SERVER_INTERNAL_ERROR                        = 1330;
+    /** 503 : Service Unavailable */
+    public static final int SIP_SERVICE_UNAVAILABLE                          = 1331;
+    /** 504 : Server Time-out */
+    public static final int SIP_SERVER_TIMEOUT                               = 1332;
+    /** Others */
+    public static final int SIP_SERVER_ERROR                                 = 1333;
+    /** 6xx responses
+     *  603 : Decline
+     */
+    public static final int SIP_USER_REJECTED                                = 1340;
+    /** Others */
+    public static final int SIP_GLOBAL_ERROR                                 = 1341;
+    /** Emergency failure */
+    public static final int EMERGENCY_TEMP_FAILURE                           = 1342;
+    public static final int EMERGENCY_PERM_FAILURE                           = 1343;
+    /** Media resource initialization failed */
+    public static final int MEDIA_INIT_FAILED                                = 1400;
+    /** RTP timeout (no audio / video traffic in the session) */
+    public static final int MEDIA_NO_DATA                                    = 1401;
+    /** Media is not supported; so dropped the call */
+    public static final int MEDIA_NOT_ACCEPTABLE                             = 1402;
+    /** Unknown media related errors */
+    public static final int MEDIA_UNSPECIFIED                                = 1403;
+    /** User triggers the call end */
+    public static final int USER_TERMINATED                                  = 1500;
+    /** No action while an incoming call is ringing */
+    public static final int USER_NOANSWER                                    = 1501;
+    /** User ignores an incoming call */
+    public static final int USER_IGNORE                                      = 1502;
+    /** User declines an incoming call */
+    public static final int USER_DECLINE                                     = 1503;
+    /** Device declines/ends a call due to low battery */
+    public static final int LOW_BATTERY                                      = 1504;
+    /** Device declines call due to blacklisted call ID */
+    public static final int BLACKLISTED_CALL_ID                              = 1505;
+    /** The call is terminated by the network or remote user */
+    public static final int USER_TERMINATED_BY_REMOTE                        = 1510;
+
+    /**
+     * UT
+     */
+    public static final int UT_NOT_SUPPORTED                                 = 1800;
+    public static final int UT_SERVICE_UNAVAILABLE                           = 1801;
+    public static final int UT_OPERATION_NOT_ALLOWED                         = 1802;
+    public static final int UT_NETWORK_ERROR                                 = 1803;
+    public static final int UT_CB_PASSWORD_MISMATCH                          = 1804;
+
+    /**
+     * ECBM
+     */
+    public static final int ECBM_NOT_SUPPORTED                               = 1900;
+
+    /**
+     * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework.
+     */
+    public static final int MULTIENDPOINT_NOT_SUPPORTED                      = 1901;
+
+    /**
+     * CALL DROP error codes (Call could drop because of many reasons like Network not available,
+     *  handover, failed, etc)
+     */
+
+    /**
+     * CALL DROP error code for the case when a device is ePDG capable and when the user is on an
+     * active wifi call and at the edge of coverage and there is no qualified LTE network available
+     * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error
+     * code is received as part of the handover message.
+     */
+    public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE               = 2000;
+
+    /**
+     * MT call has ended due to a release from the network
+     * because the call was answered elsewhere
+     */
+    public static final int ANSWERED_ELSEWHERE                               = 2100;
+
+    /**
+     * For MultiEndpoint - Call Pull request has failed
+     */
+    public static final int CALL_PULL_OUT_OF_SYNC                            = 2101;
+
+    /**
+     * For MultiEndpoint - Call has been pulled from primary to secondary
+     */
+    public static final int CALL_PULLED                                      = 2102;
+
+    /**
+     * Supplementary services (HOLD/RESUME) failure error codes.
+     * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision.
+     */
+    public static final int SUPP_SVC_FAILED                                  = 2300;
+    public static final int SUPP_SVC_CANCELLED                               = 2301;
+    public static final int SUPP_SVC_REINVITE_COLLISION                      = 2302;
+
+    /**
+     * DPD Procedure received no response or send failed
+     */
+    public static final int IWLAN_DPD_FAILURE                                = 2400;
+
+    /**
+     * Establishment of the ePDG Tunnel Failed
+     */
+    public static final int EPDG_TUNNEL_ESTABLISH_FAILURE                    = 2500;
+
+    /**
+     * Re-keying of the ePDG Tunnel Failed; may not always result in teardown
+     */
+    public static final int EPDG_TUNNEL_REKEY_FAILURE                        = 2501;
+
+    /**
+     * Connection to the packet gateway is lost
+     */
+    public static final int EPDG_TUNNEL_LOST_CONNECTION                      = 2502;
+
+    /**
+     * The maximum number of calls allowed has been reached.  Used in a multi-endpoint scenario
+     * where the number of calls across all connected devices has reached the maximum.
+     */
+    public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED                  = 2503;
+
+    /**
+     * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has
+     * declined the call.  Used in a multi-endpoint scenario where a remote device declined an
+     * incoming call.
+     */
+    public static final int REMOTE_CALL_DECLINE                              = 2504;
+
+    /**
+     * Indicates the call was disconnected due to the user reaching their data limit.
+     */
+    public static final int DATA_LIMIT_REACHED                               = 2505;
+
+    /**
+     * Indicates the call was disconnected due to the user disabling cellular data.
+     */
+    public static final int DATA_DISABLED                                    = 2506;
+
+    /**
+     * Indicates a call was disconnected due to loss of wifi signal.
+     */
+    public static final int WIFI_LOST                                        = 2507;
+
+
+    /* OEM specific error codes. To be used by OEMs when they don't want to
+       reveal error code which would be replaced by ERROR_UNSPECIFIED */
+    public static final int OEM_CAUSE_1                                      = 0xf001;
+    public static final int OEM_CAUSE_2                                      = 0xf002;
+    public static final int OEM_CAUSE_3                                      = 0xf003;
+    public static final int OEM_CAUSE_4                                      = 0xf004;
+    public static final int OEM_CAUSE_5                                      = 0xf005;
+    public static final int OEM_CAUSE_6                                      = 0xf006;
+    public static final int OEM_CAUSE_7                                      = 0xf007;
+    public static final int OEM_CAUSE_8                                      = 0xf008;
+    public static final int OEM_CAUSE_9                                      = 0xf009;
+    public static final int OEM_CAUSE_10                                     = 0xf00a;
+    public static final int OEM_CAUSE_11                                     = 0xf00b;
+    public static final int OEM_CAUSE_12                                     = 0xf00c;
+    public static final int OEM_CAUSE_13                                     = 0xf00d;
+    public static final int OEM_CAUSE_14                                     = 0xf00e;
+    public static final int OEM_CAUSE_15                                     = 0xf00f;
+
     /** Disconnected due to unspecified reasons */
-    public static final int ERROR_UNSPECIFIED              = 0xffff;
+    public static final int ERROR_UNSPECIFIED                                = 0xffff;
 
     /** Private constructor to avoid class instantiation. */
     private PreciseDisconnectCause() {
diff --git a/telephony/java/android/telephony/Rlog.java b/telephony/java/android/telephony/Rlog.java
index cd0a012..2a31e3a 100644
--- a/telephony/java/android/telephony/Rlog.java
+++ b/telephony/java/android/telephony/Rlog.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -32,6 +33,8 @@
  */
 public final class Rlog {
 
+    private static final boolean USER_BUILD = Build.TYPE.equals("user");
+
     private Rlog() {
     }
 
@@ -125,10 +128,15 @@
     /**
      * Returns a secure hash (using the SHA1 algorithm) of the provided input.
      *
-     * @return the hash
+     * @return "****" if the build type is user, otherwise the hash
      * @param input the bytes for which the secure hash should be computed.
      */
     private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (USER_BUILD) {
+            return "****";
+        }
+
         MessageDigest messageDigest;
 
         try {
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 7a83979..5fb83ab 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -243,6 +243,10 @@
 
     private boolean mIsUsingCarrierAggregation;
 
+    /* EARFCN stands for E-UTRA Absolute Radio Frequency Channel Number,
+     * Reference: 3GPP TS 36.104 5.4.3 */
+    private int mLteEarfcnRsrpBoost = 0;
+
     /**
      * get String description of roaming type
      * @hide
@@ -322,6 +326,7 @@
         mIsEmergencyOnly = s.mIsEmergencyOnly;
         mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
         mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation;
+        mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost;
     }
 
     /**
@@ -351,6 +356,7 @@
         mIsEmergencyOnly = in.readInt() != 0;
         mIsDataRoamingFromRegistration = in.readInt() != 0;
         mIsUsingCarrierAggregation = in.readInt() != 0;
+        mLteEarfcnRsrpBoost = in.readInt();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -377,6 +383,7 @@
         out.writeInt(mIsEmergencyOnly ? 1 : 0);
         out.writeInt(mIsDataRoamingFromRegistration ? 1 : 0);
         out.writeInt(mIsUsingCarrierAggregation ? 1 : 0);
+        out.writeInt(mLteEarfcnRsrpBoost);
     }
 
     public int describeContents() {
@@ -814,7 +821,8 @@
                 + " DefRoamInd=" + mCdmaDefaultRoamingIndicator
                 + " EmergOnly=" + mIsEmergencyOnly
                 + " IsDataRoamingFromRegistration=" + mIsDataRoamingFromRegistration
-                + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation);
+                + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation
+                + " mLteEarfcnRsrpBoost=" + mLteEarfcnRsrpBoost);
     }
 
     private void setNullState(int state) {
@@ -842,6 +850,7 @@
         mIsEmergencyOnly = false;
         mIsDataRoamingFromRegistration = false;
         mIsUsingCarrierAggregation = false;
+        mLteEarfcnRsrpBoost = 0;
     }
 
     public void setStateOutOfService() {
@@ -1016,6 +1025,7 @@
         mIsEmergencyOnly = m.getBoolean("emergencyOnly");
         mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration");
         mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation");
+        mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost");
     }
 
     /**
@@ -1046,6 +1056,7 @@
         m.putBoolean("emergencyOnly", mIsEmergencyOnly);
         m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration);
         m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation);
+        m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
     }
 
     /** @hide */
@@ -1081,6 +1092,16 @@
     }
 
     /** @hide */
+    public int getLteEarfcnRsrpBoost() {
+        return mLteEarfcnRsrpBoost;
+    }
+
+    /** @hide */
+    public void setLteEarfcnRsrpBoost(int LteEarfcnRsrpBoost) {
+        mLteEarfcnRsrpBoost = LteEarfcnRsrpBoost;
+    }
+
+    /** @hide */
     public void setCssIndicator(int css) {
         this.mCssIndicator = (css != 0);
     }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index c484fd3..9e02399 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -64,6 +64,8 @@
     private int mLteRsrq;
     private int mLteRssnr;
     private int mLteCqi;
+    private int mLteRsrpBoost; // offset to be reduced from the rsrp threshold while calculating
+                                // signal strength level
     private int mTdScdmaRscp;
 
     private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
@@ -104,6 +106,7 @@
         mLteRsrq = INVALID;
         mLteRssnr = INVALID;
         mLteCqi = INVALID;
+        mLteRsrpBoost = 0;
         mTdScdmaRscp = INVALID;
         isGsm = true;
     }
@@ -129,6 +132,7 @@
         mLteRsrq = INVALID;
         mLteRssnr = INVALID;
         mLteCqi = INVALID;
+        mLteRsrpBoost = 0;
         mTdScdmaRscp = INVALID;
         isGsm = gsmFlag;
     }
@@ -142,10 +146,26 @@
             int cdmaDbm, int cdmaEcio,
             int evdoDbm, int evdoEcio, int evdoSnr,
             int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
+            int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) {
+        initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
+                evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
+                lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag);
+        mTdScdmaRscp = tdScdmaRscp;
+    }
+
+    /**
+     * Constructor
+     *
+     * @hide
+     */
+    public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate,
+            int cdmaDbm, int cdmaEcio,
+            int evdoDbm, int evdoEcio, int evdoSnr,
+            int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
             int tdScdmaRscp, boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
         mTdScdmaRscp = tdScdmaRscp;
     }
 
@@ -161,7 +181,7 @@
             boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp,
-                lteRsrq, lteRssnr, lteCqi, gsmFlag);
+                lteRsrq, lteRssnr, lteCqi, 0, gsmFlag);
     }
 
     /**
@@ -175,7 +195,7 @@
             boolean gsmFlag) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
-                INVALID, INVALID, INVALID, gsmFlag);
+                INVALID, INVALID, INVALID, 0, gsmFlag);
     }
 
     /**
@@ -209,7 +229,7 @@
             boolean gsm) {
         initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio,
                 evdoDbm, evdoEcio, evdoSnr, 99, INVALID,
-                INVALID, INVALID, INVALID, gsm);
+                INVALID, INVALID, INVALID, 0, gsm);
     }
 
     /**
@@ -227,6 +247,7 @@
      * @param lteRsrq
      * @param lteRssnr
      * @param lteCqi
+     * @param lteRsrpBoost
      * @param gsm
      *
      * @hide
@@ -235,7 +256,7 @@
             int cdmaDbm, int cdmaEcio,
             int evdoDbm, int evdoEcio, int evdoSnr,
             int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi,
-            boolean gsm) {
+            int lteRsrpBoost, boolean gsm) {
         mGsmSignalStrength = gsmSignalStrength;
         mGsmBitErrorRate = gsmBitErrorRate;
         mCdmaDbm = cdmaDbm;
@@ -248,6 +269,7 @@
         mLteRsrq = lteRsrq;
         mLteRssnr = lteRssnr;
         mLteCqi = lteCqi;
+        mLteRsrpBoost = lteRsrpBoost;
         mTdScdmaRscp = INVALID;
         isGsm = gsm;
         if (DBG) log("initialize: " + toString());
@@ -269,6 +291,7 @@
         mLteRsrq = s.mLteRsrq;
         mLteRssnr = s.mLteRssnr;
         mLteCqi = s.mLteCqi;
+        mLteRsrpBoost = s.mLteRsrpBoost;
         mTdScdmaRscp = s.mTdScdmaRscp;
         isGsm = s.isGsm;
     }
@@ -293,6 +316,7 @@
         mLteRsrq = in.readInt();
         mLteRssnr = in.readInt();
         mLteCqi = in.readInt();
+        mLteRsrpBoost = in.readInt();
         mTdScdmaRscp = in.readInt();
         isGsm = (in.readInt() != 0);
     }
@@ -340,6 +364,7 @@
         out.writeInt(mLteRsrq);
         out.writeInt(mLteRssnr);
         out.writeInt(mLteCqi);
+        out.writeInt(mLteRsrpBoost);
         out.writeInt(mTdScdmaRscp);
         out.writeInt(isGsm ? 1 : 0);
     }
@@ -416,6 +441,18 @@
     }
 
     /**
+     * @param lteRsrpBoost - signal strength offset
+     *
+     * Used by phone to set the lte signal strength offset which will be
+     * reduced from rsrp threshold while calculating signal strength level
+     *
+     * @hide
+     */
+    public void setLteRsrpBoost(int lteRsrpBoost) {
+        mLteRsrpBoost = lteRsrpBoost;
+    }
+
+    /**
      * Get the GSM Signal Strength, valid values are (0-31, 99) as defined in TS
      * 27.007 8.5
      */
@@ -490,6 +527,11 @@
         return mLteCqi;
     }
 
+    /** @hide */
+    public int getLteRsrpBoost() {
+        return mLteRsrpBoost;
+    }
+
     /**
      * Retrieve an abstract level value for the overall signal strength.
      *
@@ -793,12 +835,19 @@
             Log.wtf(LOG_TAG, "getLteLevel - config_lteDbmThresholds has invalid num of elements."
                     + " Cannot evaluate RSRP signal.");
         } else {
-            if (mLteRsrp > threshRsrp[5]) rsrpIconLevel = -1;
-            else if (mLteRsrp >= threshRsrp[4]) rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
-            else if (mLteRsrp >= threshRsrp[3]) rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
-            else if (mLteRsrp >= threshRsrp[2]) rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
-            else if (mLteRsrp >= threshRsrp[1]) rsrpIconLevel = SIGNAL_STRENGTH_POOR;
-            else if (mLteRsrp >= threshRsrp[0]) rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+            if (mLteRsrp > threshRsrp[5]) {
+                rsrpIconLevel = -1;
+            } else if (mLteRsrp >= (threshRsrp[4] - mLteRsrpBoost)) {
+                rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
+            } else if (mLteRsrp >= (threshRsrp[3] - mLteRsrpBoost)) {
+                rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
+            } else if (mLteRsrp >= (threshRsrp[2] - mLteRsrpBoost)) {
+                rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
+            } else if (mLteRsrp >= (threshRsrp[1] - mLteRsrpBoost)) {
+                rsrpIconLevel = SIGNAL_STRENGTH_POOR;
+            } else if (mLteRsrp >= threshRsrp[0]) {
+                rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+            }
         }
 
         /*
@@ -816,7 +865,8 @@
             snrIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
 
         if (DBG) log("getLTELevel - rsrp:" + mLteRsrp + " snr:" + mLteRssnr + " rsrpIconLevel:"
-                + rsrpIconLevel + " snrIconLevel:" + snrIconLevel);
+                + rsrpIconLevel + " snrIconLevel:" + snrIconLevel
+                + " lteRsrpBoost:" + mLteRsrpBoost);
 
         /* Choose a measurement type to use for notification */
         if (snrIconLevel != -1 && rsrpIconLevel != -1) {
@@ -939,7 +989,7 @@
                 + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)
                 + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
                 + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
-                + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
+                + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0));
     }
 
     /**
@@ -971,6 +1021,7 @@
                 && mLteRsrq == s.mLteRsrq
                 && mLteRssnr == s.mLteRssnr
                 && mLteCqi == s.mLteCqi
+                && mLteRsrpBoost == s.mLteRsrpBoost
                 && mTdScdmaRscp == s.mTdScdmaRscp
                 && isGsm == s.isGsm);
     }
@@ -993,6 +1044,7 @@
                 + " " + mLteRsrq
                 + " " + mLteRssnr
                 + " " + mLteCqi
+                + " " + mLteRsrpBoost
                 + " " + mTdScdmaRscp
                 + " " + (isGsm ? "gsm|lte" : "cdma"));
     }
@@ -1016,6 +1068,7 @@
         mLteRsrq = m.getInt("LteRsrq");
         mLteRssnr = m.getInt("LteRssnr");
         mLteCqi = m.getInt("LteCqi");
+        mLteRsrpBoost = m.getInt("lteRsrpBoost");
         mTdScdmaRscp = m.getInt("TdScdma");
         isGsm = m.getBoolean("isGsm");
     }
@@ -1039,6 +1092,7 @@
         m.putInt("LteRsrq", mLteRsrq);
         m.putInt("LteRssnr", mLteRssnr);
         m.putInt("LteCqi", mLteCqi);
+        m.putInt("lteRsrpBoost", mLteRsrpBoost);
         m.putInt("TdScdma", mTdScdmaRscp);
         m.putBoolean("isGsm", isGsm);
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5a15009..d8a4892 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SdkConstant;
@@ -38,9 +39,13 @@
 import android.service.carrier.CarrierIdentifier;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.ClientRequestStats;
 import android.telephony.TelephonyHistogram;
+import android.telephony.ims.feature.ImsFeature;
 import android.util.Log;
 
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IPhoneSubInfo;
@@ -53,6 +58,8 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -773,6 +780,21 @@
             "android.telephony.event.EVENT_DOWNGRADE_DATA_DISABLED";
 
     /**
+     * {@link android.telecom.Connection} event used to indicate that the InCall UI should notify
+     * the user when an international call is placed while on WFC only.
+     * <p>
+     * Used when the carrier config value
+     * {@link CarrierConfigManager#KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL} is true, the device
+     * is on WFC (VoLTE not available) and an international number is dialed.
+     * <p>
+     * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}.
+     * The {@link Bundle} parameter is expected to be null when this connection event is used.
+     * @hide
+     */
+    public static final String EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC =
+            "android.telephony.event.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC";
+
+    /**
      * Response codes for sim activation. Activation completed successfully.
      * @hide
      */
@@ -2719,6 +2741,148 @@
     }
 
     /**
+     * Initial SIM activation state, unknown. Not set by any carrier apps.
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0;
+
+    /**
+     * indicate SIM is under activation procedure now.
+     * intermediate state followed by another state update with activation procedure result:
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1;
+
+    /**
+     * Indicate SIM has been successfully activated with full service
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2;
+
+    /**
+     * Indicate SIM has been deactivated by the carrier so that service is not available
+     * and requires activation service to enable services.
+     * Carrier apps could be signalled to set activation state to deactivated if detected
+     * deactivated sim state and set it back to activated after successfully run activation service.
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3;
+
+    /**
+     * Restricted state indicate SIM has been activated but service are restricted.
+     * note this is currently available for data activation state. For example out of byte sim.
+     * @hide
+     */
+    public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4;
+
+    /**
+     * Sets the voice activation state for the given subscriber.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param subId The subscription id.
+     * @param activationState The voice activation state of the given subscriber.
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @hide
+     */
+    public void setVoiceActivationState(int subId, int activationState) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                telephony.setVoiceActivationState(subId, activationState);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
+
+    /**
+     * Sets the data activation state for the given subscriber.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @param subId The subscription id.
+     * @param activationState The data activation state of the given subscriber.
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @hide
+     */
+    public void setDataActivationState(int subId, int activationState) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                telephony.setDataActivationState(subId, activationState);
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+    }
+
+    /**
+     * Returns the voice activation state for the given subscriber.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE}
+     *
+     * @param subId The subscription id.
+     *
+     * @return voiceActivationState for the given subscriber
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @hide
+     */
+    public int getVoiceActivationState(int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                return telephony.getVoiceActivationState(subId, getOpPackageName());
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return SIM_ACTIVATION_STATE_UNKNOWN;
+    }
+
+    /**
+     * Returns the data activation state for the given subscriber.
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE}
+     *
+     * @param subId The subscription id.
+     *
+     * @return dataActivationState for the given subscriber
+     * @see #SIM_ACTIVATION_STATE_UNKNOWN
+     * @see #SIM_ACTIVATION_STATE_ACTIVATING
+     * @see #SIM_ACTIVATION_STATE_ACTIVATED
+     * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     * @hide
+     */
+    public int getDataActivationState(int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                return telephony.getDataActivationState(subId, getOpPackageName());
+        } catch (RemoteException ex) {
+        } catch (NullPointerException ex) {
+        }
+        return SIM_ACTIVATION_STATE_UNKNOWN;
+    }
+
+    /**
      * Returns the voice mail count. Return 0 if unavailable, -1 if there are unread voice messages
      * but the count is unknown.
      * <p>
@@ -3040,7 +3204,7 @@
     public void listen(PhoneStateListener listener, int events) {
         if (mContext == null) return;
         try {
-            Boolean notifyNow = (getITelephony() != null);
+            boolean notifyNow = (getITelephony() != null);
             // If the listener has not explicitly set the subId (for example, created with the
             // default constructor), replace the subId so it will listen to the account the
             // telephony manager is created with.
@@ -3766,9 +3930,8 @@
             }
         }
 
-        if (property.length() > SystemProperties.PROP_NAME_MAX
-                || propVal.length() > SystemProperties.PROP_VALUE_MAX) {
-            Rlog.d(TAG, "setTelephonyProperty: property to long phoneId=" + phoneId +
+        if (propVal.length() > SystemProperties.PROP_VALUE_MAX) {
+            Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
                     " property=" + property + " value: " + value + " propVal=" + propVal);
             return;
         }
@@ -4025,6 +4188,45 @@
     }
 
     /**
+     * Returns an array of Forbidden PLMNs from the USIM App
+     * Returns null if the query fails.
+     *
+     *
+     * <p>Requires that the caller has READ_PRIVILEGED_PHONE_STATE
+     *
+     * @return an array of forbidden PLMNs or null if not available
+     */
+    public String[] getForbiddenPlmns() {
+      return getForbiddenPlmns(getSubId(), APPTYPE_USIM);
+    }
+
+    /**
+     * Returns an array of Forbidden PLMNs from the specified SIM App
+     * Returns null if the query fails.
+     *
+     *
+     * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+     *
+     * @param subId subscription ID used for authentication
+     * @param appType the icc application type, like {@link #APPTYPE_USIM}
+     * @return fplmns an array of forbidden PLMNs
+     * @hide
+     */
+    public String[] getForbiddenPlmns(int subId, int appType) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null)
+                return null;
+            return telephony.getForbiddenPlmns(subId, appType);
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone starts
+            return null;
+        }
+    }
+
+    /**
      * Get P-CSCF address from PCO after data connection is established or modified.
      * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN
      * @return array of P-CSCF address
@@ -4041,6 +4243,37 @@
         }
     }
 
+    /** @hide */
+    @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Feature {}
+
+    /**
+     * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
+     * feature or {@link null} if the service is not available. If an ImsServiceController is
+     * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
+     * feature updates.
+     * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
+     * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
+     * @param callback Listener that will send updates to ImsManager when there are updates to
+     * ImsServiceController.
+     * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
+     * it is unavailable.
+     * @hide
+     */
+    public IImsServiceController getImsServiceControllerAndListen(int slotId, @Feature int feature,
+            IImsServiceFeatureListener callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getImsServiceControllerAndListen(slotId, feature, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
+        }
+        return null;
+    }
+
     /**
      * Set IMS registration state
      *
@@ -4860,7 +5093,9 @@
      *         0 request was handled succesfully, but no response data
      *         positive value success, data length of response
      * @hide
+     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
      */
+    @Deprecated
     public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
         try {
             ITelephony telephony = getITelephony();
@@ -5153,6 +5388,44 @@
     }
 
     /**
+     * Set SIM card power state. Request is equivalent to inserting or removing the card.
+     *
+     * @param powerUp True if powering up the SIM, otherwise powering down
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @hide
+     **/
+    public void setSimPowerState(boolean powerUp) {
+        setSimPowerStateForSlot(getDefaultSim(), powerUp);
+    }
+
+    /**
+     * Set SIM card power state. Request is equivalent to inserting or removing the card.
+     *
+     * @param slotId SIM slot id
+     * @param powerUp True if powering up the SIM, otherwise powering down
+     *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     *
+     * @hide
+     **/
+    public void setSimPowerStateForSlot(int slotId, boolean powerUp) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setSimPowerStateForSlot(slotId, powerUp);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
+        } catch (SecurityException e) {
+            Log.e(TAG, "Permission error calling ITelephony#setSimPowerStateForSlot", e);
+        }
+    }
+
+    /**
      * Set baseband version for the default phone.
      *
      * @param version baseband version
@@ -5682,10 +5955,17 @@
      * Set the allowed carrier list for slotId
      * Require system privileges. In the future we may add this to carrier APIs.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     *
+     * <p>This method works only on devices with {@link
+     * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled.
+     *
      * @return The number of carriers set successfully. Should be length of
      * carrierList on success; -1 on error.
      * @hide
      */
+    @SystemApi
     public int setAllowedCarriers(int slotId, List<CarrierIdentifier> carriers) {
         try {
             ITelephony service = getITelephony();
@@ -5694,6 +5974,8 @@
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
         }
         return -1;
     }
@@ -5702,10 +5984,17 @@
      * Get the allowed carrier list for slotId.
      * Require system privileges. In the future we may add this to carrier APIs.
      *
+     * <p>Requires Permission:
+     *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
+     *
+     * <p>This method returns valid data on devices with {@link
+     * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled.
+     *
      * @return List of {@link android.telephony.CarrierIdentifier}; empty list
      * means all carriers are allowed.
      * @hide
      */
+    @SystemApi
     public List<CarrierIdentifier> getAllowedCarriers(int slotId) {
         try {
             ITelephony service = getITelephony();
@@ -5714,6 +6003,8 @@
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e);
         }
         return new ArrayList<CarrierIdentifier>(0);
     }
@@ -5789,5 +6080,27 @@
             Log.e(TAG, "Error calling ITelephony#setPolicyDataEnabled", e);
         }
     }
+
+    /**
+     * Get Client request stats which will contain statistical information
+     * on each request made by client.
+     * Callers require either READ_PRIVILEGED_PHONE_STATE or
+     * READ_PHONE_STATE to retrieve the information.
+     * @param subId sub id
+     * @return List of Client Request Stats
+     * @hide
+     */
+    public List<ClientRequestStats> getClientRequestStats(int subId) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getClientRequestStats(getOpPackageName(), subId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getClientRequestStats", e);
+        }
+
+        return null;
+    }
 }
 
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
new file mode 100644
index 0000000..f1f683c
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -0,0 +1,467 @@
+/*
+ * 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.ims;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MMTelFeature;
+import android.telephony.ims.feature.RcsFeature;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsUt;
+import com.android.internal.annotations.VisibleForTesting;
+
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+
+/**
+ * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
+ * ImsService must register the service in their AndroidManifest to be detected by the framework.
+ * First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE"
+ * permission. Then, the ImsService definition in the manifest must follow the following format:
+ *
+ * ...
+ * <service android:name=".EgImsService"
+ *     android:permission="android.permission.BIND_IMS_SERVICE" >
+ *     <!-- Apps must declare which features they support as metadata. The different categories are
+ *     defined below. In this example, the RCS_FEATURE feature is supported. -->
+ *     <meta-data android:name="android.telephony.ims.RCS_FEATURE" android:value="true" />
+ *     <intent-filter>
+ *         <action android:name="android.telephony.ims.ImsService" />
+ *     </intent-filter>
+ * </service>
+ * ...
+ *
+ * The telephony framework will then bind to the ImsService you have defined in your manifest
+ * if you are either:
+ * 1) Defined as the default ImsService for the device in the device overlay using
+ *    "config_ims_package".
+ * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using
+ *    {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}.
+ *
+ * The features that are currently supported in an ImsService are:
+ * - RCS_FEATURE: This ImsService implements the {@link RcsFeature} class.
+ * - MMTEL_FEATURE: This ImsService implements the {@link MMTelFeature} class.
+ * - EMERGENCY_MMTEL_FEATURE: This ImsService implements the {@link MMTelFeature} class and will be
+ *   available to place emergency calls at all times. This MUST be implemented by the default
+ *   ImsService provided in the device overlay.
+ *
+ * @hide
+ */
+public abstract class ImsService extends ImsServiceBase {
+
+    private static final String LOG_TAG = "ImsService";
+
+    /**
+     * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
+     */
+    public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService";
+
+    // A map of slot Id -> Set of features corresponding to that slot.
+    private final SparseArray<SparseArray<ImsFeature>> mFeatures = new SparseArray<>();
+
+    // Implements all supported features as a flat interface.
+    protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
+
+        @Override
+        public void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
+                throws RemoteException {
+            synchronized (mFeatures) {
+                enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createImsFeature");
+                onCreateImsFeatureInternal(slotId, feature, c);
+            }
+        }
+
+        @Override
+        public void removeImsFeature(int slotId, int feature) throws RemoteException {
+            synchronized (mFeatures) {
+                enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature");
+                onRemoveImsFeatureInternal(slotId, feature);
+            }
+        }
+
+        @Override
+        public int startSession(int slotId, int featureType, PendingIntent incomingCallIntent,
+                IImsRegistrationListener listener) throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "startSession");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.startSession(incomingCallIntent, listener);
+                }
+            }
+            return 0;
+        }
+
+        @Override
+        public void endSession(int slotId, int featureType, int sessionId) throws RemoteException {
+            synchronized (mFeatures) {
+                enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "endSession");
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    feature.endSession(sessionId);
+                }
+            }
+        }
+
+        @Override
+        public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
+                throws RemoteException {
+            enforceReadPhoneStatePermission("isConnected");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.isConnected(callSessionType, callType);
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public boolean isOpened(int slotId, int featureType) throws RemoteException {
+            enforceReadPhoneStatePermission("isOpened");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.isOpened();
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public int getFeatureStatus(int slotId, int featureType) throws RemoteException {
+            enforceReadPhoneStatePermission("getFeatureStatus");
+            int status = ImsFeature.STATE_NOT_AVAILABLE;
+            synchronized (mFeatures) {
+                SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
+                if (featureMap != null) {
+                    ImsFeature feature = getImsFeatureFromType(featureMap, featureType);
+                    if (feature != null) {
+                        status = feature.getFeatureState();
+                    }
+                }
+            }
+            return status;
+        }
+
+        @Override
+        public void addRegistrationListener(int slotId, int featureType,
+                IImsRegistrationListener listener) throws RemoteException {
+            enforceReadPhoneStatePermission("addRegistrationListener");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    feature.addRegistrationListener(listener);
+                }
+            }
+        }
+
+        @Override
+        public void removeRegistrationListener(int slotId, int featureType,
+                IImsRegistrationListener listener) throws RemoteException {
+            enforceReadPhoneStatePermission("removeRegistrationListener");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    feature.removeRegistrationListener(listener);
+                }
+            }
+        }
+
+        @Override
+        public ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
+                int callSessionType, int callType) throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallProfile");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.createCallProfile(sessionId, callSessionType,  callType);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
+                ImsCallProfile profile, IImsCallSessionListener listener) throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallSession");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.createCallSession(sessionId, profile, listener);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
+                String callId) throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getPendingCallSession");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.getPendingCallSession(sessionId, callId);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public IImsUt getUtInterface(int slotId, int featureType)
+                throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getUtInterface");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.getUtInterface();
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public IImsConfig getConfigInterface(int slotId, int featureType)
+                throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getConfigInterface");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.getConfigInterface();
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void turnOnIms(int slotId, int featureType) throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOnIms");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    feature.turnOnIms();
+                }
+            }
+        }
+
+        @Override
+        public void turnOffIms(int slotId, int featureType) throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOffIms");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    feature.turnOffIms();
+                }
+            }
+        }
+
+        @Override
+        public IImsEcbm getEcbmInterface(int slotId, int featureType)
+                throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getEcbmInterface");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.getEcbmInterface();
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete)
+                throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setUiTTYMode");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    feature.setUiTTYMode(uiTtyMode, onComplete);
+                }
+            }
+        }
+
+        @Override
+        public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType)
+                throws RemoteException {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getMultiEndpointInterface");
+            synchronized (mFeatures) {
+                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
+                if (feature != null) {
+                    return feature.getMultiEndpointInterface();
+                }
+            }
+            return null;
+        }
+
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if(SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mImsServiceController;
+        }
+        return null;
+    }
+
+    /**
+     * Called from the ImsResolver to create the requested ImsFeature, as defined by the slot and
+     * featureType
+     * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
+     * @param featureType An integer representing the type of ImsFeature being created. This is
+     * defined in {@link ImsFeature}.
+     */
+    // Be sure to lock on mFeatures before accessing this method
+    private void onCreateImsFeatureInternal(int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
+        SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
+        if (featureMap == null) {
+            featureMap = new SparseArray<>();
+            mFeatures.put(slotId, featureMap);
+        }
+        ImsFeature f = makeImsFeature(slotId, featureType);
+        if (f != null) {
+            f.setContext(this);
+            f.setSlotId(slotId);
+            f.setImsFeatureStatusCallback(c);
+            featureMap.put(featureType, f);
+        }
+
+    }
+    /**
+     * Called from the ImsResolver to remove an existing ImsFeature, as defined by the slot and
+     * featureType.
+     * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
+     * @param featureType An integer representing the type of ImsFeature being removed. This is
+     * defined in {@link ImsFeature}.
+     */
+    // Be sure to lock on mFeatures before accessing this method
+    private void onRemoveImsFeatureInternal(int slotId, int featureType) {
+        SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
+        if (featureMap == null) {
+            return;
+        }
+
+        ImsFeature featureToRemove = getImsFeatureFromType(featureMap, featureType);
+        if (featureToRemove != null) {
+            featureMap.remove(featureType);
+            featureToRemove.notifyFeatureRemoved(slotId);
+            // Remove reference to Binder
+            featureToRemove.setImsFeatureStatusCallback(null);
+        }
+    }
+
+    // Be sure to lock on mFeatures before accessing this method
+    private MMTelFeature resolveMMTelFeature(int slotId, int featureType) {
+        SparseArray<ImsFeature> features = getImsFeatureMap(slotId);
+        MMTelFeature feature = null;
+        if (features != null) {
+            feature = resolveImsFeature(features, featureType, MMTelFeature.class);
+        }
+        return feature;
+    }
+
+    // Be sure to lock on mFeatures before accessing this method
+    private <T extends ImsFeature> T resolveImsFeature(SparseArray<ImsFeature> set, int featureType,
+            Class<T> className) {
+        ImsFeature feature = getImsFeatureFromType(set, featureType);
+        if (feature == null) {
+            return null;
+        }
+        try {
+            return className.cast(feature);
+        } catch (ClassCastException e)
+        {
+            Log.e(LOG_TAG, "Can not cast ImsFeature! Exception: " + e.getMessage());
+        }
+        return null;
+    }
+
+    @VisibleForTesting
+    // Be sure to lock on mFeatures before accessing this method
+    public SparseArray<ImsFeature> getImsFeatureMap(int slotId) {
+        return mFeatures.get(slotId);
+    }
+
+    @VisibleForTesting
+    // Be sure to lock on mFeatures before accessing this method
+    public ImsFeature getImsFeatureFromType(SparseArray<ImsFeature> set, int featureType) {
+        return set.get(featureType);
+    }
+
+    private ImsFeature makeImsFeature(int slotId, int feature) {
+        switch (feature) {
+            case ImsFeature.EMERGENCY_MMTEL: {
+                return onCreateEmergencyMMTelImsFeature(slotId);
+            }
+            case ImsFeature.MMTEL: {
+                return onCreateMMTelImsFeature(slotId);
+            }
+            case ImsFeature.RCS: {
+                return onCreateRcsFeature(slotId);
+            }
+        }
+        // Tried to create feature that is not defined.
+        return null;
+    }
+
+    /**
+     * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a
+     * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps.
+     */
+    private void enforceReadPhoneStatePermission(String fn) {
+        if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
+                != PackageManager.PERMISSION_GRANTED) {
+            enforceCallingOrSelfPermission(READ_PHONE_STATE, fn);
+        }
+    }
+
+    /**
+     * @return An implementation of MMTelFeature that will be used by the system for MMTel
+     * functionality. Must be able to handle emergency calls at any time as well.
+     */
+    public abstract MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId);
+
+    /**
+     * @return An implementation of MMTelFeature that will be used by the system for MMTel
+     * functionality.
+     */
+    public abstract MMTelFeature onCreateMMTelImsFeature(int slotId);
+
+    /**
+     * @return An implementation of RcsFeature that will be used by the system for RCS.
+     */
+    public abstract RcsFeature onCreateRcsFeature(int slotId);
+}
diff --git a/telephony/java/android/telephony/ims/ImsServiceBase.java b/telephony/java/android/telephony/ims/ImsServiceBase.java
new file mode 100644
index 0000000..bb36862
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceBase.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ims;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Base ImsService Implementation, which is used by the ImsResolver to bind. ImsServices that do not
+ * need to provide an ImsService implementation but still wish to be managed by the ImsResolver
+ * lifecycle may implement this class directly.
+ * @hide
+ */
+@SystemApi
+public class ImsServiceBase extends Service {
+
+    /**
+     * Binder connection that does nothing but keep the connection between this Service and the
+     * framework active. If this service crashes, the framework will be notified.
+     */
+    private IBinder mConnection = new Binder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mConnection;
+    }
+
+}
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
new file mode 100644
index 0000000..38ea6e6f
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -0,0 +1,313 @@
+/*
+ * 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.ims;
+
+import android.app.PendingIntent;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.ims.feature.IRcsFeature;
+import android.telephony.ims.feature.ImsFeature;
+import android.util.Log;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * A container of the IImsServiceController binder, which implements all of the ImsFeatures that
+ * the platform currently supports: MMTel and RCS.
+ * @hide
+ */
+
+public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeature {
+
+    protected String LOG_TAG = "ImsServiceProxy";
+    private final int mSupportedFeature;
+
+    // Start by assuming the proxy is available for usage.
+    private boolean mIsAvailable = true;
+    // ImsFeature Status from the ImsService. Cached.
+    private Integer mFeatureStatusCached = null;
+    private ImsServiceProxy.INotifyStatusChanged mStatusCallback;
+    private final Object mLock = new Object();
+
+    public interface INotifyStatusChanged {
+        void notifyStatusChanged();
+    }
+
+    private final IImsServiceFeatureListener mListenerBinder =
+            new IImsServiceFeatureListener.Stub() {
+
+        @Override
+        public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
+            // The feature has been re-enabled. This may happen when the service crashes.
+            synchronized (mLock) {
+                if (!mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) {
+                    Log.i(LOG_TAG, "Feature enabled on slotId: " + slotId + " for feature: " +
+                            feature);
+                    mIsAvailable = true;
+                }
+            }
+        }
+
+        @Override
+        public void imsFeatureRemoved(int slotId, int feature) throws RemoteException {
+            synchronized (mLock) {
+                if (mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) {
+                    Log.i(LOG_TAG, "Feature disabled on slotId: " + slotId + " for feature: " +
+                            feature);
+                    mIsAvailable = false;
+                }
+            }
+        }
+
+        @Override
+        public void imsStatusChanged(int slotId, int feature, int status) throws RemoteException {
+            synchronized (mLock) {
+                Log.i(LOG_TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature +
+                        " status: " + status);
+                if (mSlotId == slotId && feature == mSupportedFeature) {
+                    mFeatureStatusCached = status;
+                }
+            }
+            if (mStatusCallback != null) {
+                mStatusCallback.notifyStatusChanged();
+            }
+        }
+    };
+
+    public ImsServiceProxy(int slotId, IBinder binder, int featureType) {
+        super(slotId, binder);
+        mSupportedFeature = featureType;
+    }
+
+    public ImsServiceProxy(int slotId, int featureType) {
+        super(slotId, null /*IBinder*/);
+        mSupportedFeature = featureType;
+    }
+
+    public IImsServiceFeatureListener getListener() {
+        return mListenerBinder;
+    }
+
+    public void setBinder(IBinder binder) {
+        mBinder = binder;
+    }
+
+    @Override
+    public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
+                    incomingCallIntent, listener);
+        }
+    }
+
+    @Override
+    public void endSession(int sessionId) throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
+        }
+    }
+
+    @Override
+    public boolean isConnected(int callServiceType, int callType)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
+                    callServiceType, callType);
+        }
+    }
+
+    @Override
+    public boolean isOpened() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
+        }
+    }
+
+    @Override
+    public void addRegistrationListener(IImsRegistrationListener listener)
+    throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
+                    listener);
+        }
+    }
+
+    @Override
+    public void removeRegistrationListener(IImsRegistrationListener listener)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
+                    listener);
+        }
+    }
+
+    @Override
+    public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
+                    sessionId, callServiceType, callType);
+        }
+    }
+
+    @Override
+    public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+            IImsCallSessionListener listener) throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
+                    sessionId, profile, listener);
+        }
+    }
+
+    @Override
+    public IImsCallSession getPendingCallSession(int sessionId, String callId)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
+                    sessionId, callId);
+        }
+    }
+
+    @Override
+    public IImsUt getUtInterface() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
+        }
+    }
+
+    @Override
+    public IImsConfig getConfigInterface() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
+        }
+    }
+
+    @Override
+    public void turnOnIms() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
+        }
+    }
+
+    @Override
+    public void turnOffIms() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
+        }
+    }
+
+    @Override
+    public IImsEcbm getEcbmInterface() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
+        }
+    }
+
+    @Override
+    public void setUiTTYMode(int uiTtyMode, Message onComplete)
+            throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
+                    onComplete);
+        }
+    }
+
+    @Override
+    public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
+        synchronized (mLock) {
+            checkBinderConnection();
+            return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
+                    mSupportedFeature);
+        }
+    }
+
+    @Override
+    public int getFeatureStatus() {
+        synchronized (mLock) {
+            if (mFeatureStatusCached != null) {
+                return mFeatureStatusCached;
+            }
+        }
+        // Don't synchronize on Binder call.
+        Integer status = retrieveFeatureStatus();
+        synchronized (mLock) {
+            if (status == null) {
+                return ImsFeature.STATE_NOT_AVAILABLE;
+            }
+            // Cache only non-null value for feature status.
+            mFeatureStatusCached = status;
+        }
+        return status;
+    }
+
+    /**
+     * Internal method used to retrieve the feature status from the corresponding ImsService.
+     */
+    private Integer retrieveFeatureStatus() {
+        if (mBinder != null) {
+            try {
+                return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature);
+            } catch (RemoteException e) {
+                // Status check failed, don't update cache
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param c Callback that will fire when the feature status has changed.
+     */
+    public void setStatusCallback(INotifyStatusChanged c) {
+        mStatusCallback = c;
+    }
+
+    @Override
+    public boolean isBinderAlive() {
+        return mIsAvailable && getFeatureStatus() == ImsFeature.STATE_READY && mBinder != null &&
+                mBinder.isBinderAlive();
+    }
+
+    private IImsServiceController getServiceInterface(IBinder b) {
+        return IImsServiceController.Stub.asInterface(b);
+    }
+}
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
new file mode 100644
index 0000000..bbd5f02
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
@@ -0,0 +1,183 @@
+/*
+ * 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.ims;
+
+import android.app.PendingIntent;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.ims.feature.IMMTelFeature;
+import android.telephony.ims.feature.ImsFeature;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsService;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * Compatibility class that implements the new ImsService IMMTelFeature interface, but
+ * uses the old IImsService interface to support older devices that implement the deprecated
+ * opt/net/ims interface.
+ * @hide
+ */
+
+public class ImsServiceProxyCompat implements IMMTelFeature {
+
+    private static final int SERVICE_ID = ImsFeature.MMTEL;
+
+    protected final int mSlotId;
+    protected IBinder mBinder;
+
+    public ImsServiceProxyCompat(int slotId, IBinder binder) {
+        mSlotId = slotId;
+        mBinder = binder;
+    }
+
+    @Override
+    public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
+            throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).open(mSlotId, ImsFeature.MMTEL, incomingCallIntent,
+                listener);
+    }
+
+    @Override
+    public void endSession(int sessionId) throws RemoteException {
+        checkBinderConnection();
+        getServiceInterface(mBinder).close(sessionId);
+    }
+
+    @Override
+    public boolean isConnected(int callServiceType, int callType)
+            throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).isConnected(SERVICE_ID,  callServiceType, callType);
+    }
+
+    @Override
+    public boolean isOpened() throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).isOpened(SERVICE_ID);
+    }
+
+    @Override
+    public void addRegistrationListener(IImsRegistrationListener listener)
+            throws RemoteException {
+        checkBinderConnection();
+        getServiceInterface(mBinder).addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener);
+    }
+
+    @Override
+    public void removeRegistrationListener(IImsRegistrationListener listener)
+            throws RemoteException {
+        // Not Implemented in old ImsService. If the registration listener becomes invalid, the
+        // ImsService will remove.
+    }
+
+    @Override
+    public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
+            throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType, callType);
+    }
+
+    @Override
+    public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+            IImsCallSessionListener listener) throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener);
+    }
+
+    @Override
+    public IImsCallSession getPendingCallSession(int sessionId, String callId)
+            throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId);
+    }
+
+    @Override
+    public IImsUt getUtInterface() throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).getUtInterface(SERVICE_ID);
+    }
+
+    @Override
+    public IImsConfig getConfigInterface() throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).getConfigInterface(mSlotId);
+    }
+
+    @Override
+    public void turnOnIms() throws RemoteException {
+        checkBinderConnection();
+        getServiceInterface(mBinder).turnOnIms(mSlotId);
+    }
+
+    @Override
+    public void turnOffIms() throws RemoteException {
+        checkBinderConnection();
+        getServiceInterface(mBinder).turnOffIms(mSlotId);
+    }
+
+    @Override
+    public IImsEcbm getEcbmInterface() throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).getEcbmInterface(SERVICE_ID);
+    }
+
+    @Override
+    public void setUiTTYMode(int uiTtyMode, Message onComplete)
+            throws RemoteException {
+        checkBinderConnection();
+        getServiceInterface(mBinder).setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete);
+    }
+
+    @Override
+    public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
+        checkBinderConnection();
+        return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
+    }
+
+    /**
+     * Base implementation, always returns READY for compatibility with old ImsService.
+     */
+    public int getFeatureStatus() {
+        return ImsFeature.STATE_READY;
+    }
+
+    /**
+     * @return false if the binder connection is no longer alive.
+     */
+    public boolean isBinderAlive() {
+        return mBinder != null && mBinder.isBinderAlive();
+    }
+
+    private IImsService getServiceInterface(IBinder b) {
+        return IImsService.Stub.asInterface(b);
+    }
+
+    protected void checkBinderConnection() throws RemoteException {
+        if (!isBinderAlive()) {
+            throw new RemoteException("ImsServiceProxy is not available for that feature.");
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/feature/IMMTelFeature.java b/telephony/java/android/telephony/ims/feature/IMMTelFeature.java
new file mode 100644
index 0000000..d65e27e
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/IMMTelFeature.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.telephony.ims.feature;
+
+import android.app.PendingIntent;
+import android.os.Message;
+import android.os.RemoteException;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * MMTel interface for an ImsService. When updating this interface, ensure that base implementations
+ * of your changes are also present in MMTelFeature for compatibility with older versions of the
+ * MMTel feature.
+ * @hide
+ */
+
+public interface IMMTelFeature {
+
+    /**
+     * Notifies the MMTel feature that you would like to start a session. This should always be
+     * done before making/receiving IMS calls. The IMS service will register the device to the
+     * operator's network with the credentials (from ISIM) periodically in order to receive calls
+     * from the operator's network. When the IMS service receives a new call, it will send out an
+     * intent with the provided action string. The intent contains a call ID extra
+     * {@link IImsCallSession#getCallId} and it can be used to take a call.
+     *
+     * @param incomingCallIntent When an incoming call is received, the IMS service will call
+     * {@link PendingIntent#send} to send back the intent to the caller with
+     * {@link #INCOMING_CALL_RESULT_CODE} as the result code and the intent to fill in the call ID;
+     * It cannot be null.
+     * @param listener To listen to IMS registration events; It cannot be null
+     * @return an integer (greater than 0) representing the session id associated with the session
+     * that has been started.
+     */
+    int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
+            throws RemoteException;
+
+    /**
+     * End a previously started session using the associated sessionId.
+     * @param sessionId an integer (greater than 0) representing the ongoing session. See
+     * {@link #startSession}.
+     */
+    void endSession(int sessionId) throws RemoteException;
+
+    /**
+     * Checks if the IMS service has successfully registered to the IMS network with the specified
+     * service & call type.
+     *
+     * @param callServiceType a service type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+     * @param callType a call type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
+     *        {@link ImsCallProfile#CALL_TYPE_VT}
+     *        {@link ImsCallProfile#CALL_TYPE_VS}
+     * @return true if the specified service id is connected to the IMS network; false otherwise
+     * @throws RemoteException
+     */
+    boolean isConnected(int callServiceType, int callType) throws RemoteException;
+
+    /**
+     * Checks if the specified IMS service is opened.
+     *
+     * @return true if the specified service id is opened; false otherwise
+     */
+    boolean isOpened() throws RemoteException;
+
+    /**
+     * Add a new registration listener for the client associated with the session Id.
+     * @param listener An implementation of IImsRegistrationListener.
+     */
+    void addRegistrationListener(IImsRegistrationListener listener)
+            throws RemoteException;
+
+    /**
+     * Remove a previously registered listener using {@link #addRegistrationListener} for the client
+     * associated with the session Id.
+     * @param listener A previously registered IImsRegistrationListener
+     */
+    void removeRegistrationListener(IImsRegistrationListener listener)
+            throws RemoteException;
+
+    /**
+     * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
+     *
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     * @param callServiceType a service type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NONE}
+     *        {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+     *        {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+     * @param callType a call type that is specified in {@link ImsCallProfile}
+     *        {@link ImsCallProfile#CALL_TYPE_VOICE}
+     *        {@link ImsCallProfile#CALL_TYPE_VT}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_TX}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_RX}
+     *        {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
+     *        {@link ImsCallProfile#CALL_TYPE_VS}
+     *        {@link ImsCallProfile#CALL_TYPE_VS_TX}
+     *        {@link ImsCallProfile#CALL_TYPE_VS_RX}
+     * @return a {@link ImsCallProfile} object
+     */
+    ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
+            throws RemoteException;
+
+    /**
+     * Creates a {@link ImsCallSession} with the specified call profile.
+     * Use other methods, if applicable, instead of interacting with
+     * {@link ImsCallSession} directly.
+     *
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     * @param profile a call profile to make the call
+     * @param listener An implementation of IImsCallSessionListener.
+     */
+    IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+            IImsCallSessionListener listener) throws RemoteException;
+
+    /**
+     * Retrieves the call session associated with a pending call.
+     *
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     * @param callId a call id to make the call
+     */
+    IImsCallSession getPendingCallSession(int sessionId, String callId) throws RemoteException;
+
+    /**
+     * @return The Ut interface for the supplementary service configuration.
+     */
+    IImsUt getUtInterface() throws RemoteException;
+
+    /**
+     * @return The config interface for IMS Configuration
+     */
+    IImsConfig getConfigInterface() throws RemoteException;
+
+    /**
+     * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms}
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     */
+    void turnOnIms() throws RemoteException;
+
+    /**
+     * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms}
+     * @param sessionId a session id which is obtained from {@link #startSession}
+     */
+    void turnOffIms() throws RemoteException;
+
+    /**
+     * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
+     */
+    IImsEcbm getEcbmInterface() throws RemoteException;
+
+    /**
+     * Sets the current UI TTY mode for the MMTelFeature.
+     * @param uiTtyMode An integer containing the new UI TTY Mode.
+     * @param onComplete A {@link Message} to be used when the mode has been set.
+     * @throws RemoteException
+     */
+    void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException;
+
+    /**
+     * @return MultiEndpoint interface for DEP notifications
+     */
+    IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException;
+}
diff --git a/telephony/java/android/telephony/ims/feature/IRcsFeature.java b/telephony/java/android/telephony/ims/feature/IRcsFeature.java
new file mode 100644
index 0000000..e28e1b3
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/IRcsFeature.java
@@ -0,0 +1,26 @@
+/*
+ * 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.ims.feature;
+
+/**
+ * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
+ * in the framework.
+ * @hide
+ */
+
+public interface IRcsFeature {
+}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
new file mode 100644
index 0000000..988dd58
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -0,0 +1,190 @@
+/*
+ * 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.ims.feature;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.ims.internal.IImsFeatureStatusCallback;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class for all IMS features that are supported by the framework.
+ * @hide
+ */
+public abstract class ImsFeature {
+
+    private static final String LOG_TAG = "ImsFeature";
+
+    /**
+     * Action to broadcast when ImsService is up.
+     * Internal use only.
+     * Only defined here separately compatibility purposes with the old ImsService.
+     * @hide
+     */
+    public static final String ACTION_IMS_SERVICE_UP =
+            "com.android.ims.IMS_SERVICE_UP";
+
+    /**
+     * Action to broadcast when ImsService is down.
+     * Internal use only.
+     * Only defined here separately for compatibility purposes with the old ImsService.
+     * @hide
+     */
+    public static final String ACTION_IMS_SERVICE_DOWN =
+            "com.android.ims.IMS_SERVICE_DOWN";
+
+    /**
+     * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents.
+     * A long value; the phone ID corresponding to the IMS service coming up or down.
+     * Only defined here separately for compatibility purposes with the old ImsService.
+     * @hide
+     */
+    public static final String EXTRA_PHONE_ID = "android:phone_id";
+
+    // Invalid feature value
+    public static final int INVALID = -1;
+    // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously
+    // defined values in ImsServiceClass for compatibility purposes.
+    public static final int EMERGENCY_MMTEL = 0;
+    public static final int MMTEL = 1;
+    public static final int RCS = 2;
+    // Total number of features defined
+    public static final int MAX = 3;
+
+    // Integer values defining the state of the ImsFeature at any time.
+    @IntDef(flag = true,
+            value = {
+                    STATE_NOT_AVAILABLE,
+                    STATE_INITIALIZING,
+                    STATE_READY,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ImsState {}
+    public static final int STATE_NOT_AVAILABLE = 0;
+    public static final int STATE_INITIALIZING = 1;
+    public static final int STATE_READY = 2;
+
+    private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
+    private IImsFeatureStatusCallback mStatusCallback;
+    private @ImsState int mState = STATE_NOT_AVAILABLE;
+    private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+    private Context mContext;
+
+    public interface INotifyFeatureRemoved {
+        void onFeatureRemoved(int slotId);
+    }
+
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
+    public void setSlotId(int slotId) {
+        mSlotId = slotId;
+    }
+
+    public void addFeatureRemovedListener(INotifyFeatureRemoved listener) {
+        synchronized (mRemovedListeners) {
+            mRemovedListeners.add(listener);
+        }
+    }
+
+    public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) {
+        synchronized (mRemovedListeners) {
+            mRemovedListeners.remove(listener);
+        }
+    }
+
+    // Not final for testing.
+    public void notifyFeatureRemoved(int slotId) {
+        synchronized (mRemovedListeners) {
+            mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId));
+            onFeatureRemoved();
+        }
+    }
+
+    public int getFeatureState() {
+        return mState;
+    }
+
+    protected final void setFeatureState(@ImsState int state) {
+        if (mState != state) {
+            mState = state;
+            notifyFeatureState(state);
+        }
+    }
+
+    // Not final for testing.
+    public void setImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
+        mStatusCallback = c;
+        // If we have just connected, send queued status.
+        notifyFeatureState(mState);
+    }
+
+    /**
+     * Internal method called by ImsFeature when setFeatureState has changed.
+     * @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());
+            }
+        }
+        sendImsServiceIntent(state);
+    }
+
+    /**
+     * Provide backwards compatibility using deprecated service UP/DOWN intents.
+     */
+    private void sendImsServiceIntent(@ImsState int state) {
+        if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            return;
+        }
+        Intent intent;
+        switch (state) {
+            case ImsFeature.STATE_NOT_AVAILABLE:
+            case ImsFeature.STATE_INITIALIZING:
+                intent = new Intent(ACTION_IMS_SERVICE_DOWN);
+                break;
+            case ImsFeature.STATE_READY:
+                intent = new Intent(ACTION_IMS_SERVICE_UP);
+                break;
+            default:
+                intent = new Intent(ACTION_IMS_SERVICE_DOWN);
+        }
+        intent.putExtra(EXTRA_PHONE_ID, mSlotId);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Called when the feature is being removed and must be cleaned up.
+     */
+    public abstract void onFeatureRemoved();
+}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
new file mode 100644
index 0000000..a71f0bf
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -0,0 +1,122 @@
+/*
+ * 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.ims.feature;
+
+import android.app.PendingIntent;
+import android.os.Message;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsUt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base implementation, which implements all methods in IMMTelFeature. Any class wishing to use
+ * MMTelFeature should extend this class and implement all methods that the service supports.
+ *
+ * @hide
+ */
+
+public class MMTelFeature extends ImsFeature implements IMMTelFeature {
+
+    @Override
+    public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) {
+        return 0;
+    }
+
+    @Override
+    public void endSession(int sessionId) {
+    }
+
+    @Override
+    public boolean isConnected(int callSessionType, int callType) {
+        return false;
+    }
+
+    @Override
+    public boolean isOpened() {
+        return false;
+    }
+
+    @Override
+    public void addRegistrationListener(IImsRegistrationListener listener) {
+    }
+
+    @Override
+    public void removeRegistrationListener(IImsRegistrationListener listener) {
+    }
+
+    @Override
+    public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) {
+        return null;
+    }
+
+    @Override
+    public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+            IImsCallSessionListener listener) {
+        return null;
+    }
+
+    @Override
+    public IImsCallSession getPendingCallSession(int sessionId, String callId) {
+        return null;
+    }
+
+    @Override
+    public IImsUt getUtInterface() {
+        return null;
+    }
+
+    @Override
+    public IImsConfig getConfigInterface() {
+        return null;
+    }
+
+    @Override
+    public void turnOnIms() {
+    }
+
+    @Override
+    public void turnOffIms() {
+    }
+
+    @Override
+    public IImsEcbm getEcbmInterface() {
+        return null;
+    }
+
+    @Override
+    public void setUiTTYMode(int uiTtyMode, Message onComplete) {
+    }
+
+    @Override
+    public IImsMultiEndpoint getMultiEndpointInterface() {
+        return null;
+    }
+
+    @Override
+    public void onFeatureRemoved() {
+
+    }
+}
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
new file mode 100644
index 0000000..9cddc1b
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ims.feature;
+
+/**
+ * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
+ * this class and provide implementations of the IRcsFeature methods that they support.
+ * @hide
+ */
+
+public class RcsFeature extends ImsFeature implements IRcsFeature {
+
+    public RcsFeature() {
+        super();
+    }
+
+    @Override
+    public void onFeatureRemoved() {
+
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
new file mode 100644
index 0000000..69b8acc
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.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 android.telephony.ims.stub;
+
+import android.os.Message;
+import android.os.RemoteException;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.internal.ImsCallSession;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsVideoCallProvider;
+
+/**
+ * Base implementation of IImsCallSession, which implements stub versions of the methods in the
+ * IImsCallSession AIDL. Override the methods that your implementation of ImsCallSession supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsCallSession maintained by other ImsServices.
+ *
+ * @hide
+ */
+
+public class ImsCallSessionImplBase extends IImsCallSession.Stub {
+
+    /**
+     * Closes the object. This object is not usable after being closed.
+     */
+    @Override
+    public void close() throws RemoteException {
+
+    }
+
+    /**
+     * Gets the call ID of the session.
+     *
+     * @return the call ID
+     */
+    @Override
+    public String getCallId() throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Gets the call profile that this session is associated with
+     *
+     * @return the {@link ImsCallProfile} that this session is associated with
+     */
+    @Override
+    public ImsCallProfile getCallProfile() throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Gets the local call profile that this session is associated with
+     *
+     * @return the local {@link ImsCallProfile} that this session is associated with
+     */
+    @Override
+    public ImsCallProfile getLocalCallProfile() throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Gets the remote call profile that this session is associated with
+     *
+     * @return the remote {@link ImsCallProfile} that this session is associated with
+     */
+    @Override
+    public ImsCallProfile getRemoteCallProfile() throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Gets the value associated with the specified property of this session.
+     *
+     * @return the string value associated with the specified property
+     */
+    @Override
+    public String getProperty(String name) throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Gets the session state.
+     * The value returned must be one of the states in {@link ImsCallSession.State}.
+     *
+     * @return the session state
+     */
+    @Override
+    public int getState() throws RemoteException {
+        return ImsCallSession.State.INVALID;
+    }
+
+    /**
+     * Checks if the session is in call.
+     *
+     * @return true if the session is in call, false otherwise
+     */
+    @Override
+    public boolean isInCall() throws RemoteException {
+        return false;
+    }
+
+    /**
+     * Sets the listener to listen to the session events. An {@link ImsCallSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the session events of this object
+     */
+    @Override
+    public void setListener(IImsCallSessionListener listener) throws RemoteException {
+    }
+
+    /**
+     * Mutes or unmutes the mic for the active call.
+     *
+     * @param muted true if the call is muted, false otherwise
+     */
+    @Override
+    public void setMute(boolean muted) throws RemoteException {
+    }
+
+    /**
+     * Initiates an IMS call with the specified target and call profile.
+     * The session listener set in {@link #setListener} is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession.State#IDLE}.
+     *
+     * @param callee dialed string to make the call to
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see {@link ImsCallSession.Listener#callSessionStarted},
+     * {@link ImsCallSession.Listener#callSessionStartFailed}
+     */
+    @Override
+    public void start(String callee, ImsCallProfile profile) throws RemoteException {
+    }
+
+    /**
+     * Initiates an IMS call with the specified participants and call profile.
+     * The session listener set in {@link #setListener} is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession.State#IDLE}.
+     *
+     * @param participants participant list to initiate an IMS conference call
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see {@link ImsCallSession.Listener#callSessionStarted},
+     * {@link ImsCallSession.Listener#callSessionStartFailed}
+     */
+    @Override
+    public void startConference(String[] participants, ImsCallProfile profile)
+            throws RemoteException {
+    }
+
+    /**
+     * Accepts an incoming call or session update.
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be answered
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
+     * @see {@link ImsCallSession.Listener#callSessionStarted}
+     */
+    @Override
+    public void accept(int callType, ImsStreamMediaProfile profile) throws RemoteException {
+    }
+
+    /**
+     * Rejects an incoming call or session update.
+     *
+     * @param reason reason code to reject an incoming call, defined in
+     *         com.android.ims.ImsReasonInfo
+     * {@link ImsCallSession.Listener#callSessionStartFailed}
+     */
+    @Override
+    public void reject(int reason) throws RemoteException {
+    }
+
+    /**
+     * Terminates a call.
+     *
+     * @param reason reason code to terminate a call, defined in
+     *         com.android.ims.ImsReasonInfo
+     *
+     * @see {@link ImsCallSession.Listener#callSessionTerminated}
+     */
+    @Override
+    public void terminate(int reason) throws RemoteException {
+    }
+
+    /**
+     * Puts a call on hold. When it succeeds, {@link ImsCallSession.Listener#callSessionHeld} is
+     * called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
+     * @see {@link ImsCallSession.Listener#callSessionHeld},
+     * {@link ImsCallSession.Listener#callSessionHoldFailed}
+     */
+    @Override
+    public void hold(ImsStreamMediaProfile profile) throws RemoteException {
+    }
+
+    /**
+     * Continues a call that's on hold. When it succeeds,
+     * {@link ImsCallSession.Listener#callSessionResumed} is called.
+     *
+     * @param profile stream media profile with {@link ImsStreamMediaProfile} to resume the call
+     * @see {@link ImsCallSession.Listener#callSessionResumed},
+     * {@link ImsCallSession.Listener#callSessionResumeFailed}
+     */
+    @Override
+    public void resume(ImsStreamMediaProfile profile) throws RemoteException {
+    }
+
+    /**
+     * Merges the active & hold call. When the merge starts,
+     * {@link ImsCallSession.Listener#callSessionMergeStarted} is called.
+     * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is
+     * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge
+     * fails.
+     *
+     * @see {@link ImsCallSession.Listener#callSessionMergeStarted},
+     * {@link ImsCallSession.Listener#callSessionMergeComplete},
+     *      {@link ImsCallSession.Listener#callSessionMergeFailed}
+     */
+    @Override
+    public void merge() throws RemoteException {
+    }
+
+    /**
+     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be updated
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
+     * @see {@link ImsCallSession.Listener#callSessionUpdated},
+     * {@link ImsCallSession.Listener#callSessionUpdateFailed}
+     */
+    @Override
+    public void update(int callType, ImsStreamMediaProfile profile) throws RemoteException {
+    }
+
+    /**
+     * Extends this call to the conference call with the specified recipients.
+     *
+     * @param participants participant list to be invited to the conference call after extending the
+     * call
+     * @see {@link ImsCallSession.Listener#callSessionConferenceExtended},
+     * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed}
+     */
+    @Override
+    public void extendToConference(String[] participants) throws RemoteException {
+    }
+
+    /**
+     * Requests the conference server to invite an additional participants to the conference.
+     *
+     * @param participants participant list to be invited to the conference call
+     * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered},
+     *      {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed}
+     */
+    @Override
+    public void inviteParticipants(String[] participants) throws RemoteException {
+    }
+
+    /**
+     * Requests the conference server to remove the specified participants from the conference.
+     *
+     * @param participants participant list to be removed from the conference call
+     * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered},
+     *      {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed}
+     */
+    @Override
+    public void removeParticipants(String[] participants) throws RemoteException {
+    }
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
+     */
+    @Override
+    public void sendDtmf(char c, Message result) throws RemoteException {
+    }
+
+    /**
+     * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
+     */
+    @Override
+    public void startDtmf(char c) throws RemoteException {
+    }
+
+    /**
+     * Stop a DTMF code.
+     */
+    @Override
+    public void stopDtmf() throws RemoteException {
+    }
+
+    /**
+     * Sends an USSD message.
+     *
+     * @param ussdMessage USSD message to send
+     */
+    @Override
+    public void sendUssd(String ussdMessage) throws RemoteException {
+    }
+
+    /**
+     * Returns a binder for the video call provider implementation contained within the IMS service
+     * process. This binder is used by the VideoCallProvider subclass in Telephony which
+     * intermediates between the propriety implementation and Telecomm/InCall.
+     */
+    @Override
+    public IImsVideoCallProvider getVideoCallProvider() throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Determines if the current session is multiparty.
+     * @return {@code True} if the session is multiparty.
+     */
+    @Override
+    public boolean isMultiparty() throws RemoteException {
+        return false;
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java
new file mode 100644
index 0000000..46f8f80
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java
@@ -0,0 +1,251 @@
+/*
+ * 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.ims.stub;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsConferenceState;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.ImsSuppServiceNotification;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+
+/**
+ * Base implementation of ImsCallSessionListenerBase, which implements stub versions of the methods
+ * in the IImsCallSessionListener AIDL. Override the methods that your implementation of
+ * ImsCallSessionListener supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsCallSessionListener maintained by other ImsServices.
+ *
+ * @hide
+ */
+public class ImsCallSessionListenerImplBase extends IImsCallSessionListener.Stub {
+    /**
+     * Notifies the result of the basic session operation (setup / terminate).
+     */
+    @Override
+    public void callSessionProgressing(IImsCallSession session, ImsStreamMediaProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionStarted(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionStartFailed(IImsCallSession session, ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionTerminated(IImsCallSession session, ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    /**
+     * Notifies the result of the call hold/resume operation.
+     */
+    @Override
+    public void callSessionHeld(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionHoldFailed(IImsCallSession session, ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionHoldReceived(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionResumed(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionResumeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionResumeReceived(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    /**
+     * Notifies the result of call merge operation.
+     */
+    @Override
+    public void callSessionMergeStarted(IImsCallSession session, IImsCallSession newSession,
+            ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionMergeComplete(IImsCallSession session) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionMergeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    /**
+     * Notifies the result of call upgrade / downgrade or any other call
+     * updates.
+     */
+    @Override
+    public void callSessionUpdated(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionUpdateFailed(IImsCallSession session, ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionUpdateReceived(IImsCallSession session, ImsCallProfile profile) {
+        // no-op
+    }
+
+    /**
+     * Notifies the result of conference extension.
+     */
+    @Override
+    public void callSessionConferenceExtended(IImsCallSession session, IImsCallSession newSession,
+            ImsCallProfile profile) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionConferenceExtendFailed(IImsCallSession session,
+            ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionConferenceExtendReceived(IImsCallSession session,
+            IImsCallSession newSession,
+            ImsCallProfile profile) {
+        // no-op
+    }
+
+    /**
+     * Notifies the result of the participant invitation / removal to/from the
+     * conference session.
+     */
+    @Override
+    public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionInviteParticipantsRequestFailed(IImsCallSession session,
+            ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session,
+            ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    /**
+     * Notifies the changes of the conference info. the conference session.
+     */
+    @Override
+    public void callSessionConferenceStateUpdated(IImsCallSession session,
+            ImsConferenceState state) {
+        // no-op
+    }
+
+    /**
+     * Notifies the incoming USSD message.
+     */
+    @Override
+    public void callSessionUssdMessageReceived(IImsCallSession session, int mode,
+            String ussdMessage) {
+        // no-op
+    }
+
+    /**
+     * Notifies of handover information for this call
+     */
+    @Override
+    public void callSessionHandover(IImsCallSession session, int srcAccessTech,
+            int targetAccessTech,
+            ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    @Override
+    public void callSessionHandoverFailed(IImsCallSession session, int srcAccessTech,
+            int targetAccessTech,
+            ImsReasonInfo reasonInfo) {
+        // no-op
+    }
+
+    /**
+     * Notifies the TTY mode change by remote party.
+     *
+     * @param mode one of the following: -
+     *            {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} -
+     *            {@link com.android.internal.telephony.Phone#TTY_MODE_FULL} -
+     *            {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} -
+     *            {@link com.android.internal.telephony.Phone#TTY_MODE_VCO}
+     */
+    @Override
+    public void callSessionTtyModeReceived(IImsCallSession session, int mode) {
+        // no-op
+    }
+
+    /**
+     * Notifies of a change to the multiparty state for this
+     * {@code ImsCallSession}.
+     *
+     * @param session The call session.
+     * @param isMultiParty {@code true} if the session became multiparty,
+     *            {@code false} otherwise.
+     */
+    @Override
+    public void callSessionMultipartyStateChanged(IImsCallSession session, boolean isMultiParty) {
+        // no-op
+    }
+
+    /**
+     * Notifies the supplementary service information for the current session.
+     */
+    @Override
+    public void callSessionSuppServiceReceived(IImsCallSession session,
+            ImsSuppServiceNotification suppSrvNotification) {
+        // no-op
+    }
+}
+
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
new file mode 100644
index 0000000..5a4db99
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.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.ims.stub;
+
+import android.os.RemoteException;
+
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsConfigListener;
+import com.android.ims.internal.IImsConfig;
+
+/**
+ * Base implementation of ImsConfig, which implements stub versions of the methods
+ * in the IImsConfig AIDL. Override the methods that your implementation of ImsConfig supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsConfig maintained by other ImsServices.
+ *
+ * Provides APIs to get/set the IMS service feature/capability/parameters.
+ * The config items include:
+ * 1) Items provisioned by the operator.
+ * 2) Items configured by user. Mainly service feature class.
+ *
+ * @hide
+ */
+
+public class ImsConfigImplBase extends IImsConfig.Stub {
+
+    /**
+     * Gets the value for ims service/capabilities parameters from the provisioned
+     * value storage. Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @return value in Integer format.
+     */
+    @Override
+    public int getProvisionedValue(int item) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Gets the value for ims service/capabilities parameters from the provisioned
+     * value storage. Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @return value in String format.
+     */
+    @Override
+    public String getProvisionedStringValue(int item) throws RemoteException {
+        return null;
+    }
+
+    /**
+     * Sets the value for IMS service/capabilities parameters by the operator device
+     * management entity. It sets the config item value in the provisioned storage
+     * from which the master value is derived. Synchronous blocking call.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @param value in Integer format.
+     * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+     */
+    @Override
+    public int setProvisionedValue(int item, int value) throws RemoteException {
+        return ImsConfig.OperationStatusConstants.FAILED;
+    }
+
+    /**
+     * Sets the value for IMS service/capabilities parameters by the operator device
+     * management entity. It sets the config item value in the provisioned storage
+     * from which the master value is derived.  Synchronous blocking call.
+     *
+     * @param item as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @param value in String format.
+     * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+     */
+    @Override
+    public int setProvisionedStringValue(int item, String value) throws RemoteException {
+        return ImsConfig.OperationStatusConstants.FAILED;
+    }
+
+    /**
+     * Gets the value of the specified IMS feature item for specified network type.
+     * This operation gets the feature config value from the master storage (i.e. final
+     * value). Asynchronous non-blocking call.
+     *
+     * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param listener feature value returned asynchronously through listener.
+     */
+    @Override
+    public void getFeatureValue(int feature, int network, ImsConfigListener listener)
+            throws RemoteException {
+    }
+
+    /**
+     * Sets the value for IMS feature item for specified network type.
+     * This operation stores the user setting in setting db from which master db
+     * is derived.
+     *
+     * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants.
+     * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+     * @param listener, provided if caller needs to be notified for set result.
+     */
+    @Override
+    public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener)
+            throws RemoteException {
+    }
+
+    /**
+     * Gets the value for IMS VoLTE provisioned.
+     * This should be the same as the operator provisioned value if applies.
+     */
+    @Override
+    public boolean getVolteProvisioned() throws RemoteException {
+        return false;
+    }
+
+    /**
+     * Gets the value for IMS feature item video quality.
+     *
+     * @param listener Video quality value returned asynchronously through listener.
+     */
+    @Override
+    public void getVideoQuality(ImsConfigListener listener) throws RemoteException {
+    }
+
+    /**
+     * Sets the value for IMS feature item video quality.
+     *
+     * @param quality, defines the value of video quality.
+     * @param listener, provided if caller needs to be notified for set result.
+     */
+    @Override
+    public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException {
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
new file mode 100644
index 0000000..89f95ff
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
@@ -0,0 +1,51 @@
+/*
+ * 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.ims.stub;
+
+import android.os.RemoteException;
+
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsEcbmListener;
+
+/**
+ * Base implementation of ImsEcbm, which implements stub versions of the methods
+ * in the IImsEcbm AIDL. Override the methods that your implementation of ImsEcbm supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsEcbm maintained by other ImsServices.
+ *
+ * @hide
+ */
+
+public class ImsEcbmImplBase extends IImsEcbm.Stub {
+
+    /**
+     * Sets the listener.
+     */
+    @Override
+    public void setListener(IImsEcbmListener listener) throws RemoteException {
+
+    }
+
+    /**
+     * Requests Modem to come out of ECBM mode
+     */
+    @Override
+    public void exitEmergencyCallbackMode() throws RemoteException {
+
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
new file mode 100644
index 0000000..05da9da
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.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 android.telephony.ims.stub;
+
+import android.os.RemoteException;
+
+import com.android.ims.internal.IImsExternalCallStateListener;
+import com.android.ims.internal.IImsMultiEndpoint;
+
+/**
+ * Base implementation of ImsMultiEndpoint, which implements stub versions of the methods
+ * in the IImsMultiEndpoint AIDL. Override the methods that your implementation of
+ * ImsMultiEndpoint supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsMultiEndpoint maintained by other ImsServices.
+ *
+ * @hide
+ */
+
+public class ImsMultiEndpointImplBase extends IImsMultiEndpoint.Stub {
+
+    /**
+     * Sets the listener.
+     */
+    @Override
+    public void setListener(IImsExternalCallStateListener listener) throws RemoteException {
+
+    }
+
+    /**
+     * Query API to get the latest Dialog Event Package information
+     * Should be invoked only after setListener is done
+     */
+    @Override
+    public void requestImsExternalCallStateInfo() throws RemoteException {
+
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsStreamMediaSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsStreamMediaSessionImplBase.java
new file mode 100644
index 0000000..92f1a01
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsStreamMediaSessionImplBase.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ims.stub;
+
+import android.os.RemoteException;
+
+import com.android.ims.internal.IImsStreamMediaSession;
+
+/**
+ * Base implementation of ImsStreamMediaSession, which implements stub versions of the methods
+ * in the IImsStreamMediaSession AIDL. Override the methods that your implementation of
+ * ImsStreamMediaSession supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsStreamMediaSession maintained by other ImsServices.
+ *
+ * @hide
+ */
+
+public class ImsStreamMediaSessionImplBase extends IImsStreamMediaSession.Stub {
+
+    @Override
+    public void close() throws RemoteException {
+
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
new file mode 100644
index 0000000..dc74094
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
@@ -0,0 +1,174 @@
+/*
+ * 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.ims.stub;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.ims.internal.IImsUt;
+import com.android.ims.internal.IImsUtListener;
+
+/**
+ * Base implementation of ImsUt, which implements stub versions of the methods
+ * in the IImsUt AIDL. Override the methods that your implementation of ImsUt supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsUt maintained by other ImsServices.
+ *
+ * Provides the Ut interface interworking to get/set the supplementary service configuration.
+ *
+ * @hide
+ */
+
+public class ImsUtImplBase extends IImsUt.Stub {
+
+    /**
+     * Closes the object. This object is not usable after being closed.
+     */
+    @Override
+    public void close() throws RemoteException {
+
+    }
+
+    /**
+     * Retrieves the configuration of the call barring.
+     */
+    @Override
+    public int queryCallBarring(int cbType) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Retrieves the configuration of the call forward.
+     */
+    @Override
+    public int queryCallForward(int condition, String number) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Retrieves the configuration of the call waiting.
+     */
+    @Override
+    public int queryCallWaiting() throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Retrieves the default CLIR setting.
+     */
+    @Override
+    public int queryCLIR() throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Retrieves the CLIP call setting.
+     */
+    @Override
+    public int queryCLIP() throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Retrieves the COLR call setting.
+     */
+    @Override
+    public int queryCOLR() throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Retrieves the COLP call setting.
+     */
+    @Override
+    public int queryCOLP() throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates or retrieves the supplementary service configuration.
+     */
+    @Override
+    public int transact(Bundle ssInfo) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the call barring.
+     */
+    @Override
+    public int updateCallBarring(int cbType, int action, String[] barrList) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the call forward.
+     */
+    @Override
+    public int updateCallForward(int action, int condition, String number, int serviceClass,
+            int timeSeconds) throws RemoteException {
+        return 0;
+    }
+
+    /**
+     * Updates the configuration of the call waiting.
+     */
+    @Override
+    public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the CLIR supplementary service.
+     */
+    @Override
+    public int updateCLIR(int clirMode) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the CLIP supplementary service.
+     */
+    @Override
+    public int updateCLIP(boolean enable) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the COLR supplementary service.
+     */
+    @Override
+    public int updateCOLR(int presentation) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Updates the configuration of the COLP supplementary service.
+     */
+    @Override
+    public int updateCOLP(boolean enable) throws RemoteException {
+        return -1;
+    }
+
+    /**
+     * Sets the listener.
+     */
+    @Override
+    public void setListener(IImsUtListener listener) throws RemoteException {
+    }
+}
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
new file mode 100644
index 0000000..b371efb
--- /dev/null
+++ b/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ims.stub;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsSsInfo;
+import com.android.ims.internal.IImsUt;
+import com.android.ims.internal.IImsUtListener;
+
+/**
+ * Base implementation of ImsUtListener, which implements stub versions of the methods
+ * in the IImsUtListener AIDL. Override the methods that your implementation of
+ * ImsUtListener supports.
+ *
+ * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
+ * will break other implementations of ImsUtListener maintained by other ImsServices.
+ *
+ * @hide
+ */
+
+public class ImsUtListenerImplBase extends IImsUtListener.Stub {
+
+    /**
+     * Notifies the result of the supplementary service configuration udpate.
+     */
+    @Override
+    public void utConfigurationUpdated(IImsUt ut, int id) throws RemoteException {
+    }
+
+    @Override
+    public void utConfigurationUpdateFailed(IImsUt ut, int id, ImsReasonInfo error)
+            throws RemoteException {
+    }
+
+    /**
+     * Notifies the result of the supplementary service configuration query.
+     */
+    @Override
+    public void utConfigurationQueried(IImsUt ut, int id, Bundle ssInfo) throws RemoteException {
+    }
+
+    @Override
+    public void utConfigurationQueryFailed(IImsUt ut, int id, ImsReasonInfo error)
+            throws RemoteException {
+    }
+
+    /**
+     * Notifies the status of the call barring supplementary service.
+     */
+    @Override
+    public void utConfigurationCallBarringQueried(IImsUt ut, int id, ImsSsInfo[] cbInfo)
+            throws RemoteException {
+    }
+
+    /**
+     * Notifies the status of the call forwarding supplementary service.
+     */
+    @Override
+    public void utConfigurationCallForwardQueried(IImsUt ut, int id, ImsCallForwardInfo[] cfInfo)
+            throws RemoteException {
+    }
+
+    /**
+     * Notifies the status of the call waiting supplementary service.
+     */
+    @Override
+    public void utConfigurationCallWaitingQueried(IImsUt ut, int id, ImsSsInfo[] cwInfo)
+            throws RemoteException {
+    }
+}
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
new file mode 100644
index 0000000..cd076b1
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -0,0 +1,694 @@
+/*
+ * 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.ims;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.telephony.Rlog;
+
+import com.android.ims.internal.IImsConfig;
+
+/**
+ * Provides APIs to get/set the IMS service feature/capability/parameters.
+ * The config items include:
+ * 1) Items provisioned by the operator.
+ * 2) Items configured by user. Mainly service feature class.
+ *
+ * @hide
+ */
+public class ImsConfig {
+    private static final String TAG = "ImsConfig";
+    private boolean DBG = true;
+    private final IImsConfig miConfig;
+    private Context mContext;
+
+    /**
+     * Broadcast action: the feature enable status was changed
+     *
+     * @hide
+     */
+    public static final String ACTION_IMS_FEATURE_CHANGED =
+            "com.android.intent.action.IMS_FEATURE_CHANGED";
+
+    /**
+     * Broadcast action: the configuration was changed
+     *
+     * @hide
+     */
+    public static final String ACTION_IMS_CONFIG_CHANGED =
+            "com.android.intent.action.IMS_CONFIG_CHANGED";
+
+    /**
+     * Extra parameter "item" of intent ACTION_IMS_FEATURE_CHANGED and ACTION_IMS_CONFIG_CHANGED.
+     * It is the value of FeatureConstants or ConfigConstants.
+     *
+     * @hide
+     */
+    public static final String EXTRA_CHANGED_ITEM = "item";
+
+    /**
+     * Extra parameter "value" of intent ACTION_IMS_FEATURE_CHANGED and ACTION_IMS_CONFIG_CHANGED.
+     * It is the new value of "item".
+     *
+     * @hide
+     */
+    public static final String EXTRA_NEW_VALUE = "value";
+
+    /**
+    * Defines IMS service/capability feature constants.
+    */
+    public static class FeatureConstants {
+        public static final int FEATURE_TYPE_UNKNOWN = -1;
+
+        /**
+         * FEATURE_TYPE_VOLTE supports features defined in 3GPP and
+         * GSMA IR.92 over LTE.
+         */
+        public static final int FEATURE_TYPE_VOICE_OVER_LTE = 0;
+
+        /**
+         * FEATURE_TYPE_LVC supports features defined in 3GPP and
+         * GSMA IR.94 over LTE.
+         */
+        public static final int FEATURE_TYPE_VIDEO_OVER_LTE = 1;
+
+        /**
+         * FEATURE_TYPE_VOICE_OVER_WIFI supports features defined in 3GPP and
+         * GSMA IR.92 over WiFi.
+         */
+        public static final int FEATURE_TYPE_VOICE_OVER_WIFI = 2;
+
+        /**
+         * FEATURE_TYPE_VIDEO_OVER_WIFI supports features defined in 3GPP and
+         * GSMA IR.94 over WiFi.
+         */
+        public static final int FEATURE_TYPE_VIDEO_OVER_WIFI = 3;
+
+        /**
+         * FEATURE_TYPE_UT supports features defined in 3GPP and
+         * GSMA IR.92 over LTE.
+         */
+        public static final int FEATURE_TYPE_UT_OVER_LTE = 4;
+
+       /**
+         * FEATURE_TYPE_UT_OVER_WIFI supports features defined in 3GPP and
+         * GSMA IR.92 over WiFi.
+         */
+        public static final int FEATURE_TYPE_UT_OVER_WIFI = 5;
+    }
+
+    /**
+    * Defines IMS service/capability parameters.
+    */
+    public static class ConfigConstants {
+
+        // Define IMS config items
+        public static final int CONFIG_START = 0;
+
+        // Define operator provisioned config items
+        public static final int PROVISIONED_CONFIG_START = CONFIG_START;
+
+        /**
+         * AMR CODEC Mode Value set, 0-7 in comma separated sequence.
+         * Value is in String format.
+         */
+        public static final int VOCODER_AMRMODESET = CONFIG_START;
+
+        /**
+         * Wide Band AMR CODEC Mode Value set,0-7 in comma separated sequence.
+         * Value is in String format.
+         */
+        public static final int VOCODER_AMRWBMODESET = 1;
+
+        /**
+         * SIP Session Timer value (seconds).
+         * Value is in Integer format.
+         */
+        public static final int SIP_SESSION_TIMER = 2;
+
+        /**
+         * Minimum SIP Session Expiration Timer in (seconds).
+         * Value is in Integer format.
+         */
+        public static final int MIN_SE = 3;
+
+        /**
+         * SIP_INVITE cancellation time out value (in milliseconds). Integer format.
+         * Value is in Integer format.
+         */
+        public static final int CANCELLATION_TIMER = 4;
+
+        /**
+         * Delay time when an iRAT transition from eHRPD/HRPD/1xRTT to LTE.
+         * Value is in Integer format.
+         */
+        public static final int TDELAY = 5;
+
+        /**
+         * Silent redial status of Enabled (True), or Disabled (False).
+         * Value is in Integer format.
+         */
+        public static final int SILENT_REDIAL_ENABLE = 6;
+
+        /**
+         * SIP T1 timer value in milliseconds. See RFC 3261 for define.
+         * Value is in Integer format.
+         */
+        public static final int SIP_T1_TIMER = 7;
+
+        /**
+         * SIP T2 timer value in milliseconds.  See RFC 3261 for define.
+         * Value is in Integer format.
+         */
+        public static final int SIP_T2_TIMER  = 8;
+
+         /**
+         * SIP TF timer value in milliseconds.  See RFC 3261 for define.
+         * Value is in Integer format.
+         */
+        public static final int SIP_TF_TIMER = 9;
+
+        /**
+         * VoLTE status for VLT/s status of Enabled (1), or Disabled (0).
+         * Value is in Integer format.
+         */
+        public static final int VLT_SETTING_ENABLED = 10;
+
+        /**
+         * VoLTE status for LVC/s status of Enabled (1), or Disabled (0).
+         * Value is in Integer format.
+         */
+        public static final int LVC_SETTING_ENABLED = 11;
+        /**
+         * Domain Name for the device to populate the request URI for REGISTRATION.
+         * Value is in String format.
+         */
+        public static final int DOMAIN_NAME = 12;
+         /**
+         * Device Outgoing SMS based on either 3GPP or 3GPP2 standards.
+         * Value is in Integer format. 3GPP2(0), 3GPP(1)
+         */
+        public static final int SMS_FORMAT = 13;
+         /**
+         * Turns IMS ON/OFF on the device.
+         * Value is in Integer format. ON (1), OFF(0).
+         */
+        public static final int SMS_OVER_IP = 14;
+        /**
+         * Requested expiration for Published Online availability.
+         * Value is in Integer format.
+         */
+        public static final int PUBLISH_TIMER = 15;
+        /**
+         * Requested expiration for Published Offline availability.
+         * Value is in Integer format.
+         */
+        public static final int PUBLISH_TIMER_EXTENDED = 16;
+        /**
+         *
+         * Value is in Integer format.
+         */
+        public static final int CAPABILITY_DISCOVERY_ENABLED = 17;
+        /**
+         * Period of time the capability information of the  contact is cached on handset.
+         * Value is in Integer format.
+         */
+        public static final int CAPABILITIES_CACHE_EXPIRATION = 18;
+        /**
+         * Peiod of time the availability information of a contact is cached on device.
+         * Value is in Integer format.
+         */
+        public static final int AVAILABILITY_CACHE_EXPIRATION = 19;
+        /**
+         * Interval between successive capabilities polling.
+         * Value is in Integer format.
+         */
+        public static final int CAPABILITIES_POLL_INTERVAL = 20;
+        /**
+         * Minimum time between two published messages from the device.
+         * Value is in Integer format.
+         */
+        public static final int SOURCE_THROTTLE_PUBLISH = 21;
+        /**
+         * The Maximum number of MDNs contained in one Request Contained List.
+         * Value is in Integer format.
+         */
+        public static final int MAX_NUMENTRIES_IN_RCL = 22;
+        /**
+         * Expiration timer for subscription of a Request Contained List, used in capability
+         * polling.
+         * Value is in Integer format.
+         */
+        public static final int CAPAB_POLL_LIST_SUB_EXP = 23;
+        /**
+         * Applies compression to LIST Subscription.
+         * Value is in Integer format. Enable (1), Disable(0).
+         */
+        public static final int GZIP_FLAG = 24;
+        /**
+         * VOLTE Status for EAB/s status of Enabled (1), or Disabled (0).
+         * Value is in Integer format.
+         */
+        public static final int EAB_SETTING_ENABLED = 25;
+        /**
+         * Wi-Fi calling roaming status.
+         * Value is in Integer format. ON (1), OFF(0).
+         */
+        public static final int VOICE_OVER_WIFI_ROAMING = 26;
+        /**
+         * Wi-Fi calling modem - WfcModeFeatureValueConstants.
+         * Value is in Integer format.
+         */
+        public static final int VOICE_OVER_WIFI_MODE = 27;
+        /**
+         * VOLTE Status for voice over wifi status of Enabled (1), or Disabled (0).
+         * Value is in Integer format.
+         */
+        public static final int VOICE_OVER_WIFI_SETTING_ENABLED = 28;
+        /**
+         * Mobile data enabled.
+         * Value is in Integer format. On (1), OFF(0).
+         */
+        public static final int MOBILE_DATA_ENABLED = 29;
+        /**
+         * VoLTE user opted in status.
+         * Value is in Integer format. Opted-in (1) Opted-out (0).
+         */
+        public static final int VOLTE_USER_OPT_IN_STATUS = 30;
+        /**
+         * Proxy for Call Session Control Function(P-CSCF) address for Local-BreakOut(LBO).
+         * Value is in String format.
+         */
+        public static final int LBO_PCSCF_ADDRESS = 31;
+        /**
+         * Keep Alive Enabled for SIP.
+         * Value is in Integer format. On(1), OFF(0).
+         */
+        public static final int KEEP_ALIVE_ENABLED = 32;
+        /**
+         * Registration retry Base Time value in seconds.
+         * Value is in Integer format.
+         */
+        public static final int REGISTRATION_RETRY_BASE_TIME_SEC = 33;
+        /**
+         * Registration retry Max Time value in seconds.
+         * Value is in Integer format.
+         */
+        public static final int REGISTRATION_RETRY_MAX_TIME_SEC = 34;
+        /**
+         * Smallest RTP port for speech codec.
+         * Value is in integer format.
+         */
+        public static final int SPEECH_START_PORT = 35;
+        /**
+         * Largest RTP port for speech code.
+         * Value is in Integer format.
+         */
+        public static final int SPEECH_END_PORT = 36;
+        /**
+         * SIP Timer A's value in milliseconds. Timer A is the INVITE request
+         * retransmit interval, for UDP only.
+         * Value is in Integer format.
+         */
+        public static final int SIP_INVITE_REQ_RETX_INTERVAL_MSEC = 37;
+        /**
+         * SIP Timer B's value in milliseconds. Timer B is the wait time for
+         * INVITE message to be acknowledged.
+         * Value is in Integer format.
+         */
+        public static final int SIP_INVITE_RSP_WAIT_TIME_MSEC = 38;
+        /**
+         * SIP Timer D's value in milliseconds. Timer D is the wait time for
+         * response retransmits of the invite client transactions.
+         * Value is in Integer format.
+         */
+        public static final int SIP_INVITE_RSP_RETX_WAIT_TIME_MSEC = 39;
+        /**
+         * SIP Timer E's value in milliseconds. Timer E is the value Non-INVITE
+         * request retransmit interval, for UDP only.
+         * Value is in Integer format.
+         */
+        public static final int SIP_NON_INVITE_REQ_RETX_INTERVAL_MSEC = 40;
+        /**
+         * SIP Timer F's value in milliseconds. Timer F is the Non-INVITE transaction
+         * timeout timer.
+         * Value is in Integer format.
+         */
+        public static final int SIP_NON_INVITE_TXN_TIMEOUT_TIMER_MSEC = 41;
+        /**
+         * SIP Timer G's value in milliseconds. Timer G is the value of INVITE response
+         * retransmit interval.
+         * Value is in Integer format.
+         */
+        public static final int SIP_INVITE_RSP_RETX_INTERVAL_MSEC = 42;
+        /**
+         * SIP Timer H's value in milliseconds. Timer H is the value of wait time for
+         * ACK receipt.
+         * Value is in Integer format.
+         */
+        public static final int SIP_ACK_RECEIPT_WAIT_TIME_MSEC = 43;
+        /**
+         * SIP Timer I's value in milliseconds. Timer I is the value of wait time for
+         * ACK retransmits.
+         * Value is in Integer format.
+         */
+        public static final int SIP_ACK_RETX_WAIT_TIME_MSEC = 44;
+        /**
+         * SIP Timer J's value in milliseconds. Timer J is the value of wait time for
+         * non-invite request retransmission.
+         * Value is in Integer format.
+         */
+        public static final int SIP_NON_INVITE_REQ_RETX_WAIT_TIME_MSEC = 45;
+        /**
+         * SIP Timer K's value in milliseconds. Timer K is the value of wait time for
+         * non-invite response retransmits.
+         * Value is in Integer format.
+         */
+        public static final int SIP_NON_INVITE_RSP_RETX_WAIT_TIME_MSEC = 46;
+        /**
+         * AMR WB octet aligned dynamic payload type.
+         * Value is in Integer format.
+         */
+        public static final int AMR_WB_OCTET_ALIGNED_PT = 47;
+        /**
+         * AMR WB bandwidth efficient payload type.
+         * Value is in Integer format.
+         */
+        public static final int AMR_WB_BANDWIDTH_EFFICIENT_PT = 48;
+        /**
+         * AMR octet aligned dynamic payload type.
+         * Value is in Integer format.
+         */
+        public static final int AMR_OCTET_ALIGNED_PT = 49;
+        /**
+         * AMR bandwidth efficient payload type.
+         * Value is in Integer format.
+         */
+        public static final int AMR_BANDWIDTH_EFFICIENT_PT = 50;
+        /**
+         * DTMF WB payload type.
+         * Value is in Integer format.
+         */
+        public static final int DTMF_WB_PT = 51;
+        /**
+         * DTMF NB payload type.
+         * Value is in Integer format.
+         */
+        public static final int DTMF_NB_PT = 52;
+        /**
+         * AMR Default encoding mode.
+         * Value is in Integer format.
+         */
+        public static final int AMR_DEFAULT_MODE = 53;
+        /**
+         * SMS Public Service Identity.
+         * Value is in String format.
+         */
+        public static final int SMS_PSI = 54;
+        /**
+         * Video Quality - VideoQualityFeatureValuesConstants.
+         * Value is in Integer format.
+         */
+        public static final int VIDEO_QUALITY = 55;
+        /**
+         * LTE threshold.
+         * Handover from LTE to WiFi if LTE < THLTE1 and WiFi >= VOWT_A.
+         */
+        public static final int TH_LTE1 = 56;
+        /**
+         * LTE threshold.
+         * Handover from WiFi to LTE if LTE >= THLTE3 or (WiFi < VOWT_B and LTE >= THLTE2).
+         */
+        public static final int TH_LTE2 = 57;
+        /**
+         * LTE threshold.
+         * Handover from WiFi to LTE if LTE >= THLTE3 or (WiFi < VOWT_B and LTE >= THLTE2).
+         */
+        public static final int TH_LTE3 = 58;
+        /**
+         * 1x threshold.
+         * Handover from 1x to WiFi if 1x < TH1x
+         */
+        public static final int TH_1x = 59;
+        /**
+         * WiFi threshold.
+         * Handover from LTE to WiFi if LTE < THLTE1 and WiFi >= VOWT_A.
+         */
+        public static final int VOWT_A = 60;
+        /**
+         * WiFi threshold.
+         * Handover from WiFi to LTE if LTE >= THLTE3 or (WiFi < VOWT_B and LTE >= THLTE2).
+         */
+        public static final int VOWT_B = 61;
+        /**
+         * LTE ePDG timer.
+         * Device shall not handover back to LTE until the T_ePDG_LTE timer expires.
+         */
+        public static final int T_EPDG_LTE = 62;
+        /**
+         * WiFi ePDG timer.
+         * Device shall not handover back to WiFi until the T_ePDG_WiFi timer expires.
+         */
+        public static final int T_EPDG_WIFI = 63;
+        /**
+         * 1x ePDG timer.
+         * Device shall not re-register on 1x until the T_ePDG_1x timer expires.
+         */
+        public static final int T_EPDG_1X = 64;
+        /**
+         * MultiEndpoint status: Enabled (1), or Disabled (0).
+         * Value is in Integer format.
+         */
+        public static final int VICE_SETTING_ENABLED = 65;
+
+        // Expand the operator config items as needed here, need to change
+        // PROVISIONED_CONFIG_END after that.
+        public static final int PROVISIONED_CONFIG_END = VICE_SETTING_ENABLED;
+
+        // Expand the operator config items as needed here.
+    }
+
+    /**
+    * Defines IMS set operation status.
+    */
+    public static class OperationStatusConstants {
+        public static final int UNKNOWN = -1;
+        public static final int SUCCESS = 0;
+        public static final int FAILED =  1;
+        public static final int UNSUPPORTED_CAUSE_NONE = 2;
+        public static final int UNSUPPORTED_CAUSE_RAT = 3;
+        public static final int UNSUPPORTED_CAUSE_DISABLED = 4;
+    }
+
+    /**
+     * Defines IMS get operation values.
+     */
+    public static class OperationValuesConstants {
+        /**
+         * Values related to Video Quality
+         */
+        public static final int VIDEO_QUALITY_UNKNOWN = -1;
+        public static final int VIDEO_QUALITY_LOW = 0;
+        public static final int VIDEO_QUALITY_HIGH = 1;
+    }
+
+    /**
+     * Defines IMS video quality feature value.
+     */
+    public static class VideoQualityFeatureValuesConstants {
+        public static final int LOW = 0;
+        public static final int HIGH = 1;
+    }
+
+   /**
+    * Defines IMS feature value.
+    */
+    public static class FeatureValueConstants {
+        public static final int OFF = 0;
+        public static final int ON = 1;
+    }
+
+    /**
+     * Defines IMS feature value.
+     */
+    public static class WfcModeFeatureValueConstants {
+        public static final int WIFI_ONLY = 0;
+        public static final int CELLULAR_PREFERRED = 1;
+        public static final int WIFI_PREFERRED = 2;
+    }
+
+    public ImsConfig(IImsConfig iconfig, Context context) {
+        if (DBG) Rlog.d(TAG, "ImsConfig creates");
+        miConfig = iconfig;
+        mContext = context;
+    }
+
+    /**
+     * Gets the provisioned value for IMS service/capabilities parameters used by IMS stack.
+     * This function should not be called from the mainthread as it could block the
+     * mainthread.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @return the value in Integer format.
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+    public int getProvisionedValue(int item) throws ImsException {
+        int ret = 0;
+        try {
+            ret = miConfig.getProvisionedValue(item);
+        }  catch (RemoteException e) {
+            throw new ImsException("getValue()", e,
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+        }
+        if (DBG) Rlog.d(TAG, "getProvisionedValue(): item = " + item + ", ret =" + ret);
+
+        return ret;
+    }
+
+    /**
+     * Gets the provisioned value for IMS service/capabilities parameters used by IMS stack.
+     * This function should not be called from the mainthread as it could block the
+     * mainthread.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @return value in String format.
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+    public String getProvisionedStringValue(int item) throws ImsException {
+        String ret = "Unknown";
+        try {
+            ret = miConfig.getProvisionedStringValue(item);
+        }  catch (RemoteException e) {
+            throw new ImsException("getProvisionedStringValue()", e,
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+        }
+        if (DBG) Rlog.d(TAG, "getProvisionedStringValue(): item = " + item + ", ret =" + ret);
+
+        return ret;
+    }
+
+    /**
+     * Sets the value for IMS service/capabilities parameters by
+     * the operator device management entity.
+     * This function should not be called from main thread as it could block
+     * mainthread.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @param value in Integer format.
+     * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+    public int setProvisionedValue(int item, int value)
+            throws ImsException {
+        int ret = OperationStatusConstants.UNKNOWN;
+        if (DBG) {
+            Rlog.d(TAG, "setProvisionedValue(): item = " + item +
+                    "value = " + value);
+        }
+        try {
+            ret = miConfig.setProvisionedValue(item, value);
+        }  catch (RemoteException e) {
+            throw new ImsException("setProvisionedValue()", e,
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+        }
+        if (DBG) {
+            Rlog.d(TAG, "setProvisionedValue(): item = " + item +
+                    " value = " + value + " ret = " + ret);
+        }
+        return ret;
+    }
+
+    /**
+     * Sets the value for IMS service/capabilities parameters by
+     * the operator device management entity.
+     * This function should not be called from main thread as it could block
+     * mainthread.
+     *
+     * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+     * @param value in String format.
+     * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+    public int setProvisionedStringValue(int item, String value)
+            throws ImsException {
+        int ret = OperationStatusConstants.UNKNOWN;
+        try {
+            ret = miConfig.setProvisionedStringValue(item, value);
+        }  catch (RemoteException e) {
+            throw new ImsException("setProvisionedStringValue()", e,
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+        }
+        if (DBG) {
+            Rlog.d(TAG, "setProvisionedStringValue(): item = " + item +
+                    ", value =" + value);
+        }
+        return ret;
+    }
+
+    /**
+     * Gets the value for IMS feature item for specified network type.
+     *
+     * @param feature, defined as in FeatureConstants.
+     * @param network, defined as in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param listener, provided to be notified for the feature on/off status.
+     * @return void
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+    public void getFeatureValue(int feature, int network,
+            ImsConfigListener listener) throws ImsException {
+        if (DBG) {
+            Rlog.d(TAG, "getFeatureValue: feature = " + feature + ", network =" + network +
+                    ", listener =" + listener);
+        }
+        try {
+            miConfig.getFeatureValue(feature, network, listener);
+        } catch (RemoteException e) {
+            throw new ImsException("getFeatureValue()", e,
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * Sets the value for IMS feature item for specified network type.
+     *
+     * @param feature, as defined in FeatureConstants.
+     * @param network, as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+     * @param value, as defined in FeatureValueConstants.
+     * @param listener, provided if caller needs to be notified for set result.
+     * @return void
+     *
+     * @throws ImsException if calling the IMS service results in an error.
+     */
+    public void setFeatureValue(int feature, int network, int value,
+            ImsConfigListener listener) throws ImsException {
+        if (DBG) {
+            Rlog.d(TAG, "setFeatureValue: feature = " + feature + ", network =" + network +
+                    ", value =" + value + ", listener =" + listener);
+        }
+        try {
+            miConfig.setFeatureValue(feature, network, value, listener);
+        } catch (RemoteException e) {
+            throw new ImsException("setFeatureValue()", e,
+                    ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+        }
+    }
+}
diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java
new file mode 100644
index 0000000..74b20f4
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsException.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ims;
+
+/**
+ * This class defines a general IMS-related exception.
+ *
+ * @hide
+ */
+public class ImsException extends Exception {
+
+    /**
+     * Refer to CODE_LOCAL_* in {@link ImsReasonInfo}
+     */
+    private int mCode;
+
+    public ImsException() {
+    }
+
+    public ImsException(String message, int code) {
+        super(message + ", code = " + code);
+        mCode = code;
+    }
+
+    public ImsException(String message, Throwable cause, int code) {
+        super(message, cause);
+        mCode = code;
+    }
+
+    /**
+     * Gets the detailed exception code when ImsException is throwed
+     *
+     * @return the exception code in {@link ImsReasonInfo}
+     */
+    public int getCode() {
+        return mCode;
+    }
+}
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index 56b8822..bd8492b3 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -308,6 +308,77 @@
     public static final int CODE_DATA_DISABLED = 1406;
 
     /**
+     * Indicates a call was disconnected due to loss of wifi signal.
+     */
+    public static final int CODE_WIFI_LOST = 1407;
+
+    /**
+     * Indicates the registration attempt on IWLAN failed due to IKEv2 authetication failure
+     * during tunnel establishment.
+     */
+    public static final int CODE_IKEV2_AUTH_FAILURE = 1408;
+
+    /** The call cannot be established because RADIO is OFF */
+    public static final int CODE_RADIO_OFF = 1500;
+
+    /** The call cannot be established because of no valid SIM */
+    public static final int CODE_NO_VALID_SIM = 1501;
+
+    /** The failure is due internal error at modem */
+    public static final int CODE_RADIO_INTERNAL_ERROR = 1502;
+
+    /** The failure is due to UE timer expired while waiting for a response from network */
+    public static final int CODE_NETWORK_RESP_TIMEOUT = 1503;
+
+    /** The failure is due to explicit reject from network */
+    public static final int CODE_NETWORK_REJECT = 1504;
+
+    /** The failure is due to radio access failure. ex. RACH failure */
+    public static final int CODE_RADIO_ACCESS_FAILURE = 1505;
+
+    /** Call/IMS registration failed/dropped because of a RLF */
+    public static final int CODE_RADIO_LINK_FAILURE = 1506;
+
+    /** Call/IMS registration failed/dropped because of radio link lost */
+    public static final int CODE_RADIO_LINK_LOST = 1507;
+
+    /** The call Call/IMS registration failed because of a radio uplink issue */
+    public static final int CODE_RADIO_UPLINK_FAILURE = 1508;
+
+    /** Call failed because of a RRC connection setup failure */
+    public static final int CODE_RADIO_SETUP_FAILURE = 1509;
+
+    /** Call failed/dropped because of RRC connection release from NW */
+    public static final int CODE_RADIO_RELEASE_NORMAL = 1510;
+
+    /** Call failed/dropped because of RRC abnormally released by modem/network */
+    public static final int CODE_RADIO_RELEASE_ABNORMAL = 1511;
+
+    /** Call failed because of access class barring */
+    public static final int CODE_ACCESS_CLASS_BLOCKED = 1512;
+
+    /** Call/IMS registration is failed/dropped because of a network detach */
+    public static final int CODE_NETWORK_DETACH = 1513;
+
+    /* OEM specific error codes. To be used by OEMs when they don't want to
+   reveal error code which would be replaced by ERROR_UNSPECIFIED */
+    public static final int CODE_OEM_CAUSE_1 = 0xf001;
+    public static final int CODE_OEM_CAUSE_2 = 0xf002;
+    public static final int CODE_OEM_CAUSE_3 = 0xf003;
+    public static final int CODE_OEM_CAUSE_4 = 0xf004;
+    public static final int CODE_OEM_CAUSE_5 = 0xf005;
+    public static final int CODE_OEM_CAUSE_6 = 0xf006;
+    public static final int CODE_OEM_CAUSE_7 = 0xf007;
+    public static final int CODE_OEM_CAUSE_8 = 0xf008;
+    public static final int CODE_OEM_CAUSE_9 = 0xf009;
+    public static final int CODE_OEM_CAUSE_10 = 0xf00a;
+    public static final int CODE_OEM_CAUSE_11 = 0xf00b;
+    public static final int CODE_OEM_CAUSE_12 = 0xf00c;
+    public static final int CODE_OEM_CAUSE_13 = 0xf00d;
+    public static final int CODE_OEM_CAUSE_14 = 0xf00e;
+    public static final int CODE_OEM_CAUSE_15 = 0xf00f;
+
+    /**
      * Network string error messages.
      * mExtraMessage may have these values.
      */
diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java
new file mode 100644
index 0000000..5984e78
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsUtInterface.java
@@ -0,0 +1,179 @@
+/*
+ * 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.ims;
+
+import android.os.Message;
+
+/**
+ * Provides APIs for the supplementary service settings using IMS (Ut interface).
+ * It is created from 3GPP TS 24.623 (XCAP(XML Configuration Access Protocol)
+ * over the Ut interface for manipulating supplementary services).
+ *
+ * @hide
+ */
+public interface ImsUtInterface {
+    /**
+     * Actions
+     * @hide
+     */
+    public static final int ACTION_DEACTIVATION = 0;
+    public static final int ACTION_ACTIVATION = 1;
+    public static final int ACTION_REGISTRATION = 3;
+    public static final int ACTION_ERASURE = 4;
+    public static final int ACTION_INTERROGATION = 5;
+
+    /**
+     * OIR (Originating Identification Restriction, 3GPP TS 24.607)
+     * OIP (Originating Identification Presentation, 3GPP TS 24.607)
+     * TIR (Terminating Identification Restriction, 3GPP TS 24.608)
+     * TIP (Terminating Identification Presentation, 3GPP TS 24.608)
+     */
+    public static final int OIR_DEFAULT = 0;    // "user subscription default value"
+    public static final int OIR_PRESENTATION_RESTRICTED = 1;
+    public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2;
+
+    /**
+     * CW (Communication Waiting, 3GPP TS 24.615)
+     */
+
+    /**
+     * CDIV (Communication Diversion, 3GPP TS 24.604)
+     *     actions: target, no reply timer
+     */
+    public static final int CDIV_CF_UNCONDITIONAL = 0;
+    public static final int CDIV_CF_BUSY = 1;
+    public static final int CDIV_CF_NO_REPLY = 2;
+    public static final int CDIV_CF_NOT_REACHABLE = 3;
+    // For CS service code: 002
+    public static final int CDIV_CF_ALL = 4;
+    // For CS service code: 004
+    public static final int CDIV_CF_ALL_CONDITIONAL = 5;
+    // It's only supported in the IMS service (CS does not define it).
+    // IR.92 recommends that an UE activates both the CFNRc and the CFNL
+    // (CDIV using condition not-registered) to the same target.
+    public static final int CDIV_CF_NOT_LOGGED_IN = 6;
+
+    /**
+     * CB (Communication Barring, 3GPP TS 24.611)
+     */
+    // Barring of All Incoming Calls
+    public static final int CB_BAIC = 1;
+    // Barring of All Outgoing Calls
+    public static final int CB_BAOC = 2;
+    // Barring of Outgoing International Calls
+    public static final int CB_BOIC = 3;
+    // Barring of Outgoing International Calls - excluding Home Country
+    public static final int CB_BOIC_EXHC = 4;
+    // Barring of Incoming Calls - when roaming
+    public static final int CB_BIC_WR = 5;
+    // Barring of Anonymous Communication Rejection (ACR) - a particular case of ICB service
+    public static final int CB_BIC_ACR = 6;
+    // Barring of All Calls
+    public static final int CB_BA_ALL = 7;
+    // Barring of Outgoing Services (Service Code 333 - 3GPP TS 22.030 Table B-1)
+    public static final int CB_BA_MO = 8;
+    // Barring of Incoming Services (Service Code 353 - 3GPP TS 22.030 Table B-1)
+    public static final int CB_BA_MT = 9;
+    // Barring of Specific Incoming calls
+    public static final int CB_BS_MT = 10;
+
+    /**
+     * Invalid result value.
+     */
+    public static final int INVALID = (-1);
+
+
+
+    /**
+     * Operations for the supplementary service configuration
+     */
+
+    /**
+     * Retrieves the configuration of the call barring.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
+     */
+    public void queryCallBarring(int cbType, Message result);
+
+    /**
+     * Retrieves the configuration of the call forward.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsCallForwardInfo}.
+     */
+    public void queryCallForward(int condition, String number, Message result);
+
+    /**
+     * Retrieves the configuration of the call waiting.
+     * The return value of ((AsyncResult)result.obj) is an array of {@link ImsSsInfo}.
+     */
+    public void queryCallWaiting(Message result);
+
+    /**
+     * Retrieves the default CLIR setting.
+     */
+    public void queryCLIR(Message result);
+
+    /**
+     * Retrieves the CLIP call setting.
+     */
+    public void queryCLIP(Message result);
+
+    /**
+     * Retrieves the COLR call setting.
+     */
+    public void queryCOLR(Message result);
+
+    /**
+     * Retrieves the COLP call setting.
+     */
+    public void queryCOLP(Message result);
+
+    /**
+     * Modifies the configuration of the call barring.
+     */
+    public void updateCallBarring(int cbType, int action,
+            Message result, String[] barrList);
+
+    /**
+     * Modifies the configuration of the call forward.
+     */
+    public void updateCallForward(int action, int condition, String number,
+            int serviceClass, int timeSeconds, Message result);
+
+    /**
+     * Modifies the configuration of the call waiting.
+     */
+    public void updateCallWaiting(boolean enable, int serviceClass, Message result);
+
+    /**
+     * Updates the configuration of the CLIR supplementary service.
+     */
+    public void updateCLIR(int clirMode, Message result);
+
+    /**
+     * Updates the configuration of the CLIP supplementary service.
+     */
+    public void updateCLIP(boolean enable, Message result);
+
+    /**
+     * Updates the configuration of the COLR supplementary service.
+     */
+    public void updateCOLR(int presentation, Message result);
+
+    /**
+     * Updates the configuration of the COLP supplementary service.
+     */
+    public void updateCOLP(boolean enable, Message result);
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl
similarity index 60%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl
index 62d5603..41b1042 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
+/*
+ * 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
+ *      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,
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package com.android.ims.internal;
 
-parcelable HomeSP;
+/**
+*  Interface from ImsFeature in the ImsService to ImsServiceController.
+ * {@hide}
+ */
+oneway interface IImsFeatureStatusCallback {
+    void notifyImsFeatureStatus(int featureStatus);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
new file mode 100644
index 0000000..712816f
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -0,0 +1,64 @@
+/*
+ * 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.ims.internal;
+
+import android.app.PendingIntent;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsUt;
+
+import android.os.Message;
+
+/**
+ * See ImsService and IMMTelFeature for more information.
+ * {@hide}
+ */
+interface IImsServiceController {
+    // ImsService Control
+    void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
+    void removeImsFeature(int slotId, int feature);
+    // MMTel Feature
+    int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
+            in IImsRegistrationListener listener);
+    void endSession(int slotId, int featureType, int sessionId);
+    boolean isConnected(int slotId, int featureType, int callSessionType, int callType);
+    boolean isOpened(int slotId, int featureType);
+    int getFeatureStatus(int slotId, int featureType);
+    void addRegistrationListener(int slotId, int featureType, in IImsRegistrationListener listener);
+    void removeRegistrationListener(int slotId, int featureType,
+            in IImsRegistrationListener listener);
+    ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
+            int callSessionType, int callType);
+    IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
+            in ImsCallProfile profile, IImsCallSessionListener listener);
+    IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
+            String callId);
+    IImsUt getUtInterface(int slotId, int featureType);
+    IImsConfig getConfigInterface(int slotId, int featureType);
+    void turnOnIms(int slotId, int featureType);
+    void turnOffIms(int slotId, int featureType);
+    IImsEcbm getEcbmInterface(int slotId, int featureType);
+    void setUiTTYMode(int slotId, int featureType, int uiTtyMode, in Message onComplete);
+    IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
new file mode 100644
index 0000000..df10700
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.ims.internal;
+
+/**
+ *  Interface from ImsResolver to ImsServiceProxy in ImsManager.
+ * Callback to ImsManager when a feature changes in the ImsServiceController.
+ * {@hide}
+ */
+oneway interface IImsServiceFeatureListener {
+    void imsFeatureCreated(int slotId, int feature);
+    void imsFeatureRemoved(int slotId, int feature);
+    void imsStatusChanged(int slotId, int feature, int status);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl b/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl
index 39e83c6..0da27e1 100644
--- a/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl
+++ b/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl
@@ -43,7 +43,7 @@
 oneway interface IImsVideoCallProvider {
     void setCallback(IImsVideoCallCallback callback);
 
-    void setCamera(String cameraId);
+    void setCamera(String cameraId, int uid);
 
     void setPreviewSurface(in Surface surface);
 
diff --git a/telephony/java/com/android/ims/internal/ImsCallSession.java b/telephony/java/com/android/ims/internal/ImsCallSession.java
new file mode 100644
index 0000000..8196b23
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/ImsCallSession.java
@@ -0,0 +1,1290 @@
+/*
+ * 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.ims.internal;
+
+import android.os.Message;
+import android.os.RemoteException;
+
+import java.util.Objects;
+
+import android.telephony.ims.stub.ImsCallSessionListenerImplBase;
+import android.util.Log;
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsConferenceState;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.ImsSuppServiceNotification;
+
+/**
+ * Provides the call initiation/termination, and media exchange between two IMS endpoints.
+ * It directly communicates with IMS service which implements the IMS protocol behavior.
+ *
+ * @hide
+ */
+public class ImsCallSession {
+    private static final String TAG = "ImsCallSession";
+
+    /**
+     * Defines IMS call session state.
+     */
+    public static class State {
+        public static final int IDLE = 0;
+        public static final int INITIATED = 1;
+        public static final int NEGOTIATING = 2;
+        public static final int ESTABLISHING = 3;
+        public static final int ESTABLISHED = 4;
+
+        public static final int RENEGOTIATING = 5;
+        public static final int REESTABLISHING = 6;
+
+        public static final int TERMINATING = 7;
+        public static final int TERMINATED = 8;
+
+        public static final int INVALID = (-1);
+
+        /**
+         * Converts the state to string.
+         */
+        public static String toString(int state) {
+            switch (state) {
+                case IDLE:
+                    return "IDLE";
+                case INITIATED:
+                    return "INITIATED";
+                case NEGOTIATING:
+                    return "NEGOTIATING";
+                case ESTABLISHING:
+                    return "ESTABLISHING";
+                case ESTABLISHED:
+                    return "ESTABLISHED";
+                case RENEGOTIATING:
+                    return "RENEGOTIATING";
+                case REESTABLISHING:
+                    return "REESTABLISHING";
+                case TERMINATING:
+                    return "TERMINATING";
+                case TERMINATED:
+                    return "TERMINATED";
+                default:
+                    return "UNKNOWN";
+            }
+        }
+
+        private State() {
+        }
+    }
+
+    /**
+     * Listener for events relating to an IMS session, such as when a session is being
+     * recieved ("on ringing") or a call is outgoing ("on calling").
+     * <p>Many of these events are also received by {@link ImsCall.Listener}.</p>
+     */
+    public static class Listener {
+        /**
+         * Called when a request is sent out to initiate a new session
+         * and 1xx response is received from the network.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionProgressing(ImsCallSession session,
+                ImsStreamMediaProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the session is established.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionStarted(ImsCallSession session,
+                ImsCallProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the session establishment is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session establishment failure
+         */
+        public void callSessionStartFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session is terminated.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session termination
+         */
+        public void callSessionTerminated(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session is in hold.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionHeld(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session hold is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session hold failure
+         */
+        public void callSessionHoldFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session hold is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionHoldReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session resume is done.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionResumed(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session resume is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session resume failure
+         */
+        public void callSessionResumeFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session resume is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionResumeReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session merge has been started.  At this point, the {@code newSession}
+         * represents the session which has been initiated to the IMS conference server for the
+         * new merged conference.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param newSession the session object that is merged with an active & hold session
+         */
+        public void callSessionMergeStarted(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session merge is successful and the merged session is active.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionMergeComplete(ImsCallSession session) {
+        }
+
+        /**
+         * Called when the session merge has failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the call merge failure
+         */
+        public void callSessionMergeFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session is updated (except for hold/unhold).
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionUpdated(ImsCallSession session,
+                ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the session update is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the session update failure
+         */
+        public void callSessionUpdateFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the session update is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionUpdateReceived(ImsCallSession session,
+                ImsCallProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the session is extended to the conference session.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param newSession the session object that is extended to the conference
+         *      from the active session
+         */
+        public void callSessionConferenceExtended(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+        }
+
+        /**
+         * Called when the conference extension is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the conference extension failure
+         */
+        public void callSessionConferenceExtendFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+        }
+
+        /**
+         * Called when the conference extension is received from the remote user.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionConferenceExtendReceived(ImsCallSession session,
+                ImsCallSession newSession, ImsCallProfile profile) {
+            // no-op
+        }
+
+        /**
+         * Called when the invitation request of the participants is delivered to the conference
+         * server.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
+            // no-op
+        }
+
+        /**
+         * Called when the invitation request of the participants is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the conference invitation failure
+         */
+        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when the removal request of the participants is delivered to the conference
+         * server.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
+            // no-op
+        }
+
+        /**
+         * Called when the removal request of the participants is failed.
+         *
+         * @param session the session object that carries out the IMS session
+         * @param reasonInfo detailed reason of the conference removal failure
+         */
+        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when the conference state is updated.
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionConferenceStateUpdated(ImsCallSession session,
+                ImsConferenceState state) {
+            // no-op
+        }
+
+        /**
+         * Called when the USSD message is received from the network.
+         *
+         * @param mode mode of the USSD message (REQUEST / NOTIFY)
+         * @param ussdMessage USSD message
+         */
+        public void callSessionUssdMessageReceived(ImsCallSession session,
+                int mode, String ussdMessage) {
+            // no-op
+        }
+
+        /**
+         * Called when session access technology changes
+         *
+         * @param session IMS session object
+         * @param srcAccessTech original access technology
+         * @param targetAccessTech new access technology
+         * @param reasonInfo
+         */
+        public void callSessionHandover(ImsCallSession session,
+                                 int srcAccessTech, int targetAccessTech,
+                                 ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when session access technology change fails
+         *
+         * @param session IMS session object
+         * @param srcAccessTech original access technology
+         * @param targetAccessTech new access technology
+         * @param reasonInfo handover failure reason
+         */
+        public void callSessionHandoverFailed(ImsCallSession session,
+                                       int srcAccessTech, int targetAccessTech,
+                                       ImsReasonInfo reasonInfo) {
+            // no-op
+        }
+
+        /**
+         * Called when TTY mode of remote party changed
+         *
+         * @param session IMS session object
+         * @param mode TTY mode of remote party
+         */
+        public void callSessionTtyModeReceived(ImsCallSession session,
+                                       int mode) {
+            // no-op
+        }
+
+        /**
+         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
+         *
+         * @param session The call session.
+         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
+         *      otherwise.
+         */
+        public void callSessionMultipartyStateChanged(ImsCallSession session,
+                boolean isMultiParty) {
+            // no-op
+        }
+
+        /**
+         * Called when the session supplementary service is received
+         *
+         * @param session the session object that carries out the IMS session
+         */
+        public void callSessionSuppServiceReceived(ImsCallSession session,
+                ImsSuppServiceNotification suppServiceInfo) {
+        }
+    }
+
+    private final IImsCallSession miSession;
+    private boolean mClosed = false;
+    private Listener mListener;
+
+    public ImsCallSession(IImsCallSession iSession) {
+        miSession = iSession;
+
+        if (iSession != null) {
+            try {
+                iSession.setListener(new IImsCallSessionListenerProxy());
+            } catch (RemoteException e) {
+            }
+        } else {
+            mClosed = true;
+        }
+    }
+
+    public ImsCallSession(IImsCallSession iSession, Listener listener) {
+        this(iSession);
+        setListener(listener);
+    }
+
+    /**
+     * Closes this object. This object is not usable after being closed.
+     */
+    public synchronized void close() {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.close();
+            mClosed = true;
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Gets the call ID of the session.
+     *
+     * @return the call ID
+     */
+    public String getCallId() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getCallId();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the call profile that this session is associated with
+     *
+     * @return the call profile that this session is associated with
+     */
+    public ImsCallProfile getCallProfile() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getCallProfile();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the local call profile that this session is associated with
+     *
+     * @return the local call profile that this session is associated with
+     */
+    public ImsCallProfile getLocalCallProfile() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getLocalCallProfile();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the remote call profile that this session is associated with
+     *
+     * @return the remote call profile that this session is associated with
+     */
+    public ImsCallProfile getRemoteCallProfile() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getRemoteCallProfile();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the video call provider for the session.
+     *
+     * @return The video call provider.
+     */
+    public IImsVideoCallProvider getVideoCallProvider() {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getVideoCallProvider();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the value associated with the specified property of this session.
+     *
+     * @return the string value associated with the specified property
+     */
+    public String getProperty(String name) {
+        if (mClosed) {
+            return null;
+        }
+
+        try {
+            return miSession.getProperty(name);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the session state.
+     * The value returned must be one of the states in {@link State}.
+     *
+     * @return the session state
+     */
+    public int getState() {
+        if (mClosed) {
+            return State.INVALID;
+        }
+
+        try {
+            return miSession.getState();
+        } catch (RemoteException e) {
+            return State.INVALID;
+        }
+    }
+
+    /**
+     * Determines if the {@link ImsCallSession} is currently alive (e.g. not in a terminated or
+     * closed state).
+     *
+     * @return {@code True} if the session is alive.
+     */
+    public boolean isAlive() {
+        if (mClosed) {
+            return false;
+        }
+
+        int state = getState();
+        switch (state) {
+            case State.IDLE:
+            case State.INITIATED:
+            case State.NEGOTIATING:
+            case State.ESTABLISHING:
+            case State.ESTABLISHED:
+            case State.RENEGOTIATING:
+            case State.REESTABLISHING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Gets the native IMS call session.
+     * @hide
+     */
+    public IImsCallSession getSession() {
+        return miSession;
+    }
+
+    /**
+     * Checks if the session is in call.
+     *
+     * @return true if the session is in call
+     */
+    public boolean isInCall() {
+        if (mClosed) {
+            return false;
+        }
+
+        try {
+            return miSession.isInCall();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Sets the listener to listen to the session events. A {@link ImsCallSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the session events of this object
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Mutes or unmutes the mic for the active call.
+     *
+     * @param muted true if the call is muted, false otherwise
+     */
+    public void setMute(boolean muted) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.setMute(muted);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Initiates an IMS call with the specified target and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession.State#IDLE}.
+     *
+     * @param callee dialed string to make the call to
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    public void start(String callee, ImsCallProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.start(callee, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Initiates an IMS conference call with the specified target and call profile.
+     * The session listener is called back upon defined session events.
+     * The method is only valid to call when the session state is in
+     * {@link ImsCallSession.State#IDLE}.
+     *
+     * @param participants participant list to initiate an IMS conference call
+     * @param profile call profile to make the call with the specified service type,
+     *      call type and media information
+     * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+     */
+    public void start(String[] participants, ImsCallProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.startConference(participants, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Accepts an incoming call or session update.
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be answered
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
+     * @see Listener#callSessionStarted
+     */
+    public void accept(int callType, ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.accept(callType, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Rejects an incoming call or session update.
+     *
+     * @param reason reason code to reject an incoming call
+     * @see Listener#callSessionStartFailed
+     */
+    public void reject(int reason) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.reject(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Terminates a call.
+     *
+     * @see Listener#callSessionTerminated
+     */
+    public void terminate(int reason) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.terminate(reason);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
+     * @see Listener#callSessionHeld, Listener#callSessionHoldFailed
+     */
+    public void hold(ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.hold(profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Continues a call that's on hold. When it succeeds,
+     * {@link Listener#callSessionResumed} is called.
+     *
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call
+     * @see Listener#callSessionResumed, Listener#callSessionResumeFailed
+     */
+    public void resume(ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.resume(profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Merges the active & hold call. When it succeeds,
+     * {@link Listener#callSessionMergeStarted} is called.
+     *
+     * @see Listener#callSessionMergeStarted , Listener#callSessionMergeFailed
+     */
+    public void merge() {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.merge();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+     *
+     * @param callType call type specified in {@link ImsCallProfile} to be updated
+     * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
+     * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed
+     */
+    public void update(int callType, ImsStreamMediaProfile profile) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.update(callType, profile);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Extends this call to the conference call with the specified recipients.
+     *
+     * @param participants list to be invited to the conference call after extending the call
+     * @see Listener#callSessionConferenceExtended
+     * @see Listener#callSessionConferenceExtendFailed
+     */
+    public void extendToConference(String[] participants) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.extendToConference(participants);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Requests the conference server to invite an additional participants to the conference.
+     *
+     * @param participants list to be invited to the conference call
+     * @see Listener#callSessionInviteParticipantsRequestDelivered
+     * @see Listener#callSessionInviteParticipantsRequestFailed
+     */
+    public void inviteParticipants(String[] participants) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.inviteParticipants(participants);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Requests the conference server to remove the specified participants from the conference.
+     *
+     * @param participants participant list to be removed from the conference call
+     * @see Listener#callSessionRemoveParticipantsRequestDelivered
+     * @see Listener#callSessionRemoveParticipantsRequestFailed
+     */
+    public void removeParticipants(String[] participants) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.removeParticipants(participants);
+        } catch (RemoteException e) {
+        }
+    }
+
+
+    /**
+     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
+     */
+    public void sendDtmf(char c, Message result) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.sendDtmf(c, result);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Starts a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+     * and event flash to 16. Currently, event flash is not supported.
+     *
+     * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
+     */
+    public void startDtmf(char c) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.startDtmf(c);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Stops a DTMF code.
+     */
+    public void stopDtmf() {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.stopDtmf();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Sends an USSD message.
+     *
+     * @param ussdMessage USSD message to send
+     */
+    public void sendUssd(String ussdMessage) {
+        if (mClosed) {
+            return;
+        }
+
+        try {
+            miSession.sendUssd(ussdMessage);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Determines if the session is multiparty.
+     *
+     * @return {@code True} if the session is multiparty.
+     */
+    public boolean isMultiparty() {
+        if (mClosed) {
+            return false;
+        }
+
+        try {
+            return miSession.isMultiparty();
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * A listener type for receiving notification on IMS call session events.
+     * When an event is generated for an {@link IImsCallSession},
+     * the application is notified by having one of the methods called on
+     * the {@link IImsCallSessionListener}.
+     */
+    private class IImsCallSessionListenerProxy extends ImsCallSessionListenerImplBase {
+        /**
+         * Notifies the result of the basic session operation (setup / terminate).
+         */
+        @Override
+        public void callSessionProgressing(IImsCallSession session,
+                ImsStreamMediaProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionProgressing(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionStarted(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionStarted(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionStartFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionTerminated(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the result of the call hold/resume operation.
+         */
+        @Override
+        public void callSessionHeld(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionHeld(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionHoldFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionHoldReceived(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionResumed(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionResumed(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionResumeFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionResumeReceived(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+            }
+        }
+
+        /**
+         * Notifies the start of a call merge operation.
+         *
+         * @param session The call session.
+         * @param newSession The merged call session.
+         * @param profile The call profile.
+         */
+        @Override
+        public void callSessionMergeStarted(IImsCallSession session,
+                IImsCallSession newSession, ImsCallProfile profile) {
+            // This callback can be used for future use to add additional
+            // functionality that may be needed between conference start and complete
+            Log.d(TAG, "callSessionMergeStarted");
+        }
+
+        /**
+         * Notifies the successful completion of a call merge operation.
+         *
+         * @param newSession The call session.
+         */
+        @Override
+        public void callSessionMergeComplete(IImsCallSession newSession) {
+            if (mListener != null) {
+                if (newSession != null) {
+                    // Check if the active session is the same session that was
+                    // active before the merge request was sent.
+                    ImsCallSession validActiveSession = ImsCallSession.this;
+                    try {
+                        if (!Objects.equals(miSession.getCallId(), newSession.getCallId())) {
+                            // New session created after conference
+                            validActiveSession = new ImsCallSession(newSession);
+                        }
+                    } catch (RemoteException rex) {
+                        Log.e(TAG, "callSessionMergeComplete: exception for getCallId!");
+                    }
+                    mListener.callSessionMergeComplete(validActiveSession);
+               } else {
+                   // Session already exists. Hence no need to pass
+                   mListener.callSessionMergeComplete(null);
+               }
+            }
+        }
+
+        /**
+         * Notifies of a failure to perform a call merge operation.
+         *
+         * @param session The call session.
+         * @param reasonInfo The merge failure reason.
+         */
+        @Override
+        public void callSessionMergeFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the result of call upgrade / downgrade or any other call updates.
+         */
+        @Override
+        public void callSessionUpdated(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionUpdated(ImsCallSession.this, profile);
+            }
+        }
+
+        @Override
+        public void callSessionUpdateFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionUpdateReceived(IImsCallSession session,
+                ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+            }
+        }
+
+        /**
+         * Notifies the result of conference extension.
+         */
+        @Override
+        public void callSessionConferenceExtended(IImsCallSession session,
+                IImsCallSession newSession, ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionConferenceExtended(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile);
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtendFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionConferenceExtendReceived(IImsCallSession session,
+                IImsCallSession newSession, ImsCallProfile profile) {
+            if (mListener != null) {
+                mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile);
+            }
+        }
+
+        /**
+         * Notifies the result of the participant invitation / removal to/from
+         * the conference session.
+         */
+        @Override
+        public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) {
+            if (mListener != null) {
+                mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
+            }
+        }
+
+        @Override
+        public void callSessionInviteParticipantsRequestFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo);
+            }
+        }
+
+        @Override
+        public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) {
+            if (mListener != null) {
+                mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+            }
+        }
+
+        @Override
+        public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session,
+                ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the changes of the conference info. in the conference session.
+         */
+        @Override
+        public void callSessionConferenceStateUpdated(IImsCallSession session,
+                ImsConferenceState state) {
+            if (mListener != null) {
+                mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+            }
+        }
+
+        /**
+         * Notifies the incoming USSD message.
+         */
+        @Override
+        public void callSessionUssdMessageReceived(IImsCallSession session,
+                int mode, String ussdMessage) {
+            if (mListener != null) {
+                mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
+            }
+        }
+
+        /**
+         * Notifies of handover information for this call
+         */
+        @Override
+        public void callSessionHandover(IImsCallSession session,
+                                 int srcAccessTech, int targetAccessTech,
+                                 ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionHandover(ImsCallSession.this, srcAccessTech,
+                        targetAccessTech, reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies of handover failure info for this call
+         */
+        @Override
+        public void callSessionHandoverFailed(IImsCallSession session,
+                                       int srcAccessTech, int targetAccessTech,
+                                       ImsReasonInfo reasonInfo) {
+            if (mListener != null) {
+                mListener.callSessionHandoverFailed(ImsCallSession.this, srcAccessTech,
+                        targetAccessTech, reasonInfo);
+            }
+        }
+
+        /**
+         * Notifies the TTY mode received from remote party.
+         */
+        @Override
+        public void callSessionTtyModeReceived(IImsCallSession session,
+                int mode) {
+            if (mListener != null) {
+                mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
+            }
+        }
+
+        /**
+         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
+         *
+         * @param session The call session.
+         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
+         *      otherwise.
+         */
+        public void callSessionMultipartyStateChanged(IImsCallSession session,
+                boolean isMultiParty) {
+
+            if (mListener != null) {
+                mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
+            }
+        }
+
+        @Override
+        public void callSessionSuppServiceReceived(IImsCallSession session,
+                ImsSuppServiceNotification suppServiceInfo ) {
+            if (mListener != null) {
+                mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo);
+            }
+        }
+
+    }
+
+    /**
+     * Provides a string representation of the {@link ImsCallSession}.  Primarily intended for
+     * use in log statements.
+     *
+     * @return String representation of session.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[ImsCallSession objId:");
+        sb.append(System.identityHashCode(this));
+        sb.append(" state:");
+        sb.append(State.toString(getState()));
+        sb.append(" callId:");
+        sb.append(getCallId());
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java b/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java
new file mode 100644
index 0000000..432dc39
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java
@@ -0,0 +1,294 @@
+/*
+ * 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.ims.internal;
+
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telecom.Connection;
+import android.telecom.VideoProfile;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.view.Surface;
+
+import com.android.internal.os.SomeArgs;
+
+/**
+ * @hide
+ */
+public abstract class ImsVideoCallProvider {
+    private static final int MSG_SET_CALLBACK = 1;
+    private static final int MSG_SET_CAMERA = 2;
+    private static final int MSG_SET_PREVIEW_SURFACE = 3;
+    private static final int MSG_SET_DISPLAY_SURFACE = 4;
+    private static final int MSG_SET_DEVICE_ORIENTATION = 5;
+    private static final int MSG_SET_ZOOM = 6;
+    private static final int MSG_SEND_SESSION_MODIFY_REQUEST = 7;
+    private static final int MSG_SEND_SESSION_MODIFY_RESPONSE = 8;
+    private static final int MSG_REQUEST_CAMERA_CAPABILITIES = 9;
+    private static final int MSG_REQUEST_CALL_DATA_USAGE = 10;
+    private static final int MSG_SET_PAUSE_IMAGE = 11;
+
+    private final ImsVideoCallProviderBinder mBinder;
+
+    private IImsVideoCallCallback mCallback;
+
+    /**
+     * Default handler used to consolidate binder method calls onto a single thread.
+     */
+    private final Handler mProviderHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_CALLBACK:
+                    mCallback = (IImsVideoCallCallback) msg.obj;
+                    break;
+                case MSG_SET_CAMERA:
+                {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        onSetCamera((String) args.arg1);
+                        onSetCamera((String) args.arg1, args.argi1);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SET_PREVIEW_SURFACE:
+                    onSetPreviewSurface((Surface) msg.obj);
+                    break;
+                case MSG_SET_DISPLAY_SURFACE:
+                    onSetDisplaySurface((Surface) msg.obj);
+                    break;
+                case MSG_SET_DEVICE_ORIENTATION:
+                    onSetDeviceOrientation(msg.arg1);
+                    break;
+                case MSG_SET_ZOOM:
+                    onSetZoom((Float) msg.obj);
+                    break;
+                case MSG_SEND_SESSION_MODIFY_REQUEST: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        VideoProfile fromProfile = (VideoProfile) args.arg1;
+                        VideoProfile toProfile = (VideoProfile) args.arg2;
+
+                        onSendSessionModifyRequest(fromProfile, toProfile);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                }
+                case MSG_SEND_SESSION_MODIFY_RESPONSE:
+                    onSendSessionModifyResponse((VideoProfile) msg.obj);
+                    break;
+                case MSG_REQUEST_CAMERA_CAPABILITIES:
+                    onRequestCameraCapabilities();
+                    break;
+                case MSG_REQUEST_CALL_DATA_USAGE:
+                    onRequestCallDataUsage();
+                    break;
+                case MSG_SET_PAUSE_IMAGE:
+                    onSetPauseImage((Uri) msg.obj);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    /**
+     * IImsVideoCallProvider stub implementation.
+     */
+    private final class ImsVideoCallProviderBinder extends IImsVideoCallProvider.Stub {
+        public void setCallback(IImsVideoCallCallback callback) {
+            mProviderHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget();
+        }
+
+        public void setCamera(String cameraId, int uid) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = cameraId;
+            args.argi1 = uid;
+            mProviderHandler.obtainMessage(MSG_SET_CAMERA, args).sendToTarget();
+        }
+
+        public void setPreviewSurface(Surface surface) {
+            mProviderHandler.obtainMessage(MSG_SET_PREVIEW_SURFACE, surface).sendToTarget();
+        }
+
+        public void setDisplaySurface(Surface surface) {
+            mProviderHandler.obtainMessage(MSG_SET_DISPLAY_SURFACE, surface).sendToTarget();
+        }
+
+        public void setDeviceOrientation(int rotation) {
+            mProviderHandler.obtainMessage(MSG_SET_DEVICE_ORIENTATION, rotation, 0).sendToTarget();
+        }
+
+        public void setZoom(float value) {
+            mProviderHandler.obtainMessage(MSG_SET_ZOOM, value).sendToTarget();
+        }
+
+        public void sendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = fromProfile;
+            args.arg2 = toProfile;
+            mProviderHandler.obtainMessage(MSG_SEND_SESSION_MODIFY_REQUEST, args).sendToTarget();
+        }
+
+        public void sendSessionModifyResponse(VideoProfile responseProfile) {
+            mProviderHandler.obtainMessage(
+                    MSG_SEND_SESSION_MODIFY_RESPONSE, responseProfile).sendToTarget();
+        }
+
+        public void requestCameraCapabilities() {
+            mProviderHandler.obtainMessage(MSG_REQUEST_CAMERA_CAPABILITIES).sendToTarget();
+        }
+
+        public void requestCallDataUsage() {
+            mProviderHandler.obtainMessage(MSG_REQUEST_CALL_DATA_USAGE).sendToTarget();
+        }
+
+        public void setPauseImage(Uri uri) {
+            mProviderHandler.obtainMessage(MSG_SET_PAUSE_IMAGE, uri).sendToTarget();
+        }
+    }
+
+    public ImsVideoCallProvider() {
+        mBinder = new ImsVideoCallProviderBinder();
+    }
+
+    /**
+     * Returns binder object which can be used across IPC methods.
+     */
+    public final IImsVideoCallProvider getInterface() {
+        return mBinder;
+    }
+
+    /** @see Connection.VideoProvider#onSetCamera */
+    public abstract void onSetCamera(String cameraId);
+
+    /**
+     * Similar to {@link #onSetCamera(String)}, except includes the UID of the calling process which
+     * the IMS service uses when opening the camera.  This ensures camera permissions are verified
+     * by the camera service.
+     *
+     * @param cameraId The id of the camera to be opened.
+     * @param uid The uid of the caller, used when opening the camera for permission verification.
+     * @see Connection.VideoProvider#onSetCamera
+     */
+    public void onSetCamera(String cameraId, int uid) {
+    }
+
+    /** @see Connection.VideoProvider#onSetPreviewSurface */
+    public abstract void onSetPreviewSurface(Surface surface);
+
+    /** @see Connection.VideoProvider#onSetDisplaySurface */
+    public abstract void onSetDisplaySurface(Surface surface);
+
+    /** @see Connection.VideoProvider#onSetDeviceOrientation */
+    public abstract void onSetDeviceOrientation(int rotation);
+
+    /** @see Connection.VideoProvider#onSetZoom */
+    public abstract void onSetZoom(float value);
+
+    /** @see Connection.VideoProvider#onSendSessionModifyRequest */
+    public abstract void onSendSessionModifyRequest(VideoProfile fromProfile,
+            VideoProfile toProfile);
+
+    /** @see Connection.VideoProvider#onSendSessionModifyResponse */
+    public abstract void onSendSessionModifyResponse(VideoProfile responseProfile);
+
+    /** @see Connection.VideoProvider#onRequestCameraCapabilities */
+    public abstract void onRequestCameraCapabilities();
+
+    /** @see Connection.VideoProvider#onRequestCallDataUsage */
+    public abstract void onRequestCallDataUsage();
+
+    /** @see Connection.VideoProvider#onSetPauseImage */
+    public abstract void onSetPauseImage(Uri uri);
+
+    /** @see Connection.VideoProvider#receiveSessionModifyRequest */
+    public void receiveSessionModifyRequest(VideoProfile VideoProfile) {
+        if (mCallback != null) {
+            try {
+                mCallback.receiveSessionModifyRequest(VideoProfile);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** @see Connection.VideoProvider#receiveSessionModifyResponse */
+    public void receiveSessionModifyResponse(
+            int status, VideoProfile requestedProfile, VideoProfile responseProfile) {
+        if (mCallback != null) {
+            try {
+                mCallback.receiveSessionModifyResponse(status, requestedProfile, responseProfile);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** @see Connection.VideoProvider#handleCallSessionEvent */
+    public void handleCallSessionEvent(int event) {
+        if (mCallback != null) {
+            try {
+                mCallback.handleCallSessionEvent(event);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** @see Connection.VideoProvider#changePeerDimensions */
+    public void changePeerDimensions(int width, int height) {
+        if (mCallback != null) {
+            try {
+                mCallback.changePeerDimensions(width, height);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** @see Connection.VideoProvider#changeCallDataUsage */
+    public void changeCallDataUsage(long dataUsage) {
+        if (mCallback != null) {
+            try {
+                mCallback.changeCallDataUsage(dataUsage);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** @see Connection.VideoProvider#changeCameraCapabilities */
+    public void changeCameraCapabilities(CameraCapabilities CameraCapabilities) {
+        if (mCallback != null) {
+            try {
+                mCallback.changeCameraCapabilities(CameraCapabilities);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    /** @see Connection.VideoProvider#changeVideoQuality */
+    public void changeVideoQuality(int videoQuality) {
+        if (mCallback != null) {
+            try {
+                mCallback.changeVideoQuality(videoQuality);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index 5f3f773..c9c48dc 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -30,9 +30,9 @@
 import android.provider.ContactsContract.PhoneLookup;
 import android.provider.ContactsContract.RawContacts;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.telephony.Rlog;
 import android.util.Log;
 
 import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
@@ -595,7 +595,8 @@
             pn = util.parse(number, countryIso);
             if (VDBG) Rlog.v(TAG, "- parsed number: " + pn);
         } catch (NumberParseException e) {
-            Rlog.w(TAG, "getGeoDescription: NumberParseException for incoming number '" + number + "'");
+            Rlog.w(TAG, "getGeoDescription: NumberParseException for incoming number '"
+                    + Rlog.pii(TAG, number) + "'");
         }
 
         if (pn != null) {
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index cbedb95..e9c5461 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -43,6 +43,8 @@
     void onPreciseDataConnectionStateChanged(in PreciseDataConnectionState dataConnectionState);
     void onDataConnectionRealTimeInfoChanged(in DataConnectionRealTimeInfo dcRtInfo);
     void onVoLteServiceStateChanged(in VoLteServiceState lteState);
+    void onVoiceActivationStateChanged(int activationState);
+    void onDataActivationStateChanged(int activationState);
     void onOemHookRawEvent(in byte[] rawData);
     void onCarrierNetworkChange(in boolean active);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a8eaf36..d21efc6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -24,6 +24,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CellInfo;
+import android.telephony.ClientRequestStats;
 import android.telephony.IccOpenLogicalChannelResponse;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
@@ -31,6 +32,8 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyHistogram;
 import android.telephony.VisualVoicemailSmsFilterSettings;
+import com.android.ims.internal.IImsServiceController;
+import com.android.ims.internal.IImsServiceFeatureListener;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
 
@@ -442,6 +445,30 @@
      */
     boolean setVoiceMailNumber(int subId, String alphaTag, String number);
 
+     /**
+      * Sets the voice activation state for a particular subscriber.
+      */
+    void setVoiceActivationState(int subId, int activationState);
+
+     /**
+      * Sets the data activation state for a particular subscriber.
+      */
+    void setDataActivationState(int subId, int activationState);
+
+     /**
+      * Returns the voice activation state for a particular subscriber.
+      * @param subId user preferred sub
+      * @param callingPackage package queries voice activation state
+      */
+    int getVoiceActivationState(int subId, String callingPackage);
+
+     /**
+      * Returns the data activation state for a particular subscriber.
+      * @param subId user preferred sub
+      * @param callingPackage package queris data activation state
+      */
+    int getDataActivationState(int subId, String callingPackage);
+
     /**
       * Returns the unread count of voicemails
       */
@@ -715,6 +742,14 @@
     int getTetherApnRequired();
 
     /**
+     *  Get ImsServiceController binder from ImsResolver that corresponds to the subId and feature
+     *  requested as well as registering the ImsServiceController for callbacks using the
+     *  IImsServiceFeatureListener interface.
+     */
+    IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+                IImsServiceFeatureListener callback);
+
+    /**
      * Set the network selection mode to automatic.
      *
      * @param subId the id of the subscription to update.
@@ -1182,4 +1217,33 @@
      * @hide
      */
     void setPolicyDataEnabled(boolean enabled, int subId);
+
+    /**
+     * Get Client request stats which will contain statistical information
+     * on each request made by client.
+     * @param callingPackage package making the call.
+     * @param subId Subscription index
+     * @hide
+     */
+    List<ClientRequestStats> getClientRequestStats(String callingPackage, int subid);
+
+    /**
+     * Set SIM card power state. Request is equivalent to inserting or removing the card.
+     * @param slotId SIM slot id
+     * @param powerUp True if powering up the SIM, otherwise powering down
+     * @hide
+     * */
+    void setSimPowerStateForSlot(int slotId, boolean powerUp);
+
+    /**
+     * Returns a list of Forbidden PLMNs from the specified SIM App
+     * Returns null if the query fails.
+     *
+     *
+     * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+     *
+     * @param subId subscription ID used for authentication
+     * @param appType the icc application type, like {@link #APPTYPE_USIM}
+     */
+    String[] getForbiddenPlmns(int subId, int appType);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 2c6be62..2c2206c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -65,6 +65,8 @@
             String failCause);
     void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
     void notifyVoLteServiceStateChanged(in VoLteServiceState lteState);
+    void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
+            int activationState, int activationType);
     void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData);
     void notifySubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index fdc68b9..f9de776 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -74,6 +74,9 @@
     public static final int PRESENTATION_UNKNOWN = 3;    // no specified or unknown by network
     public static final int PRESENTATION_PAYPHONE = 4;   // show pay phone info
 
+    // Sim activation type
+    public static final int SIM_ACTIVATION_TYPE_VOICE = 0;
+    public static final int SIM_ACTIVATION_TYPE_DATA = 1;
 
     public static final String PHONE_NAME_KEY = "phoneName";
     public static final String FAILURE_REASON_KEY = "reason";
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index a91e9beb..1e1d7a6 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -411,13 +411,16 @@
     int RIL_REQUEST_GET_ACTIVITY_INFO = 135;
     int RIL_REQUEST_SET_ALLOWED_CARRIERS = 136;
     int RIL_REQUEST_GET_ALLOWED_CARRIERS = 137;
+    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_RESPONSE_ACKNOWLEDGEMENT = 800;
 
     int RIL_UNSOL_RESPONSE_BASE = 1000;
     int RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED = 1000;
     int RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED = 1001;
-    int RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED = 1002;
+    int RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED = 1002;
     int RIL_UNSOL_RESPONSE_NEW_SMS = 1003;
     int RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT = 1004;
     int RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM = 1005;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 0168874..682b672 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -264,15 +264,6 @@
             = "android.intent.action.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS";
 
     /**
-     * Activity Action: Start this activity to invoke the carrier setup app.
-     * The carrier app must be signed using a certificate that matches the UICC access rules.
-     *
-     * <p class="note">Callers of this should hold the android.permission.INVOKE_CARRIER_SETUP
-     * permission.</p>
-     */
-    public static final String ACTION_CARRIER_SETUP = "android.intent.action.ACTION_CARRIER_SETUP";
-
-    /**
      * <p>Broadcast Action: Indicates that the action is forbidden by network.
      * <p class="note">
      * This is for the OEM applications to understand about possible provisioning issues.
@@ -411,7 +402,7 @@
      * <p class="note">This is a protected intent that can only be sent by the system.</p>
      */
     public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
-            "android.intent.action.CARRIER_SIGNAL_REDIRECTED";
+            "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
     /**
      * <p>Broadcast Action: when data connections setup fails.
      * intended for sim/account status checks and only sent to the specified carrier app
@@ -424,7 +415,7 @@
      * <p class="note">This is a protected intent that can only be sent by the system. </p>
      */
     public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
-            "android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+            "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
 
     /**
      * <p>Broadcast Action: when pco value is available.
@@ -441,7 +432,7 @@
      * <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 =
-            "android.intent.action.CARRIER_SIGNAL_PCO_VALUE";
+            "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
 
     // CARRIER_SIGNAL_ACTION extra keys
     public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
diff --git a/test-runner/src/android/test/suitebuilder/TestMethod.java b/test-runner/src/android/test/suitebuilder/TestMethod.java
index 08568d5..ae1db5e 100644
--- a/test-runner/src/android/test/suitebuilder/TestMethod.java
+++ b/test-runner/src/android/test/suitebuilder/TestMethod.java
@@ -26,7 +26,11 @@
 /**
  * Represents a test to be run. Can be constructed without instantiating the TestCase or even
  * loading the class.
+ *
+ * @deprecated New tests should be written using the
+ * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
  */
+@Deprecated
 public class TestMethod {
 
     private final String enclosingClassname;
diff --git a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
index 8c89489..3b920cf 100644
--- a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
+++ b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java
@@ -38,7 +38,11 @@
 /**
  * Build suites based on a combination of included packages, excluded packages,
  * and predicates that must be satisfied.
+ *
+ * @deprecated New tests should be written using the
+ * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
  */
+@Deprecated
 public class TestSuiteBuilder {
 
     private Context context;
@@ -223,7 +227,11 @@
     /**
      * A special {@link junit.framework.TestCase} used to indicate a failure during the build()
      * step.
+     *
+     * @deprecated New tests should be written using the
+     * <a href="{@docRoot}tools/testing-support-library/index.html">Android Testing Support Library</a>.
      */
+    @Deprecated
     public static class FailedToCreateTests extends TestCase {
         private final Exception exception;
 
diff --git a/test-runner/src/junit/runner/package-info.java b/test-runner/src/junit/runner/package-info.java
index b746185..364e362 100644
--- a/test-runner/src/junit/runner/package-info.java
+++ b/test-runner/src/junit/runner/package-info.java
@@ -1,4 +1,4 @@
 /**
- * Provides JUnit v3.x test runners.
+ * Utility classes supporting the junit test framework.
  */
 package junit.runner;
\ No newline at end of file
diff --git a/test-runner/src/junit/runner/package.html b/test-runner/src/junit/runner/package.html
deleted file mode 100644
index f08fa70..0000000
--- a/test-runner/src/junit/runner/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<HTML>
-<BODY>
-Utility classes supporting the junit test framework.
-</BODY>
-</HTML>
diff --git a/test-runner/src/junit/textui/package-info.java b/test-runner/src/junit/textui/package-info.java
index 2dcc10c..28b2ef4 100644
--- a/test-runner/src/junit/textui/package-info.java
+++ b/test-runner/src/junit/textui/package-info.java
@@ -1,5 +1,5 @@
 /**
- * Provides JUnit v3.x command line based tool to run tests.
+ * Utility classes supporting the junit test framework.
  * {@hide}
  */
 package junit.textui;
\ No newline at end of file
diff --git a/test-runner/src/junit/textui/package.html b/test-runner/src/junit/textui/package.html
deleted file mode 100644
index 723f2ae..0000000
--- a/test-runner/src/junit/textui/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<HTML>
-<BODY>
-Utility classes supporting the junit test framework.
-{@hide} - Not needed for 1.0 SDK
-</BODY>
-</HTML>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
index db6421e..0defe92 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
@@ -42,7 +42,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
index 535f865..ffb8689 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TransparentListActivity.java
@@ -42,7 +42,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity.java
index 0ddd7fd..7168478 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity.java
@@ -126,7 +126,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
index e795f02..a037d70 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
@@ -76,7 +76,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity3.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity3.java
index c8ae75b..e65dd63 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity3.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity3.java
@@ -73,7 +73,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity4.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity4.java
index 6072c6e..17f78af 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity4.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity4.java
@@ -80,7 +80,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java
index cbbb7ef..2dd7b6a 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity5.java
@@ -145,7 +145,7 @@
             "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
             "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
             "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
             "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
             "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
             "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityLandscape.java b/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityLandscape.java
index b659135..5ab874f 100644
--- a/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityLandscape.java
+++ b/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityLandscape.java
@@ -66,7 +66,7 @@
    "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
    "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
    "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-   "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+   "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
    "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
    "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
    "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityPortrait.java b/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityPortrait.java
index 5bfe456..570cb6b 100644
--- a/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityPortrait.java
+++ b/tests/ImfTest/src/com/android/imftest/samples/AutoCompleteTextViewActivityPortrait.java
@@ -60,7 +60,7 @@
    "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
    "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
    "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
-   "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+   "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czechia",
    "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
    "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
    "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
diff --git a/tests/TtsTests/Android.mk b/tests/TtsTests/Android.mk
index ed63e12..3c3cd77 100644
--- a/tests/TtsTests/Android.mk
+++ b/tests/TtsTests/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_STATIC_JAVA_LIBRARIES := littlemock junit legacy-android-test
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target legacy-android-test
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_PACKAGE_NAME := TtsTests
diff --git a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
index faf6827..918873b 100644
--- a/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
+++ b/tests/TtsTests/src/com/android/speech/tts/TextToSpeechTests.java
@@ -22,9 +22,12 @@
 import android.test.InstrumentationTestCase;
 
 import com.android.speech.tts.MockableTextToSpeechService.IDelegate;
-import com.google.testing.littlemock.ArgumentCaptor;
-import com.google.testing.littlemock.Behaviour;
-import com.google.testing.littlemock.LittleMock;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.internal.stubbing.StubberImpl;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Stubber;
 import junit.framework.Assert;
 
 import java.util.Locale;
@@ -40,16 +43,16 @@
 
     @Override
     public void setUp() throws Exception {
-        IDelegate passThrough = LittleMock.mock(IDelegate.class);
+        IDelegate passThrough = Mockito.mock(IDelegate.class);
         MockableTextToSpeechService.setMocker(passThrough);
 
         // For the default voice selection
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
             .onIsLanguageAvailable(
-                    LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(passThrough)
             .onLoadLanguage(
-                    LittleMock.anyString(), LittleMock.anyString(), LittleMock.anyString());
+                    Mockito.anyString(), Mockito.anyString(), Mockito.anyString());
 
         blockingInitAndVerify(MOCK_ENGINE, TextToSpeech.SUCCESS);
         assertEquals(MOCK_ENGINE, mTts.getCurrentEngine());
@@ -71,42 +74,42 @@
     }
 
     public void testSetLanguage_delegation() {
-        IDelegate delegate = LittleMock.mock(IDelegate.class);
+        IDelegate delegate = Mockito.mock(IDelegate.class);
         MockableTextToSpeechService.setMocker(delegate);
 
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onIsLanguageAvailable(
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onIsLanguageAvailable(
                 "eng", "USA", "variant");
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onLoadLanguage(
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE).when(delegate).onLoadLanguage(
                 "eng", "USA", "variant");
 
         // Test 1 :Tests that calls to onLoadLanguage( ) are delegated through to the
         // service without any caching or intermediate steps.
         assertEquals(TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE, mTts.setLanguage(new Locale("eng", "USA", "variant")));
-        LittleMock.verify(delegate, LittleMock.anyTimes()).onIsLanguageAvailable(
+        Mockito.verify(delegate, Mockito.atLeast(0)).onIsLanguageAvailable(
             "eng", "USA", "variant");
-        LittleMock.verify(delegate, LittleMock.anyTimes()).onLoadLanguage(
+        Mockito.verify(delegate, Mockito.atLeast(0)).onLoadLanguage(
             "eng", "USA", "variant");
     }
 
     public void testSetLanguage_availableLanguage() throws Exception {
-        IDelegate delegate = LittleMock.mock(IDelegate.class);
+        IDelegate delegate = Mockito.mock(IDelegate.class);
         MockableTextToSpeechService.setMocker(delegate);
 
         // ---------------------------------------------------------
         // Test 2 : Tests that when the language is successfully set
         // like above (returns LANG_COUNTRY_AVAILABLE). That the
         // request language changes from that point on.
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
                 "eng", "USA", "variant");
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onIsLanguageAvailable(
                 "eng", "USA", "");
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage(
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(delegate).onLoadLanguage(
                 "eng", "USA", "");
         mTts.setLanguage(new Locale("eng", "USA", "variant"));
         blockingCallSpeak("foo bar", delegate);
-        ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor();
-        LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(),
-                LittleMock.<SynthesisCallback>anyObject());
+        ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class);
+        Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(),
+                Mockito.<SynthesisCallback>anyObject());
 
         assertEquals("eng", req.getValue().getLanguage());
         assertEquals("USA", req.getValue().getCountry());
@@ -115,21 +118,21 @@
     }
 
     public void testSetLanguage_unavailableLanguage() throws Exception {
-        IDelegate delegate = LittleMock.mock(IDelegate.class);
+        IDelegate delegate = Mockito.mock(IDelegate.class);
         MockableTextToSpeechService.setMocker(delegate);
 
         // ---------------------------------------------------------
         // TEST 3 : Tests that the language that is set does not change when the
         // engine reports it could not load the specified language.
-        LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
+        Mockito.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
                 delegate).onIsLanguageAvailable("fra", "FRA", "");
-        LittleMock.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
+        Mockito.doReturn(TextToSpeech.LANG_NOT_SUPPORTED).when(
                 delegate).onLoadLanguage("fra", "FRA", "");
         mTts.setLanguage(Locale.FRANCE);
         blockingCallSpeak("le fou barre", delegate);
-        ArgumentCaptor<SynthesisRequest> req2 = LittleMock.createCaptor();
-        LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req2.capture(),
-                        LittleMock.<SynthesisCallback>anyObject());
+        ArgumentCaptor<SynthesisRequest> req2 = ArgumentCaptor.forClass(SynthesisRequest.class);
+        Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req2.capture(),
+                        Mockito.<SynthesisCallback>anyObject());
 
         // The params are basically unchanged.
         assertEquals("eng", req2.getValue().getLanguage());
@@ -139,41 +142,41 @@
     }
 
     public void testIsLanguageAvailable() {
-        IDelegate delegate = LittleMock.mock(IDelegate.class);
+        IDelegate delegate = Mockito.mock(IDelegate.class);
         MockableTextToSpeechService.setMocker(delegate);
 
         // Test1: Simple end to end test.
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).when(
                 delegate).onIsLanguageAvailable("eng", "USA", "");
 
         assertEquals(TextToSpeech.LANG_COUNTRY_AVAILABLE, mTts.isLanguageAvailable(Locale.US));
-        LittleMock.verify(delegate, LittleMock.times(1)).onIsLanguageAvailable(
+        Mockito.verify(delegate, Mockito.times(1)).onIsLanguageAvailable(
                 "eng", "USA", "");
     }
 
     public void testDefaultLanguage_setsVoiceName() throws Exception {
-        IDelegate delegate = LittleMock.mock(IDelegate.class);
+        IDelegate delegate = Mockito.mock(IDelegate.class);
         MockableTextToSpeechService.setMocker(delegate);
         Locale defaultLocale = Locale.getDefault();
 
         // ---------------------------------------------------------
         // Test that default language also sets the default voice
         // name
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
             when(delegate).onIsLanguageAvailable(
                 defaultLocale.getISO3Language(),
                 defaultLocale.getISO3Country().toUpperCase(),
                 defaultLocale.getVariant());
-        LittleMock.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
+        Mockito.doReturn(TextToSpeech.LANG_COUNTRY_AVAILABLE).
             when(delegate).onLoadLanguage(
                 defaultLocale.getISO3Language(),
                 defaultLocale.getISO3Country(),
                 defaultLocale.getVariant());
 
         blockingCallSpeak("foo bar", delegate);
-        ArgumentCaptor<SynthesisRequest> req = LittleMock.createCaptor();
-        LittleMock.verify(delegate, LittleMock.times(1)).onSynthesizeText(req.capture(),
-                LittleMock.<SynthesisCallback>anyObject());
+        ArgumentCaptor<SynthesisRequest> req = ArgumentCaptor.forClass(SynthesisRequest.class);
+        Mockito.verify(delegate, Mockito.times(1)).onSynthesizeText(req.capture(),
+                Mockito.<SynthesisCallback>anyObject());
 
         assertEquals(defaultLocale.getISO3Language(), req.getValue().getLanguage());
         assertEquals(defaultLocale.getISO3Country(), req.getValue().getCountry());
@@ -185,8 +188,8 @@
     private void blockingCallSpeak(String speech, IDelegate mock) throws
             InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
-        doCountDown(latch).when(mock).onSynthesizeText(LittleMock.<SynthesisRequest>anyObject(),
-                LittleMock.<SynthesisCallback>anyObject());
+        doCountDown(latch).when(mock).onSynthesizeText(Mockito.<SynthesisRequest>anyObject(),
+                Mockito.<SynthesisCallback>anyObject());
         mTts.speak(speech, TextToSpeech.QUEUE_ADD, null);
 
         awaitCountDown(latch, 5, TimeUnit.SECONDS);
@@ -194,7 +197,7 @@
 
     private void blockingInitAndVerify(final String engine, int errorCode) throws
             InterruptedException {
-        TextToSpeech.OnInitListener listener = LittleMock.mock(
+        TextToSpeech.OnInitListener listener = Mockito.mock(
                 TextToSpeech.OnInitListener.class);
 
         final CountDownLatch latch = new CountDownLatch(1);
@@ -206,18 +209,18 @@
         awaitCountDown(latch, 5, TimeUnit.SECONDS);
     }
 
-    public interface CountDownBehaviour extends Behaviour {
+    public static abstract class CountDownBehaviour extends StubberImpl {
         /** Used to mock methods that return a result. */
-        Behaviour andReturn(Object result);
+        public abstract Stubber andReturn(Object result);
     }
 
     public static CountDownBehaviour doCountDown(final CountDownLatch latch) {
         return new CountDownBehaviour() {
             @Override
             public <T> T when(T mock) {
-                return LittleMock.doAnswer(new Callable<Void>() {
+                return Mockito.doAnswer(new Answer<Void>() {
                     @Override
-                    public Void call() throws Exception {
+                    public Void answer(InvocationOnMock invocation) throws Exception {
                         latch.countDown();
                         return null;
                     }
@@ -225,13 +228,13 @@
             }
 
             @Override
-            public Behaviour andReturn(final Object result) {
-                return new Behaviour() {
+            public Stubber andReturn(final Object result) {
+                return new StubberImpl() {
                     @Override
                     public <T> T when(T mock) {
-                        return LittleMock.doAnswer(new Callable<Object>() {
+                        return Mockito.doAnswer(new Answer<Object>() {
                             @Override
-                            public Object call() throws Exception {
+                            public Object answer(InvocationOnMock invocation) throws Exception {
                                 latch.countDown();
                                 return result;
                             }
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
new file mode 100644
index 0000000..b984bbf
--- /dev/null
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+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 android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityManagerTest {
+    static NetworkCapabilities verifyNetworkCapabilities(
+            int legacyType, int transportType, int... capabilities) {
+        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        assertNotNull(nc);
+        assertTrue(nc.hasTransport(transportType));
+        for (int capability : capabilities) {
+            assertTrue(nc.hasCapability(capability));
+        }
+
+        return nc;
+    }
+
+    static void verifyUnrestrictedNetworkCapabilities(int legacyType, int transportType) {
+        verifyNetworkCapabilities(
+                legacyType,
+                transportType,
+                NET_CAPABILITY_INTERNET,
+                NET_CAPABILITY_NOT_RESTRICTED,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+    }
+
+    static void verifyRestrictedMobileNetworkCapabilities(int legacyType, int capability) {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                legacyType,
+                TRANSPORT_CELLULAR,
+                capability,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobile() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE, TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileCbs() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_CBS, NET_CAPABILITY_CBS);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileDun() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_DUN, NET_CAPABILITY_DUN);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileFota() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_FOTA, NET_CAPABILITY_FOTA);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileHipri() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_HIPRI, TRANSPORT_CELLULAR);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileIms() {
+        verifyRestrictedMobileNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_IMS, NET_CAPABILITY_IMS);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileMms() {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_MMS,
+                TRANSPORT_CELLULAR,
+                NET_CAPABILITY_MMS,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeMobileSupl() {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                ConnectivityManager.TYPE_MOBILE_SUPL,
+                TRANSPORT_CELLULAR,
+                NET_CAPABILITY_SUPL,
+                NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeWifi() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_WIFI, TRANSPORT_WIFI);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeWifiP2p() {
+        final NetworkCapabilities nc = verifyNetworkCapabilities(
+                ConnectivityManager.TYPE_WIFI_P2P,
+                TRANSPORT_WIFI,
+                NET_CAPABILITY_NOT_RESTRICTED, NET_CAPABILITY_NOT_VPN,
+                NET_CAPABILITY_TRUSTED, NET_CAPABILITY_WIFI_P2P);
+
+        assertFalse(nc.hasCapability(NET_CAPABILITY_INTERNET));
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeBluetooth() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_BLUETOOTH, TRANSPORT_BLUETOOTH);
+    }
+
+    @Test
+    public void testNetworkCapabilitiesForTypeEthernet() {
+        verifyUnrestrictedNetworkCapabilities(
+                ConnectivityManager.TYPE_ETHERNET, TRANSPORT_ETHERNET);
+    }
+}
diff --git a/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java b/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
deleted file mode 100644
index f896030..0000000
--- a/tests/net/java/android/net/ConnectivityMetricsLoggerTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.os.Bundle;
-import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
-import java.util.List;
-import junit.framework.TestCase;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class ConnectivityMetricsLoggerTest extends TestCase {
-
-    // use same Parcel object everywhere for pointer equality
-    static final Bundle FAKE_EV = new Bundle();
-    static final int FAKE_COMPONENT = 1;
-    static final int FAKE_EVENT = 2;
-
-    @Mock IConnectivityMetricsLogger mService;
-    ArgumentCaptor<ConnectivityMetricsEvent> evCaptor;
-    ArgumentCaptor<ConnectivityMetricsEvent[]> evArrayCaptor;
-
-    ConnectivityMetricsLogger mLog;
-
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        evCaptor = ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
-        evArrayCaptor = ArgumentCaptor.forClass(ConnectivityMetricsEvent[].class);
-        mLog = new ConnectivityMetricsLogger(mService);
-    }
-
-    @SmallTest
-    public void testLogEvents() throws Exception {
-        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(3, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-
-        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(3);
-        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
-        assertEventsEqual(expectedEvent(2), gotEvents.get(1));
-        assertEventsEqual(expectedEvent(3), gotEvents.get(2));
-    }
-
-    @SmallTest
-    public void testLogEventTriggerThrottling() throws Exception {
-        when(mService.logEvent(any())).thenReturn(1234L);
-
-        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-
-        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
-        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
-    }
-
-    @SmallTest
-    public void testLogEventFails() throws Exception {
-        when(mService.logEvent(any())).thenReturn(-1L); // Error.
-
-        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-
-        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
-        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
-    }
-
-    @SmallTest
-    public void testLogEventWhenThrottling() throws Exception {
-        when(mService.logEvent(any())).thenReturn(Long.MAX_VALUE); // Throttled
-
-        // No events are logged. The service is only called once
-        // After that, throttling state is maintained locally.
-        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-
-        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
-        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
-    }
-
-    @SmallTest
-    public void testLogEventRecoverFromThrottling() throws Exception {
-        final long throttleTimeout = System.currentTimeMillis() + 10;
-        when(mService.logEvent(any())).thenReturn(throttleTimeout, 0L);
-
-        mLog.logEvent(1, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(2, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        mLog.logEvent(3, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-        Thread.sleep(100);
-        mLog.logEvent(53, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-
-        List<ConnectivityMetricsEvent> gotEvents = verifyEvents(1);
-        assertEventsEqual(expectedEvent(1), gotEvents.get(0));
-
-        verify(mService, times(1)).logEvents(evArrayCaptor.capture());
-        ConnectivityMetricsEvent[] gotOtherEvents = evArrayCaptor.getAllValues().get(0);
-        assertEquals(ConnectivityMetricsLogger.TAG_SKIPPED_EVENTS, gotOtherEvents[0].eventTag);
-        assertEventsEqual(expectedEvent(53), gotOtherEvents[1]);
-    }
-
-    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
-        verify(mService, times(n)).logEvent(evCaptor.capture());
-        return evCaptor.getAllValues();
-    }
-
-    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
-        return new ConnectivityMetricsEvent((long)timestamp, FAKE_COMPONENT, FAKE_EVENT, FAKE_EV);
-    }
-
-    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
-    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
-        assertEquals(expected.timestamp, got.timestamp);
-        assertEquals(expected.componentTag, got.componentTag);
-        assertEquals(expected.eventTag, got.eventTag);
-        assertEquals(expected.data, got.data);
-    }
-}
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index ff61754..91d6c68 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -29,9 +29,11 @@
 import android.net.metrics.RaEvent;
 import android.os.ConditionVariable;
 import android.os.Parcelable;
+import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.test.AndroidTestCase;
+import android.text.format.DateUtils;
 import android.test.suitebuilder.annotation.SmallTest;
 import static android.system.OsConstants.*;
 
@@ -604,6 +606,8 @@
         public final static byte[] MOCK_MAC_ADDR = {1,2,3,4,5,6};
         private FileDescriptor mWriteSocket;
 
+        private final long mFixedTimeMs = SystemClock.elapsedRealtime();
+
         public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter,
                 IpConnectivityLog log) throws Exception {
             super(new ApfCapabilities(2, 1700, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
@@ -617,6 +621,11 @@
         }
 
         @Override
+        protected long currentTimeSeconds() {
+            return mFixedTimeMs / DateUtils.SECOND_IN_MILLIS;
+        }
+
+        @Override
         void maybeStartFilter() {
             mHardwareAddress = MOCK_MAC_ADDR;
             installNewProgramLocked();
@@ -969,27 +978,30 @@
 
     // Verify that the last program pushed to the IpManager.Callback properly filters the
     // given packet for the given lifetime.
-    private void verifyRaLifetime(MockIpManagerCallback ipManagerCallback, ByteBuffer packet,
-            int lifetime) {
-        byte[] program = ipManagerCallback.getApfProgram();
+    private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
+        final int FRACTION_OF_LIFETIME = 6;
+        final int ageLimit = lifetime / FRACTION_OF_LIFETIME;
 
-        // Verify new program should drop RA for 1/6th its lifetime
+        // Verify new program should drop RA for 1/6th its lifetime and pass afterwards.
         assertDrop(program, packet.array());
-        assertDrop(program, packet.array(), lifetime/6);
-        assertPass(program, packet.array(), lifetime/6 + 1);
+        assertDrop(program, packet.array(), ageLimit);
+        assertPass(program, packet.array(), ageLimit + 1);
         assertPass(program, packet.array(), lifetime);
-
         // Verify RA checksum is ignored
+        final short originalChecksum = packet.getShort(ICMP6_RA_CHECKSUM_OFFSET);
         packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
         assertDrop(program, packet.array());
         packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
         assertDrop(program, packet.array());
+        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, originalChecksum);
 
         // Verify other changes to RA make it not match filter
+        final byte originalFirstByte = packet.get(0);
         packet.put(0, (byte)-1);
         assertPass(program, packet.array());
         packet.put(0, (byte)0);
         assertDrop(program, packet.array());
+        packet.put(0, originalFirstByte);
     }
 
     // Test that when ApfFilter is shown the given packet, it generates a program to filter it
@@ -999,9 +1011,8 @@
         // Verify new program generated if ApfFilter witnesses RA
         ipManagerCallback.resetApfProgramWait();
         apfFilter.pretendPacketReceived(packet.array());
-        ipManagerCallback.getApfProgram();
-
-        verifyRaLifetime(ipManagerCallback, packet, lifetime);
+        byte[] program = ipManagerCallback.getApfProgram();
+        verifyRaLifetime(program, packet, lifetime);
     }
 
     private void verifyRaEvent(RaEvent expected) {
@@ -1046,18 +1057,26 @@
         TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, mLog);
         byte[] program = ipManagerCallback.getApfProgram();
 
+        final int ROUTER_LIFETIME = 1000;
+        final int PREFIX_VALID_LIFETIME = 200;
+        final int PREFIX_PREFERRED_LIFETIME = 100;
+        final int RDNSS_LIFETIME  = 300;
+        final int ROUTE_LIFETIME  = 400;
+        // Note that lifetime of 2000 will be ignored in favor of shorter route lifetime of 1000.
+        final int DNSSL_LIFETIME  = 2000;
+
         // Verify RA is passed the first time
         ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
         basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
         basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
         basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
-        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)1000);
+        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)ROUTER_LIFETIME);
         basePacket.position(IPV6_DEST_ADDR_OFFSET);
         basePacket.put(IPV6_ALL_NODES_ADDRESS);
         assertPass(program, basePacket.array());
 
-        testRaLifetime(apfFilter, ipManagerCallback, basePacket, 1000);
-        verifyRaEvent(new RaEvent(1000, -1, -1, -1, -1, -1));
+        testRaLifetime(apfFilter, ipManagerCallback, basePacket, ROUTER_LIFETIME);
+        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
 
         // Ensure zero-length options cause the packet to be silently skipped.
         // Do this before we test other packets. http://b/29586253
@@ -1079,11 +1098,14 @@
         prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
         prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
         prefixOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET, 100);
+                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
+                PREFIX_PREFERRED_LIFETIME);
         prefixOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET, 200);
-        testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, 100);
-        verifyRaEvent(new RaEvent(1000, 200, 100, -1, -1, -1));
+                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
+                PREFIX_VALID_LIFETIME);
+        testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+        verifyRaEvent(new RaEvent(
+                ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
 
         ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
@@ -1092,9 +1114,9 @@
         rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
         rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
         rdnssOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 300);
-        testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, 300);
-        verifyRaEvent(new RaEvent(1000, -1, -1, -1, 300, -1));
+                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
+        testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, RDNSS_LIFETIME);
+        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
 
         ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
@@ -1103,9 +1125,9 @@
         routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
         routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
         routeInfoOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 400);
-        testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, 400);
-        verifyRaEvent(new RaEvent(1000, -1, -1, 400, -1, -1));
+                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
+        testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
+        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
 
         ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
                 new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
@@ -1114,18 +1136,17 @@
         dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
         dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
         dnsslOptionPacket.putInt(
-                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 2000);
-        // Note that lifetime of 2000 will be ignored in favor of shorter
-        // route lifetime of 1000.
-        testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, 1000);
-        verifyRaEvent(new RaEvent(1000, -1, -1, -1, -1, 2000));
+                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
+        testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, ROUTER_LIFETIME);
+        verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
 
         // Verify that current program filters all five RAs:
-        verifyRaLifetime(ipManagerCallback, basePacket, 1000);
-        verifyRaLifetime(ipManagerCallback, prefixOptionPacket, 100);
-        verifyRaLifetime(ipManagerCallback, rdnssOptionPacket, 300);
-        verifyRaLifetime(ipManagerCallback, routeInfoOptionPacket, 400);
-        verifyRaLifetime(ipManagerCallback, dnsslOptionPacket, 1000);
+        program = ipManagerCallback.getApfProgram();
+        verifyRaLifetime(program, basePacket, ROUTER_LIFETIME);
+        verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+        verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME);
+        verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME);
+        verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME);
 
         apfFilter.shutdown();
     }
diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java
new file mode 100644
index 0000000..025b017
--- /dev/null
+++ b/tests/net/java/android/net/ip/IpManagerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.INetworkManagementService;
+import android.provider.Settings;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for IpManager.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpManagerTest {
+    private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1;
+
+    @Mock private Context mContext;
+    @Mock private INetworkManagementService mNMService;
+    @Mock private Resources mResources;
+    private MockContentResolver mContentResolver;
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getInteger(R.integer.config_networkAvoidBadWifi))
+                .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE);
+
+        mContentResolver = new MockContentResolver();
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+    }
+
+    @Test
+    public void testNullCallbackDoesNotThrow() throws Exception {
+        final IpManager ipm = new IpManager(mContext, "lo", null, mNMService);
+    }
+
+    @Test
+    public void testInvalidInterfaceDoesNotThrow() throws Exception {
+        final IpManager.Callback cb = new IpManager.Callback();
+        final IpManager ipm = new IpManager(mContext, "test_wlan0", cb, mNMService);
+    }
+}
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
index 766e5c0..dd679bc 100644
--- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
+++ b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
@@ -135,6 +135,30 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    public void testInvalidICMPv6NDLength() {
+        final String packet =
+                // Ethernet
+                "807ABF6F48F3 100E7E263FC1 86DD" +
+                // IPv6
+                "600000000068 3A FF" +
+                "FE80000000000000FA000004FD000001" +
+                "FE80000000000000827ABFFFFE6F48F3" +
+                // ICMPv6 RA
+                "86 00 8141" +
+                "40 00 0E10" +
+                "00000000" +
+                "00000000" +
+                "01 01 00005E000265" +
+                "00 00 0102030405D6";
+
+        final String expected =
+                "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
+                " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
+                " ra slla 00:00:5e:00:02:65 <malformed>";
+
+        assertEquals(expected, getSummary(packet));
+    }
+
     public void testParseICMPv6NA() {
         final String packet =
                 // Ethernet
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 46b6403..b033382 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -53,7 +53,7 @@
 import android.net.NetworkRequest;
 import android.net.RouteInfo;
 import android.net.metrics.IpConnectivityLog;
-import android.net.util.AvoidBadWifiTracker;
+import android.net.util.MultinetworkPolicyTracker;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -68,7 +68,6 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
-import android.test.FlakyTest;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -154,49 +153,20 @@
     }
 
     /**
-     * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle
-     * will return immediately if the handler is already idle.
+     * Block until the given handler becomes idle, or until timeoutMs has passed.
      */
-    private class IdleableHandlerThread extends HandlerThread {
-        private IdleHandler mIdleHandler;
-
-        public IdleableHandlerThread(String name) {
-            super(name);
-        }
-
-        public void waitForIdle(int timeoutMs) {
-            final ConditionVariable cv = new ConditionVariable();
-            final MessageQueue queue = getLooper().getQueue();
-
-            synchronized (queue) {
-                if (queue.isIdle()) {
-                    return;
-                }
-
-                assertNull("BUG: only one idle handler allowed", mIdleHandler);
-                mIdleHandler = new IdleHandler() {
-                    public boolean queueIdle() {
-                        synchronized (queue) {
-                            cv.open();
-                            mIdleHandler = null;
-                            return false;  // Remove the handler.
-                        }
-                    }
-                };
-                queue.addIdleHandler(mIdleHandler);
-            }
-
-            if (!cv.block(timeoutMs)) {
-                fail("HandlerThread " + getName() +
-                        " did not become idle after " + timeoutMs + " ms");
-                queue.removeIdleHandler(mIdleHandler);
-            }
+    private static void waitForIdleHandler(HandlerThread handlerThread, int timeoutMs) {
+        final ConditionVariable cv = new ConditionVariable();
+        final Handler handler = new Handler(handlerThread.getLooper());
+        handler.post(() -> cv.open());
+        if (!cv.block(timeoutMs)) {
+            fail("HandlerThread " + handlerThread.getName() +
+                    " did not become idle after " + timeoutMs + " ms");
         }
     }
 
-    // Tests that IdleableHandlerThread works as expected.
     @SmallTest
-    public void testIdleableHandlerThread() {
+    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.
@@ -220,9 +190,9 @@
         }
     }
 
-    @SmallTest
-    @FlakyTest(tolerance = 3)
-    public void testNotWaitingForIdleCausesRaceConditions() {
+    // This test has an inherent race condition in it, and cannot be enabled for continuous testing
+    // or presubmit tests. It is kept for manual runs and documentation purposes.
+    public void verifyThatNotWaitingForIdleCausesRaceConditions() {
         // Bring up a network that we can use to send messages to ConnectivityService.
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -249,7 +219,7 @@
         private final WrappedNetworkMonitor mWrappedNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
-        private final IdleableHandlerThread mHandlerThread;
+        private final HandlerThread mHandlerThread;
         private final ConditionVariable mDisconnected = new ConditionVariable();
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
         private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
@@ -281,7 +251,7 @@
                 default:
                     throw new UnsupportedOperationException("unimplemented network type");
             }
-            mHandlerThread = new IdleableHandlerThread("Mock-" + typeName);
+            mHandlerThread = new HandlerThread("Mock-" + typeName);
             mHandlerThread.start();
             mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
                     "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
@@ -321,7 +291,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            mHandlerThread.waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -399,6 +369,11 @@
             connect(false);
         }
 
+        public void suspend() {
+            mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null);
+            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+        }
+
         public void disconnect() {
             mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
             mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -623,10 +598,10 @@
         }
     }
 
-    private class WrappedAvoidBadWifiTracker extends AvoidBadWifiTracker {
+    private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
         public volatile boolean configRestrictsAvoidBadWifi;
 
-        public WrappedAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
+        public WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
             super(c, h, r);
         }
 
@@ -637,7 +612,7 @@
     }
 
     private class WrappedConnectivityService extends ConnectivityService {
-        public WrappedAvoidBadWifiTracker wrappedAvoidBadWifiTracker;
+        public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
         private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -648,11 +623,6 @@
         }
 
         @Override
-        protected HandlerThread createHandlerThread() {
-            return new IdleableHandlerThread("WrappedConnectivityService");
-        }
-
-        @Override
         protected int getDefaultTcpRwnd() {
             // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
             return 0;
@@ -689,14 +659,14 @@
         }
 
         @Override
-        public AvoidBadWifiTracker createAvoidBadWifiTracker(
+        public MultinetworkPolicyTracker createMultinetworkPolicyTracker(
                 Context c, Handler h, Runnable r) {
-            final WrappedAvoidBadWifiTracker tracker = new WrappedAvoidBadWifiTracker(c, h, r);
+            final WrappedMultinetworkPolicyTracker tracker = new WrappedMultinetworkPolicyTracker(c, h, r);
             return tracker;
         }
 
-        public WrappedAvoidBadWifiTracker getAvoidBadWifiTracker() {
-            return (WrappedAvoidBadWifiTracker) mAvoidBadWifiTracker;
+        public WrappedMultinetworkPolicyTracker getMultinetworkPolicyTracker() {
+            return (WrappedMultinetworkPolicyTracker) mMultinetworkPolicyTracker;
         }
 
         @Override
@@ -710,7 +680,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -718,22 +688,6 @@
         }
     }
 
-    private interface Criteria {
-        public boolean get();
-    }
-
-    /**
-     * Wait up to 500ms for {@code criteria.get()} to become true, polling.
-     * Fails if 500ms goes by before {@code criteria.get()} to become true.
-     */
-    static private void waitFor(Criteria criteria) {
-        int delays = 0;
-        while (!criteria.get()) {
-            sleepFor(50);
-            if (++delays == 10) fail();
-        }
-    }
-
     /**
      * Wait up to TIMEOUT_MS for {@code conditionVariable} to open.
      * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens.
@@ -869,8 +823,9 @@
         assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
                 mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
         // Test cellular linger timeout.
-        waitFor(new Criteria() {
-                public boolean get() { return mCm.getAllNetworks().length == 1; } });
+        waitFor(mCellNetworkAgent.getDisconnectedCV());
+        mService.waitForIdle();
+        assertEquals(1, mCm.getAllNetworks().length);
         verifyActiveNetwork(TRANSPORT_WIFI);
         assertEquals(1, mCm.getAllNetworks().length);
         assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
@@ -1098,6 +1053,7 @@
         AVAILABLE,
         NETWORK_CAPABILITIES,
         LINK_PROPERTIES,
+        SUSPENDED,
         LOSING,
         LOST,
         UNAVAILABLE
@@ -1111,7 +1067,7 @@
             state = s; network = n; arg = o;
         }
         public String toString() {
-            return String.format("%s (%s)", state, network);
+            return String.format("%s (%s) (%s)", state, network, arg);
         }
         @Override
         public boolean equals(Object o) {
@@ -1135,7 +1091,7 @@
         // Chosen to be much less than the linger timeout. This ensures that we can distinguish
         // between a LOST callback that arrives immediately and a LOST callback that arrives after
         // the linger timeout.
-        private final static int TIMEOUT_MS = 50;
+        private final static int TIMEOUT_MS = 100;
 
         private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
 
@@ -1149,11 +1105,26 @@
         }
 
         @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
+            setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
+        }
+
+        @Override
+        public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
+            setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
+        }
+
+        @Override
         public void onUnavailable() {
             setLastCallback(CallbackState.UNAVAILABLE, null, null);
         }
 
         @Override
+        public void onNetworkSuspended(Network network) {
+            setLastCallback(CallbackState.SUSPENDED, network, null);
+        }
+
+        @Override
         public void onLosing(Network network, int maxMsToLive) {
             setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
         }
@@ -1176,11 +1147,12 @@
             return cb;
         }
 
-        void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) {
-            CallbackInfo expected = new CallbackInfo(
-                    state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0);
+        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs) {
+            final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null;
+            CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0);
             CallbackInfo actual = nextCallback(timeoutMs);
             assertEquals("Unexpected callback:", expected, actual);
+
             if (state == CallbackState.LOSING) {
                 String msg = String.format(
                         "Invalid linger time value %d, must be between %d and %d",
@@ -1188,10 +1160,46 @@
                 int maxMsToLive = (Integer) actual.arg;
                 assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= TEST_LINGER_DELAY_MS);
             }
+
+            return actual;
         }
 
-        void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
-            expectCallback(state, mockAgent, TIMEOUT_MS);
+        CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) {
+            return expectCallback(state, agent, TIMEOUT_MS);
+        }
+
+        void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+            expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
+            if (expectSuspended) {
+                expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
+            }
+            expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
+            expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
+        }
+
+        void expectAvailableCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+        }
+
+        void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, true, TIMEOUT_MS);
+        }
+
+        void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) {
+            expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+            expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+        }
+
+        void expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
+            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
+            assertTrue(nc.hasCapability(capability));
+        }
+
+        void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
+            CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+            NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
+            assertFalse(nc.hasCapability(capability));
         }
 
         void assertNoCallback() {
@@ -1228,8 +1236,8 @@
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1243,8 +1251,8 @@
         cv = waitForConnectivityBroadcasts(2);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         waitFor(cv);
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1267,8 +1275,8 @@
         // Test validated networks
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
 
@@ -1280,9 +1288,10 @@
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1318,28 +1327,32 @@
         mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
 
         mCellNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.connect(true);
         // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
         // We then get LOSING when wifi validates and cell is outscored.
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+        callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
         assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mEthernetNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
 
         for (int i = 0; i < 4; i++) {
             MockNetworkAgent oldNetwork, newNetwork;
@@ -1356,7 +1369,7 @@
             callback.expectCallback(CallbackState.LOSING, oldNetwork);
             // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
             // longer lingering?
-            defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork);
+            defaultCallback.expectAvailableCallbacks(newNetwork);
             assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
         }
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1364,17 +1377,19 @@
         // Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even
         // if the network is still up.
         mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+        // We expect a notification about the capabilities change, and nothing else.
+        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
+        defaultCallback.assertNoCallback();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Wifi no longer satisfies our listen, which is for an unmetered network.
         // But because its score is 55, it's still up (and the default network).
-        defaultCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Disconnect our test networks.
         mWiFiNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
 
@@ -1390,22 +1405,22 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(false);   // Score: 10
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 20.
         // Cell stays up because it would satisfy the default request if it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);   // Score: 20
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi with a score of 70.
@@ -1413,31 +1428,33 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.adjustScore(50);
         mWiFiNetworkAgent.connect(false);   // Score: 70
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Tear down wifi.
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
         // it's arguably correct to linger it, since it was the default network before it validated.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
 
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1445,13 +1462,15 @@
         // If a network is lingering, and we add and remove a request from it, resume lingering.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         NetworkRequest cellRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build();
@@ -1467,7 +1486,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
 
         // Cell is now the default network. Pin it with a cell-specific request.
         noopCallback = new NetworkCallback();  // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1476,8 +1495,8 @@
         // Now connect wifi, and expect it to become the default network.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
         // The default request is lingering on cell, but nothing happens to cell, and we send no
         // callbacks for it, because it's kept up by cellRequest.
         callback.assertNoCallback();
@@ -1487,8 +1506,8 @@
 
         // Let linger run its course.
         callback.assertNoCallback();
-        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent,
-                TEST_LINGER_DELAY_MS /* timeoutMs */);
+        final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+        callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, lingerTimeoutMs);
 
         // Clean up.
         mWiFiNetworkAgent.disconnect();
@@ -1645,8 +1664,8 @@
         ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
         mCellNetworkAgent.connectWithoutInternet();
         waitFor(cv);
-        waitFor(new Criteria() {
-                public boolean get() { return mCm.getAllNetworks().length == 0; } });
+        mService.waitForIdle();
+        assertEquals(0, mCm.getAllNetworks().length);
         verifyNoNetwork();
         // Test bringing up validated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1663,7 +1682,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mCellNetworkAgent.connectWithoutInternet();
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         verifyActiveNetwork(TRANSPORT_WIFI);
         // Test releasing NetworkRequest disconnects cellular with MMS
         cv = mCellNetworkAgent.getDisconnectedCV();
@@ -1689,7 +1708,7 @@
         MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
         mmsNetworkAgent.connectWithoutInternet();
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mmsNetworkAgent);
+        networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
         verifyActiveNetwork(TRANSPORT_CELLULAR);
         // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
         cv = mmsNetworkAgent.getDisconnectedCV();
@@ -1715,7 +1734,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String firstRedirectUrl = "http://example.com/firstPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
-        captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
 
         // Take down network.
@@ -1728,7 +1747,7 @@
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         String secondRedirectUrl = "http://example.com/secondPath";
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-        captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
 
         // Make captive portal disappear then revalidate.
@@ -1738,7 +1757,9 @@
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        // TODO: Investigate only sending available callbacks.
+        validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -1783,7 +1804,7 @@
         mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
 
         // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         // But there should be no CaptivePortal callback.
         captivePortalCallback.assertNoCallback();
     }
@@ -1836,14 +1857,14 @@
         // Bring up cell and expect CALLBACK_AVAILABLE.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
 
         // Bring up wifi and expect CALLBACK_AVAILABLE.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
         cellNetworkCallback.assertNoCallback();
-        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
 
         // Bring down cell. Expect no default network callback, since it wasn't the default.
         mCellNetworkAgent.disconnect();
@@ -1853,7 +1874,7 @@
         // Bring up cell. Expect no default network callback, since it won't be the default.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         defaultNetworkCallback.assertNoCallback();
 
         // Bring down wifi. Expect the default network callback to notified of LOST wifi
@@ -1861,28 +1882,16 @@
         mWiFiNetworkAgent.disconnect();
         cellNetworkCallback.assertNoCallback();
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
         mCellNetworkAgent.disconnect();
         cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
     }
 
-    private class TestRequestUpdateCallback extends TestNetworkCallback {
-        @Override
-        public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
-            setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
-        }
-
-        @Override
-        public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
-            setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
-        }
-    }
-
     @SmallTest
-    public void testRequestCallbackUpdates() throws Exception {
+    public void testAdditionalStateCallbacks() throws Exception {
         // File a network request for mobile.
-        final TestNetworkCallback cellNetworkCallback = new TestRequestUpdateCallback();
+        final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
         final NetworkRequest cellRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_CELLULAR).build();
         mCm.requestNetwork(cellRequest, cellNetworkCallback);
@@ -1891,10 +1900,10 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
 
-        // We should get onAvailable().
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        // We should get onCapabilitiesChanged(), when the mobile network successfully validates.
-        cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
+        // We should get onAvailable(), onCapabilitiesChanged(), and
+        // onLinkPropertiesChanged() in rapid succession. Additionally, we
+        // should get onCapabilitiesChanged() when the mobile network validates.
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
         // Update LinkProperties.
@@ -1905,23 +1914,17 @@
         cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
         cellNetworkCallback.assertNoCallback();
 
+        // Suspend the network.
+        mCellNetworkAgent.suspend();
+        cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent);
+        cellNetworkCallback.assertNoCallback();
+
         // Register a garden variety default network request.
-        final TestNetworkCallback dfltNetworkCallback = new TestRequestUpdateCallback();
+        final TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
-        // Only onAvailable() is called; no other information is delivered.
-        dfltNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        dfltNetworkCallback.assertNoCallback();
-
-        // Request a NetworkCapabilities update; only the requesting callback is notified.
-        mCm.requestNetworkCapabilities(dfltNetworkCallback);
-        dfltNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
-        cellNetworkCallback.assertNoCallback();
-        dfltNetworkCallback.assertNoCallback();
-
-        // Request a LinkProperties update; only the requesting callback is notified.
-        mCm.requestLinkProperties(dfltNetworkCallback);
-        dfltNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
-        cellNetworkCallback.assertNoCallback();
+        // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
+        // as well as onNetworkSuspended() in rapid succession.
+        dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent);
         dfltNetworkCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -1961,24 +1964,29 @@
 
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
 
         // When wifi connects, cell lingers.
-        callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        callback.expectAvailableCallbacks(mWiFiNetworkAgent);
         callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+        fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
         // When lingering is complete, cell is still there but is now in the background.
-        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, TEST_LINGER_DELAY_MS);
-        callback.assertNoCallback();
+        mService.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.
+        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
@@ -1987,9 +1995,15 @@
                 .addTransportType(TRANSPORT_CELLULAR).build();
         final TestNetworkCallback cellCallback = new TestNetworkCallback();
         mCm.requestNetwork(cellRequest, cellCallback);
-        cellCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        callback.assertNoCallback();  // Because the network is already up.
+        // NOTE: This request causes the network's capabilities to change. This
+        // is currently delivered before the onAvailable() callbacks.
+        // TODO: Fix this.
+        cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
+        cellCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+        // Expect a network capabilities update with FOREGROUND, because the most recent
+        // request causes its state to change.
+        callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
@@ -1997,7 +2011,8 @@
         // lingering.
         mCm.unregisterNetworkCallback(cellCallback);
         fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        callback.assertNoCallback();
+        // Expect a network capabilities update sans FOREGROUND.
+        callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
 
@@ -2005,7 +2020,7 @@
         mWiFiNetworkAgent.disconnect();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
-        fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertTrue(isForegroundNetwork(mCellNetworkAgent));
 
         mCm.unregisterNetworkCallback(callback);
@@ -2146,7 +2161,7 @@
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         testFactory.expectAddRequests(2);  // Because the cell request changes score twice.
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         testFactory.waitForNetworkRequests(2);
         assertFalse(testFactory.getMyStartRequested());  // Because the cell network outscores us.
 
@@ -2172,7 +2187,7 @@
     @SmallTest
     public void testAvoidBadWifiSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedAvoidBadWifiTracker tracker = mService.getAvoidBadWifiTracker();
+        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
         final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
 
         tracker.configRestrictsAvoidBadWifi = false;
@@ -2182,7 +2197,7 @@
             tracker.reevaluate();
             mService.waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
-            assertEventuallyTrue(() -> mService.avoidBadWifi(), 50);
+            assertTrue(mService.avoidBadWifi());
             assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
         }
 
@@ -2191,26 +2206,26 @@
         Settings.Global.putInt(cr, settingName, 0);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertEventuallyTrue(() -> !mService.avoidBadWifi(), 50);
+        assertFalse(mService.avoidBadWifi());
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putInt(cr, settingName, 1);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertEventuallyTrue(() -> mService.avoidBadWifi(), 50);
+        assertTrue(mService.avoidBadWifi());
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putString(cr, settingName, null);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertEventuallyTrue(() -> !mService.avoidBadWifi(), 50);
+        assertFalse(mService.avoidBadWifi());
         assertTrue(tracker.shouldNotifyWifiUnvalidated());
     }
 
     @SmallTest
     public void testAvoidBadWifi() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
-        final WrappedAvoidBadWifiTracker tracker = mService.getAvoidBadWifiTracker();
+        final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
 
         // Pretend we're on a carrier that restricts switching away from bad wifi.
         tracker.configRestrictsAvoidBadWifi = true;
@@ -2237,20 +2252,22 @@
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         mCellNetworkAgent.connect(true);
-        cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
         Network cellNetwork = mCellNetworkAgent.getNetwork();
 
         // Bring up validated wifi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
         mCm.reportNetworkConnectivity(wifiNetwork, false);
+        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Because avoid bad wifi is off, we don't switch to cellular.
@@ -2265,18 +2282,18 @@
         // that we switch back to cell.
         tracker.configRestrictsAvoidBadWifi = false;
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // Switch back to a restrictive carrier.
         tracker.configRestrictsAvoidBadWifi = true;
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
 
         // Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
         mCm.setAvoidUnvalidated(wifiNetwork);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2287,13 +2304,15 @@
         mWiFiNetworkAgent.disconnect();
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
-        validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+        validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
         mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
         mCm.reportNetworkConnectivity(wifiNetwork, false);
+        defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // Simulate the user selecting "switch" and checking the don't ask again checkbox.
@@ -2301,7 +2320,7 @@
         tracker.reevaluate();
 
         // We now switch to cell.
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
                 NET_CAPABILITY_VALIDATED));
         assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2312,17 +2331,17 @@
         // We switch to wifi and then to cell.
         Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
         tracker.reevaluate();
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
-        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
         validatedWifiCallback.assertNoCallback();
 
         mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2339,14 +2358,14 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 10);
+        final int timeoutMs = 150;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        sleepFor(15);
         networkCallback.assertNoCallback();
     }
 
@@ -2359,17 +2378,19 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 500);
+        final int requestTimeoutMs = 100;
+        mCm.requestNetwork(nr, networkCallback, requestTimeoutMs);
 
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(false);
-        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        final int assertTimeoutMs = 150;
+        networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
         sleepFor(20);
         mWiFiNetworkAgent.disconnect();
         networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        sleepFor(600);
+        sleepFor(100);
         networkCallback.assertNoCallback();
     }
 
@@ -2383,7 +2404,8 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 10);
+        final int timeoutMs = 10;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is called
         networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
@@ -2403,7 +2425,8 @@
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
         final TestNetworkCallback networkCallback = new TestNetworkCallback();
-        mCm.requestNetwork(nr, networkCallback, 10);
+        final int timeoutMs = 10;
+        mCm.requestNetwork(nr, networkCallback, timeoutMs);
 
         // remove request
         mCm.unregisterNetworkCallback(networkCallback);
@@ -2420,17 +2443,6 @@
         networkCallback.assertNoCallback();
     }
 
-    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
-        long start = SystemClock.elapsedRealtime();
-        while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
-            if (fn.getAsBoolean()) {
-                return;
-            }
-            Thread.sleep(10);
-        }
-        assertTrue(fn.getAsBoolean());
-    }
-
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
@@ -2591,10 +2603,13 @@
         ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4);
         callback.expectStarted();
         mWiFiNetworkAgent.disconnect();
+        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
-        assertNull(mCm.getNetworkCapabilities(myNet));
+        mService.waitForIdle();
+        final Network myNetAlias = myNet;
+        assertNull(mCm.getNetworkCapabilities(myNetAlias));
         ka.stop();
 
         // Reconnect.
@@ -2606,6 +2621,7 @@
         callback.expectStarted();
         ka.stop();
         mWiFiNetworkAgent.disconnect();
+        waitFor(mWiFiNetworkAgent.getDisconnectedCV());
         mService.waitForIdle();
         callback.expectStopped();
 
@@ -2838,11 +2854,11 @@
     }
 
     /* test utilities */
+    // TODO: eliminate all usages of sleepFor and replace by proper timeouts/waitForIdle.
     static private void sleepFor(int ms) {
         try {
             Thread.sleep(ms);
         } catch (InterruptedException e) {
         }
-
     }
 }
diff --git a/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java b/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
deleted file mode 100644
index 5981f48..0000000
--- a/tests/net/java/com/android/server/connectivity/MetricsLoggerServiceTest.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity;
-
-import android.content.Context;
-import android.net.ConnectivityMetricsEvent;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
-import static android.net.ConnectivityMetricsEvent.Reference;
-
-import junit.framework.TestCase;
-import org.junit.Before;
-import org.junit.Test;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertArrayEquals;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/*
- * TODO:
- *  - allow overriding MetricsLoggerService constants in tests.
- *  - test intents are correctly sent after the notification threshold.
- *  - test oldest events are correctly pushed out when internal deque is full.
- *  - test throttling triggers correctly.
- */
-public class MetricsLoggerServiceTest extends TestCase {
-
-    static final int COMPONENT_TAG = 1;
-    static final long N_EVENTS = 10L;
-    static final ConnectivityMetricsEvent EVENTS[] = new ConnectivityMetricsEvent[(int)N_EVENTS];
-    static {
-        for (int i = 0; i < N_EVENTS; i++) {
-            EVENTS[i] = new ConnectivityMetricsEvent(i, COMPONENT_TAG, i, new Bundle());
-        }
-    }
-
-    static final ConnectivityMetricsEvent NO_EVENTS[] = new ConnectivityMetricsEvent[0];
-
-    @Mock Context mContext;
-    MetricsLoggerService mService;
-
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mService = new MetricsLoggerService(mContext);
-        mService.onStart();
-    }
-
-    @SmallTest
-    public void testGetNoEvents() throws Exception {
-        Reference r = new Reference(0);
-        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
-        assertEquals(0, r.getValue());
-    }
-
-    @SmallTest
-    public void testLogAndGetEvents() throws Exception {
-        mService.mBinder.logEvents(EVENTS);
-
-        Reference r = new Reference(0);
-
-        assertArrayEquals(EVENTS, mService.mBinder.getEvents(r));
-        assertEquals(N_EVENTS, r.getValue());
-
-        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
-        assertEquals(N_EVENTS, r.getValue());
-    }
-
-    @SmallTest
-    public void testLogOneByOne() throws Exception {
-        for (ConnectivityMetricsEvent ev : EVENTS) {
-            mService.mBinder.logEvent(ev);
-        }
-
-        Reference r = new Reference(0);
-
-        assertArrayEquals(EVENTS, mService.mBinder.getEvents(r));
-        assertEquals(N_EVENTS, r.getValue());
-
-        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
-        assertEquals(N_EVENTS, r.getValue());
-    }
-
-    @SmallTest
-    public void testInterleavedLogAndGet() throws Exception {
-        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 0, 3));
-
-        Reference r = new Reference(0);
-
-        assertArrayEquals(Arrays.copyOfRange(EVENTS, 0, 3), mService.mBinder.getEvents(r));
-        assertEquals(3, r.getValue());
-
-        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 3, 8));
-        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 8, 10));
-
-        assertArrayEquals(Arrays.copyOfRange(EVENTS, 3, 10), mService.mBinder.getEvents(r));
-        assertEquals(N_EVENTS, r.getValue());
-
-        assertArrayEquals(NO_EVENTS, mService.mBinder.getEvents(r));
-        assertEquals(N_EVENTS, r.getValue());
-    }
-
-    @SmallTest
-    public void testMultipleGetAll() throws Exception {
-        mService.mBinder.logEvents(Arrays.copyOf(EVENTS, 3));
-
-        Reference r1 = new Reference(0);
-        assertArrayEquals(Arrays.copyOf(EVENTS, 3), mService.mBinder.getEvents(r1));
-        assertEquals(3, r1.getValue());
-
-        mService.mBinder.logEvents(Arrays.copyOfRange(EVENTS, 3, 10));
-
-        Reference r2 = new Reference(0);
-        assertArrayEquals(EVENTS, mService.mBinder.getEvents(r2));
-        assertEquals(N_EVENTS, r2.getValue());
-    }
-
-    @SmallTest
-    public void testLogAndDumpConcurrently() throws Exception {
-        for (int i = 0; i < 50; i++) {
-            mContext = null;
-            mService = null;
-            setUp();
-            logAndDumpConcurrently();
-        }
-    }
-
-    public void logAndDumpConcurrently() throws Exception {
-        final CountDownLatch latch = new CountDownLatch((int)N_EVENTS);
-        final FileDescriptor fd = new FileOutputStream("/dev/null").getFD();
-
-        for (ConnectivityMetricsEvent ev : EVENTS) {
-            new Thread() {
-                public void run() {
-                    mService.mBinder.logEvent(ev);
-                    latch.countDown();
-                }
-            }.start();
-        }
-
-        new Thread() {
-            public void run() {
-                while (latch.getCount() > 0) {
-                    mService.mBinder.dump(fd, new String[]{"--all"});
-                }
-            }
-        }.start();
-
-        latch.await(100, TimeUnit.MILLISECONDS);
-
-        Reference r = new Reference(0);
-        ConnectivityMetricsEvent[] got = mService.mBinder.getEvents(r);
-        Arrays.sort(got, new EventComparator());
-        assertArrayEquals(EVENTS, got);
-        assertEquals(N_EVENTS, r.getValue());
-    }
-
-    static class EventComparator implements Comparator<ConnectivityMetricsEvent> {
-        public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) {
-            return Long.compare(ev1.timestamp, ev2.timestamp);
-        }
-        public boolean equal(Object o) {
-            return o instanceof EventComparator;
-        }
-    };
-}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
new file mode 100644
index 0000000..813e928
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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 com.android.server.connectivity;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import junit.framework.TestCase;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class NetworkNotificationManagerTest extends TestCase {
+
+    static final String NOTIFICATION_ID = NetworkNotificationManager.NOTIFICATION_ID;
+
+    static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
+    static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
+    static {
+        CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+        WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    @Mock Context mCtx;
+    @Mock Resources mResources;
+    @Mock PackageManager mPm;
+    @Mock TelephonyManager mTelephonyManager;
+    @Mock NotificationManager mNotificationManager;
+    @Mock NetworkAgentInfo mWifiNai;
+    @Mock NetworkAgentInfo mCellNai;
+    @Mock NetworkInfo mNetworkInfo;
+    ArgumentCaptor<Notification> mCaptor;
+
+    NetworkNotificationManager mManager;
+
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mCaptor = ArgumentCaptor.forClass(Notification.class);
+        mWifiNai.networkCapabilities = WIFI_CAPABILITIES;
+        mWifiNai.networkInfo = mNetworkInfo;
+        mCellNai.networkCapabilities = CELL_CAPABILITIES;
+        mCellNai.networkInfo = mNetworkInfo;
+        when(mCtx.getResources()).thenReturn(mResources);
+        when(mCtx.getPackageManager()).thenReturn(mPm);
+        when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
+
+        mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager);
+    }
+
+    @SmallTest
+    public void testNotificationsShownAndCleared() {
+        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++) {
+            ids.add(NETWORK_ID_BASE + i);
+        }
+        Collections.shuffle(ids);
+        Collections.shuffle(types);
+
+        for (int i = 0; i < ids.size(); i++) {
+            mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false);
+        }
+
+        Collections.shuffle(ids);
+        for (int i = 0; i < ids.size(); i++) {
+            mManager.clearNotification(ids.get(i));
+        }
+
+        for (int i = 0; i < ids.size(); i++) {
+            final int expectedId = NETWORK_ID_BASE + i;
+            verify(mNotificationManager, times(1))
+                    .notifyAsUser(eq(NOTIFICATION_ID), eq(expectedId), any(), any());
+            verify(mNotificationManager, times(1))
+                    .cancelAsUser(eq(NOTIFICATION_ID), eq(expectedId), any());
+        }
+    }
+
+    @SmallTest
+    public void testNoInternetNotificationsNotShownForCellular() {
+        mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false);
+        mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false);
+
+        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+
+        mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false);
+
+        verify(mNotificationManager, times(1))
+                .notifyAsUser(eq(NOTIFICATION_ID), eq(102), any(), any());
+    }
+
+    @SmallTest
+    public void testNotificationsNotShownIfNoInternetCapability() {
+        mWifiNai.networkCapabilities = new NetworkCapabilities();
+        mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false);
+        mManager.showNotification(103, LOST_INTERNET, mWifiNai, mCellNai, null, false);
+        mManager.showNotification(104, NETWORK_SWITCH, mWifiNai, mCellNai, null, false);
+
+        verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
+    }
+}
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 9f7261d..32e1b96 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -28,6 +28,9 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
+import static android.net.ConnectivityManager.TETHERING_USB;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
 import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
 import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
 import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
@@ -92,7 +95,7 @@
     @Test
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
-                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper,
+                TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper,
                 mIPv6TetheringInterfaceServices);
         mTestedSm.start();
         mLooper.dispatchAll();
@@ -103,7 +106,7 @@
 
     @Test
     public void shouldDoNothingUntilRequested() throws Exception {
-        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+        initStateMachine(TETHERING_BLUETOOTH);
         final int [] NOOP_COMMANDS = {
             TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
             TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
@@ -123,7 +126,7 @@
 
     @Test
     public void handlesImmediateInterfaceDown() throws Exception {
-        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+        initStateMachine(TETHERING_BLUETOOTH);
 
         dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
         verify(mTetherHelper).notifyInterfaceStateChange(
@@ -133,7 +136,7 @@
 
     @Test
     public void canBeTethered() throws Exception {
-        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+        initStateMachine(TETHERING_BLUETOOTH);
 
         dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
         InOrder inOrder = inOrder(mTetherHelper, mNMService);
@@ -145,7 +148,7 @@
 
     @Test
     public void canUnrequestTethering() throws Exception {
-        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
         InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
@@ -157,7 +160,7 @@
 
     @Test
     public void canBeTetheredAsUsb() throws Exception {
-        initStateMachine(ConnectivityManager.TETHERING_USB);
+        initStateMachine(TETHERING_USB);
 
         dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
         InOrder inOrder = inOrder(mTetherHelper, mNMService);
@@ -171,7 +174,7 @@
 
     @Test
     public void handlesFirstUpstreamChange() throws Exception {
-        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         // Telling the state machine about its upstream interface triggers a little more configuration.
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
@@ -183,7 +186,7 @@
 
     @Test
     public void handlesChangingUpstream() throws Exception {
-        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
         InOrder inOrder = inOrder(mNMService, mStatsService);
@@ -196,8 +199,44 @@
     }
 
     @Test
+    public void handlesChangingUpstreamNatFailure() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+    }
+
+    @Test
+    public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
+                IFACE_NAME, UPSTREAM_IFACE2);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+    }
+
+    @Test
     public void canUnrequestTetheringWithUpstream() throws Exception {
-        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
         InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
@@ -213,7 +252,7 @@
     @Test
     public void interfaceDownLeadsToUnavailable() throws Exception {
         for (boolean shouldThrow : new boolean[]{true, false}) {
-            initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+            initTetheredStateMachine(TETHERING_USB, null);
 
             if (shouldThrow) {
                 doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
@@ -230,7 +269,7 @@
 
     @Test
     public void usbShouldBeTornDownOnTetherError() throws Exception {
-        initStateMachine(ConnectivityManager.TETHERING_USB);
+        initStateMachine(TETHERING_USB);
 
         doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
         dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
@@ -244,7 +283,7 @@
 
     @Test
     public void shouldTearDownUsbOnUpstreamError() throws Exception {
-        initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+        initTetheredStateMachine(TETHERING_USB, null);
 
         doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
@@ -255,6 +294,18 @@
                 IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
     }
 
+    @Test
+    public void ignoresDuplicateUpstreamNotifications() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+
+        for (int i = 0; i < 5; i++) {
+            dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+            verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+        }
+    }
+
     /**
      * Send a command to the state machine under test, and run the event loop to idle.
      *
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 00420e9..c72efb0 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -22,9 +22,18 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 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.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 android.content.Context;
+import android.os.Handler;
+import android.os.Message;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.IConnectivityManager;
@@ -35,12 +44,18 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -55,6 +70,7 @@
     @Mock private Context mContext;
     @Mock private IConnectivityManager mCS;
 
+    private TestStateMachine mSM;
     private TestConnectivityManager mCM;
     private UpstreamNetworkMonitor mUNM;
 
@@ -63,18 +79,27 @@
         reset(mContext);
         reset(mCS);
 
-        mCM = new TestConnectivityManager(mContext, mCS);
-        mUNM = new UpstreamNetworkMonitor(null, EVENT_UNM_UPDATE, (ConnectivityManager) mCM);
+        mCM = spy(new TestConnectivityManager(mContext, mCS));
+        mSM = new TestStateMachine();
+        mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM);
+    }
+
+    @After public void tearDown() throws Exception {
+        if (mSM != null) {
+            mSM.quit();
+            mSM = null;
+        }
     }
 
     @Test
     public void testDoesNothingBeforeStarted() {
-        UpstreamNetworkMonitor unm = new UpstreamNetworkMonitor(null, null, EVENT_UNM_UPDATE);
-        assertFalse(unm.mobileNetworkRequested());
-        // Given a null Context, and therefore a null ConnectivityManager,
-        // these would cause an exception, if they actually attempted anything.
-        unm.mobileUpstreamRequiresDun(true);
-        unm.mobileUpstreamRequiresDun(false);
+        assertTrue(mCM.hasNoCallbacks());
+        assertFalse(mUNM.mobileNetworkRequested());
+
+        mUNM.updateMobileRequiresDun(true);
+        assertTrue(mCM.hasNoCallbacks());
+        mUNM.updateMobileRequiresDun(false);
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
@@ -85,23 +110,23 @@
         assertEquals(1, mCM.trackingDefault.size());
 
         mUNM.stop();
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
-    public void testListensForDunNetworks() throws Exception {
+    public void testListensForAllNetworks() throws Exception {
         assertTrue(mCM.listening.isEmpty());
 
         mUNM.start();
         assertFalse(mCM.listening.isEmpty());
-        assertTrue(mCM.isListeningForDun());
+        assertTrue(mCM.isListeningForAll());
 
         mUNM.stop();
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
-    public void testCanRequestMobileNetwork() throws Exception {
+    public void testRequestsMobileNetwork() throws Exception {
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -109,25 +134,60 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.mobileUpstreamRequiresDun(false);
+        mUNM.updateMobileRequiresDun(false);
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
         mUNM.registerMobileNetworkRequest();
         assertTrue(mUNM.mobileNetworkRequested());
-        assertEquals(1, mCM.requested.size());
-        assertEquals(1, mCM.legacyTypeMap.size());
-        assertEquals(Integer.valueOf(TYPE_MOBILE_HIPRI),
-                mCM.legacyTypeMap.values().iterator().next());
+        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
         assertFalse(mCM.isDunRequested());
 
         mUNM.stop();
         assertFalse(mUNM.mobileNetworkRequested());
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
     @Test
-    public void testCanRequestDunNetwork() throws Exception {
+    public void testDuplicateMobileRequestsIgnored() throws Exception {
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.start();
+        verify(mCM, Mockito.times(1)).registerNetworkCallback(
+                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
+        verify(mCM, Mockito.times(1)).registerDefaultNetworkCallback(
+                any(NetworkCallback.class), any(Handler.class));
+        assertFalse(mUNM.mobileNetworkRequested());
+        assertEquals(0, mCM.requested.size());
+
+        mUNM.updateMobileRequiresDun(true);
+        mUNM.registerMobileNetworkRequest();
+        verify(mCM, Mockito.times(1)).requestNetwork(
+                any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(),
+                any(Handler.class));
+
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
+        assertTrue(mCM.isDunRequested());
+
+        // Try a few things that must not result in any state change.
+        mUNM.registerMobileNetworkRequest();
+        mUNM.updateMobileRequiresDun(true);
+        mUNM.registerMobileNetworkRequest();
+
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
+        assertTrue(mCM.isDunRequested());
+
+        mUNM.stop();
+        verify(mCM, times(3)).unregisterNetworkCallback(any(NetworkCallback.class));
+
+        verifyNoMoreInteractions(mCM);
+    }
+
+    @Test
+    public void testRequestsDunNetwork() throws Exception {
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
@@ -135,24 +195,54 @@
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
-        mUNM.mobileUpstreamRequiresDun(true);
+        mUNM.updateMobileRequiresDun(true);
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());
 
         mUNM.registerMobileNetworkRequest();
         assertTrue(mUNM.mobileNetworkRequested());
-        assertEquals(1, mCM.requested.size());
-        assertEquals(1, mCM.legacyTypeMap.size());
-        assertEquals(Integer.valueOf(TYPE_MOBILE_DUN),
-                mCM.legacyTypeMap.values().iterator().next());
+        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
         assertTrue(mCM.isDunRequested());
 
         mUNM.stop();
         assertFalse(mUNM.mobileNetworkRequested());
-        assertTrue(mCM.isEmpty());
+        assertTrue(mCM.hasNoCallbacks());
     }
 
-    private static class TestConnectivityManager extends ConnectivityManager {
+    @Test
+    public void testUpdateMobileRequiresDun() throws Exception {
+        mUNM.start();
+
+        // Test going from no-DUN to DUN correctly re-registers callbacks.
+        mUNM.updateMobileRequiresDun(false);
+        mUNM.registerMobileNetworkRequest();
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+        assertFalse(mCM.isDunRequested());
+        mUNM.updateMobileRequiresDun(true);
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
+        assertTrue(mCM.isDunRequested());
+
+        // Test going from DUN to no-DUN correctly re-registers callbacks.
+        mUNM.updateMobileRequiresDun(false);
+        assertTrue(mUNM.mobileNetworkRequested());
+        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
+        assertFalse(mCM.isDunRequested());
+
+        mUNM.stop();
+        assertFalse(mUNM.mobileNetworkRequested());
+    }
+
+    private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
+        assertEquals(1, mCM.requested.size());
+        assertEquals(1, mCM.legacyTypeMap.size());
+        assertEquals(Integer.valueOf(upstreamType),
+                mCM.legacyTypeMap.values().iterator().next());
+    }
+
+    public static class TestConnectivityManager extends ConnectivityManager {
+        public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
         public Set<NetworkCallback> trackingDefault = new HashSet<>();
         public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
         public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
@@ -162,16 +252,20 @@
             super(ctx, svc);
         }
 
-        boolean isEmpty() {
-            return trackingDefault.isEmpty() &&
+        boolean hasNoCallbacks() {
+            return allCallbacks.isEmpty() &&
+                   trackingDefault.isEmpty() &&
                    listening.isEmpty() &&
                    requested.isEmpty() &&
                    legacyTypeMap.isEmpty();
         }
 
-        boolean isListeningForDun() {
+        boolean isListeningForAll() {
+            final NetworkCapabilities empty = new NetworkCapabilities();
+            empty.clearAll();
+
             for (NetworkRequest req : listening.values()) {
-                if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
+                if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
                     return true;
                 }
             }
@@ -188,14 +282,23 @@
         }
 
         @Override
-        public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
+        public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
+            assertFalse(allCallbacks.containsKey(cb));
+            allCallbacks.put(cb, h);
             assertFalse(requested.containsKey(cb));
             requested.put(cb, req);
         }
 
         @Override
+        public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
+            fail("Should never be called.");
+        }
+
+        @Override
         public void requestNetwork(NetworkRequest req, NetworkCallback cb,
-                int timeoutMs, int legacyType) {
+                int timeoutMs, int legacyType, Handler h) {
+            assertFalse(allCallbacks.containsKey(cb));
+            allCallbacks.put(cb, h);
             assertFalse(requested.containsKey(cb));
             requested.put(cb, req);
             assertFalse(legacyTypeMap.containsKey(cb));
@@ -205,18 +308,32 @@
         }
 
         @Override
-        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
+        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
+            assertFalse(allCallbacks.containsKey(cb));
+            allCallbacks.put(cb, h);
             assertFalse(listening.containsKey(cb));
             listening.put(cb, req);
         }
 
         @Override
-        public void registerDefaultNetworkCallback(NetworkCallback cb) {
+        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
+            fail("Should never be called.");
+        }
+
+        @Override
+        public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
+            assertFalse(allCallbacks.containsKey(cb));
+            allCallbacks.put(cb, h);
             assertFalse(trackingDefault.contains(cb));
             trackingDefault.add(cb);
         }
 
         @Override
+        public void registerDefaultNetworkCallback(NetworkCallback cb) {
+            fail("Should never be called.");
+        }
+
+        @Override
         public void unregisterNetworkCallback(NetworkCallback cb) {
             if (trackingDefault.contains(cb)) {
                 trackingDefault.remove(cb);
@@ -225,11 +342,38 @@
             } else if (requested.containsKey(cb)) {
                 requested.remove(cb);
                 legacyTypeMap.remove(cb);
+            } else {
+                fail("Unexpected callback removed");
             }
+            allCallbacks.remove(cb);
 
+            assertFalse(allCallbacks.containsKey(cb));
             assertFalse(trackingDefault.contains(cb));
             assertFalse(listening.containsKey(cb));
             assertFalse(requested.containsKey(cb));
         }
     }
+
+    public static class TestStateMachine extends StateMachine {
+        public final ArrayList<Message> messages = new ArrayList<>();
+        private final State mLoggingState = new LoggingState();
+
+        class LoggingState extends State {
+            @Override public void enter() { messages.clear(); }
+
+            @Override public void exit() { messages.clear(); }
+
+            @Override public boolean processMessage(Message msg) {
+                messages.add(msg);
+                return true;
+            }
+        }
+
+        public TestStateMachine() {
+            super("UpstreamNetworkMonitor.TestStateMachine");
+            addState(mLoggingState);
+            setInitialState(mLoggingState);
+            super.start();
+        }
+    }
 }
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index 7ec46a3e..c5598f0 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -14,7 +14,9 @@
 
 LANG_TO_SCRIPT = {
     'as': 'Beng',
+    'bg': 'Cyrl',
     'bn': 'Beng',
+    'cu': 'Cyrl',
     'cy': 'Latn',
     'da': 'Latn',
     'de': 'Latn',
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
index c7f2c41..7611cde 100644
--- a/tools/layoutlib/create/Android.mk
+++ b/tools/layoutlib/create/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_JAR_MANIFEST := manifest.txt
 LOCAL_STATIC_JAVA_LIBRARIES := \
-	asm-5.0
+	asm-5.2
 
 LOCAL_MODULE := layoutlib_create
 
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index 61e381d..488d7d6 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -24,7 +24,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_JAVA_LIBRARIES := layoutlib_create junit-host
-LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0
+LOCAL_STATIC_JAVA_LIBRARIES := asm-5.2
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 18c1245..af48d0a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -16,7 +16,11 @@
 
 package android.net.wifi;
 
+
+import android.content.pm.ParceledListSlice;
+
 import android.net.wifi.hotspot2.PasspointConfiguration;
+
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.ScanSettings;
@@ -51,9 +55,9 @@
      */
     oneway void requestActivityInfo(in ResultReceiver result);
 
-    List<WifiConfiguration> getConfiguredNetworks();
+    ParceledListSlice getConfiguredNetworks();
 
-    List<WifiConfiguration> getPrivilegedConfiguredNetworks();
+    ParceledListSlice getPrivilegedConfiguredNetworks();
 
     WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
 
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/wifi/java/android/net/wifi/IconInfo.aidl
similarity index 82%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to wifi/java/android/net/wifi/IconInfo.aidl
index 62d5603..a7bb2ef 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/wifi/java/android/net/wifi/IconInfo.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2016, The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net.wifi.hotspot2.pps;
+package android.net.wifi;
 
-parcelable HomeSP;
+parcelable IconInfo;
diff --git a/wifi/java/android/net/wifi/IconInfo.java b/wifi/java/android/net/wifi/IconInfo.java
new file mode 100644
index 0000000..0eae363
--- /dev/null
+++ b/wifi/java/android/net/wifi/IconInfo.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.os.Parcel;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A class representing icon information.
+ */
+public final class IconInfo implements Parcelable {
+    /**
+     * Name of the icon file.
+     */
+    private final String mFilename;
+
+    /**
+     * Raw binary data of the icon.
+     */
+    private final byte[] mData;
+
+    public IconInfo(String filename, byte[] data) {
+        mFilename = filename;
+        mData = data;
+    }
+
+    public IconInfo(IconInfo source) {
+        if (source == null) {
+            mFilename = null;
+            mData = null;
+            return;
+        }
+
+        mFilename = source.mFilename;
+        if (source.mData != null) {
+            mData = Arrays.copyOf(source.mData, source.mData.length);
+        } else {
+            mData = null;
+        }
+    }
+
+    public String getFilename() {
+        return mFilename;
+    }
+
+    public byte[] getData() {
+        return mData;
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof IconInfo)) {
+            return false;
+        }
+        IconInfo that = (IconInfo) thatObject;
+        return TextUtils.equals(mFilename, that.mFilename)
+                && Arrays.equals(mData, that.mData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFilename, mData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mFilename);
+        dest.writeByteArray(mData);
+    }
+
+    public static final Creator<IconInfo> CREATOR =
+        new Creator<IconInfo>() {
+            @Override
+            public IconInfo createFromParcel(Parcel in) {
+                String filename = in.readString();
+                byte[] data = in.createByteArray();
+                return new IconInfo(filename, data);
+            }
+
+            @Override
+            public IconInfo[] newArray(int size) {
+                return new IconInfo[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 3a45671..dac511e 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -281,7 +281,9 @@
     public int apChannel = 0;
 
     /**
-     * Pre-shared key for use with WPA-PSK.
+     * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in
+     * double quotation marks (e.g., {@code "abcdefghij"} for PSK passphrase or
+     * a string of 64 hex digits for raw PSK.
      * <p/>
      * When the value of this key is read, the actual key is
      * not returned, just a "*" if the key has a value, or the null
@@ -305,7 +307,9 @@
     /**
      * Priority determines the preference given to a network by {@code wpa_supplicant}
      * when choosing an access point with which to associate.
+     * @deprecated This field does not exist anymore.
      */
+    @Deprecated
     public int priority;
 
     /**
@@ -373,6 +377,14 @@
     public String providerFriendlyName;
 
     /**
+     * Flag indicating if this network is provided by a home Passpoint provider or a roaming
+     * Passpoint provider.  This flag will be {@code true} if this network is provided by
+     * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
+     * or is a non-Passpoint network.
+     */
+    public boolean isHomeProviderNetwork;
+
+    /**
      * Roaming Consortium Id list for passpoint credential; identifies a set of networks where
      * passpoint credential will be considered valid
      */
@@ -424,6 +436,13 @@
     public int dtimInterval = 0;
 
     /**
+     * Flag indicating if this configuration represents a legacy Passpoint configuration
+     * (Release N or older).  This is used for migrating Passpoint configuration from N to O.
+     * This will no longer be needed after O.
+     * @hide
+     */
+    public boolean isLegacyPasspointConfig = false;
+    /**
      * @hide
      * Uid of app creating the configuration
      */
@@ -837,6 +856,7 @@
          * This network is disabled because EAP-TLS failure
          */
         public static final int DISABLED_TLS_VERSION_MISMATCH = 7;
+        // Values above are for temporary disablement; values below are for permanent disablement.
         /**
          * This network is disabled due to absence of user credentials
          */
@@ -961,6 +981,28 @@
         private boolean mHasEverConnected;
 
         /**
+         * Boolean indicating whether {@link com.android.server.wifi.RecommendedNetworkEvaluator}
+         * chose not to connect to this network in the last qualified network selection process.
+         */
+        private boolean mNotRecommended;
+
+        /**
+         * Set whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
+         * recommend connecting to this network.
+         */
+        public void setNotRecommended(boolean notRecommended) {
+            mNotRecommended = notRecommended;
+        }
+
+        /**
+         * Returns whether {@link com.android.server.wifi.RecommendedNetworkEvaluator} does not
+         * recommend connecting to this network.
+         */
+        public boolean isNotRecommended() {
+            return mNotRecommended;
+        }
+
+        /**
          * set whether this network is visible in latest Qualified Network Selection
          * @param seen value set to candidate
          */
@@ -1264,6 +1306,7 @@
             setConnectChoice(source.getConnectChoice());
             setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
             setHasEverConnected(source.getHasEverConnected());
+            setNotRecommended(source.isNotRecommended());
         }
 
         public void writeToParcel(Parcel dest) {
@@ -1283,6 +1326,7 @@
                 dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
             }
             dest.writeInt(getHasEverConnected() ? 1 : 0);
+            dest.writeInt(isNotRecommended() ? 1 : 0);
         }
 
         public void readFromParcel(Parcel in) {
@@ -1302,6 +1346,7 @@
                 setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
             }
             setHasEverConnected(in.readInt() != 0);
+            setNotRecommended(in.readInt() != 0);
         }
     }
 
@@ -1881,6 +1926,7 @@
             FQDN = source.FQDN;
             roamingConsortiumIds = source.roamingConsortiumIds.clone();
             providerFriendlyName = source.providerFriendlyName;
+            isHomeProviderNetwork = source.isHomeProviderNetwork;
             preSharedKey = source.preSharedKey;
 
             mNetworkSelectionStatus.copy(source.getNetworkSelectionStatus());
@@ -1914,6 +1960,7 @@
             mCachedConfigKey = null; //force null configKey
             selfAdded = source.selfAdded;
             validatedInternetAccess = source.validatedInternetAccess;
+            isLegacyPasspointConfig = source.isLegacyPasspointConfig;
             ephemeral = source.ephemeral;
             meteredHint = source.meteredHint;
             meteredOverride = source.meteredOverride;
@@ -1961,6 +2008,7 @@
         dest.writeInt(apChannel);
         dest.writeString(FQDN);
         dest.writeString(providerFriendlyName);
+        dest.writeInt(isHomeProviderNetwork ? 1 : 0);
         dest.writeInt(roamingConsortiumIds.length);
         for (long roamingConsortiumId : roamingConsortiumIds) {
             dest.writeLong(roamingConsortiumId);
@@ -1989,6 +2037,7 @@
         dest.writeInt(selfAdded ? 1 : 0);
         dest.writeInt(didSelfAdd ? 1 : 0);
         dest.writeInt(validatedInternetAccess ? 1 : 0);
+        dest.writeInt(isLegacyPasspointConfig ? 1 : 0);
         dest.writeInt(ephemeral ? 1 : 0);
         dest.writeInt(meteredHint ? 1 : 0);
         dest.writeInt(meteredOverride ? 1 : 0);
@@ -2026,6 +2075,7 @@
                 config.apChannel = in.readInt();
                 config.FQDN = in.readString();
                 config.providerFriendlyName = in.readString();
+                config.isHomeProviderNetwork = in.readInt() != 0;
                 int numRoamingConsortiumIds = in.readInt();
                 config.roamingConsortiumIds = new long[numRoamingConsortiumIds];
                 for (int i = 0; i < numRoamingConsortiumIds; i++) {
@@ -2054,6 +2104,7 @@
                 config.selfAdded = in.readInt() != 0;
                 config.didSelfAdd = in.readInt() != 0;
                 config.validatedInternetAccess = in.readInt() != 0;
+                config.isLegacyPasspointConfig = in.readInt() != 0;
                 config.ephemeral = in.readInt() != 0;
                 config.meteredHint = in.readInt() != 0;
                 config.meteredOverride = in.readInt() != 0;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index e410a9c..4268f24 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -142,7 +142,7 @@
     private HashMap<String, String> mFields = new HashMap<String, String>();
     private X509Certificate[] mCaCerts;
     private PrivateKey mClientPrivateKey;
-    private X509Certificate mClientCertificate;
+    private X509Certificate[] mClientCertificateChain;
     private int mEapMethod = Eap.NONE;
     private int mPhase2Method = Phase2.NONE;
 
@@ -161,9 +161,19 @@
         for (String key : source.mFields.keySet()) {
             mFields.put(key, source.mFields.get(key));
         }
-        mCaCerts = source.mCaCerts;
+        if (source.mCaCerts != null) {
+            mCaCerts = Arrays.copyOf(source.mCaCerts, source.mCaCerts.length);
+        } else {
+            mCaCerts = null;
+        }
         mClientPrivateKey = source.mClientPrivateKey;
-        mClientCertificate = source.mClientCertificate;
+        if (source.mClientCertificateChain != null) {
+            mClientCertificateChain = Arrays.copyOf(
+                    source.mClientCertificateChain,
+                    source.mClientCertificateChain.length);
+        } else {
+            mClientCertificateChain = null;
+        }
         mEapMethod = source.mEapMethod;
         mPhase2Method = source.mPhase2Method;
     }
@@ -185,7 +195,7 @@
         dest.writeInt(mPhase2Method);
         ParcelUtil.writeCertificates(dest, mCaCerts);
         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
-        ParcelUtil.writeCertificate(dest, mClientCertificate);
+        ParcelUtil.writeCertificates(dest, mClientCertificateChain);
     }
 
     public static final Creator<WifiEnterpriseConfig> CREATOR =
@@ -204,7 +214,7 @@
                     enterpriseConfig.mPhase2Method = in.readInt();
                     enterpriseConfig.mCaCerts = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mClientPrivateKey = ParcelUtil.readPrivateKey(in);
-                    enterpriseConfig.mClientCertificate = ParcelUtil.readCertificate(in);
+                    enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
                     return enterpriseConfig;
                 }
 
@@ -226,11 +236,11 @@
         public static final int TTLS    = 2;
         /** EAP-Password */
         public static final int PWD     = 3;
-        /** EAP-Subscriber Identity Module */
+        /** EAP-Subscriber Identity Module [RFC-4186] */
         public static final int SIM     = 4;
-        /** EAP-Authentication and Key Agreement */
+        /** EAP-Authentication and Key Agreement [RFC-4187] */
         public static final int AKA     = 5;
-        /** EAP-Authentication and Key Agreement Prime */
+        /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
         public static final int AKA_PRIME = 6;
         /** Hotspot 2.0 r2 OSEN */
         public static final int UNAUTH_TLS = 7;
@@ -253,11 +263,17 @@
         public static final int MSCHAPV2    = 3;
         /** Generic Token Card */
         public static final int GTC         = 4;
+        /** EAP-Subscriber Identity Module [RFC-4186] */
+        public static final int SIM         = 5;
+        /** EAP-Authentication and Key Agreement [RFC-4187] */
+        public static final int AKA         = 6;
+        /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
+        public static final int AKA_PRIME   = 7;
         private static final String AUTH_PREFIX = "auth=";
         private static final String AUTHEAP_PREFIX = "autheap=";
         /** @hide */
         public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP",
-                "MSCHAPV2", "GTC" };
+                "MSCHAPV2", "GTC", "SIM", "AKA", "AKA'" };
 
         /** Prevent initialization */
         private Phase2() {}
@@ -416,6 +432,9 @@
             case Phase2.MSCHAP:
             case Phase2.MSCHAPV2:
             case Phase2.GTC:
+            case Phase2.SIM:
+            case Phase2.AKA:
+            case Phase2.AKA_PRIME:
                 mPhase2Method = phase2Method;
                 break;
             default:
@@ -737,15 +756,61 @@
      * key entry when the config is saved and removing the key entry when
      * the config is removed.
 
-     * @param privateKey
-     * @param clientCertificate
+     * @param privateKey a PrivateKey instance for the end certificate.
+     * @param clientCertificate an X509Certificate representing the end certificate.
      * @throws IllegalArgumentException for an invalid key or certificate.
      */
     public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
+        X509Certificate[] clientCertificates = null;
         if (clientCertificate != null) {
-            if (clientCertificate.getBasicConstraints() != -1) {
-                throw new IllegalArgumentException("Cannot be a CA certificate");
+            clientCertificates = new X509Certificate[] {clientCertificate};
+        }
+        setClientKeyEntryWithCertificateChain(privateKey, clientCertificates);
+    }
+
+    /**
+     * Specify a private key and client certificate chain for client authorization.
+     *
+     * <p>A default name is automatically assigned to the key entry and used
+     * with this configuration.  The framework takes care of installing the
+     * key entry when the config is saved and removing the key entry when
+     * the config is removed.
+     *
+     * @param privateKey a PrivateKey instance for the end certificate.
+     * @param clientCertificateChain an array of X509Certificate instances which starts with
+     *         end certificate and continues with additional CA certificates necessary to
+     *         link the end certificate with some root certificate known by the authenticator.
+     * @throws IllegalArgumentException for an invalid key or certificate.
+     */
+    public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
+            X509Certificate[] clientCertificateChain) {
+        X509Certificate[] newCerts = null;
+        if (clientCertificateChain != null && clientCertificateChain.length > 0) {
+            // We validate that this is a well formed chain that starts
+            // with an end-certificate and is followed by CA certificates.
+            // We don't validate that each following certificate verifies
+            // the previous. https://en.wikipedia.org/wiki/Chain_of_trust
+            //
+            // Basic constraints is an X.509 extension type that defines
+            // whether a given certificate is allowed to sign additional
+            // certificates and what path length restrictions may exist.
+            // We use this to judge whether the certificate is an end
+            // certificate or a CA certificate.
+            // https://cryptography.io/en/latest/x509/reference/
+            if (clientCertificateChain[0].getBasicConstraints() != -1) {
+                throw new IllegalArgumentException(
+                        "First certificate in the chain must be a client end certificate");
             }
+
+            for (int i = 1; i < clientCertificateChain.length; i++) {
+                if (clientCertificateChain[i].getBasicConstraints() == -1) {
+                    throw new IllegalArgumentException(
+                            "All certificates following the first must be CA certificates");
+                }
+            }
+            newCerts = Arrays.copyOf(clientCertificateChain,
+                    clientCertificateChain.length);
+
             if (privateKey == null) {
                 throw new IllegalArgumentException("Client cert without a private key");
             }
@@ -755,7 +820,7 @@
         }
 
         mClientPrivateKey = privateKey;
-        mClientCertificate = clientCertificate;
+        mClientCertificateChain = newCerts;
     }
 
     /**
@@ -764,7 +829,32 @@
      * @return X.509 client certificate
      */
     public X509Certificate getClientCertificate() {
-        return mClientCertificate;
+        if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+            return mClientCertificateChain[0];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Get the complete client certificate chain in the same order as it was last supplied.
+     *
+     * <p>If the chain was last supplied by a call to
+     * {@link #setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate)}
+     * with a non-null * certificate instance, a single-element array containing the certificate
+     * will be * returned. If {@link #setClientKeyEntryWithCertificateChain(
+     * java.security.PrivateKey, java.security.cert.X509Certificate[])} was last called with a
+     * non-empty array, this array will be returned in the same order as it was supplied.
+     * Otherwise, {@code null} will be returned.
+     *
+     * @return X.509 client certificates
+     */
+    @Nullable public X509Certificate[] getClientCertificateChain() {
+        if (mClientCertificateChain != null && mClientCertificateChain.length > 0) {
+            return mClientCertificateChain;
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -772,7 +862,7 @@
      */
     public void resetClientKeyEntry() {
         mClientPrivateKey = null;
-        mClientCertificate = null;
+        mClientCertificateChain = null;
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 3b6e76f..4f2881b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -21,6 +21,7 @@
 import android.annotation.SystemApi;
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
 import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.Network;
@@ -46,6 +47,7 @@
 import java.net.InetAddress;
 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
@@ -114,49 +116,118 @@
     public static final int WIFI_CREDENTIAL_FORGOT = 1;
 
     /**
-     * Broadcast intent action indicating that the a Passpoint release 2 icon has been received.
-     * @hide
+     * Broadcast intent action indicating that a Passpoint provider icon has been received.
+     *
+     * Included extras:
+     * {@link #EXTRA_BSSID_LONG}
+     * {@link #EXTRA_ICON_INFO}
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     *
+     * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+     * components will be launched.
      */
-    public static final String PASSPOINT_ICON_RECEIVED_ACTION =
-            "android.net.wifi.PASSPOINT_ICON_RECEIVED";
-    /** @hide */
-    public static final String EXTRA_PASSPOINT_ICON_BSSID = "bssid";
-    /** @hide */
-    public static final String EXTRA_PASSPOINT_ICON_FILE = "file";
-    /** @hide */
-    public static final String EXTRA_PASSPOINT_ICON_DATA = "icon";
+    public static final String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON";
+    /**
+     * BSSID of an AP in long representation.  The {@link #EXTRA_BSSID} contains BSSID in
+     * String representation.
+     *
+     * Retrieve with {@link android.content.Intent#getLongExtra(String, long)}.
+     */
+    public static final String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG";
+    /**
+     * Icon information.
+     *
+     * Retrieve with {@link android.content.Intent#getParcelableExtra(String)} and cast into
+     * {@link IconInfo}.
+     */
+    public static final String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO";
 
     /**
-     * Broadcast intent action indicating that the a Passpoint release
-     * 2 WNM frame has been received.
-     * @hide
+     * Broadcast intent action indicating a Passpoint OSU Providers List element has been received.
+     *
+     * Included extras:
+     * {@link #EXTRA_BSSID_LONG}
+     * {@link #EXTRA_ANQP_ELEMENT_DATA}
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     *
+     * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+     * components will be launched.
+     *
      */
-    public static final String PASSPOINT_WNM_FRAME_RECEIVED_ACTION =
-            "android.net.wifi.PASSPOINT_WNM_FRAME_RECEIVED";
+    public static final String ACTION_PASSPOINT_OSU_PROVIDERS_LIST =
+            "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
     /**
-     * Originating BSS
-     * @hide */
-    public static final String EXTRA_PASSPOINT_WNM_BSSID = "bssid";
+     * Raw binary data of an ANQP (Access Network Query Protocol) element.
+     *
+     * Retrieve with {@link android.content.Intent#getByteArrayExtra(String)}.
+     */
+    public static final String EXTRA_ANQP_ELEMENT_DATA =
+            "android.net.wifi.extra.ANQP_ELEMENT_DATA";
+
     /**
-     * SOAP-XML or OMA-DM
-     * @hide */
-    public static final String EXTRA_PASSPOINT_WNM_METHOD = "method";
+     * Broadcast intent action indicating that a Passpoint Deauth Imminent frame has been received.
+     *
+     * Included extras:
+     * {@link #EXTRA_BSSID_LONG}
+     * {@link #EXTRA_ESS}
+     * {@link #EXTRA_DELAY}
+     * {@link #EXTRA_URL}
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     *
+     * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+     * components will be launched.
+     *
+     */
+    public static final String ACTION_PASSPOINT_DEAUTH_IMMINENT =
+            "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
     /**
-     * Type of Passpoint match
-     * @hide */
-    public static final String EXTRA_PASSPOINT_WNM_PPOINT_MATCH = "match";
+     * Flag indicating BSS (Basic Service Set) or ESS (Extended Service Set). This will be set to
+     * {@code true} for ESS.
+     *
+     * Retrieve with {@link android.content.Intent#getBooleanExtra(String, boolean)}.
+     */
+    public static final String EXTRA_ESS = "android.net.wifi.extra.ESS";
     /**
-     * String
-     * @hide */
-    public static final String EXTRA_PASSPOINT_WNM_URL = "url";
+     * Delay in seconds.
+     *
+     * Retrieve with {@link android.content.Intent#getIntExtra(String, int)}.
+     */
+    public static final String EXTRA_DELAY = "android.net.wifi.extra.DELAY";
     /**
-     * Boolean true=ess, false=bss
-     * @hide */
-    public static final String EXTRA_PASSPOINT_WNM_ESS = "ess";
+     * String representation of an URL.
+     *
+     * Retrieve with {@link android.content.Intent#getStringExtra(String)}.
+     */
+    public static final String EXTRA_URL = "android.net.wifi.extra.URL";
+
     /**
-     * Delay in seconds
-     * @hide */
-    public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay";
+     * Broadcast intent action indicating a Passpoint subscription remediation frame has been
+     * received.
+     *
+     * Included extras:
+     * {@link #EXTRA_BSSID_LONG}
+     * {@link #EXTRA_SUBSCRIPTION_REMEDIATION_METHOD}
+     * {@link #EXTRA_URL}
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     *
+     ** <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+     * components will be launched.
+     */
+    public static final String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION =
+            "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
+    /**
+     * The protocol supported by the subscription remediation server. The possible values are:
+     * 0 - OMA DM
+     * 1 - SOAP XML SPP
+     *
+     * Retrieve with {@link android.content.Intent#getIntExtra(String, int)}.
+     */
+    public static final String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD =
+            "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD";
 
     /**
      * Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
@@ -721,7 +792,8 @@
     }
 
     /**
-     * Return a list of all the networks configured in the supplicant.
+     * Return a list of all the networks configured for the current foreground
+     * user.
      * Not all fields of WifiConfiguration are returned. Only the following
      * fields are filled in:
      * <ul>
@@ -741,7 +813,12 @@
      */
     public List<WifiConfiguration> getConfiguredNetworks() {
         try {
-            return mService.getConfiguredNetworks();
+            ParceledListSlice<WifiConfiguration> parceledList =
+                mService.getConfiguredNetworks();
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -751,7 +828,12 @@
     @SystemApi
     public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         try {
-            return mService.getPrivilegedConfiguredNetworks();
+            ParceledListSlice<WifiConfiguration> parceledList =
+                mService.getPrivilegedConfiguredNetworks();
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -854,13 +936,15 @@
      * Name).  In the case when there is an existing configuration with the same
      * FQDN, the new configuration will replace the existing configuration.
      *
+     * An {@link IllegalArgumentException} will be thrown on failure.
+     *
      * @param config The Passpoint configuration to be added
-     * @return true on success
-     * @hide
      */
-    public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
+    public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
         try {
-            return mService.addOrUpdatePasspointConfiguration(config);
+            if (!mService.addOrUpdatePasspointConfiguration(config)) {
+                throw new IllegalArgumentException();
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -869,13 +953,15 @@
     /**
      * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
      *
+     * An {@link IllegalArgumentException} will be thrown on failure.
+     *
      * @param fqdn The FQDN of the passpoint configuration to be removed
-     * @return true on success
-     * @hide
      */
-    public boolean removePasspointConfiguration(String fqdn) {
+    public void removePasspointConfiguration(String fqdn) {
         try {
-            return mService.removePasspointConfiguration(fqdn);
+            if (!mService.removePasspointConfiguration(fqdn)) {
+                throw new IllegalArgumentException();
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -887,7 +973,6 @@
      * An empty list will be returned when no configurations are installed.
      *
      * @return A list of {@link PasspointConfiguration}
-     * @hide
      */
     public List<PasspointConfiguration> getPasspointConfigurations() {
         try {
@@ -898,10 +983,13 @@
     }
 
     /**
-     * Query for a Hotspot 2.0 release 2 OSU icon
+     * Query for a Hotspot 2.0 release 2 OSU icon file. An {@link #ACTION_PASSPOINT_ICON} intent
+     * will be broadcasted once the request is completed.  The return value of
+     * {@link IconInfo#getData} from the intent extra will indicate the result of the request.
+     * A value of {@code null} will indicate a failure.
+     *
      * @param bssid The BSSID of the AP
-     * @param fileName Icon file name
-     * @hide
+     * @param fileName Name of the icon file (remote file) to query from the AP
      */
     public void queryPasspointIcon(long bssid, String fileName) {
         try {
@@ -943,8 +1031,12 @@
      * Remove the specified network from the list of configured networks.
      * This may result in the asynchronous delivery of state change
      * events.
-     * @param netId the integer that identifies the network configuration
-     * to the supplicant
+     *
+     * Applications are not allowed to remove networks created by other
+     * applications.
+     *
+     * @param netId the ID of the network as returned by {@link #addNetwork} or {@link
+     *        #getConfiguredNetworks}.
      * @return {@code true} if the operation succeeded
      */
     public boolean removeNetwork(int netId) {
@@ -957,8 +1049,7 @@
 
     /**
      * Allow a previously configured network to be associated with. If
-     * <code>disableOthers</code> is true, then all other configured
-     * networks are disabled, and an attempt to connect to the selected
+     * <code>attemptConnect</code> is true, an attempt to connect to the selected
      * network is initiated. This may result in the asynchronous delivery
      * of state change events.
      * <p>
@@ -975,14 +1066,17 @@
      * {@link Network#openConnection(java.net.URL)}, or
      * {@link ConnectivityManager#bindProcessToNetwork} to do so.
      *
-     * @param netId the ID of the network in the list of configured networks
-     * @param disableOthers if true, disable all other networks. The way to
-     * select a particular network to connect to is specify {@code true}
-     * for this parameter.
+     * Applications are not allowed to enable networks created by other
+     * applications.
+     *
+     * @param netId the ID of the network as returned by {@link #addNetwork} or {@link
+     *        #getConfiguredNetworks}.
+     * @param attemptConnect The way to select a particular network to connect to is specify
+     *        {@code true} for this parameter.
      * @return {@code true} if the operation succeeded
      */
-    public boolean enableNetwork(int netId, boolean disableOthers) {
-        final boolean pin = disableOthers && mTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP;
+    public boolean enableNetwork(int netId, boolean attemptConnect) {
+        final boolean pin = attemptConnect && mTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP;
         if (pin) {
             NetworkRequest request = new NetworkRequest.Builder()
                     .clearCapabilities()
@@ -993,7 +1087,7 @@
 
         boolean success;
         try {
-            success = mService.enableNetwork(netId, disableOthers);
+            success = mService.enableNetwork(netId, attemptConnect);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1009,7 +1103,12 @@
      * Disable a configured network. The specified network will not be
      * a candidate for associating. This may result in the asynchronous
      * delivery of state change events.
-     * @param netId the ID of the network as returned by {@link #addNetwork}.
+     *
+     * Applications are not allowed to disable networks created by other
+     * applications.
+     *
+     * @param netId the ID of the network as returned by {@link #addNetwork} or {@link
+     *        #getConfiguredNetworks}.
      * @return {@code true} if the operation succeeded
      */
     public boolean disableNetwork(int netId) {
@@ -1068,15 +1167,11 @@
      * Check that the supplicant daemon is responding to requests.
      * @return {@code true} if we were able to communicate with the supplicant and
      * it returned the expected response to the PING message.
+     * @deprecated Will return the output of {@link #isWifiEnabled()} instead.
      */
+    @Deprecated
     public boolean pingSupplicant() {
-        if (mService == null)
-            return false;
-        try {
-            return mService.pingSupplicant();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return isWifiEnabled();
     }
 
     /** @hide */
@@ -1395,14 +1490,18 @@
     }
 
     /**
-     * Tell the supplicant to persist the current list of configured networks.
+     * Tell the device to persist the current list of configured networks.
      * <p>
      * Note: It is possible for this method to change the network IDs of
      * existing networks. You should assume the network IDs can be different
      * after calling this method.
      *
      * @return {@code true} if the operation succeeded
+     * @deprecated There is no need to call this method -
+     * {@link #addNetwork(WifiConfiguration)}, {@link #updateNetwork(WifiConfiguration)}
+     * and {@link #removeNetwork(int)} already persist the configurations automatically.
      */
+    @Deprecated
     public boolean saveConfiguration() {
         try {
             return mService.saveConfiguration();
@@ -1981,7 +2080,7 @@
 
     /**
      * Connect to a network with the given configuration. The network also
-     * gets added to the supplicant configuration.
+     * gets added to the list of configured networks for the foreground user.
      *
      * For a new network, this function is used instead of a
      * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
@@ -2010,8 +2109,8 @@
      * This function is used instead of a enableNetwork(), saveConfiguration() and
      * reconnect()
      *
-     * @param networkId the network id identifiying the network in the
-     *                supplicant configuration list
+     * @param networkId the ID of the network as returned by {@link #addNetwork} or {@link
+     *        getConfiguredNetworks}.
      * @param listener for callbacks on success or failure. Can be null.
      * @throws IllegalStateException if the WifiManager instance needs to be
      * initialized again
@@ -2023,9 +2122,9 @@
     }
 
     /**
-     * Save the given network in the supplicant config. If the network already
-     * exists, the configuration is updated. A new network is enabled
-     * by default.
+     * Save the given network to the list of configured networks for the
+     * foreground user. If the network already exists, the configuration
+     * is updated. Any new network is enabled by default.
      *
      * For a new network, this function is used instead of a
      * sequence of addNetwork(), enableNetwork() and saveConfiguration().
@@ -2046,7 +2145,8 @@
     }
 
     /**
-     * Delete the network in the supplicant config.
+     * Delete the network from the list of configured networks for the
+     * foreground user.
      *
      * This function is used instead of a sequence of removeNetwork()
      * and saveConfiguration().
@@ -2737,7 +2837,8 @@
 
     /**
      * Restore state from the older version of back up data.
-     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
+     * The old backup data was essentially a backup of wpa_supplicant.conf
+     * and ipconfig.txt file.
      * @hide
      */
     public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
index 9dd118b..35d7a12 100755
--- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -19,16 +19,16 @@
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.content.Context;
-import android.os.Handler;
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
 import android.net.ScoredNetwork;
+import android.os.Handler;
+import android.os.Process;
 import android.util.Log;
 
-import com.android.internal.util.Preconditions;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -76,7 +76,7 @@
     public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
         mContext = context.getApplicationContext();
         mListener = listener;
-        mNetworkCache = new HashMap<String, ScoredNetwork>();
+        mNetworkCache = new HashMap<>();
     }
 
     @Override public final void updateScores(List<ScoredNetwork> networks) {
@@ -210,7 +210,9 @@
 
     @Override protected final void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
-        writer.println("WifiNetworkScoreCache");
+        String header = String.format("WifiNetworkScoreCache (%s/%d)",
+                mContext.getPackageName(), Process.myUid());
+        writer.println(header);
         writer.println("  All score curves:");
         for (Map.Entry<String, ScoredNetwork> entry : mNetworkCache.entrySet()) {
             ScoredNetwork scoredNetwork = entry.getValue();
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index c53cd3c..7a3cddf 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -49,6 +49,14 @@
     private WifiSsid() {
     }
 
+    public static WifiSsid createFromByteArray(byte ssid[]) {
+        WifiSsid wifiSsid = new WifiSsid();
+        if (ssid != null) {
+            wifiSsid.octets.write(ssid, 0/* the start offset */, ssid.length);;
+        }
+        return wifiSsid;
+    }
+
     public static WifiSsid createFromAsciiEncoded(String asciiEncoded) {
         WifiSsid a = new WifiSsid();
         a.convertToBytes(asciiEncoded);
diff --git a/wifi/java/android/net/wifi/aware/ConfigRequest.java b/wifi/java/android/net/wifi/aware/ConfigRequest.java
index 6a5957b..cc14ab2 100644
--- a/wifi/java/android/net/wifi/aware/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/aware/ConfigRequest.java
@@ -19,6 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Arrays;
+
 /**
  * Defines a request object to configure a Wi-Fi Aware network. Built using
  * {@link ConfigRequest.Builder}. Configuration is requested using
@@ -41,6 +43,18 @@
     public static final int CLUSTER_ID_MAX = 0xFFFF;
 
     /**
+     * Indices for configuration variables which are specified per band.
+     */
+    public static final int NAN_BAND_24GHZ = 0;
+    public static final int NAN_BAND_5GHZ = 1;
+
+    /**
+     * Magic values for Discovery Window (DW) interval configuration
+     */
+    public static final int DW_INTERVAL_NOT_INIT = -1;
+    public static final int DW_DISABLE = 0; // only valid for 5GHz
+
+    /**
      * Indicates whether 5G band support is requested.
      */
     public final boolean mSupport5gBand;
@@ -62,19 +76,26 @@
      */
     public final int mClusterHigh;
 
+    /**
+     * Specifies the discovery window interval for the device on NAN_BAND_*.
+     */
+    public final int mDiscoveryWindowInterval[];
+
     private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
-            int clusterHigh) {
+            int clusterHigh, int discoveryWindowInterval[]) {
         mSupport5gBand = support5gBand;
         mMasterPreference = masterPreference;
         mClusterLow = clusterLow;
         mClusterHigh = clusterHigh;
+        mDiscoveryWindowInterval = discoveryWindowInterval;
     }
 
     @Override
     public String toString() {
         return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
                 + mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
-                + mClusterHigh + "]";
+                + mClusterHigh + ", mDiscoveryWindowInterval="
+                + Arrays.toString(mDiscoveryWindowInterval) + "]";
     }
 
     @Override
@@ -88,6 +109,7 @@
         dest.writeInt(mMasterPreference);
         dest.writeInt(mClusterLow);
         dest.writeInt(mClusterHigh);
+        dest.writeIntArray(mDiscoveryWindowInterval);
     }
 
     public static final Creator<ConfigRequest> CREATOR = new Creator<ConfigRequest>() {
@@ -102,7 +124,10 @@
             int masterPreference = in.readInt();
             int clusterLow = in.readInt();
             int clusterHigh = in.readInt();
-            return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh);
+            int discoveryWindowInterval[] = in.createIntArray();
+
+            return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh,
+                    discoveryWindowInterval);
         }
     };
 
@@ -119,17 +144,8 @@
         ConfigRequest lhs = (ConfigRequest) o;
 
         return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
-                && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh;
-    }
-
-    /**
-     * Checks whether the configuration's settings are non-default.
-     *
-     * @return true if any of the settings are non-default.
-     */
-    public boolean isNonDefault() {
-        return mSupport5gBand || mMasterPreference != 0 || mClusterLow != CLUSTER_ID_MIN
-                || mClusterHigh != CLUSTER_ID_MAX;
+                && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh
+                && Arrays.equals(mDiscoveryWindowInterval, lhs.mDiscoveryWindowInterval);
     }
 
     @Override
@@ -140,6 +156,7 @@
         result = 31 * result + mMasterPreference;
         result = 31 * result + mClusterLow;
         result = 31 * result + mClusterHigh;
+        result = 31 * result + Arrays.hashCode(mDiscoveryWindowInterval);
 
         return result;
     }
@@ -173,6 +190,23 @@
             throw new IllegalArgumentException(
                     "Invalid argument combination - must have Cluster Low <= Cluster High");
         }
+        if (mDiscoveryWindowInterval.length != 2) {
+            throw new IllegalArgumentException(
+                    "Invalid discovery window interval: must have 2 elements (2.4 & 5");
+        }
+        if (mDiscoveryWindowInterval[NAN_BAND_24GHZ] != DW_INTERVAL_NOT_INIT &&
+                (mDiscoveryWindowInterval[NAN_BAND_24GHZ] < 1 // valid for 2.4GHz: [1-5]
+                || mDiscoveryWindowInterval[NAN_BAND_24GHZ] > 5)) {
+            throw new IllegalArgumentException(
+                    "Invalid discovery window interval for 2.4GHz: valid is UNSET or [1,5]");
+        }
+        if (mDiscoveryWindowInterval[NAN_BAND_5GHZ] != DW_INTERVAL_NOT_INIT &&
+                (mDiscoveryWindowInterval[NAN_BAND_5GHZ] < 0 // valid for 5GHz: [0-5]
+                || mDiscoveryWindowInterval[NAN_BAND_5GHZ] > 5)) {
+            throw new IllegalArgumentException(
+                "Invalid discovery window interval for 5GHz: valid is UNSET or [0,5]");
+        }
+
     }
 
     /**
@@ -183,6 +217,7 @@
         private int mMasterPreference = 0;
         private int mClusterLow = CLUSTER_ID_MIN;
         private int mClusterHigh = CLUSTER_ID_MAX;
+        private int mDiscoveryWindowInterval[] = {DW_INTERVAL_NOT_INIT, DW_INTERVAL_NOT_INIT};
 
         /**
          * Specify whether 5G band support is required in this request. Disabled by default.
@@ -271,6 +306,33 @@
         }
 
         /**
+         * The discovery window interval specifies the discovery windows in which the device will be
+         * awake. The configuration enables trading off latency vs. power (higher interval means
+         * higher discovery latency but lower power).
+         *
+         * @param band Either {@link #NAN_BAND_24GHZ} or {@link #NAN_BAND_5GHZ}.
+         * @param interval A value of 1, 2, 3, 4, or 5 indicating an interval of 2^(interval-1). For
+         *                 the 5GHz band a value of 0 indicates that the device will not be awake
+         *                 for any discovery windows.
+         *
+         * @return The builder itself to facilitate chaining operations
+         *         {@code builder.setDiscoveryWindowInterval(...).setMasterPreference(...)}.
+         */
+        public Builder setDiscoveryWindowInterval(int band, int interval) {
+            if (band != NAN_BAND_24GHZ && band != NAN_BAND_5GHZ) {
+                throw new IllegalArgumentException("Invalid band value");
+            }
+            if ((band == NAN_BAND_24GHZ && (interval < 1 || interval > 5))
+                    || (band == NAN_BAND_5GHZ && (interval < 0 || interval > 5))) {
+                throw new IllegalArgumentException(
+                        "Invalid interval value: 2.4 GHz [1,5] or 5GHz [0,5]");
+            }
+
+            mDiscoveryWindowInterval[band] = interval;
+            return this;
+        }
+
+        /**
          * Build {@link ConfigRequest} given the current requests made on the
          * builder.
          */
@@ -280,7 +342,8 @@
                         "Invalid argument combination - must have Cluster Low <= Cluster High");
             }
 
-            return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh);
+            return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh,
+                    mDiscoveryWindowInterval);
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index adf189b..82b3792 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.wifi.RttManager;
 import android.util.Log;
 
@@ -31,10 +32,10 @@
  * {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This
  * class provides functionality common to both publish and subscribe discovery sessions:
  * <ul>
- *     <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} or
- *     {@link #sendMessage(PeerHandle, int, byte[], int)} methods.
+ *     <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
  *     <li>Creating a network-specifier when requesting a Aware connection:
- *     {@link #createNetworkSpecifier(PeerHandle, byte[])}.
+ *     {@link #createNetworkSpecifierOpen(PeerHandle)} or
+ *     {@link #createNetworkSpecifierPassphrase(PeerHandle, String)}.
  * </ul>
  * The {@link #destroy()} method must be called to destroy discovery sessions once they are
  * no longer needed.
@@ -62,6 +63,8 @@
      * {@link #sendMessage(PeerHandle, int, byte[], int)}.
      *
      * @return Maximum retry count when sending messages.
+     *
+     * @hide
      */
     public static int getMaxSendRetryCount() {
         return MAX_SEND_RETRY_COUNT;
@@ -115,6 +118,7 @@
             Log.w(TAG, "terminate: already terminated.");
             return;
         }
+
         mTerminated = true;
         mMgr.clear();
         mCloseGuard.close();
@@ -163,21 +167,23 @@
      *            or MAC level) retries should be attempted if there is no ACK from the receiver
      *            (note: no retransmissions are attempted in other failure cases). A value of 0
      *            indicates no retries. Max permitted value is {@link #getMaxSendRetryCount()}.
+     *
+     * @hide
      */
     public void sendMessage(@NonNull PeerHandle peerHandle, int messageId,
             @Nullable byte[] message, int retryCount) {
         if (mTerminated) {
             Log.w(TAG, "sendMessage: called on terminated session");
             return;
-        } else {
-            WifiAwareManager mgr = mMgr.get();
-            if (mgr == null) {
-                Log.w(TAG, "sendMessage: called post GC on WifiAwareManager");
-                return;
-            }
-
-            mgr.sendMessage(mClientId, mSessionId, peerHandle, message, messageId, retryCount);
         }
+
+        WifiAwareManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.w(TAG, "sendMessage: called post GC on WifiAwareManager");
+            return;
+        }
+
+        mgr.sendMessage(mClientId, mSessionId, peerHandle, message, messageId, retryCount);
     }
 
     /**
@@ -195,8 +201,6 @@
      * The peer will get a callback indicating a message was received using
      * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
      * byte[])}.
-     * Equivalent to {@link #sendMessage(PeerHandle, int, byte[], int)}
-     * with a {@code retryCount} of 0.
      *
      * @param peerHandle The peer's handle for the message. Must be a result of an
      * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
@@ -234,45 +238,96 @@
         if (mTerminated) {
             Log.w(TAG, "startRanging: called on terminated session");
             return;
-        } else {
-            WifiAwareManager mgr = mMgr.get();
-            if (mgr == null) {
-                Log.w(TAG, "startRanging: called post GC on WifiAwareManager");
-                return;
-            }
-
-            mgr.startRanging(mClientId, mSessionId, params, listener);
         }
+
+        WifiAwareManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.w(TAG, "startRanging: called post GC on WifiAwareManager");
+            return;
+        }
+
+        mgr.startRanging(mClientId, mSessionId, params, listener);
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for a
-     * WiFi Aware connection to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} 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>
      * This method should be used when setting up a connection with a peer discovered through Aware
      * discovery or communication (in such scenarios the MAC address of the peer is shielded by
-     * an opaque peer ID handle). If a Aware connection is needed to a peer discovered using other
+     * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
      * OOB (out-of-band) mechanism then use the alternative
-     * {@link WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])} method - which uses the
+     * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} method - which uses the
      * peer's MAC address.
      * <p>
      * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
      * and a Publisher is a RESPONDER.
+     * <p>
+     * To set up an encrypted link use the
+     * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} API.
+     *
+     * @param peerHandle The peer's handle obtained through
+     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
+     *                   or
+     *                   {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
+     *                   On a RESPONDER this value is used to gate the acceptance of a connection
+     *                   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
+     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+     * android.net.ConnectivityManager.NetworkCallback)}
+     * [or other varieties of that API].
+     */
+    public String createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
+        if (mTerminated) {
+            Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
+            return null;
+        }
+
+        WifiAwareManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.w(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
+            return null;
+        }
+
+        int role = this instanceof SubscribeDiscoverySession
+                ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+        return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null, null);
+    }
+
+    /**
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} 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>
+     * This method should be used when setting up a connection with a peer discovered through Aware
+     * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+     * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
+     * OOB (out-of-band) mechanism then use the alternative
+     * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)} method -
+     * which uses the peer's MAC address.
+     * <p>
+     * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
+     * and a Publisher is a RESPONDER.
      *
      * @param peerHandle The peer's handle obtained through
      * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
      * byte[], java.util.List)} or
      * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
      * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
-     *                   from only that peer. A RESPONDER may specified a null - indicating that
-     *                   it will accept connection requests from any device.
-     * @param token An arbitrary token (message) to be used to match connection initiation request
-     *              to a responder setup. A RESPONDER is set up with a {@code token} which must
-     *              be matched by the token provided by the INITIATOR. A null token is permitted
-     *              on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
-     *              not the same as a null token and requires the peer token to be empty as well.
+     *                   from only that peer. A RESPONDER may specify a {@code null} - indicating
+     *                   that it will accept connection requests from any device.
+     * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
+     *                   the passphrase. Use the
+     *                   {@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
@@ -280,23 +335,90 @@
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
-    public String createNetworkSpecifier(@Nullable PeerHandle peerHandle,
-            @Nullable byte[] token) {
-        if (mTerminated) {
-            Log.w(TAG, "createNetworkSpecifier: called on terminated session");
-            return null;
-        } else {
-            WifiAwareManager mgr = mMgr.get();
-            if (mgr == null) {
-                Log.w(TAG, "createNetworkSpecifier: called post GC on WifiAwareManager");
-                return null;
-            }
-
-            int role = this instanceof SubscribeDiscoverySession
-                    ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
-                    : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
-
-            return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, token);
+    public String createNetworkSpecifierPassphrase(@Nullable PeerHandle peerHandle,
+            @NonNull String passphrase) {
+        if (passphrase == null || passphrase.length() == 0) {
+            throw new IllegalArgumentException("Passphrase must not be null or empty");
         }
+
+        if (mTerminated) {
+            Log.w(TAG, "createNetworkSpecifierPassphrase: called on terminated session");
+            return null;
+        }
+
+        WifiAwareManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.w(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
+            return null;
+        }
+
+        int role = this instanceof SubscribeDiscoverySession
+                ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+        return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null,
+                passphrase);
+    }
+
+    /**
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} 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>
+     * This method should be used when setting up a connection with a peer discovered through Aware
+     * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+     * an opaque peer ID handle). If an Aware connection is needed to a peer discovered using other
+     * OOB (out-of-band) mechanism then use the alternative
+     * {@link WifiAwareSession#createNetworkSpecifierPmk(int, byte[], byte[])} method - which uses
+     * the peer's MAC address.
+     * <p>
+     * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
+     * and a Publisher is a RESPONDER.
+     *
+     * @param peerHandle The peer's handle obtained through
+     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+     * byte[], java.util.List)} or
+     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+     * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
+     *                   from only that peer. A RESPONDER may specify a null - indicating that
+     *                   it will accept connection requests from any device.
+     * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+     *            encrypting the data-path. Use the
+     *            {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} to specify a
+     *            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
+     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+     * android.net.ConnectivityManager.NetworkCallback)}
+     * [or other varieties of that API].
+     *
+     * @hide
+     */
+    @SystemApi
+    public String createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
+            @NonNull byte[] pmk) {
+        if (pmk == null || pmk.length == 0) {
+            throw new IllegalArgumentException("PMK must not be null or empty");
+        }
+
+        if (mTerminated) {
+            Log.w(TAG, "createNetworkSpecifierPmk: called on terminated session");
+            return null;
+        }
+
+        WifiAwareManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.w(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
+            return null;
+        }
+
+        int role = this instanceof SubscribeDiscoverySession
+                ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+                : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+        return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, pmk, null);
     }
 }
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
index 33da182..9645b1d 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySessionCallback.java
@@ -124,10 +124,9 @@
     }
 
     /**
-     * Called when message transmission fails - when no ACK is received from the peer.
-     * Retries when ACKs are not received are done by hardware, MAC, and in the Aware stack (using
-     * the {@link DiscoverySession#sendMessage(PeerHandle, int,
-     * byte[], int)} method) - this event is received after all retries are exhausted.
+     * Called when message transmission initiated with
+     * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} fails. E.g. when no ACK is
+     * received from the peer.
      * <p>
      * Note that either this callback or
      * {@link DiscoverySessionCallback#onMessageSendSucceeded(int)} will be received
@@ -141,9 +140,7 @@
 
     /**
      * Called when a message is received from a discovery session peer - in response to the
-     * peer's {@link DiscoverySession#sendMessage(PeerHandle, int,
-     * byte[])} or {@link DiscoverySession#sendMessage(PeerHandle,
-     * int, byte[], int)}.
+     * peer's {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])}.
      *
      * @param peerHandle An opaque handle to the peer matching our discovery operation.
      * @param message A byte array containing the message.
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
index 794c142..0f4910f 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
@@ -34,8 +34,6 @@
 interface IWifiAwareManager
 {
     // Aware API
-    void enableUsage();
-    void disableUsage();
     boolean isUsageEnabled();
     Characteristics getCharacteristics();
 
diff --git a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
index cae8706..81a06e8 100644
--- a/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
+++ b/wifi/java/android/net/wifi/aware/IdentityChangedListener.java
@@ -24,7 +24,8 @@
  * your identity - e.g. by starting a discovery session. This actual MAC address of the
  * interface may also be useful if the application uses alternative (non-Aware) discovery but needs
  * to set up a Aware connection. The provided Aware discovery interface MAC address can then be used
- * in {@link WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])}.
+ * in {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} or
+ * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}.
  */
 public class IdentityChangedListener {
     /**
diff --git a/wifi/java/android/net/wifi/aware/PeerHandle.java b/wifi/java/android/net/wifi/aware/PeerHandle.java
index bbe9f54..cd45c52 100644
--- a/wifi/java/android/net/wifi/aware/PeerHandle.java
+++ b/wifi/java/android/net/wifi/aware/PeerHandle.java
@@ -19,9 +19,10 @@
 /**
  * Opaque object used to represent a Wi-Fi Aware peer. Obtained from discovery sessions in
  * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}, used
- * when sending messages e,g, {@link PublishDiscoverySession#sendMessage(PeerHandle, int, byte[])},
+ * when sending messages e,g, {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])},
  * or when configuring a network link to a peer, e.g.
- * {@link PublishDiscoverySession#createNetworkSpecifier(PeerHandle, byte[])}.
+ * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)} or
+ * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
  */
 public class PeerHandle {
     /** @hide */
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 043925ed..4d3957a 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -65,8 +65,10 @@
  * <li>Create a Aware network specifier to be used with
  * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
  * to set-up a Aware connection with a peer. Refer to
- * {@link DiscoverySession#createNetworkSpecifier(PeerHandle, byte[])} and
- * {@link WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])}.
+ * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)},
+ * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)},
+ * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])}, and
+ * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}.
  * </ul>
  * <p>
  *     Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that
@@ -115,8 +117,10 @@
  *        <li>{@link NetworkRequest.Builder#addTransportType(int)} of
  *        {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
  *        <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
- *        {@link WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])} or
- *        {@link DiscoverySession#createNetworkSpecifier(PeerHandle, byte[])}.
+ *        {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])},
+ *        {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)},
+ *        {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}, or
+ *        {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
  *    </ul>
  */
 public class WifiAwareManager {
@@ -130,55 +134,34 @@
      */
 
     /**
-     * TYPE_1A: role, client_id, session_id, peer_id, token
+     * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk/passphrase optional
      * @hide
      */
-    public static final int NETWORK_SPECIFIER_TYPE_1A = 0;
+    public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
 
     /**
-     * TYPE_1B: role, client_id, session_id, peer_id [only permitted for RESPONDER]
+     * 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_1B = 1;
+    public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
 
     /**
-     * TYPE_1C: role, client_id, session_id, token [only permitted for RESPONDER]
+     * TYPE: out-of-band: role, client_id, peer_mac, pmk/passphrase optional
      * @hide
      */
-    public static final int NETWORK_SPECIFIER_TYPE_1C = 2;
+    public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
 
     /**
-     * TYPE_1C: role, client_id, session_id [only permitted for RESPONDER]
+     * TYPE: out-of-band, any peer: role, client_id, pmk/passphrase optional
+     * [only permitted for RESPONDER]
      * @hide
      */
-    public static final int NETWORK_SPECIFIER_TYPE_1D = 3;
+    public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
 
-    /**
-     * TYPE_2A: role, client_id, peer_mac, token
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_2A = 4;
-
-    /**
-     * TYPE_2B: role, client_id, peer_mac [only permitted for RESPONDER]
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_2B = 5;
-
-    /**
-     * TYPE_2C: role, client_id, token [only permitted for RESPONDER]
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_2C = 6;
-
-    /**
-     * TYPE_2D: role, client_id [only permitted for RESPONDER]
-     * @hide
-     */
-    public static final int NETWORK_SPECIFIER_TYPE_2D = 7;
 
     /** @hide */
-    public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_2D;
+    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";
@@ -199,7 +182,10 @@
     public static final String NETWORK_SPECIFIER_KEY_PEER_MAC = "peer_mac";
 
     /** @hide */
-    public static final String NETWORK_SPECIFIER_KEY_TOKEN = "token";
+    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.
@@ -224,8 +210,10 @@
      * Connection creation role is that of INITIATOR. Used to create a network specifier string
      * when requesting a Aware network.
      *
-     * @see DiscoverySession#createNetworkSpecifier(PeerHandle, byte[])
-     * @see WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])
+     * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
+     * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
+     * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
+     * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
      */
     public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0;
 
@@ -233,8 +221,10 @@
      * Connection creation role is that of RESPONDER. Used to create a network specifier string
      * when requesting a Aware network.
      *
-     * @see DiscoverySession#createNetworkSpecifier(PeerHandle, byte[])
-     * @see WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])
+     * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
+     * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
+     * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
+     * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
      */
     public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1;
 
@@ -253,36 +243,6 @@
     }
 
     /**
-     * Enable the usage of the Aware API. Doesn't actually turn on Aware cluster formation - that
-     * only happens when an attach is attempted. {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast
-     * will be triggered.
-     *
-     * @hide
-     */
-    public void enableUsage() {
-        try {
-            mService.enableUsage();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Disable the usage of the Aware API. All attempts to attach() will be rejected. All open
-     * connections and sessions will be terminated. {@link #ACTION_WIFI_AWARE_STATE_CHANGED}
-     * broadcast will be triggered.
-     *
-     * @hide
-     */
-    public void disableUsage() {
-        try {
-            mService.disableUsage();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Returns the current status of Aware API: whether or not Aware is available. To track
      * changes in the state of Aware API register for the
      * {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast.
@@ -524,23 +484,16 @@
 
     /** @hide */
     public String createNetworkSpecifier(int clientId, int role, int sessionId,
-            PeerHandle peerHandle, byte[] token) {
+            PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
         if (VDBG) {
             Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
                     + ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
-                    + ", token=" + token);
+                    + ", pmk=" + ((pmk == null) ? "null" : "non-null")
+                    + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
         }
 
-        int type;
-        if (token != null && peerHandle != null) {
-            type = NETWORK_SPECIFIER_TYPE_1A;
-        } else if (token == null && peerHandle != null) {
-            type = NETWORK_SPECIFIER_TYPE_1B;
-        } else if (token != null && peerHandle == null) {
-            type = NETWORK_SPECIFIER_TYPE_1C;
-        } else {
-            type = NETWORK_SPECIFIER_TYPE_1D;
-        }
+        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) {
@@ -549,10 +502,6 @@
                             + "specifier");
         }
         if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
-            if (token == null) {
-                throw new IllegalArgumentException(
-                        "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
-            }
             if (peerHandle == null) {
                 throw new IllegalArgumentException(
                         "createNetworkSpecifier: Invalid peer handle (value of null) - not "
@@ -570,10 +519,16 @@
             if (peerHandle != null) {
                 json.put(NETWORK_SPECIFIER_KEY_PEER_ID, peerHandle.peerId);
             }
-            if (token != null) {
-                json.put(NETWORK_SPECIFIER_KEY_TOKEN,
-                        Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+            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 "";
         }
@@ -583,21 +538,15 @@
 
     /** @hide */
     public String createNetworkSpecifier(int clientId, @DataPathRole int role,
-            @Nullable byte[] peer, @Nullable byte[] token) {
+            @Nullable byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
         if (VDBG) {
-            Log.v(TAG, "createNetworkSpecifier: role=" + role + ", token=" + token);
+            Log.v(TAG, "createNetworkSpecifier: role=" + role
+                    + ", pmk=" + ((pmk == null) ? "null" : "non-null")
+                    + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
         }
 
-        int type;
-        if (token != null && peer != null) {
-            type = NETWORK_SPECIFIER_TYPE_2A;
-        } else if (token == null && peer != null) {
-            type = NETWORK_SPECIFIER_TYPE_2B;
-        } else if (token != null && peer == null) {
-            type = NETWORK_SPECIFIER_TYPE_2C;
-        } else { // both are null
-            type = NETWORK_SPECIFIER_TYPE_2D;
-        }
+        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) {
@@ -606,19 +555,13 @@
                             + "specifier");
         }
         if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
-            if (peer == null || peer.length != 6) {
-                throw new IllegalArgumentException(
-                        "createNetworkSpecifier: Invalid peer MAC address");
+            if (peer == null) {
+                throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC "
+                        + "address - null not permitted on INITIATOR");
             }
-            if (token == null) {
-                throw new IllegalArgumentException(
-                        "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
-            }
-        } else {
-            if (peer != null && peer.length != 6) {
-                throw new IllegalArgumentException(
-                        "createNetworkSpecifier: Invalid peer MAC address");
-            }
+        }
+        if (peer != null && peer.length != 6) {
+            throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
         }
 
         JSONObject json;
@@ -630,10 +573,15 @@
             if (peer != null) {
                 json.put(NETWORK_SPECIFIER_KEY_PEER_MAC, new String(HexEncoding.encode(peer)));
             }
-            if (token != null) {
-                json.put(NETWORK_SPECIFIER_KEY_TOKEN,
-                        Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+            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 "";
         }
@@ -706,7 +654,11 @@
                             attachCallback.onAttachFailed();
                             break;
                         case CALLBACK_IDENTITY_CHANGED:
-                            identityChangedListener.onIdentityChanged((byte[]) msg.obj);
+                            if (identityChangedListener == null) {
+                                Log.e(TAG, "CALLBACK_IDENTITY_CHANGED: null listener.");
+                            } else {
+                                identityChangedListener.onIdentityChanged((byte[]) msg.obj);
+                            }
                             break;
                         case CALLBACK_RANGING_SUCCESS: {
                             RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 8696920..895defb 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -183,29 +184,26 @@
     }
 
     /**
-     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for a
-     * WiFi Aware connection to the specified peer. The
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} 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>
      *     This API is targeted for applications which can obtain the peer MAC address using OOB
      *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
      *     when using Aware discovery use the alternative network specifier method -
-     *     {@link DiscoverySession#createNetworkSpecifier(PeerHandle,
-     *     byte[])}.
+     *     {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}.
+     * <p>
+     * To set up an encrypted link use the
+     * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API.
      *
      * @param role  The role of this device:
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
      *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
      * @param peer  The MAC address of the peer's Aware discovery interface. On a RESPONDER this
      *              value is used to gate the acceptance of a connection request from only that
-     *              peer. A RESPONDER may specified a null - indicating that it will accept
+     *              peer. A RESPONDER may specify a {@code null} - indicating that it will accept
      *              connection requests from any device.
-     * @param token An arbitrary token (message) to be used to match connection initiation request
-     *              to a responder setup. A RESPONDER is set up with a {@code token} which must
-     *              be matched by the token provided by the INITIATOR. A null token is permitted
-     *              on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
-     *              not the same as a null token and requires the peer token to be empty as well.
      *
      * @return A string to be used to construct
      * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
@@ -213,17 +211,112 @@
      * android.net.ConnectivityManager.NetworkCallback)}
      * [or other varieties of that API].
      */
-    public String createNetworkSpecifier(@WifiAwareManager.DataPathRole int role,
-            @Nullable byte[] peer, @Nullable byte[] token) {
+    public String createNetworkSpecifierOpen(@WifiAwareManager.DataPathRole int role,
+            @Nullable byte[] peer) {
         WifiAwareManager mgr = mMgr.get();
         if (mgr == null) {
-            Log.e(TAG, "createNetworkSpecifier: called post GC on WifiAwareManager");
+            Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
             return "";
         }
         if (mTerminated) {
-            Log.e(TAG, "createNetworkSpecifier: called after termination");
+            Log.e(TAG, "createNetworkSpecifierOpen: called after termination");
             return "";
         }
-        return mgr.createNetworkSpecifier(mClientId, role, peer, token);
+        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
+     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+     * <p>
+     *     This API is targeted for applications which can obtain the peer MAC address using OOB
+     *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
+     *     when using Aware discovery use the alternative network specifier method -
+     *     {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+     *
+     * @param role  The role of this device:
+     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+     * @param peer  The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+     *              value is used to gate the acceptance of a connection request from only that
+     *              peer. A RESPONDER may specify a {@code null} - indicating that it will accept
+     *              connection requests from any device.
+     * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
+     *                   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
+     * {@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) {
+        WifiAwareManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.e(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
+            return "";
+        }
+        if (mTerminated) {
+            Log.e(TAG, "createNetworkSpecifierPassphrase: called after termination");
+            return "";
+        }
+        if (passphrase == null || passphrase.length() == 0) {
+            throw new IllegalArgumentException("Passphrase must not be null or empty");
+        }
+        return mgr.createNetworkSpecifier(mClientId, role, peer, null, passphrase);
+    }
+
+    /**
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} 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>
+     *     This API is targeted for applications which can obtain the peer MAC address using OOB
+     *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
+     *     when using Aware discovery use the alternative network specifier method -
+     *     {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
+     *
+     * @param role  The role of this device:
+     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+     * @param peer  The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+     *              value is used to gate the acceptance of a connection request from only that
+     *              peer. A RESPONDER may specify a null - indicating that it will accept
+     *              connection requests from any device.
+     * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+     *            encrypting the data-path. Use the
+     *            {@link #createNetworkSpecifierPassphrase(int, byte[], String)} to specify a
+     *            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
+     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+     * android.net.ConnectivityManager.NetworkCallback)}
+     * [or other varieties of that API].
+     *
+     * @hide
+     */
+    @SystemApi
+    public String 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 "";
+        }
+        if (mTerminated) {
+            Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
+            return "";
+        }
+        if (pmk == null || pmk.length == 0) {
+            throw new IllegalArgumentException("PMK must not be null or empty");
+        }
+        return mgr.createNetworkSpecifier(mClientId, role, peer, pmk, null);
     }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/ConfigBuilder.java b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
similarity index 96%
rename from wifi/java/android/net/wifi/hotspot2/ConfigBuilder.java
rename to wifi/java/android/net/wifi/hotspot2/ConfigParser.java
index 96db5d0..027b049a 100644
--- a/wifi/java/android/net/wifi/hotspot2/ConfigBuilder.java
+++ b/wifi/java/android/net/wifi/hotspot2/ConfigParser.java
@@ -16,7 +16,7 @@
 
 package android.net.wifi.hotspot2;
 
-import android.net.wifi.hotspot2.omadm.PPSMOParser;
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
@@ -41,11 +41,9 @@
 
 /**
  * Utility class for building PasspointConfiguration from an installation file.
- *
- * @hide
  */
-public final class ConfigBuilder {
-    private static final String TAG = "ConfigBuilder";
+public final class ConfigParser {
+    private static final String TAG = "ConfigParser";
 
     // Header names.
     private static final String CONTENT_TYPE = "Content-Type";
@@ -101,6 +99,10 @@
         public String encodingType = null;
     }
 
+    /**
+     * @hide
+     */
+    public ConfigParser() {}
 
     /**
      * Parse the Hotspot 2.0 Release 1 configuration data into a {@link PasspointConfiguration}
@@ -133,7 +135,7 @@
      *             certificate chain (optional).
      * @return {@link PasspointConfiguration}
      */
-    public static PasspointConfiguration buildPasspointConfig(String mimeType, byte[] data) {
+    public static PasspointConfiguration parsePasspointConfig(String mimeType, byte[] data) {
         // Verify MIME type.
         if (!TextUtils.equals(mimeType, TYPE_WIFI_CONFIG)) {
             Log.e(TAG, "Unexpected MIME type: " + mimeType);
@@ -169,13 +171,13 @@
             throw new IOException("Missing Passpoint Profile");
         }
 
-        PasspointConfiguration config = PPSMOParser.parseMOText(new String(profileData));
+        PasspointConfiguration config = PpsMoParser.parseMoText(new String(profileData));
         if (config == null) {
             throw new IOException("Failed to parse Passpoint profile");
         }
 
         // Credential is needed for storing the certificates and private client key.
-        if (config.credential == null) {
+        if (config.getCredential() == null) {
             throw new IOException("Passpoint profile missing credential");
         }
 
@@ -183,7 +185,7 @@
         byte[] caCertData = mimeParts.get(TYPE_CA_CERT);
         if (caCertData != null) {
             try {
-                config.credential.caCertificate = parseCACert(caCertData);
+                config.getCredential().setCaCertificate(parseCACert(caCertData));
             } catch (CertificateException e) {
                 throw new IOException("Failed to parse CA Certificate");
             }
@@ -194,9 +196,9 @@
         if (pkcs12Data != null) {
             try {
                 Pair<PrivateKey, List<X509Certificate>> clientKey = parsePkcs12(pkcs12Data);
-                config.credential.clientPrivateKey = clientKey.first;
-                config.credential.clientCertificateChain =
-                        clientKey.second.toArray(new X509Certificate[clientKey.second.size()]);
+                config.getCredential().setClientPrivateKey(clientKey.first);
+                config.getCredential().setClientCertificateChain(
+                        clientKey.second.toArray(new X509Certificate[clientKey.second.size()]));
             } catch(GeneralSecurityException | IOException e) {
                 throw new IOException("Failed to parse PCKS12 string");
             }
@@ -470,4 +472,4 @@
         }
         return new Pair<PrivateKey, List<X509Certificate>>(clientKey, clientCertificateChain);
     }
-}
\ No newline at end of file
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 643753a..333a4f7d 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -17,24 +17,219 @@
 package android.net.wifi.hotspot2;
 
 import android.net.wifi.hotspot2.pps.Credential;
-import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
 import android.os.Parcel;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * Class representing Passpoint configuration.  This contains configurations specified in
  * PerProviderSubscription (PPS) Management Object (MO) tree.
  *
  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
  * Release 2 Technical Specification.
- *
- * Currently, only HomeSP and Credential subtrees are supported.
- *
- * @hide
  */
 public final class PasspointConfiguration implements Parcelable {
-    public HomeSP homeSp = null;
-    public Credential credential = null;
+    private static final String TAG = "PasspointConfiguration";
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Maximum bytes for URL string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
+     * Configurations under HomeSp subtree.
+     */
+    private HomeSp mHomeSp = null;
+    public void setHomeSp(HomeSp homeSp) { mHomeSp = homeSp; }
+    public HomeSp getHomeSp() { return mHomeSp; }
+
+    /**
+     * Configurations under Credential subtree.
+     */
+    private Credential mCredential = null;
+    public void setCredential(Credential credential) {
+        mCredential = credential;
+    }
+    public Credential getCredential() {
+        return mCredential;
+    }
+
+    /**
+     * Configurations under Policy subtree.
+     */
+    private Policy mPolicy = null;
+    public void setPolicy(Policy policy) {
+        mPolicy = policy;
+    }
+    public Policy getPolicy() {
+        return mPolicy;
+    }
+
+    /**
+     * Meta data for performing subscription update.
+     */
+    private UpdateParameter mSubscriptionUpdate = null;
+    public void setSubscriptionUpdate(UpdateParameter subscriptionUpdate) {
+        mSubscriptionUpdate = subscriptionUpdate;
+    }
+    public UpdateParameter getSubscriptionUpdate() {
+        return mSubscriptionUpdate;
+    }
+
+    /**
+     * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
+     * fingerprint of the certificate.  The certificates are used for verifying AAA server's
+     * identity during EAP authentication.
+     */
+    private Map<String, byte[]> mTrustRootCertList = null;
+    public void setTrustRootCertList(Map<String, byte[]> trustRootCertList) {
+        mTrustRootCertList = trustRootCertList;
+    }
+    public Map<String, byte[]> getTrustRootCertList() {
+        return mTrustRootCertList;
+    }
+
+    /**
+     * Set by the subscription server, updated every time the configuration is updated by
+     * the subscription server.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    private int mUpdateIdentifier = Integer.MIN_VALUE;
+    public void setUpdateIdentifier(int updateIdentifier) {
+        mUpdateIdentifier = updateIdentifier;
+    }
+    public int getUpdateIdentifier() {
+        return mUpdateIdentifier;
+    }
+
+    /**
+     * The priority of the credential.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    private int mCredentialPriority = Integer.MIN_VALUE;
+    public void setCredentialPriority(int credentialPriority) {
+        mCredentialPriority = credentialPriority;
+    }
+    public int getCredentialPriority() {
+        return mCredentialPriority;
+    }
+
+    /**
+     * The time this subscription is created. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    private long mSubscriptionCreationTimeInMs = Long.MIN_VALUE;
+    public void setSubscriptionCreationTimeInMs(long subscriptionCreationTimeInMs) {
+        mSubscriptionCreationTimeInMs = subscriptionCreationTimeInMs;
+    }
+    public long getSubscriptionCreationTimeInMs() {
+        return mSubscriptionCreationTimeInMs;
+    }
+
+    /**
+     * The time this subscription will expire. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    private long mSubscriptionExpirationTimeInMs = Long.MIN_VALUE;
+    public void setSubscriptionExpirationTimeInMs(long subscriptionExpirationTimeInMs) {
+        mSubscriptionExpirationTimeInMs = subscriptionExpirationTimeInMs;
+    }
+    public long getSubscriptionExpirationTimeInMs() {
+        return mSubscriptionExpirationTimeInMs;
+    }
+
+    /**
+     * The type of the subscription.  This is defined by the provider and the value is provider
+     * specific.
+     */
+    private String mSubscriptionType = null;
+    public void setSubscriptionType(String subscriptionType) {
+        mSubscriptionType = subscriptionType;
+    }
+    public String getSubscriptionType() {
+        return mSubscriptionType;
+    }
+
+    /**
+     * The time period for usage statistics accumulation. A value of zero means that usage
+     * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
+     * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
+     */
+    private long mUsageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+    public void setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes) {
+        mUsageLimitUsageTimePeriodInMinutes = usageLimitUsageTimePeriodInMinutes;
+    }
+    public long getUsageLimitUsageTimePeriodInMinutes() {
+        return mUsageLimitUsageTimePeriodInMinutes;
+    }
+
+    /**
+     * The time at which usage statistic accumulation  begins.  It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    private long mUsageLimitStartTimeInMs = Long.MIN_VALUE;
+    public void setUsageLimitStartTimeInMs(long usageLimitStartTimeInMs) {
+        mUsageLimitStartTimeInMs = usageLimitStartTimeInMs;
+    }
+    public long getUsageLimitStartTimeInMs() {
+        return mUsageLimitStartTimeInMs;
+    }
+
+    /**
+     * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited data usage.
+     *
+     * Use Long.MIN_VALUE to indicate unset value.
+     */
+    private long mUsageLimitDataLimit = Long.MIN_VALUE;
+    public void setUsageLimitDataLimit(long usageLimitDataLimit) {
+        mUsageLimitDataLimit = usageLimitDataLimit;
+    }
+    public long getUsageLimitDataLimit() {
+        return mUsageLimitDataLimit;
+    }
+
+    /**
+     * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited time usage.
+     */
+    private long mUsageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+    public void setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes) {
+        mUsageLimitTimeLimitInMinutes = usageLimitTimeLimitInMinutes;
+    }
+    public long getUsageLimitTimeLimitInMinutes() {
+        return mUsageLimitTimeLimitInMinutes;
+    }
 
     /**
      * Constructor for creating PasspointConfiguration with default values.
@@ -47,14 +242,34 @@
      * @param source The source to copy from
      */
     public PasspointConfiguration(PasspointConfiguration source) {
-        if (source != null) {
-            if (source.homeSp != null) {
-                homeSp = new HomeSP(source.homeSp);
-            }
-            if (source.credential != null) {
-                credential = new Credential(source.credential);
-            }
+        if (source == null) {
+            return;
         }
+
+        if (source.mHomeSp != null) {
+            mHomeSp = new HomeSp(source.mHomeSp);
+        }
+        if (source.mCredential != null) {
+            mCredential = new Credential(source.mCredential);
+        }
+        if (source.mPolicy != null) {
+            mPolicy = new Policy(source.mPolicy);
+        }
+        if (source.mTrustRootCertList != null) {
+            mTrustRootCertList = Collections.unmodifiableMap(source.mTrustRootCertList);
+        }
+        if (source.mSubscriptionUpdate != null) {
+            mSubscriptionUpdate = new UpdateParameter(source.mSubscriptionUpdate);
+        }
+        mUpdateIdentifier = source.mUpdateIdentifier;
+        mCredentialPriority = source.mCredentialPriority;
+        mSubscriptionCreationTimeInMs = source.mSubscriptionCreationTimeInMs;
+        mSubscriptionExpirationTimeInMs = source.mSubscriptionExpirationTimeInMs;
+        mSubscriptionType = source.mSubscriptionType;
+        mUsageLimitDataLimit = source.mUsageLimitDataLimit;
+        mUsageLimitStartTimeInMs = source.mUsageLimitStartTimeInMs;
+        mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes;
+        mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
     }
 
     @Override
@@ -64,8 +279,20 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelable(homeSp, flags);
-        dest.writeParcelable(credential, flags);
+        dest.writeParcelable(mHomeSp, flags);
+        dest.writeParcelable(mCredential, flags);
+        dest.writeParcelable(mPolicy, flags);
+        dest.writeParcelable(mSubscriptionUpdate, flags);
+        writeTrustRootCerts(dest, mTrustRootCertList);
+        dest.writeInt(mUpdateIdentifier);
+        dest.writeInt(mCredentialPriority);
+        dest.writeLong(mSubscriptionCreationTimeInMs);
+        dest.writeLong(mSubscriptionExpirationTimeInMs);
+        dest.writeString(mSubscriptionType);
+        dest.writeLong(mUsageLimitUsageTimePeriodInMinutes);
+        dest.writeLong(mUsageLimitStartTimeInMs);
+        dest.writeLong(mUsageLimitDataLimit);
+        dest.writeLong(mUsageLimitTimeLimitInMinutes);
     }
 
     @Override
@@ -77,23 +304,120 @@
             return false;
         }
         PasspointConfiguration that = (PasspointConfiguration) thatObject;
-        return (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp)) &&
-                (credential == null ? that.credential == null :
-                    credential.equals(that.credential));
+        return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp))
+                && (mCredential == null ? that.mCredential == null
+                        : mCredential.equals(that.mCredential))
+                && (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy))
+                && (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null
+                        : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
+                && isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList)
+                && mUpdateIdentifier == that.mUpdateIdentifier
+                && mCredentialPriority == that.mCredentialPriority
+                && mSubscriptionCreationTimeInMs == that.mSubscriptionCreationTimeInMs
+                && mSubscriptionExpirationTimeInMs == that.mSubscriptionExpirationTimeInMs
+                && TextUtils.equals(mSubscriptionType, that.mSubscriptionType)
+                && mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes
+                && mUsageLimitStartTimeInMs == that.mUsageLimitStartTimeInMs
+                && mUsageLimitDataLimit == that.mUsageLimitDataLimit
+                && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList,
+                mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMs,
+                mSubscriptionExpirationTimeInMs, mUsageLimitUsageTimePeriodInMinutes,
+                mUsageLimitStartTimeInMs, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UpdateIdentifier: ").append(mUpdateIdentifier).append("\n");
+        builder.append("CredentialPriority: ").append(mCredentialPriority).append("\n");
+        builder.append("SubscriptionCreationTime: ").append(
+                mSubscriptionCreationTimeInMs != Long.MIN_VALUE
+                ? new Date(mSubscriptionCreationTimeInMs) : "Not specified").append("\n");
+        builder.append("SubscriptionExpirationTime: ").append(
+                mSubscriptionExpirationTimeInMs != Long.MIN_VALUE
+                ? new Date(mSubscriptionExpirationTimeInMs) : "Not specified").append("\n");
+        builder.append("UsageLimitStartTime: ").append(mUsageLimitStartTimeInMs != Long.MIN_VALUE
+                ? new Date(mUsageLimitStartTimeInMs) : "Not specified").append("\n");
+        builder.append("UsageTimePeriod: ").append(mUsageLimitUsageTimePeriodInMinutes)
+                .append("\n");
+        builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
+        builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
+        if (mHomeSp != null) {
+            builder.append("HomeSP Begin ---\n");
+            builder.append(mHomeSp);
+            builder.append("HomeSP End ---\n");
+        }
+        if (mCredential != null) {
+            builder.append("Credential Begin ---\n");
+            builder.append(mCredential);
+            builder.append("Credential End ---\n");
+        }
+        if (mPolicy != null) {
+            builder.append("Policy Begin ---\n");
+            builder.append(mPolicy);
+            builder.append("Policy End ---\n");
+        }
+        if (mSubscriptionUpdate != null) {
+            builder.append("SubscriptionUpdate Begin ---\n");
+            builder.append(mSubscriptionUpdate);
+            builder.append("SubscriptionUpdate End ---\n");
+        }
+        if (mTrustRootCertList != null) {
+            builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
+                    .append("\n");
+        }
+        return builder.toString();
     }
 
     /**
      * Validate the configuration data.
      *
      * @return true on success or false on failure
+     * @hide
      */
     public boolean validate() {
-        if (homeSp == null || !homeSp.validate()) {
+        if (mHomeSp == null || !mHomeSp.validate()) {
             return false;
         }
-        if (credential == null || !credential.validate()) {
+        if (mCredential == null || !mCredential.validate()) {
             return false;
         }
+        if (mPolicy != null && !mPolicy.validate()) {
+            return false;
+        }
+        if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) {
+            return false;
+        }
+        if (mTrustRootCertList != null) {
+            for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) {
+                String url = entry.getKey();
+                byte[] certFingerprint = entry.getValue();
+                if (TextUtils.isEmpty(url)) {
+                    Log.d(TAG, "Empty URL");
+                    return false;
+                }
+                if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+                    Log.d(TAG, "URL bytes exceeded the max: "
+                            + url.getBytes(StandardCharsets.UTF_8).length);
+                    return false;
+                }
+
+                if (certFingerprint == null) {
+                    Log.d(TAG, "Fingerprint not specified");
+                    return false;
+                }
+                if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
+                    Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                            + certFingerprint.length);
+                    return false;
+                }
+            }
+        }
         return true;
     }
 
@@ -102,13 +426,90 @@
             @Override
             public PasspointConfiguration createFromParcel(Parcel in) {
                 PasspointConfiguration config = new PasspointConfiguration();
-                config.homeSp = in.readParcelable(null);
-                config.credential = in.readParcelable(null);
+                config.setHomeSp(in.readParcelable(null));
+                config.setCredential(in.readParcelable(null));
+                config.setPolicy(in.readParcelable(null));
+                config.setSubscriptionUpdate(in.readParcelable(null));
+                config.setTrustRootCertList(readTrustRootCerts(in));
+                config.setUpdateIdentifier(in.readInt());
+                config.setCredentialPriority(in.readInt());
+                config.setSubscriptionCreationTimeInMs(in.readLong());
+                config.setSubscriptionExpirationTimeInMs(in.readLong());
+                config.setSubscriptionType(in.readString());
+                config.setUsageLimitUsageTimePeriodInMinutes(in.readLong());
+                config.setUsageLimitStartTimeInMs(in.readLong());
+                config.setUsageLimitDataLimit(in.readLong());
+                config.setUsageLimitTimeLimitInMinutes(in.readLong());
                 return config;
             }
+
             @Override
             public PasspointConfiguration[] newArray(int size) {
                 return new PasspointConfiguration[size];
             }
+
+            /**
+             * Helper function for reading trust root certificate info list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return The list of trust root certificate URL with the corresponding certificate
+             *         fingerprint
+             */
+            private Map<String, byte[]> readTrustRootCerts(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<String, byte[]> trustRootCerts = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    String key = in.readString();
+                    byte[] value = in.createByteArray();
+                    trustRootCerts.put(key, value);
+                }
+                return trustRootCerts;
+            }
         };
+
+    /**
+     * Helper function for writing trust root certificate information list.
+     *
+     * @param dest The Parcel to write to
+     * @param trustRootCerts The list of trust root certificate URL with the corresponding
+     *                       certificate fingerprint
+     */
+    private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
+        if (trustRootCerts == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(trustRootCerts.size());
+        for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
+            dest.writeString(entry.getKey());
+            dest.writeByteArray(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for comparing two trust root certificate list.  Cannot use Map#equals
+     * method since the value type (byte[]) doesn't override equals method.
+     *
+     * @param list1 The first trust root certificate list
+     * @param list2 The second trust root certificate list
+     * @return true if the two list are equal
+     */
+    private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
+            Map<String, byte[]> list2) {
+        if (list1 == null || list2 == null) {
+            return list1 == list2;
+        }
+        if (list1.size() != list2.size()) {
+            return false;
+        }
+        for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
+            if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
deleted file mode 100644
index 65a49ea..0000000
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ /dev/null
@@ -1,786 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.hotspot2.omadm;
-
-import android.net.wifi.hotspot2.PasspointConfiguration;
-import android.net.wifi.hotspot2.pps.Credential;
-import android.net.wifi.hotspot2.pps.HomeSP;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.xml.sax.SAXException;
-
-/**
- * Utility class for converting OMA-DM (Open Mobile Alliance's Device Management)
- * PPS-MO (PerProviderSubscription Management Object) XML tree to a
- * {@link PasspointConfiguration} object.
- *
- * Currently this only supports PerProviderSubscription/HomeSP and
- * PerProviderSubscription/Credential subtree for Hotspot 2.0 Release 1 support.
- *
- * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
- * Release 2 Technical Specification.
- *
- * Below is a sample XML string for a Release 1 PPS MO tree:
- *
- * <MgmtTree xmlns="syncml:dmddf1.2">
- *   <VerDTD>1.2</VerDTD>
- *   <Node>
- *     <NodeName>PerProviderSubscription</NodeName>
- *     <RTProperties>
- *       <Type>
- *         <DDFName>urn:wfa:mo:hotspot2dot0­perprovidersubscription:1.0</DDFName>
- *       </Type>
- *     </RTProperties>
- *     <Node>
- *       <NodeName>i001</NodeName>
- *       <Node>
- *         <NodeName>HomeSP</NodeName>
- *         <Node>
- *           <NodeName>FriendlyName</NodeName>
- *           <Value>Century House</Value>
- *         </Node>
- *         <Node>
- *           <NodeName>FQDN</NodeName>
- *           <Value>mi6.co.uk</Value>
- *         </Node>
- *         <Node>
- *           <NodeName>RoamingConsortiumOI</NodeName>
- *           <Value>112233,445566</Value>
- *         </Node>
- *       </Node>
- *       <Node>
- *         <NodeName>Credential</NodeName>
- *         <Node>
- *           <NodeName>Realm</NodeName>
- *           <Value>shaken.stirred.com</Value>
- *         </Node>
- *         <Node>
- *           <NodeName>UsernamePassword</NodeName>
- *           <Node>
- *             <NodeName>Username</NodeName>
- *             <Value>james</Value>
- *           </Node>
- *           <Node>
- *             <NodeName>Password</NodeName>
- *             <Value>Ym9uZDAwNw==</Value>
- *           </Node>
- *           <Node>
- *             <NodeName>EAPMethod</NodeName>
- *             <Node>
- *               <NodeName>EAPType</NodeName>
- *               <Value>21</Value>
- *             </Node>
- *             <Node>
- *               <NodeName>InnerMethod</NodeName>
- *               <Value>MS-CHAP-V2</Value>
- *             </Node>
- *           </Node>
- *         </Node>
- *       </Node>
- *     </Node>
- *   </Node>
- * </MgmtTree>
- *
- * @hide
- */
-public final class PPSMOParser {
-    private static final String TAG = "PPSMOParser";
-
-    /**
-     * XML tags expected in the PPS MO (PerProviderSubscription Management Object) XML tree.
-     */
-    private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
-    private static final String TAG_VER_DTD = "VerDTD";
-    private static final String TAG_NODE = "Node";
-    private static final String TAG_NODE_NAME = "NodeName";
-    private static final String TAG_RT_PROPERTIES = "RTProperties";
-    private static final String TAG_TYPE = "Type";
-    private static final String TAG_DDF_NAME = "DDFName";
-    private static final String TAG_VALUE = "Value";
-
-    /**
-     * Name for PerProviderSubscription node.
-     */
-    private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
-
-    /**
-     * Fields under HomeSP subtree.
-     */
-    private static final String NODE_HOMESP = "HomeSP";
-    private static final String NODE_FQDN = "FQDN";
-    private static final String NODE_FRIENDLY_NAME = "FriendlyName";
-    private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
-
-    /**
-     * Fields under Credential subtree.
-     */
-    private static final String NODE_CREDENTIAL = "Credential";
-    private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
-    private static final String NODE_USERNAME = "Username";
-    private static final String NODE_PASSWORD = "Password";
-    private static final String NODE_EAP_METHOD = "EAPMethod";
-    private static final String NODE_EAP_TYPE = "EAPType";
-    private static final String NODE_INNER_METHOD = "InnerMethod";
-    private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
-    private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
-    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256FingerPrint";
-    private static final String NODE_REALM = "Realm";
-    private static final String NODE_SIM = "SIM";
-    private static final String NODE_SIM_IMSI = "IMSI";
-
-    /**
-     * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
-     */
-    private static final String PPS_MO_URN =
-            "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
-
-    /**
-     * Exception for generic parsing errors.
-     */
-    private static class ParsingException extends Exception {
-        public ParsingException(String message) {
-            super(message);
-        }
-    }
-
-    /**
-     * Class representing a node within the PerProviderSubscription tree.
-     * This is used to flatten out and eliminate the extra layering in the XMLNode tree,
-     * to make the data parsing easier and cleaner.
-     *
-     * A PPSNode can be an internal or a leaf node, but not both.
-     *
-     */
-    private static abstract class PPSNode {
-        private final String mName;
-        public PPSNode(String name) {
-            mName = name;
-        }
-
-        /**
-         * @return the name of the node
-         */
-        public String getName() {
-            return mName;
-        }
-
-        /**
-         * Applies for internal node only.
-         *
-         * @return the list of children nodes.
-         */
-        public abstract List<PPSNode> getChildren();
-
-        /**
-         * Applies for leaf node only.
-         *
-         * @return the string value of the node
-         */
-        public abstract String getValue();
-
-        /**
-         * @return a flag indicating if this is a leaf or an internal node
-         */
-        public abstract boolean isLeaf();
-    }
-
-    /**
-     * Class representing a leaf node in a PPS (PerProviderSubscription) tree.
-     */
-    private static class LeafNode extends PPSNode {
-        private final String mValue;
-        public LeafNode(String nodeName, String value) {
-            super(nodeName);
-            mValue = value;
-        }
-
-        @Override
-        public String getValue() {
-            return mValue;
-        }
-
-        @Override
-        public List<PPSNode> getChildren() {
-            return null;
-        }
-
-        @Override
-        public boolean isLeaf() {
-            return true;
-        }
-    }
-
-    /**
-     * Class representing an internal node in a PPS (PerProviderSubscription) tree.
-     */
-    private static class InternalNode extends PPSNode {
-        private final List<PPSNode> mChildren;
-        public InternalNode(String nodeName, List<PPSNode> children) {
-            super(nodeName);
-            mChildren = children;
-        }
-
-        @Override
-        public String getValue() {
-            return null;
-        }
-
-        @Override
-        public List<PPSNode> getChildren() {
-            return mChildren;
-        }
-
-        @Override
-        public boolean isLeaf() {
-            return false;
-        }
-    }
-
-    /**
-     * Convert a XML string representation of a PPS MO (PerProviderSubscription
-     * Management Object) tree to a {@link PasspointConfiguration} object.
-     *
-     * @param xmlString XML string representation of a PPS MO tree
-     * @return {@link PasspointConfiguration} or null
-     */
-    public static PasspointConfiguration parseMOText(String xmlString) {
-        // Convert the XML string to a XML tree.
-        XMLParser xmlParser = new XMLParser();
-        XMLNode root = null;
-        try {
-            root = xmlParser.parse(xmlString);
-        } catch(IOException | SAXException e) {
-            return null;
-        }
-        if (root == null) {
-            return null;
-        }
-
-        // Verify root node is a "MgmtTree" node.
-        if (root.getTag() != TAG_MANAGEMENT_TREE) {
-            Log.e(TAG, "Root is not a MgmtTree");
-            return null;
-        }
-
-        String verDtd = null;    // Used for detecting duplicate VerDTD element.
-        PasspointConfiguration config = null;
-        for (XMLNode child : root.getChildren()) {
-            switch(child.getTag()) {
-                case TAG_VER_DTD:
-                    if (verDtd != null) {
-                        Log.e(TAG, "Duplicate VerDTD element");
-                        return null;
-                    }
-                    verDtd = child.getText();
-                    break;
-                case TAG_NODE:
-                    if (config != null) {
-                        Log.e(TAG, "Unexpected multiple Node element under MgmtTree");
-                        return null;
-                    }
-                    try {
-                        config = parsePpsNode(child);
-                    } catch (ParsingException e) {
-                        Log.e(TAG, e.getMessage());
-                        return null;
-                    }
-                    break;
-                default:
-                    Log.e(TAG, "Unknown node: " + child.getTag());
-                    return null;
-            }
-        }
-        return config;
-    }
-
-    /**
-     * Parse a PerProviderSubscription node. Below is the format of the XML tree (with
-     * each XML element represent a node in the tree):
-     *
-     * <Node>
-     *   <NodeName>PerProviderSubscription</NodeName>
-     *   <RTProperties>
-     *     ...
-     *   </RTPProperties>
-     *   <Node>
-     *     ...
-     *   </Node>
-     * </Node>
-     *
-     * @param node XMLNode that contains PerProviderSubscription node.
-     * @return PasspointConfiguration or null
-     * @throws ParsingException
-     */
-    private static PasspointConfiguration parsePpsNode(XMLNode node)
-            throws ParsingException {
-        PasspointConfiguration config = null;
-        String nodeName = null;
-        for (XMLNode child : node.getChildren()) {
-            switch (child.getTag()) {
-                case TAG_NODE_NAME:
-                    if (nodeName != null) {
-                        throw new ParsingException("Duplicant NodeName: " + child.getText());
-                    }
-                    nodeName = child.getText();
-                    if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
-                        throw new ParsingException("Unexpected NodeName: " + nodeName);
-                    }
-                    break;
-                case TAG_NODE:
-                    // Only one PerProviderSubscription instance is expected and allowed.
-                    if (config != null) {
-                        throw new ParsingException("Multiple PPS instance");
-                    }
-                    // Convert the XML tree to a PPS tree.
-                    PPSNode ppsInstanceRoot = buildPpsNode(child);
-                    config = parsePpsInstance(ppsInstanceRoot);
-                    break;
-                case TAG_RT_PROPERTIES:
-                    // Parse and verify URN stored in the RT (Run Time) Properties.
-                    String urn = parseUrn(child);
-                    if (!TextUtils.equals(urn, PPS_MO_URN)) {
-                        throw new ParsingException("Unknown URN: " + urn);
-                    }
-                    break;
-                default:
-                    throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
-            }
-        }
-        return config;
-    }
-
-    /**
-     * Parse the URN stored in the RTProperties. Below is the format of the RTPProperties node:
-     *
-     * <RTProperties>
-     *   <Type>
-     *     <DDFName>urn:...</DDFName>
-     *   </Type>
-     * </RTProperties>
-     *
-     * @param node XMLNode that contains RTProperties node.
-     * @return URN String of URN.
-     * @throws ParsingException
-     */
-    private static String parseUrn(XMLNode node) throws ParsingException {
-        if (node.getChildren().size() != 1)
-            throw new ParsingException("Expect RTPProperties node to only have one child");
-
-        XMLNode typeNode = node.getChildren().get(0);
-        if (typeNode.getChildren().size() != 1) {
-            throw new ParsingException("Expect Type node to only have one child");
-        }
-        if (!TextUtils.equals(typeNode.getTag(), TAG_TYPE)) {
-            throw new ParsingException("Unexpected tag for Type: " + typeNode.getTag());
-        }
-
-        XMLNode ddfNameNode = typeNode.getChildren().get(0);
-        if (!ddfNameNode.getChildren().isEmpty()) {
-            throw new ParsingException("Expect DDFName node to have no child");
-        }
-        if (!TextUtils.equals(ddfNameNode.getTag(), TAG_DDF_NAME)) {
-            throw new ParsingException("Unexpected tag for DDFName: " + ddfNameNode.getTag());
-        }
-
-        return ddfNameNode.getText();
-    }
-
-    /**
-     * Convert a XML tree represented by XMLNode to a PPS (PerProviderSubscription) instance tree
-     * represented by PPSNode.  This flattens out the XML tree to allow easier and cleaner parsing
-     * of the PPS configuration data.  Only three types of XML tag are expected: "NodeName",
-     * "Node", and "Value".
-     *
-     * The original XML tree (each XML element represent a node):
-     *
-     * <Node>
-     *   <NodeName>root</NodeName>
-     *   <Node>
-     *     <NodeName>child1</NodeName>
-     *     <Value>value1</Value>
-     *   </Node>
-     *   <Node>
-     *     <NodeName>child2</NodeName>
-     *     <Node>
-     *       <NodeName>grandchild1</NodeName>
-     *       ...
-     *     </Node>
-     *   </Node>
-     *   ...
-     * </Node>
-     *
-     * The converted PPS tree:
-     *
-     * [root] --- [child1, value1]
-     *   |
-     *   ---------[child2] --------[grandchild1] --- ...
-     *
-     * @param node XMLNode pointed to the root of a XML tree
-     * @return PPSNode pointing to the root of a PPS tree
-     * @throws ParsingException
-     */
-    private static PPSNode buildPpsNode(XMLNode node) throws ParsingException {
-        String nodeName = null;
-        String nodeValue = null;
-        List<PPSNode> childNodes = new ArrayList<PPSNode>();
-        // Names of parsed child nodes, use for detecting multiple child nodes with the same name.
-        Set<String> parsedNodes = new HashSet<String>();
-
-        for (XMLNode child : node.getChildren()) {
-            String tag = child.getTag();
-            if (TextUtils.equals(tag, TAG_NODE_NAME)) {
-                if (nodeName != null) {
-                    throw new ParsingException("Duplicate NodeName node");
-                }
-                nodeName = child.getText();
-            } else if (TextUtils.equals(tag, TAG_NODE)) {
-                PPSNode ppsNode = buildPpsNode(child);
-                if (parsedNodes.contains(ppsNode.getName())) {
-                    throw new ParsingException("Duplicate node: " + ppsNode.getName());
-                }
-                parsedNodes.add(ppsNode.getName());
-                childNodes.add(ppsNode);
-            } else if (TextUtils.equals(tag, TAG_VALUE)) {
-               if (nodeValue != null) {
-                   throw new ParsingException("Duplicate Value node");
-               }
-               nodeValue = child.getText();
-            } else {
-                throw new ParsingException("Unknown tag: " + tag);
-            }
-        }
-
-        if (nodeName == null) {
-            throw new ParsingException("Invalid node: missing NodeName");
-        }
-        if (nodeValue == null && childNodes.size() == 0) {
-            throw new ParsingException("Invalid node: " + nodeName +
-                    " missing both value and children");
-        }
-        if (nodeValue != null && childNodes.size() > 0) {
-            throw new ParsingException("Invalid node: " + nodeName +
-                    " contained both value and children");
-        }
-
-        if (nodeValue != null) {
-            return new LeafNode(nodeName, nodeValue);
-        }
-        return new InternalNode(nodeName, childNodes);
-    }
-
-    /**
-     * Return the value of a PPSNode.  An exception will be thrown if the given node
-     * is not a leaf node.
-     *
-     * @param node PPSNode to retrieve the value from
-     * @return String representing the value of the node
-     * @throws ParsingException
-     */
-    private static String getPpsNodeValue(PPSNode node) throws ParsingException {
-        if (!node.isLeaf()) {
-            throw new ParsingException("Cannot get value from a non-leaf node: " + node.getName());
-        }
-        return node.getValue();
-    }
-
-    /**
-     * Parse a PPS (PerProviderSubscription) configurations from a PPS tree.
-     *
-     * @param root PPSNode representing the root of the PPS tree
-     * @return PasspointConfiguration
-     * @throws ParsingException
-     */
-    private static PasspointConfiguration parsePpsInstance(PPSNode root)
-            throws ParsingException {
-        if (root.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for PPS instance");
-        }
-
-        PasspointConfiguration config = new PasspointConfiguration();
-        for (PPSNode child : root.getChildren()) {
-            switch(child.getName()) {
-                case NODE_HOMESP:
-                    config.homeSp = parseHomeSP(child);
-                    break;
-                case NODE_CREDENTIAL:
-                    config.credential = parseCredential(child);
-                    break;
-                default:
-                    throw new ParsingException("Unknown node: " + child.getName());
-            }
-        }
-        return config;
-    }
-
-    /**
-     * Parse configurations under PerProviderSubscription/HomeSP subtree.
-     *
-     * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP subtree
-     * @return HomeSP
-     * @throws ParsingException
-     */
-    private static HomeSP parseHomeSP(PPSNode node) throws ParsingException {
-        if (node.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for HomeSP");
-        }
-
-        HomeSP homeSp = new HomeSP();
-        for (PPSNode child : node.getChildren()) {
-            switch (child.getName()) {
-                case NODE_FQDN:
-                    homeSp.fqdn = getPpsNodeValue(child);
-                    break;
-                case NODE_FRIENDLY_NAME:
-                    homeSp.friendlyName = getPpsNodeValue(child);
-                    break;
-                case NODE_ROAMING_CONSORTIUM_OI:
-                    homeSp.roamingConsortiumOIs =
-                            parseRoamingConsortiumOI(getPpsNodeValue(child));
-                    break;
-                default:
-                    throw new ParsingException("Unknown node under HomeSP: " + child.getName());
-            }
-        }
-        return homeSp;
-    }
-
-    /**
-     * Parse the roaming consortium OI string, which contains a list of OIs separated by ",".
-     *
-     * @param oiStr string containing list of OIs (Organization Identifiers) separated by ","
-     * @return long[]
-     * @throws ParsingException
-     */
-    private static long[] parseRoamingConsortiumOI(String oiStr)
-            throws ParsingException {
-        String[] oiStrArray = oiStr.split(",");
-        long[] oiArray = new long[oiStrArray.length];
-        for (int i = 0; i < oiStrArray.length; i++) {
-            try {
-                oiArray[i] = Long.parseLong(oiStrArray[i], 16);
-            } catch (NumberFormatException e) {
-                throw new ParsingException("Invalid OI: " + oiStrArray[i]);
-            }
-        }
-        return oiArray;
-    }
-
-    /**
-     * Parse configurations under PerProviderSubscription/Credential subtree.
-     *
-     * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
-     * @return Credential
-     * @throws ParsingException
-     */
-    private static Credential parseCredential(PPSNode node) throws ParsingException {
-        if (node.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for HomeSP");
-        }
-
-        Credential credential = new Credential();
-        for (PPSNode child: node.getChildren()) {
-            switch (child.getName()) {
-                case NODE_USERNAME_PASSWORD:
-                    credential.userCredential = parseUserCredential(child);
-                    break;
-                case NODE_DIGITAL_CERTIFICATE:
-                    credential.certCredential = parseCertificateCredential(child);
-                    break;
-                case NODE_REALM:
-                    credential.realm = getPpsNodeValue(child);
-                    break;
-                case NODE_SIM:
-                    credential.simCredential = parseSimCredential(child);
-                    break;
-                default:
-                    throw new ParsingException("Unknown node under Credential: " +
-                            child.getName());
-            }
-        }
-        return credential;
-    }
-
-    /**
-     * Parse configurations under PerProviderSubscription/Credential/UsernamePassword subtree.
-     *
-     * @param node PPSNode representing the root of the
-     *             PerProviderSubscription/Credential/UsernamePassword subtree
-     * @return Credential.UserCredential
-     * @throws ParsingException
-     */
-    private static Credential.UserCredential parseUserCredential(PPSNode node)
-            throws ParsingException {
-        if (node.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for UsernamePassword");
-        }
-
-        Credential.UserCredential userCred = new Credential.UserCredential();
-        for (PPSNode child : node.getChildren()) {
-            switch (child.getName()) {
-                case NODE_USERNAME:
-                    userCred.username = getPpsNodeValue(child);
-                    break;
-                case NODE_PASSWORD:
-                    userCred.password = getPpsNodeValue(child);
-                    break;
-                case NODE_EAP_METHOD:
-                    parseEAPMethod(child, userCred);
-                    break;
-                default:
-                    throw new ParsingException("Unknown node under UsernamPassword: " +
-                            child.getName());
-            }
-        }
-        return userCred;
-    }
-
-    /**
-     * Parse configurations under PerProviderSubscription/Credential/UsernamePassword/EAPMethod
-     * subtree.
-     *
-     * @param node PPSNode representing the root of the
-     *             PerProviderSubscription/Credential/UsernamePassword/EAPMethod subtree
-     * @param userCred UserCredential to be updated with EAP method values.
-     * @throws ParsingException
-     */
-    private static void parseEAPMethod(PPSNode node, Credential.UserCredential userCred)
-            throws ParsingException {
-        if (node.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for EAPMethod");
-        }
-
-        for (PPSNode child : node.getChildren()) {
-            switch(child.getName()) {
-                case NODE_EAP_TYPE:
-                    userCred.eapType = parseInteger(getPpsNodeValue(child));
-                    break;
-                case NODE_INNER_METHOD:
-                    userCred.nonEapInnerMethod = getPpsNodeValue(child);
-                    break;
-                default:
-                    throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
-            }
-        }
-    }
-
-    /**
-     * Parse configurations under PerProviderSubscription/Credential/DigitalCertificate subtree.
-     *
-     * @param node PPSNode representing the root of the
-     *             PerProviderSubscription/Credential/DigitalCertificate subtree
-     * @return Credential.CertificateCredential
-     * @throws ParsingException
-     */
-    private static Credential.CertificateCredential parseCertificateCredential(PPSNode node)
-            throws ParsingException {
-        if (node.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for DigitalCertificate");
-        }
-
-        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
-        for (PPSNode child : node.getChildren()) {
-            switch (child.getName()) {
-                case NODE_CERTIFICATE_TYPE:
-                    certCred.certType = getPpsNodeValue(child);
-                    break;
-                case NODE_CERT_SHA256_FINGERPRINT:
-                    certCred.certSha256FingerPrint = parseHexString(getPpsNodeValue(child));
-                    break;
-                default:
-                    throw new ParsingException("Unknown node under DigitalCertificate: " +
-                            child.getName());
-            }
-        }
-        return certCred;
-    }
-
-    /**
-     * Parse configurations under PerProviderSubscription/Credential/SIM subtree.
-     *
-     * @param node PPSNode representing the root of the PerProviderSubscription/Credential/SIM
-     *             subtree
-     * @return Credential.SimCredential
-     * @throws ParsingException
-     */
-    private static Credential.SimCredential parseSimCredential(PPSNode node)
-            throws ParsingException {
-        if (node.isLeaf()) {
-            throw new ParsingException("Leaf node not expected for SIM");
-        }
-
-        Credential.SimCredential simCred = new Credential.SimCredential();
-        for (PPSNode child : node.getChildren()) {
-            switch (child.getName()) {
-                case NODE_SIM_IMSI:
-                    simCred.imsi = getPpsNodeValue(child);
-                    break;
-                case NODE_EAP_TYPE:
-                    simCred.eapType = parseInteger(getPpsNodeValue(child));
-                    break;
-                default:
-                    throw new ParsingException("Unknown node under SIM: " + child.getName());
-            }
-        }
-        return simCred;
-    }
-
-    /**
-     * Convert a hex string to a byte array.
-     *
-     * @param str String containing hex values
-     * @return byte[]
-     * @throws ParsingException
-     */
-    private static byte[] parseHexString(String str) throws ParsingException {
-        if ((str.length() & 1) == 1) {
-            throw new ParsingException("Odd length hex string: " + str.length());
-        }
-
-        byte[] result = new byte[str.length() / 2];
-        for (int i = 0; i < result.length; i++) {
-          int index = i * 2;
-          try {
-              result[i] = (byte) Integer.parseInt(str.substring(index, index + 2), 16);
-          } catch (NumberFormatException e) {
-              throw new ParsingException("Invalid hex string: " + str);
-          }
-        }
-        return result;
-    }
-
-    /**
-     * Parse an integer string.
-     *
-     * @param value String of integer value
-     * @return int
-     * @throws ParsingException
-     */
-    private static int parseInteger(String value) throws ParsingException {
-        try {
-            return Integer.parseInt(value);
-        } catch (NumberFormatException e) {
-            throw new ParsingException("Invalid integer value: " + value);
-        }
-    }
-}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
new file mode 100644
index 0000000..2ffe428
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PpsMoParser.java
@@ -0,0 +1,1652 @@
+/**
+ * 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.net.wifi.hotspot2.omadm;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Utility class for converting OMA-DM (Open Mobile Alliance's Device Management)
+ * PPS-MO (PerProviderSubscription Management Object) XML tree to a
+ * {@link PasspointConfiguration} object.
+ *
+ * Currently this only supports PerProviderSubscription/HomeSP and
+ * PerProviderSubscription/Credential subtree for Hotspot 2.0 Release 1 support.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ *
+ * Below is a sample XML string for a Release 1 PPS MO tree:
+ *
+ * <MgmtTree xmlns="syncml:dmddf1.2">
+ *   <VerDTD>1.2</VerDTD>
+ *   <Node>
+ *     <NodeName>PerProviderSubscription</NodeName>
+ *     <RTProperties>
+ *       <Type>
+ *         <DDFName>urn:wfa:mo:hotspot2dot0­perprovidersubscription:1.0</DDFName>
+ *       </Type>
+ *     </RTProperties>
+ *     <Node>
+ *       <NodeName>i001</NodeName>
+ *       <Node>
+ *         <NodeName>HomeSP</NodeName>
+ *         <Node>
+ *           <NodeName>FriendlyName</NodeName>
+ *           <Value>Century House</Value>
+ *         </Node>
+ *         <Node>
+ *           <NodeName>FQDN</NodeName>
+ *           <Value>mi6.co.uk</Value>
+ *         </Node>
+ *         <Node>
+ *           <NodeName>RoamingConsortiumOI</NodeName>
+ *           <Value>112233,445566</Value>
+ *         </Node>
+ *       </Node>
+ *       <Node>
+ *         <NodeName>Credential</NodeName>
+ *         <Node>
+ *           <NodeName>Realm</NodeName>
+ *           <Value>shaken.stirred.com</Value>
+ *         </Node>
+ *         <Node>
+ *           <NodeName>UsernamePassword</NodeName>
+ *           <Node>
+ *             <NodeName>Username</NodeName>
+ *             <Value>james</Value>
+ *           </Node>
+ *           <Node>
+ *             <NodeName>Password</NodeName>
+ *             <Value>Ym9uZDAwNw==</Value>
+ *           </Node>
+ *           <Node>
+ *             <NodeName>EAPMethod</NodeName>
+ *             <Node>
+ *               <NodeName>EAPType</NodeName>
+ *               <Value>21</Value>
+ *             </Node>
+ *             <Node>
+ *               <NodeName>InnerMethod</NodeName>
+ *               <Value>MS-CHAP-V2</Value>
+ *             </Node>
+ *           </Node>
+ *         </Node>
+ *       </Node>
+ *     </Node>
+ *   </Node>
+ * </MgmtTree>
+ */
+public final class PpsMoParser {
+    private static final String TAG = "PpsMoParser";
+
+    /**
+     * XML tags expected in the PPS MO (PerProviderSubscription Management Object) XML tree.
+     */
+    private static final String TAG_MANAGEMENT_TREE = "MgmtTree";
+    private static final String TAG_VER_DTD = "VerDTD";
+    private static final String TAG_NODE = "Node";
+    private static final String TAG_NODE_NAME = "NodeName";
+    private static final String TAG_RT_PROPERTIES = "RTProperties";
+    private static final String TAG_TYPE = "Type";
+    private static final String TAG_DDF_NAME = "DDFName";
+    private static final String TAG_VALUE = "Value";
+
+    /**
+     * Name for PerProviderSubscription node.
+     */
+    private static final String NODE_PER_PROVIDER_SUBSCRIPTION = "PerProviderSubscription";
+
+    /**
+     * Fields under PerProviderSubscription.
+     */
+    private static final String NODE_UPDATE_IDENTIFIER = "UpdateIdentifier";
+    private static final String NODE_AAA_SERVER_TRUST_ROOT = "AAAServerTrustRoot";
+    private static final String NODE_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
+    private static final String NODE_SUBSCRIPTION_PARAMETER = "SubscriptionParameter";
+    private static final String NODE_TYPE_OF_SUBSCRIPTION = "TypeOfSubscription";
+    private static final String NODE_USAGE_LIMITS = "UsageLimits";
+    private static final String NODE_DATA_LIMIT = "DataLimit";
+    private static final String NODE_START_DATE = "StartDate";
+    private static final String NODE_TIME_LIMIT = "TimeLimit";
+    private static final String NODE_USAGE_TIME_PERIOD = "UsageTimePeriod";
+    private static final String NODE_CREDENTIAL_PRIORITY = "CredentialPriority";
+    /**
+     * Fields under HomeSP subtree.
+     */
+    private static final String NODE_HOMESP = "HomeSP";
+    private static final String NODE_FQDN = "FQDN";
+    private static final String NODE_FRIENDLY_NAME = "FriendlyName";
+    private static final String NODE_ROAMING_CONSORTIUM_OI = "RoamingConsortiumOI";
+    private static final String NODE_NETWORK_ID = "NetworkID";
+    private static final String NODE_SSID = "SSID";
+    private static final String NODE_HESSID = "HESSID";
+    private static final String NODE_ICON_URL = "IconURL";
+    private static final String NODE_HOME_OI_LIST = "HomeOIList";
+    private static final String NODE_HOME_OI = "HomeOI";
+    private static final String NODE_HOME_OI_REQUIRED = "HomeOIRequired";
+    private static final String NODE_OTHER_HOME_PARTNERS = "OtherHomePartners";
+
+    /**
+     * Fields under Credential subtree.
+     */
+    private static final String NODE_CREDENTIAL = "Credential";
+    private static final String NODE_CREATION_DATE = "CreationDate";
+    private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
+    private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
+    private static final String NODE_USERNAME = "Username";
+    private static final String NODE_PASSWORD = "Password";
+    private static final String NODE_MACHINE_MANAGED = "MachineManaged";
+    private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
+    private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
+    private static final String NODE_EAP_METHOD = "EAPMethod";
+    private static final String NODE_EAP_TYPE = "EAPType";
+    private static final String NODE_VENDOR_ID = "VendorId";
+    private static final String NODE_VENDOR_TYPE = "VendorType";
+    private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
+    private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
+    private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
+    private static final String NODE_INNER_METHOD = "InnerMethod";
+    private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
+    private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
+    private static final String NODE_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
+    private static final String NODE_REALM = "Realm";
+    private static final String NODE_SIM = "SIM";
+    private static final String NODE_SIM_IMSI = "IMSI";
+    private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
+
+    /**
+     * Fields under Policy subtree.
+     */
+    private static final String NODE_POLICY = "Policy";
+    private static final String NODE_PREFERRED_ROAMING_PARTNER_LIST =
+            "PreferredRoamingPartnerList";
+    private static final String NODE_FQDN_MATCH = "FQDN_Match";
+    private static final String NODE_PRIORITY = "Priority";
+    private static final String NODE_COUNTRY = "Country";
+    private static final String NODE_MIN_BACKHAUL_THRESHOLD = "MinBackhaulThreshold";
+    private static final String NODE_NETWORK_TYPE = "NetworkType";
+    private static final String NODE_DOWNLINK_BANDWIDTH = "DLBandwidth";
+    private static final String NODE_UPLINK_BANDWIDTH = "ULBandwidth";
+    private static final String NODE_POLICY_UPDATE = "PolicyUpdate";
+    private static final String NODE_UPDATE_INTERVAL = "UpdateInterval";
+    private static final String NODE_UPDATE_METHOD = "UpdateMethod";
+    private static final String NODE_RESTRICTION = "Restriction";
+    private static final String NODE_URI = "URI";
+    private static final String NODE_TRUST_ROOT = "TrustRoot";
+    private static final String NODE_CERT_URL = "CertURL";
+    private static final String NODE_SP_EXCLUSION_LIST = "SPExclusionList";
+    private static final String NODE_REQUIRED_PROTO_PORT_TUPLE = "RequiredProtoPortTuple";
+    private static final String NODE_IP_PROTOCOL = "IPProtocol";
+    private static final String NODE_PORT_NUMBER = "PortNumber";
+    private static final String NODE_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
+    private static final String NODE_OTHER = "Other";
+
+    /**
+     * URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
+     */
+    private static final String PPS_MO_URN =
+            "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0";
+
+    /**
+     * Exception for generic parsing errors.
+     */
+    private static class ParsingException extends Exception {
+        public ParsingException(String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Class representing a node within the PerProviderSubscription tree.
+     * This is used to flatten out and eliminate the extra layering in the XMLNode tree,
+     * to make the data parsing easier and cleaner.
+     *
+     * A PPSNode can be an internal or a leaf node, but not both.
+     *
+     */
+    private static abstract class PPSNode {
+        private final String mName;
+        public PPSNode(String name) {
+            mName = name;
+        }
+
+        /**
+         * @return the name of the node
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Applies for internal node only.
+         *
+         * @return the list of children nodes.
+         */
+        public abstract List<PPSNode> getChildren();
+
+        /**
+         * Applies for leaf node only.
+         *
+         * @return the string value of the node
+         */
+        public abstract String getValue();
+
+        /**
+         * @return a flag indicating if this is a leaf or an internal node
+         */
+        public abstract boolean isLeaf();
+    }
+
+    /**
+     * Class representing a leaf node in a PPS (PerProviderSubscription) tree.
+     */
+    private static class LeafNode extends PPSNode {
+        private final String mValue;
+        public LeafNode(String nodeName, String value) {
+            super(nodeName);
+            mValue = value;
+        }
+
+        @Override
+        public String getValue() {
+            return mValue;
+        }
+
+        @Override
+        public List<PPSNode> getChildren() {
+            return null;
+        }
+
+        @Override
+        public boolean isLeaf() {
+            return true;
+        }
+    }
+
+    /**
+     * Class representing an internal node in a PPS (PerProviderSubscription) tree.
+     */
+    private static class InternalNode extends PPSNode {
+        private final List<PPSNode> mChildren;
+        public InternalNode(String nodeName, List<PPSNode> children) {
+            super(nodeName);
+            mChildren = children;
+        }
+
+        @Override
+        public String getValue() {
+            return null;
+        }
+
+        @Override
+        public List<PPSNode> getChildren() {
+            return mChildren;
+        }
+
+        @Override
+        public boolean isLeaf() {
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public PpsMoParser() {}
+
+    /**
+     * Convert a XML string representation of a PPS MO (PerProviderSubscription
+     * Management Object) tree to a {@link PasspointConfiguration} object.
+     *
+     * @param xmlString XML string representation of a PPS MO tree
+     * @return {@link PasspointConfiguration} or null
+     */
+    public static PasspointConfiguration parseMoText(String xmlString) {
+        // Convert the XML string to a XML tree.
+        XMLParser xmlParser = new XMLParser();
+        XMLNode root = null;
+        try {
+            root = xmlParser.parse(xmlString);
+        } catch(IOException | SAXException e) {
+            return null;
+        }
+        if (root == null) {
+            return null;
+        }
+
+        // Verify root node is a "MgmtTree" node.
+        if (root.getTag() != TAG_MANAGEMENT_TREE) {
+            Log.e(TAG, "Root is not a MgmtTree");
+            return null;
+        }
+
+        String verDtd = null;    // Used for detecting duplicate VerDTD element.
+        PasspointConfiguration config = null;
+        for (XMLNode child : root.getChildren()) {
+            switch(child.getTag()) {
+                case TAG_VER_DTD:
+                    if (verDtd != null) {
+                        Log.e(TAG, "Duplicate VerDTD element");
+                        return null;
+                    }
+                    verDtd = child.getText();
+                    break;
+                case TAG_NODE:
+                    if (config != null) {
+                        Log.e(TAG, "Unexpected multiple Node element under MgmtTree");
+                        return null;
+                    }
+                    try {
+                        config = parsePpsNode(child);
+                    } catch (ParsingException e) {
+                        Log.e(TAG, e.getMessage());
+                        return null;
+                    }
+                    break;
+                default:
+                    Log.e(TAG, "Unknown node: " + child.getTag());
+                    return null;
+            }
+        }
+        return config;
+    }
+
+    /**
+     * Parse a PerProviderSubscription node. Below is the format of the XML tree (with
+     * each XML element represent a node in the tree):
+     *
+     * <Node>
+     *   <NodeName>PerProviderSubscription</NodeName>
+     *   <RTProperties>
+     *     ...
+     *   </RTPProperties>
+     *   <Node>
+     *     <NodeName>UpdateIdentifier</NodeName>
+     *     <Value>...</Value>
+     *   </Node>
+     *   <Node>
+     *     ...
+     *   </Node>
+     * </Node>
+     *
+     * @param node XMLNode that contains PerProviderSubscription node.
+     * @return PasspointConfiguration or null
+     * @throws ParsingException
+     */
+    private static PasspointConfiguration parsePpsNode(XMLNode node)
+            throws ParsingException {
+        PasspointConfiguration config = null;
+        String nodeName = null;
+        int updateIdentifier = Integer.MIN_VALUE;
+        for (XMLNode child : node.getChildren()) {
+            switch (child.getTag()) {
+                case TAG_NODE_NAME:
+                    if (nodeName != null) {
+                        throw new ParsingException("Duplicate NodeName: " + child.getText());
+                    }
+                    nodeName = child.getText();
+                    if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
+                        throw new ParsingException("Unexpected NodeName: " + nodeName);
+                    }
+                    break;
+                case TAG_NODE:
+                    // A node can be either an UpdateIdentifier node or a PerProviderSubscription
+                    // instance node.  Flatten out the XML tree first by converting it to a PPS
+                    // tree to reduce the complexity of the parsing code.
+                    PPSNode ppsNodeRoot = buildPpsNode(child);
+                    if (TextUtils.equals(ppsNodeRoot.getName(), NODE_UPDATE_IDENTIFIER)) {
+                        if (updateIdentifier != Integer.MIN_VALUE) {
+                            throw new ParsingException("Multiple node for UpdateIdentifier");
+                        }
+                        updateIdentifier = parseInteger(getPpsNodeValue(ppsNodeRoot));
+                    } else {
+                        // Only one PerProviderSubscription instance is expected and allowed.
+                        if (config != null) {
+                            throw new ParsingException("Multiple PPS instance");
+                        }
+                        config = parsePpsInstance(ppsNodeRoot);
+                    }
+                    break;
+                case TAG_RT_PROPERTIES:
+                    // Parse and verify URN stored in the RT (Run Time) Properties.
+                    String urn = parseUrn(child);
+                    if (!TextUtils.equals(urn, PPS_MO_URN)) {
+                        throw new ParsingException("Unknown URN: " + urn);
+                    }
+                    break;
+                default:
+                    throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
+            }
+        }
+        if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+            config.setUpdateIdentifier(updateIdentifier);
+        }
+        return config;
+    }
+
+    /**
+     * Parse the URN stored in the RTProperties. Below is the format of the RTPProperties node:
+     *
+     * <RTProperties>
+     *   <Type>
+     *     <DDFName>urn:...</DDFName>
+     *   </Type>
+     * </RTProperties>
+     *
+     * @param node XMLNode that contains RTProperties node.
+     * @return URN String of URN.
+     * @throws ParsingException
+     */
+    private static String parseUrn(XMLNode node) throws ParsingException {
+        if (node.getChildren().size() != 1)
+            throw new ParsingException("Expect RTPProperties node to only have one child");
+
+        XMLNode typeNode = node.getChildren().get(0);
+        if (typeNode.getChildren().size() != 1) {
+            throw new ParsingException("Expect Type node to only have one child");
+        }
+        if (!TextUtils.equals(typeNode.getTag(), TAG_TYPE)) {
+            throw new ParsingException("Unexpected tag for Type: " + typeNode.getTag());
+        }
+
+        XMLNode ddfNameNode = typeNode.getChildren().get(0);
+        if (!ddfNameNode.getChildren().isEmpty()) {
+            throw new ParsingException("Expect DDFName node to have no child");
+        }
+        if (!TextUtils.equals(ddfNameNode.getTag(), TAG_DDF_NAME)) {
+            throw new ParsingException("Unexpected tag for DDFName: " + ddfNameNode.getTag());
+        }
+
+        return ddfNameNode.getText();
+    }
+
+    /**
+     * Convert a XML tree represented by XMLNode to a PPS (PerProviderSubscription) instance tree
+     * represented by PPSNode.  This flattens out the XML tree to allow easier and cleaner parsing
+     * of the PPS configuration data.  Only three types of XML tag are expected: "NodeName",
+     * "Node", and "Value".
+     *
+     * The original XML tree (each XML element represent a node):
+     *
+     * <Node>
+     *   <NodeName>root</NodeName>
+     *   <Node>
+     *     <NodeName>child1</NodeName>
+     *     <Value>value1</Value>
+     *   </Node>
+     *   <Node>
+     *     <NodeName>child2</NodeName>
+     *     <Node>
+     *       <NodeName>grandchild1</NodeName>
+     *       ...
+     *     </Node>
+     *   </Node>
+     *   ...
+     * </Node>
+     *
+     * The converted PPS tree:
+     *
+     * [root] --- [child1, value1]
+     *   |
+     *   ---------[child2] --------[grandchild1] --- ...
+     *
+     * @param node XMLNode pointed to the root of a XML tree
+     * @return PPSNode pointing to the root of a PPS tree
+     * @throws ParsingException
+     */
+    private static PPSNode buildPpsNode(XMLNode node) throws ParsingException {
+        String nodeName = null;
+        String nodeValue = null;
+        List<PPSNode> childNodes = new ArrayList<PPSNode>();
+        // Names of parsed child nodes, use for detecting multiple child nodes with the same name.
+        Set<String> parsedNodes = new HashSet<String>();
+
+        for (XMLNode child : node.getChildren()) {
+            String tag = child.getTag();
+            if (TextUtils.equals(tag, TAG_NODE_NAME)) {
+                if (nodeName != null) {
+                    throw new ParsingException("Duplicate NodeName node");
+                }
+                nodeName = child.getText();
+            } else if (TextUtils.equals(tag, TAG_NODE)) {
+                PPSNode ppsNode = buildPpsNode(child);
+                if (parsedNodes.contains(ppsNode.getName())) {
+                    throw new ParsingException("Duplicate node: " + ppsNode.getName());
+                }
+                parsedNodes.add(ppsNode.getName());
+                childNodes.add(ppsNode);
+            } else if (TextUtils.equals(tag, TAG_VALUE)) {
+               if (nodeValue != null) {
+                   throw new ParsingException("Duplicate Value node");
+               }
+               nodeValue = child.getText();
+            } else {
+                throw new ParsingException("Unknown tag: " + tag);
+            }
+        }
+
+        if (nodeName == null) {
+            throw new ParsingException("Invalid node: missing NodeName");
+        }
+        if (nodeValue == null && childNodes.size() == 0) {
+            throw new ParsingException("Invalid node: " + nodeName +
+                    " missing both value and children");
+        }
+        if (nodeValue != null && childNodes.size() > 0) {
+            throw new ParsingException("Invalid node: " + nodeName +
+                    " contained both value and children");
+        }
+
+        if (nodeValue != null) {
+            return new LeafNode(nodeName, nodeValue);
+        }
+        return new InternalNode(nodeName, childNodes);
+    }
+
+    /**
+     * Return the value of a PPSNode.  An exception will be thrown if the given node
+     * is not a leaf node.
+     *
+     * @param node PPSNode to retrieve the value from
+     * @return String representing the value of the node
+     * @throws ParsingException
+     */
+    private static String getPpsNodeValue(PPSNode node) throws ParsingException {
+        if (!node.isLeaf()) {
+            throw new ParsingException("Cannot get value from a non-leaf node: " + node.getName());
+        }
+        return node.getValue();
+    }
+
+    /**
+     * Parse a PPS (PerProviderSubscription) configurations from a PPS tree.
+     *
+     * @param root PPSNode representing the root of the PPS tree
+     * @return PasspointConfiguration
+     * @throws ParsingException
+     */
+    private static PasspointConfiguration parsePpsInstance(PPSNode root)
+            throws ParsingException {
+        if (root.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PPS instance");
+        }
+
+        PasspointConfiguration config = new PasspointConfiguration();
+        for (PPSNode child : root.getChildren()) {
+            switch(child.getName()) {
+                case NODE_HOMESP:
+                    config.setHomeSp(parseHomeSP(child));
+                    break;
+                case NODE_CREDENTIAL:
+                    config.setCredential(parseCredential(child));
+                    break;
+                case NODE_POLICY:
+                    config.setPolicy(parsePolicy(child));
+                    break;
+                case NODE_AAA_SERVER_TRUST_ROOT:
+                    config.setTrustRootCertList(parseAAAServerTrustRootList(child));
+                    break;
+                case NODE_SUBSCRIPTION_UPDATE:
+                    config.setSubscriptionUpdate(parseUpdateParameter(child));
+                    break;
+                case NODE_SUBSCRIPTION_PARAMETER:
+                    parseSubscriptionParameter(child, config);
+                    break;
+                case NODE_CREDENTIAL_PRIORITY:
+                    config.setCredentialPriority(parseInteger(getPpsNodeValue(child)));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node: " + child.getName());
+            }
+        }
+        return config;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP subtree
+     * @return HomeSP
+     * @throws ParsingException
+     */
+    private static HomeSp parseHomeSP(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for HomeSP");
+        }
+
+        HomeSp homeSp = new HomeSp();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN:
+                    homeSp.setFqdn(getPpsNodeValue(child));
+                    break;
+                case NODE_FRIENDLY_NAME:
+                    homeSp.setFriendlyName(getPpsNodeValue(child));
+                    break;
+                case NODE_ROAMING_CONSORTIUM_OI:
+                    homeSp.setRoamingConsortiumOis(
+                            parseRoamingConsortiumOI(getPpsNodeValue(child)));
+                    break;
+                case NODE_ICON_URL:
+                    homeSp.setIconUrl(getPpsNodeValue(child));
+                    break;
+                case NODE_NETWORK_ID:
+                    homeSp.setHomeNetworkIds(parseNetworkIds(child));
+                    break;
+                case NODE_HOME_OI_LIST:
+                    Pair<List<Long>, List<Long>> homeOIs = parseHomeOIList(child);
+                    homeSp.setMatchAllOis(convertFromLongList(homeOIs.first));
+                    homeSp.setMatchAnyOis(convertFromLongList(homeOIs.second));
+                    break;
+                case NODE_OTHER_HOME_PARTNERS:
+                    homeSp.setOtherHomePartners(parseOtherHomePartners(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under HomeSP: " + child.getName());
+            }
+        }
+        return homeSp;
+    }
+
+    /**
+     * Parse the roaming consortium OI string, which contains a list of OIs separated by ",".
+     *
+     * @param oiStr string containing list of OIs (Organization Identifiers) separated by ","
+     * @return long[]
+     * @throws ParsingException
+     */
+    private static long[] parseRoamingConsortiumOI(String oiStr)
+            throws ParsingException {
+        String[] oiStrArray = oiStr.split(",");
+        long[] oiArray = new long[oiStrArray.length];
+        for (int i = 0; i < oiStrArray.length; i++) {
+            oiArray[i] = parseLong(oiStrArray[i], 16);
+        }
+        return oiArray;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/NetworkID subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/NetworkID
+     *             subtree
+     * @return HashMap<String, Long> representing list of <SSID, HESSID> pair.
+     * @throws ParsingException
+     */
+    static private Map<String, Long> parseNetworkIds(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for NetworkID");
+        }
+
+        Map<String, Long> networkIds = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<String, Long> networkId = parseNetworkIdInstance(child);
+            networkIds.put(networkId.first, networkId.second);
+        }
+        return networkIds;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/NetworkID/<X+> subtree.
+     * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+     * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/NetworkID/<X+> subtree
+     * @return Pair<String, Long> representing <SSID, HESSID> pair.
+     * @throws ParsingException
+     */
+    static private Pair<String, Long> parseNetworkIdInstance(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for NetworkID instance");
+        }
+
+        String ssid = null;
+        Long hessid = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SSID:
+                    ssid = getPpsNodeValue(child);
+                    break;
+                case NODE_HESSID:
+                    hessid = parseLong(getPpsNodeValue(child), 16);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under NetworkID instance: " +
+                            child.getName());
+            }
+        }
+        if (ssid == null)
+            throw new ParsingException("NetworkID instance missing SSID");
+
+        return new Pair<String, Long>(ssid, hessid);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/HomeSP/HomeOIList
+     *             subtree
+     * @return Pair<List<Long>, List<Long>> containing both MatchAllOIs and MatchAnyOIs list.
+     * @throws ParsingException
+     */
+    private static Pair<List<Long>, List<Long>> parseHomeOIList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for HomeOIList");
+        }
+
+        List<Long> matchAllOIs = new ArrayList<Long>();
+        List<Long> matchAnyOIs = new ArrayList<Long>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<Long, Boolean> homeOI = parseHomeOIInstance(child);
+            if (homeOI.second.booleanValue()) {
+                matchAllOIs.add(homeOI.first);
+            } else {
+                matchAnyOIs.add(homeOI.first);
+            }
+        }
+        return new Pair<List<Long>, List<Long>>(matchAllOIs, matchAnyOIs);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree.
+     * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+     * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/HomeOIList/<X+> subtree
+     * @return Pair<Long, Boolean> containing a HomeOI and a HomeOIRequired flag
+     * @throws ParsingException
+     */
+    private static Pair<Long, Boolean> parseHomeOIInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for HomeOI instance");
+        }
+
+        Long oi = null;
+        Boolean required = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_HOME_OI:
+                    try {
+                        oi = Long.valueOf(getPpsNodeValue(child), 16);
+                    } catch (NumberFormatException e) {
+                        throw new ParsingException("Invalid HomeOI: " + getPpsNodeValue(child));
+                    }
+                    break;
+                case NODE_HOME_OI_REQUIRED:
+                    required = Boolean.valueOf(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under NetworkID instance: " +
+                            child.getName());
+            }
+        }
+        if (oi == null) {
+            throw new ParsingException("HomeOI instance missing OI field");
+        }
+        if (required == null) {
+            throw new ParsingException("HomeOI instance missing required field");
+        }
+        return new Pair<Long, Boolean>(oi, required);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners subtree.
+     * This contains a list of FQDN (Fully Qualified Domain Name) that are considered
+     * home partners.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/OtherHomePartners subtree
+     * @return String[] list of partner's FQDN
+     * @throws ParsingException
+     */
+    private static String[] parseOtherHomePartners(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for OtherHomePartners");
+        }
+        List<String> otherHomePartners = new ArrayList<String>();
+        for (PPSNode child : node.getChildren()) {
+            String fqdn = parseOtherHomePartnerInstance(child);
+            otherHomePartners.add(fqdn);
+        }
+        return otherHomePartners.toArray(new String[otherHomePartners.size()]);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree.
+     * The instance name (<X+>) is irrelevant and must be unique for each instance, which
+     * is verified when the PPS tree is constructed {@link #buildPpsNode}.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/HomeSP/OtherHomePartners/<X+> subtree
+     * @return String FQDN of the partner
+     * @throws ParsingException
+     */
+    private static String parseOtherHomePartnerInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for OtherHomePartner instance");
+        }
+        String fqdn = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN:
+                    fqdn = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException(
+                            "Unknown node under OtherHomePartner instance: " + child.getName());
+            }
+        }
+        if (fqdn == null) {
+            throw new ParsingException("OtherHomePartner instance missing FQDN field");
+        }
+        return fqdn;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Credential subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Credential subtree
+     * @return Credential
+     * @throws ParsingException
+     */
+    private static Credential parseCredential(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for HomeSP");
+        }
+
+        Credential credential = new Credential();
+        for (PPSNode child: node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CREATION_DATE:
+                    credential.setCreationTimeInMs(parseDate(getPpsNodeValue(child)));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    credential.setExpirationTimeInMs(parseDate(getPpsNodeValue(child)));
+                    break;
+                case NODE_USERNAME_PASSWORD:
+                    credential.setUserCredential(parseUserCredential(child));
+                    break;
+                case NODE_DIGITAL_CERTIFICATE:
+                    credential.setCertCredential(parseCertificateCredential(child));
+                    break;
+                case NODE_REALM:
+                    credential.setRealm(getPpsNodeValue(child));
+                    break;
+                case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+                    credential.setCheckAaaServerCertStatus(
+                            Boolean.parseBoolean(getPpsNodeValue(child)));
+                    break;
+                case NODE_SIM:
+                    credential.setSimCredential(parseSimCredential(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Credential: " +
+                            child.getName());
+            }
+        }
+        return credential;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Credential/UsernamePassword subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Credential/UsernamePassword subtree
+     * @return Credential.UserCredential
+     * @throws ParsingException
+     */
+    private static Credential.UserCredential parseUserCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsernamePassword");
+        }
+
+        Credential.UserCredential userCred = new Credential.UserCredential();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_USERNAME:
+                    userCred.setUsername(getPpsNodeValue(child));
+                    break;
+                case NODE_PASSWORD:
+                    userCred.setPassword(getPpsNodeValue(child));
+                    break;
+                case NODE_MACHINE_MANAGED:
+                    userCred.setMachineManaged(Boolean.parseBoolean(getPpsNodeValue(child)));
+                    break;
+                case NODE_SOFT_TOKEN_APP:
+                    userCred.setSoftTokenApp(getPpsNodeValue(child));
+                    break;
+                case NODE_ABLE_TO_SHARE:
+                    userCred.setAbleToShare(Boolean.parseBoolean(getPpsNodeValue(child)));
+                    break;
+                case NODE_EAP_METHOD:
+                    parseEAPMethod(child, userCred);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsernamPassword: " +
+                            child.getName());
+            }
+        }
+        return userCred;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Credential/UsernamePassword/EAPMethod
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Credential/UsernamePassword/EAPMethod subtree
+     * @param userCred UserCredential to be updated with EAP method values.
+     * @throws ParsingException
+     */
+    private static void parseEAPMethod(PPSNode node, Credential.UserCredential userCred)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for EAPMethod");
+        }
+
+        for (PPSNode child : node.getChildren()) {
+            switch(child.getName()) {
+                case NODE_EAP_TYPE:
+                    userCred.setEapType(parseInteger(getPpsNodeValue(child)));
+                    break;
+                case NODE_INNER_METHOD:
+                    userCred.setNonEapInnerMethod(getPpsNodeValue(child));
+                    break;
+                case NODE_VENDOR_ID:
+                case NODE_VENDOR_TYPE:
+                case NODE_INNER_EAP_TYPE:
+                case NODE_INNER_VENDOR_ID:
+                case NODE_INNER_VENDOR_TYPE:
+                    // Only EAP-TTLS is currently supported for user credential, which doesn't
+                    // use any of these parameters.
+                    Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
+            }
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Credential/DigitalCertificate subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Credential/DigitalCertificate subtree
+     * @return Credential.CertificateCredential
+     * @throws ParsingException
+     */
+    private static Credential.CertificateCredential parseCertificateCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for DigitalCertificate");
+        }
+
+        Credential.CertificateCredential certCred = new Credential.CertificateCredential();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CERTIFICATE_TYPE:
+                    certCred.setCertType(getPpsNodeValue(child));
+                    break;
+                case NODE_CERT_SHA256_FINGERPRINT:
+                    certCred.setCertSha256Fingerprint(parseHexString(getPpsNodeValue(child)));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under DigitalCertificate: " +
+                            child.getName());
+            }
+        }
+        return certCred;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Credential/SIM subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Credential/SIM
+     *             subtree
+     * @return Credential.SimCredential
+     * @throws ParsingException
+     */
+    private static Credential.SimCredential parseSimCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SIM");
+        }
+
+        Credential.SimCredential simCred = new Credential.SimCredential();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SIM_IMSI:
+                    simCred.setImsi(getPpsNodeValue(child));
+                    break;
+                case NODE_EAP_TYPE:
+                    simCred.setEapType(parseInteger(getPpsNodeValue(child)));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SIM: " + child.getName());
+            }
+        }
+        return simCred;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy subtree
+     * @return {@link Policy}
+     * @throws ParsingException
+     */
+    private static Policy parsePolicy(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Policy");
+        }
+
+        Policy policy = new Policy();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_PREFERRED_ROAMING_PARTNER_LIST:
+                    policy.setPreferredRoamingPartnerList(parsePreferredRoamingPartnerList(child));
+                    break;
+                case NODE_MIN_BACKHAUL_THRESHOLD:
+                    parseMinBackhaulThreshold(child, policy);
+                    break;
+                case NODE_POLICY_UPDATE:
+                    policy.setPolicyUpdate(parseUpdateParameter(child));
+                    break;
+                case NODE_SP_EXCLUSION_LIST:
+                    policy.setExcludedSsidList(parseSpExclusionList(child));
+                    break;
+                case NODE_REQUIRED_PROTO_PORT_TUPLE:
+                    policy.setRequiredProtoPortMap(parseRequiredProtoPortTuple(child));
+                    break;
+                case NODE_MAXIMUM_BSS_LOAD_VALUE:
+                    policy.setMaximumBssLoadValue(parseInteger(getPpsNodeValue(child)));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Policy: " + child.getName());
+            }
+        }
+        return policy;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList subtree
+     * @return List of {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static List<Policy.RoamingPartner> parsePreferredRoamingPartnerList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartnerList");
+        }
+        List<Policy.RoamingPartner> partnerList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            partnerList.add(parsePreferredRoamingPartner(child));
+        }
+        return partnerList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/PreferredRoamingPartnerList/<X+> subtree
+     * @return {@link Policy#RoamingPartner}
+     * @throws ParsingException
+     */
+    private static Policy.RoamingPartner parsePreferredRoamingPartner(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for PreferredRoamingPartner "
+                    + "instance");
+        }
+
+        Policy.RoamingPartner roamingPartner = new Policy.RoamingPartner();
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_FQDN_MATCH:
+                    // FQDN_Match field is in the format of "[FQDN],[MatchInfo]", where [MatchInfo]
+                    // is either "exactMatch" for exact match of FQDN or "includeSubdomains" for
+                    // matching all FQDNs with the same sub-domain.
+                    String fqdnMatch = getPpsNodeValue(child);
+                    String[] fqdnMatchArray = fqdnMatch.split(",");
+                    if (fqdnMatchArray.length != 2) {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    roamingPartner.setFqdn(fqdnMatchArray[0]);
+                    if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+                        roamingPartner.setFqdnExactMatch(true);
+                    } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+                        roamingPartner.setFqdnExactMatch(false);
+                    } else {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    break;
+                case NODE_PRIORITY:
+                    roamingPartner.setPriority(parseInteger(getPpsNodeValue(child)));
+                    break;
+                case NODE_COUNTRY:
+                    roamingPartner.setCountries(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under PreferredRoamingPartnerList "
+                            + "instance " + child.getName());
+            }
+        }
+        return roamingPartner;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold subtree
+     * @param policy The policy to store the MinBackhualThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThreshold(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold");
+        }
+        for (PPSNode child : node.getChildren()) {
+            parseMinBackhaulThresholdInstance(child, policy);
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * into the given policy.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/MinBackhaulThreshold/<X+> subtree
+     * @param policy The policy to store the MinBackhaulThreshold configuration
+     * @throws ParsingException
+     */
+    private static void parseMinBackhaulThresholdInstance(PPSNode node, Policy policy)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for MinBackhaulThreshold instance");
+        }
+        String networkType = null;
+        long downlinkBandwidth = Long.MIN_VALUE;
+        long uplinkBandwidth = Long.MIN_VALUE;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_NETWORK_TYPE:
+                    networkType = getPpsNodeValue(child);
+                    break;
+                case NODE_DOWNLINK_BANDWIDTH:
+                    downlinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_UPLINK_BANDWIDTH:
+                    uplinkBandwidth = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under MinBackhaulThreshold instance "
+                            + child.getName());
+            }
+        }
+        if (networkType == null) {
+            throw new ParsingException("Missing NetworkType field");
+        }
+
+        if (TextUtils.equals(networkType, "home")) {
+            policy.setMinHomeDownlinkBandwidth(downlinkBandwidth);
+            policy.setMinHomeUplinkBandwidth(uplinkBandwidth);
+        } else if (TextUtils.equals(networkType, "roaming")) {
+            policy.setMinRoamingDownlinkBandwidth(downlinkBandwidth);
+            policy.setMinRoamingUplinkBandwidth(uplinkBandwidth);
+        } else {
+            throw new ParsingException("Invalid network type: " + networkType);
+        }
+    }
+
+    /**
+     * Parse update parameters. This contained configurations from either
+     * PerProviderSubscription/Policy/PolicyUpdate or PerProviderSubscription/SubscriptionUpdate
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the PerProviderSubscription/Policy/PolicyUpdate
+     *             or PerProviderSubscription/SubscriptionUpdate subtree
+     * @return {@link UpdateParameter}
+     * @throws ParsingException
+     */
+    private static UpdateParameter parseUpdateParameter(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for Update Parameters");
+        }
+
+        UpdateParameter updateParam = new UpdateParameter();
+        for (PPSNode child : node.getChildren()) {
+            switch(child.getName()) {
+                case NODE_UPDATE_INTERVAL:
+                    updateParam.setUpdateIntervalInMinutes(parseLong(getPpsNodeValue(child), 10));
+                    break;
+                case NODE_UPDATE_METHOD:
+                    updateParam.setUpdateMethod(getPpsNodeValue(child));
+                    break;
+                case NODE_RESTRICTION:
+                    updateParam.setRestriction(getPpsNodeValue(child));
+                    break;
+                case NODE_URI:
+                    updateParam.setServerUri(getPpsNodeValue(child));
+                    break;
+                case NODE_USERNAME_PASSWORD:
+                    Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+                    updateParam.setUsername(usernamePassword.first);
+                    updateParam.setBase64EncodedPassword(usernamePassword.second);
+                    break;
+                case NODE_TRUST_ROOT:
+                    Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+                    updateParam.setTrustRootCertUrl(trustRoot.first);
+                    updateParam.setTrustRootCertSha256Fingerprint(trustRoot.second);
+                    break;
+                case NODE_OTHER:
+                    Log.d(TAG, "Ignore unsupported paramter: " + child.getName());
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under Update Parameters: "
+                            + child.getName());
+            }
+        }
+        return updateParam;
+    }
+
+    /**
+     * Parse username and password parameters associated with policy or subscription update.
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/UsernamePassword or
+     * PerProviderSubscription/SubscriptionUpdate/UsernamePassword subtree.
+     *
+     * @param node PPSNode representing the root of the UsernamePassword subtree
+     * @return Pair of username and password
+     * @throws ParsingException
+     */
+    private static Pair<String, String> parseUpdateUserCredential(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsernamePassword");
+        }
+
+        String username = null;
+        String password = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_USERNAME:
+                    username = getPpsNodeValue(child);
+                    break;
+                case NODE_PASSWORD:
+                    password = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsernamePassword: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(username, password);
+    }
+
+    /**
+     * Parse the trust root parameters associated with policy update, subscription update, or AAA
+     * server trust root.
+     *
+     * This contained configurations under either
+     * PerProviderSubscription/Policy/PolicyUpdate/TrustRoot or
+     * PerProviderSubscription/SubscriptionUpdate/TrustRoot or
+     * PerProviderSubscription/AAAServerTrustRoot/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the TrustRoot subtree
+     * @return Pair of Certificate URL and fingerprint
+     * @throws ParsingException
+     */
+    private static Pair<String, byte[]> parseTrustRoot(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for TrustRoot");
+        }
+
+        String certUrl = null;
+        byte[] certFingerprint = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CERT_URL:
+                    certUrl = getPpsNodeValue(child);
+                    break;
+                case NODE_CERT_SHA256_FINGERPRINT:
+                    certFingerprint = parseHexString(getPpsNodeValue(child));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under TrustRoot: "
+                            + child.getName());
+            }
+        }
+        return Pair.create(certUrl, certFingerprint);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList subtree
+     * @return Array of excluded SSIDs
+     * @throws ParsingException
+     */
+    private static String[] parseSpExclusionList(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusionList");
+        }
+        List<String> ssidList = new ArrayList<>();
+        for (PPSNode child : node.getChildren()) {
+            ssidList.add(parseSpExclusionInstance(child));
+        }
+        return ssidList.toArray(new String[ssidList.size()]);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/SPExclusionList/<X+> subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/SPExclusionList/<X+> subtree
+     * @return String
+     * @throws ParsingException
+     */
+    private static String parseSpExclusionInstance(PPSNode node) throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SPExclusion instance");
+        }
+        String ssid = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_SSID:
+                    ssid = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SPExclusion instance");
+            }
+        }
+        return ssid;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple subtree
+     * @return Map of IP Protocol to Port Number tuples
+     * @throws ParsingException
+     */
+    private static Map<Integer, String> parseRequiredProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple");
+        }
+        Map<Integer, String> protoPortTupleMap = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<Integer, String> protoPortTuple = parseProtoPortTuple(child);
+            protoPortTupleMap.put(protoPortTuple.first, protoPortTuple.second);
+        }
+        return protoPortTupleMap;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+>
+     * subtree.
+     *
+     * @param node PPSNode representing the root of the
+     *             PerProviderSubscription/Policy/RequiredProtoPortTuple/<X+> subtree
+     * @return Pair of IP Protocol to Port Number tuple
+     * @throws ParsingException
+     */
+    private static Pair<Integer, String> parseProtoPortTuple(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for RequiredProtoPortTuple "
+                    + "instance");
+        }
+        int proto = Integer.MIN_VALUE;
+        String ports = null;
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_IP_PROTOCOL:
+                    proto = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_PORT_NUMBER:
+                    ports = getPpsNodeValue(child);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under RequiredProtoPortTuple instance"
+                            + child.getName());
+            }
+        }
+        if (proto == Integer.MIN_VALUE) {
+            throw new ParsingException("Missing IPProtocol field");
+        }
+        if (ports == null) {
+            throw new ParsingException("Missing PortNumber field");
+        }
+        return Pair.create(proto, ports);
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/AAAServerTrustRoot subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/AAAServerTrustRoot
+     *             subtree
+     * @return Map of certificate URL with the corresponding certificate fingerprint
+     * @throws ParsingException
+     */
+    private static Map<String, byte[]> parseAAAServerTrustRootList(PPSNode node)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for AAAServerTrustRoot");
+        }
+        Map<String, byte[]> certList = new HashMap<>();
+        for (PPSNode child : node.getChildren()) {
+            Pair<String, byte[]> certTuple = parseTrustRoot(child);
+            certList.put(certTuple.first, certTuple.second);
+        }
+        return certList;
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/SubscriptionParameter subtree.
+     *
+     * @param node PPSNode representing the root of PerProviderSubscription/SubscriptionParameter
+     *             subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseSubscriptionParameter(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for SubscriptionParameter");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_CREATION_DATE:
+                    config.setSubscriptionCreationTimeInMs(parseDate(getPpsNodeValue(child)));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    config.setSubscriptionExpirationTimeInMs(parseDate(getPpsNodeValue(child)));
+                    break;
+                case NODE_TYPE_OF_SUBSCRIPTION:
+                    config.setSubscriptionType(getPpsNodeValue(child));
+                    break;
+                case NODE_USAGE_LIMITS:
+                    parseUsageLimits(child, config);
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under SubscriptionParameter"
+                            + child.getName());
+            }
+        }
+    }
+
+    /**
+     * Parse configurations under PerProviderSubscription/SubscriptionParameter/UsageLimits
+     * subtree.
+     *
+     * @param node PPSNode representing the root of
+     *             PerProviderSubscription/SubscriptionParameter/UsageLimits subtree
+     * @param config Instance of {@link PasspointConfiguration}
+     * @throws ParsingException
+     */
+    private static void parseUsageLimits(PPSNode node, PasspointConfiguration config)
+            throws ParsingException {
+        if (node.isLeaf()) {
+            throw new ParsingException("Leaf node not expected for UsageLimits");
+        }
+        for (PPSNode child : node.getChildren()) {
+            switch (child.getName()) {
+                case NODE_DATA_LIMIT:
+                    config.setUsageLimitDataLimit(parseLong(getPpsNodeValue(child), 10));
+                    break;
+                case NODE_START_DATE:
+                    config.setUsageLimitStartTimeInMs(parseDate(getPpsNodeValue(child)));
+                    break;
+                case NODE_TIME_LIMIT:
+                    config.setUsageLimitTimeLimitInMinutes(parseLong(getPpsNodeValue(child), 10));
+                    break;
+                case NODE_USAGE_TIME_PERIOD:
+                    config.setUsageLimitUsageTimePeriodInMinutes(
+                            parseLong(getPpsNodeValue(child), 10));
+                    break;
+                default:
+                    throw new ParsingException("Unknown node under UsageLimits"
+                            + child.getName());
+            }
+        }
+    }
+
+    /**
+     * Convert a hex string to a byte array.
+     *
+     * @param str String containing hex values
+     * @return byte[]
+     * @throws ParsingException
+     */
+    private static byte[] parseHexString(String str) throws ParsingException {
+        if ((str.length() & 1) == 1) {
+            throw new ParsingException("Odd length hex string: " + str.length());
+        }
+
+        byte[] result = new byte[str.length() / 2];
+        for (int i = 0; i < result.length; i++) {
+          int index = i * 2;
+          try {
+              result[i] = (byte) Integer.parseInt(str.substring(index, index + 2), 16);
+          } catch (NumberFormatException e) {
+              throw new ParsingException("Invalid hex string: " + str);
+          }
+        }
+        return result;
+    }
+
+    /**
+     * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
+     *
+     * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
+     * @return number of milliseconds
+     * @throws ParsingException
+     */
+    private static long parseDate(String dateStr) throws ParsingException {
+        try {
+            DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+            return format.parse(dateStr).getTime();
+        } catch (ParseException pe) {
+            throw new ParsingException("Badly formatted time: " + dateStr);
+        }
+    }
+
+    /**
+     * Parse an integer string.
+     *
+     * @param value String of integer value
+     * @return int
+     * @throws ParsingException
+     */
+    private static int parseInteger(String value) throws ParsingException {
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw new ParsingException("Invalid integer value: " + value);
+        }
+    }
+
+    /**
+     * Parse a string representing a long integer.
+     *
+     * @param value String of long integer value
+     * @return long
+     * @throws ParsingException
+     */
+    private static long parseLong(String value, int radix) throws ParsingException {
+        try {
+            return Long.parseLong(value, radix);
+        } catch (NumberFormatException e) {
+            throw new ParsingException("Invalid long integer value: " + value);
+        }
+    }
+
+    /**
+     * Convert a List<Long> to a primitive long array long[].
+     *
+     * @param list List to be converted
+     * @return long[]
+     */
+    private static long[] convertFromLongList(List<Long> list) {
+        Long[] objectArray = list.toArray(new Long[list.size()]);
+        long[] primitiveArray = new long[objectArray.length];
+        for (int i = 0; i < objectArray.length; i++) {
+            primitiveArray[i] = objectArray[i].longValue();
+        }
+        return primitiveArray;
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java b/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java
index e87698c..959d505 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/XMLNode.java
@@ -20,6 +20,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A class represent a node in an XML tree. Each node is an XML element.
@@ -100,4 +101,9 @@
                 TextUtils.equals(mText, that.mText) &&
                 mChildren.equals(that.mChildren);
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTag, mText, mChildren);
+    }
 }
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 790dfaf..67fa1bb 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -23,13 +23,16 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -40,10 +43,6 @@
  *
  * In addition to the fields in the Credential subtree, this will also maintain necessary
  * information for the private key and certificates associated with this credential.
- *
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
- * @hide
  */
 public final class Credential implements Parcelable {
     private static final String TAG = "Credential";
@@ -52,14 +51,59 @@
      * Max string length for realm.  Refer to Credential/Realm node in Hotspot 2.0 Release 2
      * Technical Specification Section 9.1 for more info.
      */
-    private static final int MAX_REALM_LENGTH = 253;
+    private static final int MAX_REALM_BYTES = 253;
+
+    /**
+     * The time this credential is created. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    private long mCreationTimeInMs = Long.MIN_VALUE;
+    public void setCreationTimeInMs(long creationTimeInMs) {
+        mCreationTimeInMs = creationTimeInMs;
+    }
+    public long getCreationTimeInMs() {
+        return mCreationTimeInMs;
+    }
+
+    /**
+     * The time this credential will expire. It is in the format of number
+     * of milliseconds since January 1, 1970, 00:00:00 GMT.
+    * Using Long.MIN_VALUE to indicate unset value.
+     */
+    private long mExpirationTimeInMs = Long.MIN_VALUE;
+    public void setExpirationTimeInMs(long expirationTimeInMs) {
+        mExpirationTimeInMs = expirationTimeInMs;
+    }
+    public long getExpirationTimeInMs() {
+        return mExpirationTimeInMs;
+    }
 
     /**
      * The realm associated with this credential.  It will be used to determine
      * if this credential can be used to authenticate with a given hotspot by
      * comparing the realm specified in that hotspot's ANQP element.
      */
-    public String realm = null;
+    private String mRealm = null;
+    public void setRealm(String realm) {
+        mRealm = realm;
+    }
+    public String getRealm() {
+        return mRealm;
+    }
+
+    /**
+     * When set to true, the device should check AAA (Authentication, Authorization,
+     * and Accounting) server's certificate during EAP (Extensible Authentication
+     * Protocol) authentication.
+     */
+    private boolean mCheckAaaServerCertStatus = false;
+    public void setCheckAaaServerCertStatus(boolean checkAaaServerCertStatus) {
+        mCheckAaaServerCertStatus = checkAaaServerCertStatus;
+    }
+    public boolean getCheckAaaServerCertStatus() {
+        return mCheckAaaServerCertStatus;
+    }
 
     /**
      * Username-password based credential.
@@ -70,31 +114,86 @@
          * Maximum string length for username.  Refer to Credential/UsernamePassword/Username
          * node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
          */
-        private static final int MAX_USERNAME_LENGTH = 63;
+        private static final int MAX_USERNAME_BYTES = 63;
 
         /**
          * Maximum string length for password.  Refer to Credential/UsernamePassword/Password
          * in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
          */
-        private static final int MAX_PASSWORD_LENGTH = 255;
+        private static final int MAX_PASSWORD_BYTES = 255;
+
+        /**
+         * Supported authentication methods.
+         * @hide
+         */
+        public static final String AUTH_METHOD_PAP = "PAP";
+        /** @hide */
+        public static final String AUTH_METHOD_MSCHAP = "MS-CHAP";
+        /** @hide */
+        public static final String AUTH_METHOD_MSCHAPV2 = "MS-CHAP-V2";
 
         /**
          * Supported Non-EAP inner methods.  Refer to
          * Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical
          * Specification Section 9.1 for more info.
          */
-        private static final Set<String> SUPPORTED_AUTH =
-                new HashSet<String>(Arrays.asList("PAP", "CHAP", "MS-CHAP", "MS-CHAP-V2"));
+        private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(
+                Arrays.asList(AUTH_METHOD_PAP, AUTH_METHOD_MSCHAP, AUTH_METHOD_MSCHAPV2));
 
         /**
          * Username of the credential.
          */
-        public String username = null;
+        private String mUsername = null;
+        public void setUsername(String username) {
+            mUsername = username;
+        }
+        public String getUsername() {
+            return mUsername;
+        }
 
         /**
          * Base64-encoded password.
          */
-        public String password = null;
+        private String mPassword = null;
+        public void setPassword(String password) {
+            mPassword = password;
+        }
+        public String getPassword() {
+            return mPassword;
+        }
+
+        /**
+         * Flag indicating if the password is machine managed.
+         */
+        private boolean mMachineManaged = false;
+        public void setMachineManaged(boolean machineManaged) {
+            mMachineManaged = machineManaged;
+        }
+        public boolean getMachineManaged() {
+            return mMachineManaged;
+        }
+
+        /**
+         * The name of the application used to generate the password.
+         */
+        private String mSoftTokenApp = null;
+        public void setSoftTokenApp(String softTokenApp) {
+            mSoftTokenApp = softTokenApp;
+        }
+        public String getSoftTokenApp() {
+            return mSoftTokenApp;
+        }
+
+        /**
+         * Flag indicating if this credential is usable on other mobile devices as well.
+         */
+        private boolean mAbleToShare = false;
+        public void setAbleToShare(boolean ableToShare) {
+            mAbleToShare = ableToShare;
+        }
+        public boolean getAbleToShare() {
+            return mAbleToShare;
+        }
 
         /**
          * EAP (Extensible Authentication Protocol) method type.
@@ -102,12 +201,24 @@
          * for valid values.
          * Using Integer.MIN_VALUE to indicate unset value.
          */
-        public int eapType = Integer.MIN_VALUE;
+        private int mEapType = Integer.MIN_VALUE;
+        public void setEapType(int eapType) {
+            mEapType = eapType;
+        }
+        public int getEapType() {
+            return mEapType;
+        }
 
         /**
          * Non-EAP inner authentication method.
          */
-        public String nonEapInnerMethod = null;
+        private String mNonEapInnerMethod = null;
+        public void setNonEapInnerMethod(String nonEapInnerMethod) {
+            mNonEapInnerMethod = nonEapInnerMethod;
+        }
+        public String getNonEapInnerMethod() {
+            return mNonEapInnerMethod;
+        }
 
         /**
          * Constructor for creating UserCredential with default values.
@@ -121,10 +232,13 @@
          */
         public UserCredential(UserCredential source) {
             if (source != null) {
-                username = source.username;
-                password = source.password;
-                eapType = source.eapType;
-                nonEapInnerMethod = source.nonEapInnerMethod;
+                mUsername = source.mUsername;
+                mPassword = source.mPassword;
+                mMachineManaged = source.mMachineManaged;
+                mSoftTokenApp = source.mSoftTokenApp;
+                mAbleToShare = source.mAbleToShare;
+                mEapType = source.mEapType;
+                mNonEapInnerMethod = source.mNonEapInnerMethod;
             }
         }
 
@@ -135,10 +249,13 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(username);
-            dest.writeString(password);
-            dest.writeInt(eapType);
-            dest.writeString(nonEapInnerMethod);
+            dest.writeString(mUsername);
+            dest.writeString(mPassword);
+            dest.writeInt(mMachineManaged ? 1 : 0);
+            dest.writeString(mSoftTokenApp);
+            dest.writeInt(mAbleToShare ? 1 : 0);
+            dest.writeInt(mEapType);
+            dest.writeString(mNonEapInnerMethod);
         }
 
         @Override
@@ -151,45 +268,69 @@
             }
 
             UserCredential that = (UserCredential) thatObject;
-            return TextUtils.equals(username, that.username) &&
-                    TextUtils.equals(password, that.password) &&
-                    eapType == that.eapType &&
-                    TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
+            return TextUtils.equals(mUsername, that.mUsername)
+                    && TextUtils.equals(mPassword, that.mPassword)
+                    && mMachineManaged == that.mMachineManaged
+                    && TextUtils.equals(mSoftTokenApp, that.mSoftTokenApp)
+                    && mAbleToShare == that.mAbleToShare
+                    && mEapType == that.mEapType
+                    && TextUtils.equals(mNonEapInnerMethod, that.mNonEapInnerMethod);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUsername, mPassword, mMachineManaged, mSoftTokenApp,
+                    mAbleToShare, mEapType, mNonEapInnerMethod);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Username: ").append(mUsername).append("\n");
+            builder.append("MachineManaged: ").append(mMachineManaged).append("\n");
+            builder.append("SoftTokenApp: ").append(mSoftTokenApp).append("\n");
+            builder.append("AbleToShare: ").append(mAbleToShare).append("\n");
+            builder.append("EAPType: ").append(mEapType).append("\n");
+            builder.append("AuthMethod: ").append(mNonEapInnerMethod).append("\n");
+            return builder.toString();
         }
 
         /**
          * Validate the configuration data.
          *
          * @return true on success or false on failure
+         * @hide
          */
         public boolean validate() {
-            if (TextUtils.isEmpty(username)) {
+            if (TextUtils.isEmpty(mUsername)) {
                 Log.d(TAG, "Missing username");
                 return false;
             }
-            if (username.length() > MAX_USERNAME_LENGTH) {
-                Log.d(TAG, "username exceeding maximum length: " + username.length());
+            if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+                Log.d(TAG, "username exceeding maximum length: "
+                        + mUsername.getBytes(StandardCharsets.UTF_8).length);
                 return false;
             }
 
-            if (TextUtils.isEmpty(password)) {
+            if (TextUtils.isEmpty(mPassword)) {
                 Log.d(TAG, "Missing password");
                 return false;
             }
-            if (password.length() > MAX_PASSWORD_LENGTH) {
-                Log.d(TAG, "password exceeding maximum length: " + password.length());
+            if (mPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+                Log.d(TAG, "password exceeding maximum length: "
+                        + mPassword.getBytes(StandardCharsets.UTF_8).length);
                 return false;
             }
 
             // Only supports EAP-TTLS for user credential.
-            if (eapType != EAPConstants.EAP_TTLS) {
-                Log.d(TAG, "Invalid EAP Type for user credential: " + eapType);
+            if (mEapType != EAPConstants.EAP_TTLS) {
+                Log.d(TAG, "Invalid EAP Type for user credential: " + mEapType);
                 return false;
             }
 
             // Verify Non-EAP inner method for EAP-TTLS.
-            if (!SUPPORTED_AUTH.contains(nonEapInnerMethod)) {
-                Log.d(TAG, "Invalid non-EAP inner method for EAP-TTLS: " + nonEapInnerMethod);
+            if (!SUPPORTED_AUTH.contains(mNonEapInnerMethod)) {
+                Log.d(TAG, "Invalid non-EAP inner method for EAP-TTLS: " + mNonEapInnerMethod);
                 return false;
             }
             return true;
@@ -200,10 +341,13 @@
                 @Override
                 public UserCredential createFromParcel(Parcel in) {
                     UserCredential userCredential = new UserCredential();
-                    userCredential.username = in.readString();
-                    userCredential.password = in.readString();
-                    userCredential.eapType = in.readInt();
-                    userCredential.nonEapInnerMethod = in.readString();
+                    userCredential.setUsername(in.readString());
+                    userCredential.setPassword(in.readString());
+                    userCredential.setMachineManaged(in.readInt() != 0);
+                    userCredential.setSoftTokenApp(in.readString());
+                    userCredential.setAbleToShare(in.readInt() != 0);
+                    userCredential.setEapType(in.readInt());
+                    userCredential.setNonEapInnerMethod(in.readString());
                     return userCredential;
                 }
 
@@ -213,7 +357,13 @@
                 }
             };
     }
-    public UserCredential userCredential = null;
+    private UserCredential mUserCredential = null;
+    public void setUserCredential(UserCredential userCredential) {
+        mUserCredential = userCredential;
+    }
+    public UserCredential getUserCredential() {
+        return mUserCredential;
+    }
 
     /**
      * Certificate based credential.  This is used for EAP-TLS.
@@ -222,8 +372,9 @@
     public static final class CertificateCredential implements Parcelable {
         /**
          * Supported certificate types.
+         * @hide
          */
-        private static final String CERT_TYPE_X509V3 = "x509v3";
+        public static final String CERT_TYPE_X509V3 = "x509v3";
 
         /**
          * Certificate SHA-256 fingerprint length.
@@ -233,12 +384,24 @@
         /**
          * Certificate type.
          */
-        public String certType = null;
+        private String mCertType = null;
+        public void setCertType(String certType) {
+            mCertType = certType;
+        }
+        public String getCertType() {
+            return mCertType;
+        }
 
         /**
          * The SHA-256 fingerprint of the certificate.
          */
-        public byte[] certSha256FingerPrint = null;
+        private byte[] mCertSha256Fingerprint = null;
+        public void setCertSha256Fingerprint(byte[] certSha256Fingerprint) {
+            mCertSha256Fingerprint = certSha256Fingerprint;
+        }
+        public byte[] getCertSha256Fingerprint() {
+            return mCertSha256Fingerprint;
+        }
 
         /**
          * Constructor for creating CertificateCredential with default values.
@@ -252,10 +415,10 @@
          */
         public CertificateCredential(CertificateCredential source) {
             if (source != null) {
-                certType = source.certType;
-                if (source.certSha256FingerPrint != null) {
-                    certSha256FingerPrint = Arrays.copyOf(source.certSha256FingerPrint,
-                                                          source.certSha256FingerPrint.length);
+                mCertType = source.mCertType;
+                if (source.mCertSha256Fingerprint != null) {
+                    mCertSha256Fingerprint = Arrays.copyOf(source.mCertSha256Fingerprint,
+                                                          source.mCertSha256Fingerprint.length);
                 }
             }
         }
@@ -267,8 +430,8 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(certType);
-            dest.writeByteArray(certSha256FingerPrint);
+            dest.writeString(mCertType);
+            dest.writeByteArray(mCertSha256Fingerprint);
         }
 
         @Override
@@ -281,22 +444,33 @@
             }
 
             CertificateCredential that = (CertificateCredential) thatObject;
-            return TextUtils.equals(certType, that.certType) &&
-                    Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+            return TextUtils.equals(mCertType, that.mCertType)
+                    && Arrays.equals(mCertSha256Fingerprint, that.mCertSha256Fingerprint);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mCertType, mCertSha256Fingerprint);
+        }
+
+        @Override
+        public String toString() {
+            return "CertificateType: " + mCertType + "\n";
         }
 
         /**
          * Validate the configuration data.
          *
          * @return true on success or false on failure
+         * @hide
          */
         public boolean validate() {
-            if (!TextUtils.equals(CERT_TYPE_X509V3, certType)) {
-                Log.d(TAG, "Unsupported certificate type: " + certType);
+            if (!TextUtils.equals(CERT_TYPE_X509V3, mCertType)) {
+                Log.d(TAG, "Unsupported certificate type: " + mCertType);
                 return false;
             }
-            if (certSha256FingerPrint == null ||
-                    certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+            if (mCertSha256Fingerprint == null
+                    || mCertSha256Fingerprint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
                 Log.d(TAG, "Invalid SHA-256 fingerprint");
                 return false;
             }
@@ -308,8 +482,8 @@
                 @Override
                 public CertificateCredential createFromParcel(Parcel in) {
                     CertificateCredential certCredential = new CertificateCredential();
-                    certCredential.certType = in.readString();
-                    certCredential.certSha256FingerPrint = in.createByteArray();
+                    certCredential.setCertType(in.readString());
+                    certCredential.setCertSha256Fingerprint(in.createByteArray());
                     return certCredential;
                 }
 
@@ -319,7 +493,13 @@
                 }
             };
     }
-    public CertificateCredential certCredential = null;
+    private CertificateCredential mCertCredential = null;
+    public void setCertCredential(CertificateCredential certCredential) {
+        mCertCredential = certCredential;
+    }
+    public CertificateCredential getCertCredential() {
+        return mCertCredential;
+    }
 
     /**
      * SIM (Subscriber Identify Module) based credential.
@@ -329,14 +509,20 @@
         /**
          * Maximum string length for IMSI.
          */
-        public static final int MAX_IMSI_LENGTH = 15;
+        private static final int MAX_IMSI_LENGTH = 15;
 
         /**
          * International Mobile Subscriber Identity, is used to identify the user
          * of a cellular network and is a unique identification associated with all
          * cellular networks
          */
-        public String imsi = null;
+        private String mImsi = null;
+        public void setImsi(String imsi) {
+            mImsi = imsi;
+        }
+        public String getImsi() {
+            return mImsi;
+        }
 
         /**
          * EAP (Extensible Authentication Protocol) method type for using SIM credential.
@@ -344,7 +530,13 @@
          * for valid values.
          * Using Integer.MIN_VALUE to indicate unset value.
          */
-        public int eapType = Integer.MIN_VALUE;
+        private int mEapType = Integer.MIN_VALUE;
+        public void setEapType(int eapType) {
+            mEapType = eapType;
+        }
+        public int getEapType() {
+            return mEapType;
+        }
 
         /**
          * Constructor for creating SimCredential with default values.
@@ -358,8 +550,8 @@
          */
         public SimCredential(SimCredential source) {
             if (source != null) {
-                imsi = source.imsi;
-                eapType = source.eapType;
+                mImsi = source.mImsi;
+                mEapType = source.mEapType;
             }
         }
 
@@ -378,20 +570,34 @@
             }
 
             SimCredential that = (SimCredential) thatObject;
-            return TextUtils.equals(imsi, that.imsi) &&
-                    eapType == that.eapType;
+            return TextUtils.equals(mImsi, that.mImsi)
+                    && mEapType == that.mEapType;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mImsi, mEapType);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("IMSI: ").append(mImsi).append("\n");
+            builder.append("EAPType: ").append(mEapType).append("\n");
+            return builder.toString();
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(imsi);
-            dest.writeInt(eapType);
+            dest.writeString(mImsi);
+            dest.writeInt(mEapType);
         }
 
         /**
          * Validate the configuration data.
          *
          * @return true on success or false on failure
+         * @hide
          */
         public boolean validate() {
             // Note: this only validate the format of IMSI string itself.  Additional verification
@@ -400,9 +606,9 @@
             if (!verifyImsi()) {
                 return false;
             }
-            if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA &&
-                    eapType != EAPConstants.EAP_AKA_PRIME) {
-                Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType);
+            if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA
+                    && mEapType != EAPConstants.EAP_AKA_PRIME) {
+                Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType);
                 return false;
             }
             return true;
@@ -413,8 +619,8 @@
                 @Override
                 public SimCredential createFromParcel(Parcel in) {
                     SimCredential simCredential = new SimCredential();
-                    simCredential.imsi = in.readString();
-                    simCredential.eapType = in.readInt();
+                    simCredential.setImsi(in.readString());
+                    simCredential.setEapType(in.readInt());
                     return simCredential;
                 }
 
@@ -432,51 +638,75 @@
          * @return true if IMSI is valid, false otherwise.
          */
         private boolean verifyImsi() {
-            if (TextUtils.isEmpty(imsi)) {
+            if (TextUtils.isEmpty(mImsi)) {
                 Log.d(TAG, "Missing IMSI");
                 return false;
             }
-            if (imsi.length() > MAX_IMSI_LENGTH) {
-                Log.d(TAG, "IMSI exceeding maximum length: " + imsi.length());
+            if (mImsi.length() > MAX_IMSI_LENGTH) {
+                Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length());
                 return false;
             }
 
             // Locate the first non-digit character.
             int nonDigit;
             char stopChar = '\0';
-            for (nonDigit = 0; nonDigit < imsi.length(); nonDigit++) {
-                stopChar = imsi.charAt(nonDigit);
+            for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) {
+                stopChar = mImsi.charAt(nonDigit);
                 if (stopChar < '0' || stopChar > '9') {
                     break;
                 }
             }
 
-            if (nonDigit == imsi.length()) {
+            if (nonDigit == mImsi.length()) {
                 return true;
             }
-            else if (nonDigit == imsi.length()-1 && stopChar == '*') {
+            else if (nonDigit == mImsi.length()-1 && stopChar == '*') {
                 // Prefix matching.
                 return true;
             }
             return false;
         }
     }
-    public SimCredential simCredential = null;
+    private SimCredential mSimCredential = null;
+    public void setSimCredential(SimCredential simCredential) {
+        mSimCredential = simCredential;
+    }
+    public SimCredential getSimCredential() {
+        return mSimCredential;
+    }
 
     /**
      * CA (Certificate Authority) X509 certificate.
      */
-    public X509Certificate caCertificate = null;
+    private X509Certificate mCaCertificate = null;
+    public void setCaCertificate(X509Certificate caCertificate) {
+        mCaCertificate = caCertificate;
+    }
+    public X509Certificate getCaCertificate() {
+        return mCaCertificate;
+    }
 
     /**
      * Client side X509 certificate chain.
      */
-    public X509Certificate[] clientCertificateChain = null;
+    private X509Certificate[] mClientCertificateChain = null;
+    public void setClientCertificateChain(X509Certificate[] certificateChain) {
+        mClientCertificateChain = certificateChain;
+    }
+    public X509Certificate[] getClientCertificateChain() {
+        return mClientCertificateChain;
+    }
 
     /**
      * Client side private key.
      */
-    public PrivateKey clientPrivateKey = null;
+    private PrivateKey mClientPrivateKey = null;
+    public void setClientPrivateKey(PrivateKey clientPrivateKey) {
+        mClientPrivateKey = clientPrivateKey;
+    }
+    public PrivateKey getClientPrivateKey() {
+        return mClientPrivateKey;
+    }
 
     /**
      * Constructor for creating Credential with default values.
@@ -490,22 +720,25 @@
      */
     public Credential(Credential source) {
         if (source != null) {
-            realm = source.realm;
-            if (source.userCredential != null) {
-                userCredential = new UserCredential(source.userCredential);
+            mCreationTimeInMs = source.mCreationTimeInMs;
+            mExpirationTimeInMs = source.mExpirationTimeInMs;
+            mRealm = source.mRealm;
+            mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus;
+            if (source.mUserCredential != null) {
+                mUserCredential = new UserCredential(source.mUserCredential);
             }
-            if (source.certCredential != null) {
-                certCredential = new CertificateCredential(source.certCredential);
+            if (source.mCertCredential != null) {
+                mCertCredential = new CertificateCredential(source.mCertCredential);
             }
-            if (source.simCredential != null) {
-                simCredential = new SimCredential(source.simCredential);
+            if (source.mSimCredential != null) {
+                mSimCredential = new SimCredential(source.mSimCredential);
             }
-            if (source.clientCertificateChain != null) {
-                clientCertificateChain = Arrays.copyOf(source.clientCertificateChain,
-                                                       source.clientCertificateChain.length);
+            if (source.mClientCertificateChain != null) {
+                mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
+                                                        source.mClientCertificateChain.length);
             }
-            caCertificate = source.caCertificate;
-            clientPrivateKey = source.clientPrivateKey;
+            mCaCertificate = source.mCaCertificate;
+            mClientPrivateKey = source.mClientPrivateKey;
         }
     }
 
@@ -516,13 +749,16 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(realm);
-        dest.writeParcelable(userCredential, flags);
-        dest.writeParcelable(certCredential, flags);
-        dest.writeParcelable(simCredential, flags);
-        ParcelUtil.writeCertificate(dest, caCertificate);
-        ParcelUtil.writeCertificates(dest, clientCertificateChain);
-        ParcelUtil.writePrivateKey(dest, clientPrivateKey);
+        dest.writeLong(mCreationTimeInMs);
+        dest.writeLong(mExpirationTimeInMs);
+        dest.writeString(mRealm);
+        dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0);
+        dest.writeParcelable(mUserCredential, flags);
+        dest.writeParcelable(mCertCredential, flags);
+        dest.writeParcelable(mSimCredential, flags);
+        ParcelUtil.writeCertificate(dest, mCaCertificate);
+        ParcelUtil.writeCertificates(dest, mClientCertificateChain);
+        ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
     }
 
     @Override
@@ -535,43 +771,82 @@
         }
 
         Credential that = (Credential) thatObject;
-        return TextUtils.equals(realm, that.realm) &&
-                (userCredential == null ? that.userCredential == null :
-                    userCredential.equals(that.userCredential)) &&
-                (certCredential == null ? that.certCredential == null :
-                    certCredential.equals(that.certCredential)) &&
-                (simCredential == null ? that.simCredential == null :
-                    simCredential.equals(that.simCredential)) &&
-                isX509CertificateEquals(caCertificate, that.caCertificate) &&
-                isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) &&
-                isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
+        return TextUtils.equals(mRealm, that.mRealm)
+                && mCreationTimeInMs == that.mCreationTimeInMs
+                && mExpirationTimeInMs == that.mExpirationTimeInMs
+                && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus
+                && (mUserCredential == null ? that.mUserCredential == null
+                    : mUserCredential.equals(that.mUserCredential))
+                && (mCertCredential == null ? that.mCertCredential == null
+                    : mCertCredential.equals(that.mCertCredential))
+                && (mSimCredential == null ? that.mSimCredential == null
+                    : mSimCredential.equals(that.mSimCredential))
+                && isX509CertificateEquals(mCaCertificate, that.mCaCertificate)
+                && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
+                && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRealm, mCreationTimeInMs, mExpirationTimeInMs,
+                mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
+                mCaCertificate, mClientCertificateChain, mClientPrivateKey);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Realm: ").append(mRealm).append("\n");
+        builder.append("CreationTime: ").append(mCreationTimeInMs != Long.MIN_VALUE
+                ? new Date(mCreationTimeInMs) : "Not specified").append("\n");
+        builder.append("ExpirationTime: ").append(mExpirationTimeInMs != Long.MIN_VALUE
+                ? new Date(mExpirationTimeInMs) : "Not specified").append("\n");
+        builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
+        if (mUserCredential != null) {
+            builder.append("UserCredential Begin ---\n");
+            builder.append(mUserCredential);
+            builder.append("UserCredential End ---\n");
+        }
+        if (mCertCredential != null) {
+            builder.append("CertificateCredential Begin ---\n");
+            builder.append(mCertCredential);
+            builder.append("CertificateCredential End ---\n");
+        }
+        if (mSimCredential != null) {
+            builder.append("SIMCredential Begin ---\n");
+            builder.append(mSimCredential);
+            builder.append("SIMCredential End ---\n");
+        }
+        return builder.toString();
     }
 
     /**
      * Validate the configuration data.
      *
      * @return true on success or false on failure
+     * @hide
      */
     public boolean validate() {
-        if (TextUtils.isEmpty(realm)) {
+        if (TextUtils.isEmpty(mRealm)) {
             Log.d(TAG, "Missing realm");
             return false;
         }
-        if (realm.length() > MAX_REALM_LENGTH) {
-            Log.d(TAG, "realm exceeding maximum length: " + realm.length());
+        if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+            Log.d(TAG, "realm exceeding maximum length: "
+                    + mRealm.getBytes(StandardCharsets.UTF_8).length);
             return false;
         }
 
         // Verify the credential.
-        if (userCredential != null) {
+        if (mUserCredential != null) {
             if (!verifyUserCredential()) {
                 return false;
             }
-        } else if (certCredential != null) {
+        } else if (mCertCredential != null) {
             if (!verifyCertCredential()) {
                 return false;
             }
-        } else if (simCredential != null) {
+        } else if (mSimCredential != null) {
             if (!verifySimCredential()) {
                 return false;
             }
@@ -588,13 +863,16 @@
             @Override
             public Credential createFromParcel(Parcel in) {
                 Credential credential = new Credential();
-                credential.realm = in.readString();
-                credential.userCredential = in.readParcelable(null);
-                credential.certCredential = in.readParcelable(null);
-                credential.simCredential = in.readParcelable(null);
-                credential.caCertificate = ParcelUtil.readCertificate(in);
-                credential.clientCertificateChain = ParcelUtil.readCertificates(in);
-                credential.clientPrivateKey = ParcelUtil.readPrivateKey(in);
+                credential.setCreationTimeInMs(in.readLong());
+                credential.setExpirationTimeInMs(in.readLong());
+                credential.setRealm(in.readString());
+                credential.setCheckAaaServerCertStatus(in.readInt() != 0);
+                credential.setUserCredential(in.readParcelable(null));
+                credential.setCertCredential(in.readParcelable(null));
+                credential.setSimCredential(in.readParcelable(null));
+                credential.setCaCertificate(ParcelUtil.readCertificate(in));
+                credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
+                credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
                 return credential;
             }
 
@@ -610,18 +888,18 @@
      * @return true if user credential is valid, false otherwise.
      */
     private boolean verifyUserCredential() {
-        if (userCredential == null) {
+        if (mUserCredential == null) {
             Log.d(TAG, "Missing user credential");
             return false;
         }
-        if (certCredential != null || simCredential != null) {
+        if (mCertCredential != null || mSimCredential != null) {
             Log.d(TAG, "Contained more than one type of credential");
             return false;
         }
-        if (!userCredential.validate()) {
+        if (!mUserCredential.validate()) {
             return false;
         }
-        if (caCertificate == null) {
+        if (mCaCertificate == null) {
             Log.d(TAG, "Missing CA Certificate for user credential");
             return false;
         }
@@ -635,32 +913,32 @@
      * @return true if certificate credential is valid, false otherwise.
      */
     private boolean verifyCertCredential() {
-        if (certCredential == null) {
+        if (mCertCredential == null) {
             Log.d(TAG, "Missing certificate credential");
             return false;
         }
-        if (userCredential != null || simCredential != null) {
+        if (mUserCredential != null || mSimCredential != null) {
             Log.d(TAG, "Contained more than one type of credential");
             return false;
         }
 
-        if (!certCredential.validate()) {
+        if (!mCertCredential.validate()) {
             return false;
         }
 
         // Verify required key and certificates for certificate credential.
-        if (caCertificate == null) {
+        if (mCaCertificate == null) {
             Log.d(TAG, "Missing CA Certificate for certificate credential");
             return false;
         }
-        if (clientPrivateKey == null) {
+        if (mClientPrivateKey == null) {
             Log.d(TAG, "Missing client private key for certificate credential");
             return false;
         }
         try {
             // Verify SHA-256 fingerprint for client certificate.
-            if (!verifySha256Fingerprint(clientCertificateChain,
-                    certCredential.certSha256FingerPrint)) {
+            if (!verifySha256Fingerprint(mClientCertificateChain,
+                    mCertCredential.getCertSha256Fingerprint())) {
                 Log.d(TAG, "SHA-256 fingerprint mismatch");
                 return false;
             }
@@ -678,15 +956,15 @@
      * @return true if SIM credential is valid, false otherwise.
      */
     private boolean verifySimCredential() {
-        if (simCredential == null) {
+        if (mSimCredential == null) {
             Log.d(TAG, "Missing SIM credential");
             return false;
         }
-        if (userCredential != null || certCredential != null) {
+        if (mUserCredential != null || mCertCredential != null) {
             Log.d(TAG, "Contained more than one type of credential");
             return false;
         }
-        return simCredential.validate();
+        return mSimCredential.validate();
     }
 
     private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
deleted file mode 100644
index d4a5792..0000000
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.hotspot2.pps;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.Arrays;
-
-/**
- * Class representing HomeSP subtree in PerProviderSubscription (PPS)
- * Management Object (MO) tree.
- *
- * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
- * Release 2 Technical Specification.
- *
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
- * @hide
- */
-public final class HomeSP implements Parcelable {
-    private static final String TAG = "HomeSP";
-
-    /**
-     * FQDN (Fully Qualified Domain Name) of this home service provider.
-     */
-    public String fqdn = null;
-
-    /**
-     * Friendly name of this home service provider.
-     */
-    public String friendlyName = null;
-
-    /**
-     * List of Organization Identifiers (OIs) identifying a roaming consortium of
-     * which this provider is a member.
-     */
-    public long[] roamingConsortiumOIs = null;
-
-    /**
-     * Constructor for creating HomeSP with default values.
-     */
-    public HomeSP() {}
-
-    /**
-     * Copy constructor.
-     *
-     * @param source The source to copy from
-     */
-    public HomeSP(HomeSP source) {
-        if (source != null) {
-            fqdn = source.fqdn;
-            friendlyName = source.friendlyName;
-            if (source.roamingConsortiumOIs != null) {
-                roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
-                                                     source.roamingConsortiumOIs.length);
-            }
-        }
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(fqdn);
-        dest.writeString(friendlyName);
-        dest.writeLongArray(roamingConsortiumOIs);
-    }
-
-    @Override
-    public boolean equals(Object thatObject) {
-        if (this == thatObject) {
-            return true;
-        }
-        if (!(thatObject instanceof HomeSP)) {
-            return false;
-        }
-        HomeSP that = (HomeSP) thatObject;
-
-        return TextUtils.equals(fqdn, that.fqdn) &&
-                TextUtils.equals(friendlyName, that.friendlyName) &&
-                Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs);
-    }
-
-    /**
-     * Validate HomeSP data.
-     *
-     * @return true on success or false on failure
-     */
-    public boolean validate() {
-        if (TextUtils.isEmpty(fqdn)) {
-            Log.d(TAG, "Missing FQDN");
-            return false;
-        }
-        if (TextUtils.isEmpty(friendlyName)) {
-            Log.d(TAG, "Missing friendly name");
-            return false;
-        }
-        return true;
-    }
-
-    public static final Creator<HomeSP> CREATOR =
-        new Creator<HomeSP>() {
-            @Override
-            public HomeSP createFromParcel(Parcel in) {
-                HomeSP homeSp = new HomeSP();
-                homeSp.fqdn = in.readString();
-                homeSp.friendlyName = in.readString();
-                homeSp.roamingConsortiumOIs = in.createLongArray();
-                return homeSp;
-            }
-
-            @Override
-            public HomeSP[] newArray(int size) {
-                return new HomeSP[size];
-            }
-        };
-}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.aidl
similarity index 96%
rename from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
rename to wifi/java/android/net/wifi/hotspot2/pps/HomeSp.aidl
index 62d5603..6d343bd 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.aidl
@@ -16,4 +16,4 @@
 
 package android.net.wifi.hotspot2.pps;
 
-parcelable HomeSP;
+parcelable HomeSp;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
new file mode 100644
index 0000000..9192ab0
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java
@@ -0,0 +1,353 @@
+/**
+ * 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.net.wifi.hotspot2.pps;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class representing HomeSP subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ */
+public final class HomeSp implements Parcelable {
+    private static final String TAG = "HomeSp";
+
+    /**
+     * Maximum number of bytes allowed for a SSID.
+     */
+    private static final int MAX_SSID_BYTES = 32;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
+     * FQDN (Fully Qualified Domain Name) of this home service provider.
+     */
+    private String mFqdn = null;
+    public void setFqdn(String fqdn) {
+        mFqdn = fqdn;
+    }
+    public String getFqdn() {
+        return mFqdn;
+    }
+
+    /**
+     * Friendly name of this home service provider.
+     */
+    private String mFriendlyName = null;
+    public void setFriendlyName(String friendlyName) {
+        mFriendlyName = friendlyName;
+    }
+    public String getFriendlyName() {
+        return mFriendlyName;
+    }
+
+    /**
+     * Icon URL of this home service provider.
+     */
+    private String mIconUrl = null;
+    public void setIconUrl(String iconUrl) {
+        mIconUrl = iconUrl;
+    }
+    public String getIconUrl() {
+        return mIconUrl;
+    }
+
+    /**
+     * <SSID, HESSID> duple of the networks that are consider home networks.
+     *
+     * According to the Section 9.1.2 of the Hotspot 2.0 Release 2 Technical Specification,
+     * all nodes in the PSS MO are encoded using UTF-8 unless stated otherwise.  Thus, the SSID
+     * string is assumed to be encoded using UTF-8.
+     */
+    private Map<String, Long> mHomeNetworkIds = null;
+    public void setHomeNetworkIds(Map<String, Long> homeNetworkIds) {
+        mHomeNetworkIds = homeNetworkIds;
+    }
+    public Map<String, Long> getHomeNetworkIds() {
+        return mHomeNetworkIds;
+    }
+
+    /**
+     * Used for determining if this provider is a member of a given Hotspot provider.
+     * Every Organization Identifiers (OIs) in this list are required to match an OI in the
+     * the Roaming Consortium advertised by a Hotspot, in order to consider this provider
+     * as a member of that Hotspot provider (e.g. successful authentication with such Hotspot
+     * is possible).
+     *
+     * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+     * (MO) tree for more detail.
+     */
+    private long[] mMatchAllOis = null;
+    public void setMatchAllOis(long[] matchAllOis) {
+        mMatchAllOis = matchAllOis;
+    }
+    public long[] getMatchAllOis() {
+        return mMatchAllOis;
+    }
+
+    /**
+     * Used for determining if this provider is a member of a given Hotspot provider.
+     * Matching of any Organization Identifiers (OIs) in this list with an OI in the
+     * Roaming Consortium advertised by a Hotspot, will consider this provider as a member
+     * of that Hotspot provider (e.g. successful authentication with such Hotspot
+     * is possible).
+     *
+     * {@link #mMatchAllOIs} will have precedence over this one, meaning this list will
+     * only be used for matching if {@link #mMatchAllOIs} is null or empty.
+     *
+     * Refer to HomeSP/HomeOIList subtree in PerProviderSubscription (PPS) Management Object
+     * (MO) tree for more detail.
+     */
+    private long[] mMatchAnyOis = null;
+    public void setMatchAnyOis(long[] matchAnyOis) {
+        mMatchAnyOis = matchAnyOis;
+    }
+    public long[] getMatchAnyOis() {
+        return mMatchAnyOis;
+    }
+
+    /**
+     * List of FQDN (Fully Qualified Domain Name) of partner providers.
+     * These providers should also be regarded as home Hotspot operators.
+     * This relationship is most likely achieved via a commercial agreement or
+     * operator merges between the providers.
+     */
+    private String[] mOtherHomePartners = null;
+    public void setOtherHomePartners(String[] otherHomePartners) {
+        mOtherHomePartners = otherHomePartners;
+    }
+    public String[] getOtherHomePartners() {
+        return mOtherHomePartners;
+    }
+
+    /**
+     * List of Organization Identifiers (OIs) identifying a roaming consortium of
+     * which this provider is a member.
+     */
+    private long[] mRoamingConsortiumOis = null;
+    public void setRoamingConsortiumOis(long[] roamingConsortiumOis) {
+        mRoamingConsortiumOis = roamingConsortiumOis;
+    }
+    public long[] getRoamingConsortiumOis() {
+        return mRoamingConsortiumOis;
+    }
+
+    /**
+     * Constructor for creating HomeSp with default values.
+     */
+    public HomeSp() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public HomeSp(HomeSp source) {
+        if (source == null) {
+            return;
+        }
+        mFqdn = source.mFqdn;
+        mFriendlyName = source.mFriendlyName;
+        mIconUrl = source.mIconUrl;
+        if (source.mHomeNetworkIds != null) {
+            mHomeNetworkIds = Collections.unmodifiableMap(source.mHomeNetworkIds);
+        }
+        if (source.mMatchAllOis != null) {
+            mMatchAllOis = Arrays.copyOf(source.mMatchAllOis, source.mMatchAllOis.length);
+        }
+        if (source.mMatchAnyOis != null) {
+            mMatchAnyOis = Arrays.copyOf(source.mMatchAnyOis, source.mMatchAnyOis.length);
+        }
+        if (source.mOtherHomePartners != null) {
+            mOtherHomePartners = Arrays.copyOf(source.mOtherHomePartners,
+                    source.mOtherHomePartners.length);
+        }
+        if (source.mRoamingConsortiumOis != null) {
+            mRoamingConsortiumOis = Arrays.copyOf(source.mRoamingConsortiumOis,
+                    source.mRoamingConsortiumOis.length);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mFqdn);
+        dest.writeString(mFriendlyName);
+        dest.writeString(mIconUrl);
+        writeHomeNetworkIds(dest, mHomeNetworkIds);
+        dest.writeLongArray(mMatchAllOis);
+        dest.writeLongArray(mMatchAnyOis);
+        dest.writeStringArray(mOtherHomePartners);
+        dest.writeLongArray(mRoamingConsortiumOis);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof HomeSp)) {
+            return false;
+        }
+        HomeSp that = (HomeSp) thatObject;
+
+        return TextUtils.equals(mFqdn, that.mFqdn)
+                && TextUtils.equals(mFriendlyName, that.mFriendlyName)
+                && TextUtils.equals(mIconUrl, that.mIconUrl)
+                && (mHomeNetworkIds == null ? that.mHomeNetworkIds == null
+                        : mHomeNetworkIds.equals(that.mHomeNetworkIds))
+                && Arrays.equals(mMatchAllOis, that.mMatchAllOis)
+                && Arrays.equals(mMatchAnyOis, that.mMatchAnyOis)
+                && Arrays.equals(mOtherHomePartners, that.mOtherHomePartners)
+                && Arrays.equals(mRoamingConsortiumOis, that.mRoamingConsortiumOis);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFqdn, mFriendlyName, mIconUrl, mHomeNetworkIds, mMatchAllOis,
+                mMatchAnyOis, mOtherHomePartners, mRoamingConsortiumOis);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("FQDN: ").append(mFqdn).append("\n");
+        builder.append("FriendlyName: ").append(mFriendlyName).append("\n");
+        builder.append("IconURL: ").append(mIconUrl).append("\n");
+        builder.append("HomeNetworkIDs: ").append(mHomeNetworkIds).append("\n");
+        builder.append("MatchAllOIs: ").append(mMatchAllOis).append("\n");
+        builder.append("MatchAnyOIs: ").append(mMatchAnyOis).append("\n");
+        builder.append("OtherHomePartners: ").append(mOtherHomePartners).append("\n");
+        builder.append("RoamingConsortiumOIs: ").append(mRoamingConsortiumOis).append("\n");
+        return builder.toString();
+    }
+
+    /**
+     * Validate HomeSp data.
+     *
+     * @return true on success or false on failure
+     * @hide
+     */
+    public boolean validate() {
+        if (TextUtils.isEmpty(mFqdn)) {
+            Log.d(TAG, "Missing FQDN");
+            return false;
+        }
+        if (TextUtils.isEmpty(mFriendlyName)) {
+            Log.d(TAG, "Missing friendly name");
+            return false;
+        }
+        // Verify SSIDs specified in the NetworkID
+        if (mHomeNetworkIds != null) {
+            for (Map.Entry<String, Long> entry : mHomeNetworkIds.entrySet()) {
+                if (entry.getKey() == null ||
+                        entry.getKey().getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+                    Log.d(TAG, "Invalid SSID in HomeNetworkIDs");
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static final Creator<HomeSp> CREATOR =
+        new Creator<HomeSp>() {
+            @Override
+            public HomeSp createFromParcel(Parcel in) {
+                HomeSp homeSp = new HomeSp();
+                homeSp.setFqdn(in.readString());
+                homeSp.setFriendlyName(in.readString());
+                homeSp.setIconUrl(in.readString());
+                homeSp.setHomeNetworkIds(readHomeNetworkIds(in));
+                homeSp.setMatchAllOis(in.createLongArray());
+                homeSp.setMatchAnyOis(in.createLongArray());
+                homeSp.setOtherHomePartners(in.createStringArray());
+                homeSp.setRoamingConsortiumOis(in.createLongArray());
+                return homeSp;
+            }
+
+            @Override
+            public HomeSp[] newArray(int size) {
+                return new HomeSp[size];
+            }
+
+            /**
+             * Helper function for reading a Home Network IDs map from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return Map of home network IDs
+             */
+            private Map<String, Long> readHomeNetworkIds(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<String, Long> networkIds = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    String key = in.readString();
+                    Long value = null;
+                    long readValue = in.readLong();
+                    if (readValue != NULL_VALUE) {
+                        value = Long.valueOf(readValue);
+                    }
+                    networkIds.put(key, value);
+                }
+                return networkIds;
+            }
+        };
+
+    /**
+     * Helper function for writing Home Network IDs map to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param networkIds The map of home network IDs
+     */
+    private static void writeHomeNetworkIds(Parcel dest, Map<String, Long> networkIds) {
+        if (networkIds == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(networkIds.size());
+        for (Map.Entry<String, Long> entry : networkIds.entrySet()) {
+            dest.writeString(entry.getKey());
+            if (entry.getValue() == null) {
+                dest.writeLong(NULL_VALUE);
+            } else {
+                dest.writeLong(entry.getValue());
+            }
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
similarity index 88%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
index 62d5603..e923f1f 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2016, The Android Open Source Project
+ * 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.
@@ -16,4 +16,4 @@
 
 package android.net.wifi.hotspot2.pps;
 
-parcelable HomeSP;
+parcelable Policy;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
new file mode 100644
index 0000000..1df70f8
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,574 @@
+/**
+ * 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.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class representing Policy subtree in PerProviderSubscription (PPS)
+ * Management Object (MO) tree.
+ *
+ * The Policy specifies additional criteria for Passpoint network selections, such as preferred
+ * roaming partner, minimum backhaul bandwidth, and etc. It also provides the meta data for
+ * updating the policy.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ */
+public final class Policy implements Parcelable {
+    private static final String TAG = "Policy";
+
+    /**
+     * Maximum number of SSIDs in the exclusion list.
+     */
+    private static final int MAX_EXCLUSION_SSIDS = 128;
+
+    /**
+     * Maximum byte for SSID.
+     */
+    private static final int MAX_SSID_BYTES = 32;
+
+    /**
+     * Maximum bytes for port string in {@link #requiredProtoPortMap}.
+     */
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Integer value used for indicating null value in the Parcel.
+     */
+    private static final int NULL_VALUE = -1;
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from home providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    private long mMinHomeDownlinkBandwidth = Long.MIN_VALUE;
+    public void setMinHomeDownlinkBandwidth(long minHomeDownlinkBandwidth) {
+        mMinHomeDownlinkBandwidth = minHomeDownlinkBandwidth;
+    }
+    public long getMinHomeDownlinkBandwidth() {
+        return mMinHomeDownlinkBandwidth;
+    }
+    private long mMinHomeUplinkBandwidth = Long.MIN_VALUE;
+    public void setMinHomeUplinkBandwidth(long minHomeUplinkBandwidth) {
+        mMinHomeUplinkBandwidth = minHomeUplinkBandwidth;
+    }
+    public long getMinHomeUplinkBandwidth() {
+        return mMinHomeUplinkBandwidth;
+    }
+
+    /**
+     * Minimum available downlink/uplink bandwidth (in kilobits per second) required when
+     * selecting a network from roaming providers.
+     *
+     * The bandwidth is calculated as the LinkSpeed * (1 – LinkLoad/255), where LinkSpeed
+     * and LinkLoad parameters are drawn from the WAN Metrics ANQP element at that hotspot.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    private long mMinRoamingDownlinkBandwidth = Long.MIN_VALUE;
+    public void setMinRoamingDownlinkBandwidth(long minRoamingDownlinkBandwidth) {
+        mMinRoamingDownlinkBandwidth = minRoamingDownlinkBandwidth;
+    }
+    public long getMinRoamingDownlinkBandwidth() {
+        return mMinRoamingDownlinkBandwidth;
+    }
+    private long mMinRoamingUplinkBandwidth = Long.MIN_VALUE;
+    public void setMinRoamingUplinkBandwidth(long minRoamingUplinkBandwidth) {
+        mMinRoamingUplinkBandwidth = minRoamingUplinkBandwidth;
+    }
+    public long getMinRoamingUplinkBandwidth() {
+        return mMinRoamingUplinkBandwidth;
+    }
+
+    /**
+     * List of SSIDs that are not preferred by the Home SP.
+     */
+    private String[] mExcludedSsidList = null;
+    public void setExcludedSsidList(String[] excludedSsidList) {
+        mExcludedSsidList = excludedSsidList;
+    }
+    public String[] getExcludedSsidList() {
+        return mExcludedSsidList;
+    }
+
+    /**
+     * List of IP protocol and port number required by one or more operator supported application.
+     * The port string contained one or more port numbers delimited by ",".
+     */
+    private Map<Integer, String> mRequiredProtoPortMap = null;
+    public void setRequiredProtoPortMap(Map<Integer, String> requiredProtoPortMap) {
+        mRequiredProtoPortMap = requiredProtoPortMap;
+    }
+    public Map<Integer, String> getRequiredProtoPortMap() {
+        return mRequiredProtoPortMap;
+    }
+
+    /**
+     * This specifies the maximum acceptable BSS load policy.  This is used to prevent device
+     * from joining an AP whose channel is overly congested with traffic.
+     * Using Integer.MIN_VALUE to indicate unset value.
+     */
+    private int mMaximumBssLoadValue = Integer.MIN_VALUE;
+    public void setMaximumBssLoadValue(int maximumBssLoadValue) {
+        mMaximumBssLoadValue = maximumBssLoadValue;
+    }
+    public int getMaximumBssLoadValue() {
+        return mMaximumBssLoadValue;
+    }
+
+    /**
+     * Policy associated with a roaming provider.  This specifies a priority associated
+     * with a roaming provider for given list of countries.
+     *
+     * Contains field under PerProviderSubscription/Policy/PreferredRoamingPartnerList.
+     */
+    public static final class RoamingPartner implements Parcelable {
+        /**
+         * FQDN of the roaming partner.
+         */
+        private String mFqdn = null;
+        public void setFqdn(String fqdn) {
+            mFqdn = fqdn;
+        }
+        public String getFqdn() {
+            return mFqdn;
+        }
+
+        /**
+         * Flag indicating the exact match of FQDN is required for FQDN matching.
+         *
+         * When this flag is set to false, sub-domain matching is used.  For example, when
+         * {@link #fqdn} s set to "example.com", "host.example.com" would be a match.
+         */
+        private boolean mFqdnExactMatch = false;
+        public void setFqdnExactMatch(boolean fqdnExactMatch) {
+            mFqdnExactMatch = fqdnExactMatch;
+        }
+        public boolean getFqdnExactMatch() {
+            return mFqdnExactMatch;
+        }
+
+        /**
+         * Priority associated with this roaming partner policy.
+         * Using Integer.MIN_VALUE to indicate unset value.
+         */
+        private int mPriority = Integer.MIN_VALUE;
+        public void setPriority(int priority) {
+            mPriority = priority;
+        }
+        public int getPriority() {
+            return mPriority;
+        }
+
+        /**
+         * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+         * character country strings or the country-independent value, "*".
+         */
+        private String mCountries = null;
+        public void setCountries(String countries) {
+            mCountries = countries;
+        }
+        public String getCountries() {
+            return mCountries;
+        }
+
+        public RoamingPartner() {}
+
+        public RoamingPartner(RoamingPartner source) {
+            if (source != null) {
+                mFqdn = source.mFqdn;
+                mFqdnExactMatch = source.mFqdnExactMatch;
+                mPriority = source.mPriority;
+                mCountries = source.mCountries;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mFqdn);
+            dest.writeInt(mFqdnExactMatch ? 1 : 0);
+            dest.writeInt(mPriority);
+            dest.writeString(mCountries);
+        }
+
+        @Override
+        public boolean equals(Object thatObject) {
+            if (this == thatObject) {
+                return true;
+            }
+            if (!(thatObject instanceof RoamingPartner)) {
+                return false;
+            }
+
+            RoamingPartner that = (RoamingPartner) thatObject;
+            return TextUtils.equals(mFqdn, that.mFqdn)
+                    && mFqdnExactMatch == that.mFqdnExactMatch
+                    && mPriority == that.mPriority
+                    && TextUtils.equals(mCountries, that.mCountries);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mFqdn, mFqdnExactMatch, mPriority, mCountries);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("FQDN: ").append(mFqdn).append("\n");
+            builder.append("ExactMatch: ").append("mFqdnExactMatch").append("\n");
+            builder.append("Priority: ").append(mPriority).append("\n");
+            builder.append("Countries: ").append(mCountries).append("\n");
+            return builder.toString();
+        }
+
+        /**
+         * Validate RoamingParnter data.
+         *
+         * @return true on success
+         * @hide
+         */
+        public boolean validate() {
+            if (TextUtils.isEmpty(mFqdn)) {
+                Log.d(TAG, "Missing FQDN");
+                return false;
+            }
+            if (TextUtils.isEmpty(mCountries)) {
+                Log.d(TAG, "Missing countries");
+                return false;
+            }
+            return true;
+        }
+
+        public static final Creator<RoamingPartner> CREATOR =
+            new Creator<RoamingPartner>() {
+                @Override
+                public RoamingPartner createFromParcel(Parcel in) {
+                    RoamingPartner roamingPartner = new RoamingPartner();
+                    roamingPartner.setFqdn(in.readString());
+                    roamingPartner.setFqdnExactMatch(in.readInt() != 0);
+                    roamingPartner.setPriority(in.readInt());
+                    roamingPartner.setCountries(in.readString());
+                    return roamingPartner;
+                }
+
+                @Override
+                public RoamingPartner[] newArray(int size) {
+                    return new RoamingPartner[size];
+                }
+            };
+    }
+    private List<RoamingPartner> mPreferredRoamingPartnerList = null;
+    public void setPreferredRoamingPartnerList(List<RoamingPartner> partnerList) {
+        mPreferredRoamingPartnerList = partnerList;
+    }
+    public List<RoamingPartner> getPreferredRoamingPartnerList() {
+        return mPreferredRoamingPartnerList;
+    }
+
+    /**
+     * Meta data used for policy update.
+     */
+    private UpdateParameter mPolicyUpdate = null;
+    public void setPolicyUpdate(UpdateParameter policyUpdate) {
+        mPolicyUpdate = policyUpdate;
+    }
+    public UpdateParameter getPolicyUpdate() {
+        return mPolicyUpdate;
+    }
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public Policy() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public Policy(Policy source) {
+        if (source == null) {
+            return;
+        }
+        mMinHomeDownlinkBandwidth = source.mMinHomeDownlinkBandwidth;
+        mMinHomeUplinkBandwidth = source.mMinHomeUplinkBandwidth;
+        mMinRoamingDownlinkBandwidth = source.mMinRoamingDownlinkBandwidth;
+        mMinRoamingUplinkBandwidth = source.mMinRoamingUplinkBandwidth;
+        mMaximumBssLoadValue = source.mMaximumBssLoadValue;
+        if (source.mExcludedSsidList != null) {
+            mExcludedSsidList = Arrays.copyOf(source.mExcludedSsidList,
+                    source.mExcludedSsidList.length);
+        }
+        if (source.mRequiredProtoPortMap != null) {
+            mRequiredProtoPortMap = Collections.unmodifiableMap(source.mRequiredProtoPortMap);
+        }
+        if (source.mPreferredRoamingPartnerList != null) {
+            mPreferredRoamingPartnerList = Collections.unmodifiableList(
+                    source.mPreferredRoamingPartnerList);
+        }
+        if (source.mPolicyUpdate != null) {
+            mPolicyUpdate = new UpdateParameter(source.mPolicyUpdate);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mMinHomeDownlinkBandwidth);
+        dest.writeLong(mMinHomeUplinkBandwidth);
+        dest.writeLong(mMinRoamingDownlinkBandwidth);
+        dest.writeLong(mMinRoamingUplinkBandwidth);
+        dest.writeStringArray(mExcludedSsidList);
+        writeProtoPortMap(dest, mRequiredProtoPortMap);
+        dest.writeInt(mMaximumBssLoadValue);
+        writeRoamingPartnerList(dest, flags, mPreferredRoamingPartnerList);
+        dest.writeParcelable(mPolicyUpdate, flags);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof Policy)) {
+            return false;
+        }
+        Policy that = (Policy) thatObject;
+
+        return mMinHomeDownlinkBandwidth == that.mMinHomeDownlinkBandwidth
+                && mMinHomeUplinkBandwidth == that.mMinHomeUplinkBandwidth
+                && mMinRoamingDownlinkBandwidth == that.mMinRoamingDownlinkBandwidth
+                && mMinRoamingUplinkBandwidth == that.mMinRoamingUplinkBandwidth
+                && Arrays.equals(mExcludedSsidList, that.mExcludedSsidList)
+                && (mRequiredProtoPortMap == null ? that.mRequiredProtoPortMap == null
+                        : mRequiredProtoPortMap.equals(that.mRequiredProtoPortMap))
+                && mMaximumBssLoadValue == that.mMaximumBssLoadValue
+                && (mPreferredRoamingPartnerList == null
+                        ? that.mPreferredRoamingPartnerList == null
+                        : mPreferredRoamingPartnerList.equals(that.mPreferredRoamingPartnerList))
+                && (mPolicyUpdate == null ? that.mPolicyUpdate == null
+                        : mPolicyUpdate.equals(that.mPolicyUpdate));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMinHomeDownlinkBandwidth, mMinHomeUplinkBandwidth,
+                mMinRoamingDownlinkBandwidth, mMinRoamingUplinkBandwidth, mExcludedSsidList,
+                mRequiredProtoPortMap, mMaximumBssLoadValue, mPreferredRoamingPartnerList,
+                mPolicyUpdate);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("MinHomeDownlinkBandwidth: ").append(mMinHomeDownlinkBandwidth)
+                .append("\n");
+        builder.append("MinHomeUplinkBandwidth: ").append(mMinHomeUplinkBandwidth).append("\n");
+        builder.append("MinRoamingDownlinkBandwidth: ").append(mMinRoamingDownlinkBandwidth)
+                .append("\n");
+        builder.append("MinRoamingUplinkBandwidth: ").append(mMinRoamingUplinkBandwidth)
+                .append("\n");
+        builder.append("ExcludedSSIDList: ").append(mExcludedSsidList).append("\n");
+        builder.append("RequiredProtoPortMap: ").append(mRequiredProtoPortMap).append("\n");
+        builder.append("MaximumBSSLoadValue: ").append(mMaximumBssLoadValue).append("\n");
+        builder.append("PreferredRoamingPartnerList: ").append(mPreferredRoamingPartnerList)
+                .append("\n");
+        if (mPolicyUpdate != null) {
+            builder.append("PolicyUpdate Begin ---\n");
+            builder.append(mPolicyUpdate);
+            builder.append("PolicyUpdate End ---\n");
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Validate Policy data.
+     *
+     * @return true on success
+     * @hide
+     */
+    public boolean validate() {
+        if (mPolicyUpdate == null) {
+            Log.d(TAG, "PolicyUpdate not specified");
+            return false;
+        }
+        if (!mPolicyUpdate.validate()) {
+            return false;
+        }
+
+        // Validate SSID exclusion list.
+        if (mExcludedSsidList != null) {
+            if (mExcludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+                Log.d(TAG, "SSID exclusion list size exceeded the max: "
+                        + mExcludedSsidList.length);
+                return false;
+            }
+            for (String ssid : mExcludedSsidList) {
+                if (ssid.getBytes(StandardCharsets.UTF_8).length > MAX_SSID_BYTES) {
+                    Log.d(TAG, "Invalid SSID: " + ssid);
+                    return false;
+                }
+            }
+        }
+        // Validate required protocol to port map.
+        if (mRequiredProtoPortMap != null) {
+            for (Map.Entry<Integer, String> entry : mRequiredProtoPortMap.entrySet()) {
+                String portNumber = entry.getValue();
+                if (portNumber.getBytes(StandardCharsets.UTF_8).length > MAX_PORT_STRING_BYTES) {
+                    Log.d(TAG, "PortNumber string bytes exceeded the max: " + portNumber);
+                    return false;
+                }
+            }
+        }
+        // Validate preferred roaming partner list.
+        if (mPreferredRoamingPartnerList != null) {
+            for (RoamingPartner partner : mPreferredRoamingPartnerList) {
+                if (!partner.validate()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public static final Creator<Policy> CREATOR =
+        new Creator<Policy>() {
+            @Override
+            public Policy createFromParcel(Parcel in) {
+                Policy policy = new Policy();
+                policy.setMinHomeDownlinkBandwidth(in.readLong());
+                policy.setMinHomeUplinkBandwidth(in.readLong());
+                policy.setMinRoamingDownlinkBandwidth(in.readLong());
+                policy.setMinRoamingUplinkBandwidth(in.readLong());
+                policy.setExcludedSsidList(in.createStringArray());
+                policy.setRequiredProtoPortMap(readProtoPortMap(in));
+                policy.setMaximumBssLoadValue(in.readInt());
+                policy.setPreferredRoamingPartnerList(readRoamingPartnerList(in));
+                policy.setPolicyUpdate(in.readParcelable(null));
+                return policy;
+            }
+
+            @Override
+            public Policy[] newArray(int size) {
+                return new Policy[size];
+            }
+
+            /**
+             * Helper function for reading IP Protocol to Port Number map from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return Map of IP protocol to port number
+             */
+            private Map<Integer, String> readProtoPortMap(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                Map<Integer, String> protoPortMap = new HashMap<>(size);
+                for (int i = 0; i < size; i++) {
+                    int key = in.readInt();
+                    String value = in.readString();
+                    protoPortMap.put(key, value);
+                }
+                return protoPortMap;
+            }
+
+            /**
+             * Helper function for reading roaming partner list from a Parcel.
+             *
+             * @param in The Parcel to read from
+             * @return List of roaming partners
+             */
+            private List<RoamingPartner> readRoamingPartnerList(Parcel in) {
+                int size = in.readInt();
+                if (size == NULL_VALUE) {
+                    return null;
+                }
+                List<RoamingPartner> partnerList = new ArrayList<>();
+                for (int i = 0; i < size; i++) {
+                    partnerList.add(in.readParcelable(null));
+                }
+                return partnerList;
+            }
+
+        };
+
+    /**
+     * Helper function for writing IP Protocol to Port Number map to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param protoPortMap The map to write
+     */
+    private static void writeProtoPortMap(Parcel dest, Map<Integer, String> protoPortMap) {
+        if (protoPortMap == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(protoPortMap.size());
+        for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
+            dest.writeInt(entry.getKey());
+            dest.writeString(entry.getValue());
+        }
+    }
+
+    /**
+     * Helper function for writing roaming partner list to a Parcel.
+     *
+     * @param dest The Parcel to write to
+     * @param flags The flag about how the object should be written
+     * @param partnerList The partner list to write
+     */
+    private static void writeRoamingPartnerList(Parcel dest, int flags,
+            List<RoamingPartner> partnerList) {
+        if (partnerList == null) {
+            dest.writeInt(NULL_VALUE);
+            return;
+        }
+        dest.writeInt(partnerList.size());
+        for (RoamingPartner partner : partnerList) {
+            dest.writeParcelable(partner, flags);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
similarity index 87%
copy from wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
copy to wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
index 62d5603..701db47 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.aidl
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2016, The Android Open Source Project
+ * 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.
@@ -16,4 +16,4 @@
 
 package android.net.wifi.hotspot2.pps;
 
-parcelable HomeSP;
+parcelable UpdateParameter;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
new file mode 100644
index 0000000..a7adfeb
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,370 @@
+/**
+ * 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.hotspot2.pps;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Class representing configuration parameters for subscription or policy update in
+ * PerProviderSubscription (PPS) Management Object (MO) tree.  This is used by both
+ * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
+ * subtree.
+ *
+ * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
+ * Release 2 Technical Specification.
+ */
+public final class UpdateParameter implements Parcelable {
+    private static final String TAG = "UpdateParameter";
+
+    /**
+     * Value indicating policy update is not applicable.  Thus, never check with policy server
+     * for updates.
+     */
+    public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
+
+    /**
+     * Valid string for UpdateMethod.
+     */
+    public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
+    public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
+
+    /**
+     * Valid string for Restriction.
+     */
+    public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
+    public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
+    public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URI_BYTES = 1023;
+
+    /**
+     * Maximum bytes for URI string.
+     */
+    private static final int MAX_URL_BYTES = 1023;
+
+    /**
+     * Maximum bytes for username.
+     */
+    private static final int MAX_USERNAME_BYTES = 63;
+
+    /**
+     * Maximum bytes for password.
+     */
+    private static final int MAX_PASSWORD_BYTES = 255;
+
+    /**
+     * Number of bytes for certificate SHA-256 fingerprint byte array.
+     */
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * This specifies how often the mobile device shall check with policy server for updates.
+     *
+     * Using Long.MIN_VALUE to indicate unset value.
+     */
+    private long mUpdateIntervalInMinutes = Long.MIN_VALUE;
+    public void setUpdateIntervalInMinutes(long updateIntervalInMinutes) {
+        mUpdateIntervalInMinutes = updateIntervalInMinutes;
+    }
+    public long getUpdateIntervalInMinutes() {
+        return mUpdateIntervalInMinutes;
+    }
+
+    /**
+     * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
+     * and "SPP-ClientInitiated".
+     */
+    private String mUpdateMethod = null;
+    public void setUpdateMethod(String updateMethod) {
+        mUpdateMethod = updateMethod;
+    }
+    public String getUpdateMethod() {
+        return mUpdateMethod;
+    }
+
+    /**
+     * This specifies the hotspots at which the subscription update is permitted.  Permitted
+     * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+     */
+    private String mRestriction = null;
+    public void setRestriction(String restriction) {
+        mRestriction = restriction;
+    }
+    public String getRestriction() {
+        return mRestriction;
+    }
+
+    /**
+     * The URI of the update server.
+     */
+    private String mServerUri = null;
+    public void setServerUri(String serverUri) {
+        mServerUri = serverUri;
+    }
+    public String getServerUri() {
+        return mServerUri;
+    }
+
+    /**
+     * Username used to authenticate with the policy server.
+     */
+    private String mUsername = null;
+    public void setUsername(String username) {
+        mUsername = username;
+    }
+    public String getUsername() {
+        return mUsername;
+    }
+
+    /**
+     * Base64 encoded password used to authenticate with the policy server.
+     */
+    private String mBase64EncodedPassword = null;
+    public void setBase64EncodedPassword(String password) {
+        mBase64EncodedPassword = password;
+    }
+    public String getBase64EncodedPassword() {
+        return mBase64EncodedPassword;
+    }
+
+    /**
+     * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
+     * policy server's identity.
+     */
+    private String mTrustRootCertUrl = null;
+    public void setTrustRootCertUrl(String trustRootCertUrl) {
+        mTrustRootCertUrl = trustRootCertUrl;
+    }
+    public String getTrustRootCertUrl() {
+        return mTrustRootCertUrl;
+    }
+
+    /**
+     * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+     */
+    private byte[] mTrustRootCertSha256Fingerprint = null;
+    public void setTrustRootCertSha256Fingerprint(byte[] fingerprint) {
+        mTrustRootCertSha256Fingerprint = fingerprint;
+    }
+    public byte[] getTrustRootCertSha256Fingerprint() {
+        return mTrustRootCertSha256Fingerprint;
+    }
+
+    /**
+     * Constructor for creating Policy with default values.
+     */
+    public UpdateParameter() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public UpdateParameter(UpdateParameter source) {
+        if (source == null) {
+            return;
+        }
+        mUpdateIntervalInMinutes = source.mUpdateIntervalInMinutes;
+        mUpdateMethod = source.mUpdateMethod;
+        mRestriction = source.mRestriction;
+        mServerUri = source.mServerUri;
+        mUsername = source.mUsername;
+        mBase64EncodedPassword = source.mBase64EncodedPassword;
+        mTrustRootCertUrl = source.mTrustRootCertUrl;
+        if (source.mTrustRootCertSha256Fingerprint != null) {
+            mTrustRootCertSha256Fingerprint = Arrays.copyOf(source.mTrustRootCertSha256Fingerprint,
+                    source.mTrustRootCertSha256Fingerprint.length);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(mUpdateIntervalInMinutes);
+        dest.writeString(mUpdateMethod);
+        dest.writeString(mRestriction);
+        dest.writeString(mServerUri);
+        dest.writeString(mUsername);
+        dest.writeString(mBase64EncodedPassword);
+        dest.writeString(mTrustRootCertUrl);
+        dest.writeByteArray(mTrustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof UpdateParameter)) {
+            return false;
+        }
+        UpdateParameter that = (UpdateParameter) thatObject;
+
+        return mUpdateIntervalInMinutes == that.mUpdateIntervalInMinutes
+                && TextUtils.equals(mUpdateMethod, that.mUpdateMethod)
+                && TextUtils.equals(mRestriction, that.mRestriction)
+                && TextUtils.equals(mServerUri, that.mServerUri)
+                && TextUtils.equals(mUsername, that.mUsername)
+                && TextUtils.equals(mBase64EncodedPassword, that.mBase64EncodedPassword)
+                && TextUtils.equals(mTrustRootCertUrl, that.mTrustRootCertUrl)
+                && Arrays.equals(mTrustRootCertSha256Fingerprint,
+                        that.mTrustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mUpdateIntervalInMinutes, mUpdateMethod, mRestriction, mServerUri,
+                mUsername, mBase64EncodedPassword, mTrustRootCertUrl,
+                mTrustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("UpdateInterval: ").append(mUpdateIntervalInMinutes).append("\n");
+        builder.append("UpdateMethod: ").append(mUpdateMethod).append("\n");
+        builder.append("Restriction: ").append(mRestriction).append("\n");
+        builder.append("ServerURI: ").append(mServerUri).append("\n");
+        builder.append("Username: ").append(mUsername).append("\n");
+        builder.append("TrustRootCertURL: ").append(mTrustRootCertUrl).append("\n");
+        return builder.toString();
+    }
+
+    /**
+     * Validate UpdateParameter data.
+     *
+     * @return true on success
+     * @hide
+     */
+    public boolean validate() {
+        if (mUpdateIntervalInMinutes == Long.MIN_VALUE) {
+            Log.d(TAG, "Update interval not specified");
+            return false;
+        }
+        // Update not applicable.
+        if (mUpdateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+            return true;
+        }
+
+        if (!TextUtils.equals(mUpdateMethod, UPDATE_METHOD_OMADM)
+                && !TextUtils.equals(mUpdateMethod, UPDATE_METHOD_SSP)) {
+            Log.d(TAG, "Unknown update method: " + mUpdateMethod);
+            return false;
+        }
+
+        if (!TextUtils.equals(mRestriction, UPDATE_RESTRICTION_HOMESP)
+                && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+                && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+            Log.d(TAG, "Unknown restriction: " + mRestriction);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(mServerUri)) {
+            Log.d(TAG, "Missing update server URI");
+            return false;
+        }
+        if (mServerUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+            Log.d(TAG, "URI bytes exceeded the max: "
+                    + mServerUri.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(mUsername)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+            Log.d(TAG, "Username bytes exceeded the max: "
+                    + mUsername.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(mBase64EncodedPassword)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+            Log.d(TAG, "Password bytes exceeded the max: "
+                    + mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+        try {
+            Base64.decode(mBase64EncodedPassword, Base64.DEFAULT);
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Invalid encoding for password: " + mBase64EncodedPassword);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(mTrustRootCertUrl)) {
+            Log.d(TAG, "Missing trust root certificate URL");
+            return false;
+        }
+        if (mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+            Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+                    + mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (mTrustRootCertSha256Fingerprint == null) {
+            Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+            return false;
+        }
+        if (mTrustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+            Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                    + mTrustRootCertSha256Fingerprint.length);
+            return false;
+        }
+        return true;
+    }
+
+    public static final Creator<UpdateParameter> CREATOR =
+        new Creator<UpdateParameter>() {
+            @Override
+            public UpdateParameter createFromParcel(Parcel in) {
+                UpdateParameter updateParam = new UpdateParameter();
+                updateParam.setUpdateIntervalInMinutes(in.readLong());
+                updateParam.setUpdateMethod(in.readString());
+                updateParam.setRestriction(in.readString());
+                updateParam.setServerUri(in.readString());
+                updateParam.setUsername(in.readString());
+                updateParam.setBase64EncodedPassword(in.readString());
+                updateParam.setTrustRootCertUrl(in.readString());
+                updateParam.setTrustRootCertSha256Fingerprint(in.createByteArray());
+                return updateParam;
+            }
+
+            @Override
+            public UpdateParameter[] newArray(int size) {
+                return new UpdateParameter[size];
+            }
+        };
+}
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64 b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
index 8c1eb08..995963d 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.base64
@@ -42,7 +42,7 @@
 a05sY25ScFptbGpZWFJsVkhsd1pUd3ZUbTlrWlU1aGJXVStDaUFnSUNBZ0lDQWdJQ0FnSUR4V1lX
 eDFaVDU0TlRBNWRqTThMMVpoCmJIVmxQZ29nSUNBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lD
 QWdJQ0FnUEU1dlpHVStDaUFnSUNBZ0lDQWdJQ0FnSUR4T2IyUmwKVG1GdFpUNURaWEowVTBoQk1q
-VTJSbWx1WjJWeVVISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
+VTJSbWx1WjJWeWNISnBiblE4TDA1dlpHVk9ZVzFsUGdvZ0lDQWdJQ0FnSUNBZ0lDQThWbUZzZFdV
 KwpNV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpGbU1XWXhaakZtTVdZeFpqRm1NV1l4WmpG
 bU1XWXhaakZtTVdZeFpqRm1NV1l4ClpqRm1NV1l4Wmp3dlZtRnNkV1UrQ2lBZ0lDQWdJQ0FnSUNB
 OEwwNXZaR1UrQ2lBZ0lDQWdJQ0FnUEM5T2IyUmxQZ29nSUNBZ0lDQWcKSUR4T2IyUmxQZ29nSUNB
diff --git a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
index 6d86dd5..3ddd09f 100644
--- a/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
+++ b/wifi/tests/assets/hsr1/HSR1ProfileWithCACert.conf
@@ -35,7 +35,7 @@
 aWZpY2F0ZTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9kZT4KICAgICAgICAgICAgPE5vZGVOYW1l
 PkNlcnRpZmljYXRlVHlwZTwvTm9kZU5hbWU+CiAgICAgICAgICAgIDxWYWx1ZT54NTA5djM8L1Zh
 bHVlPgogICAgICAgICAgPC9Ob2RlPgogICAgICAgICAgPE5vZGU+CiAgICAgICAgICAgIDxOb2Rl
-TmFtZT5DZXJ0U0hBMjU2RmluZ2VyUHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
+TmFtZT5DZXJ0U0hBMjU2RmluZ2VycHJpbnQ8L05vZGVOYW1lPgogICAgICAgICAgICA8VmFsdWU+
 MWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYxZjFmMWYx
 ZjFmMWYxZjwvVmFsdWU+CiAgICAgICAgICA8L05vZGU+CiAgICAgICAgPC9Ob2RlPgogICAgICAg
 IDxOb2RlPgogICAgICAgICAgPE5vZGVOYW1lPlNJTTwvTm9kZU5hbWU+CiAgICAgICAgICA8Tm9k
diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml
index 53d38ad..7f2d95d 100644
--- a/wifi/tests/assets/pps/PerProviderSubscription.xml
+++ b/wifi/tests/assets/pps/PerProviderSubscription.xml
@@ -8,6 +8,10 @@
       </Type>
     </RTProperties>
     <Node>
+      <NodeName>UpdateIdentifier</NodeName>
+      <Value>12</Value>
+    </Node>
+    <Node>
       <NodeName>i001</NodeName>
       <Node>
         <NodeName>HomeSP</NodeName>
@@ -23,14 +27,86 @@
           <NodeName>RoamingConsortiumOI</NodeName>
           <Value>112233,445566</Value>
         </Node>
+        <Node>
+          <NodeName>IconURL</NodeName>
+          <Value>icon.test.com</Value>
+        </Node>
+        <Node>
+          <NodeName>NetworkID</NodeName>
+          <Node>
+            <NodeName>n001</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>TestSSID</Value>
+            </Node>
+            <Node>
+              <NodeName>HESSID</NodeName>
+              <Value>12345678</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>n002</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>NullHESSID</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>HomeOIList</NodeName>
+          <Node>
+            <NodeName>h001</NodeName>
+            <Node>
+              <NodeName>HomeOI</NodeName>
+              <Value>11223344</Value>
+            </Node>
+            <Node>
+              <NodeName>HomeOIRequired</NodeName>
+              <Value>true</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>h002</NodeName>
+            <Node>
+              <NodeName>HomeOI</NodeName>
+              <Value>55667788</Value>
+            </Node>
+            <Node>
+              <NodeName>HomeOIRequired</NodeName>
+              <Value>false</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>OtherHomePartners</NodeName>
+          <Node>
+            <NodeName>o001</NodeName>
+            <Node>
+              <NodeName>FQDN</NodeName>
+              <Value>other.fqdn.com</Value>
+            </Node>
+          </Node>
+        </Node>
       </Node>
       <Node>
         <NodeName>Credential</NodeName>
         <Node>
+          <NodeName>CreationDate</NodeName>
+          <Value>2016-01-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>ExpirationDate</NodeName>
+          <Value>2016-02-01T10:00:00Z</Value>
+        </Node>
+        <Node>
           <NodeName>Realm</NodeName>
           <Value>shaken.stirred.com</Value>
         </Node>
         <Node>
+          <NodeName>CheckAAAServerCertStatus</NodeName>
+          <Value>true</Value>
+        </Node>
+        <Node>
           <NodeName>UsernamePassword</NodeName>
           <Node>
             <NodeName>Username</NodeName>
@@ -41,6 +117,18 @@
             <Value>Ym9uZDAwNw==</Value>
           </Node>
           <Node>
+            <NodeName>MachineManaged</NodeName>
+            <Value>true</Value>
+          </Node>
+          <Node>
+            <NodeName>SoftTokenApp</NodeName>
+            <Value>TestApp</Value>
+          </Node>
+          <Node>
+            <NodeName>AbleToShare</NodeName>
+            <Value>true</Value>
+          </Node>
+          <Node>
             <NodeName>EAPMethod</NodeName>
             <Node>
               <NodeName>EAPType</NodeName>
@@ -59,7 +147,7 @@
             <Value>x509v3</Value>
           </Node>
           <Node>
-            <NodeName>CertSHA256FingerPrint</NodeName>
+            <NodeName>CertSHA256Fingerprint</NodeName>
             <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
           </Node>
         </Node>
@@ -75,6 +163,237 @@
           </Node>
         </Node>
       </Node>
+      <Node>
+        <NodeName>Policy</NodeName>
+        <Node>
+          <NodeName>PreferredRoamingPartnerList</NodeName>
+          <Node>
+            <NodeName>p001</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test1.fqdn.com,exactMatch</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>127</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>us,fr</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>p002</NodeName>
+            <Node>
+              <NodeName>FQDN_Match</NodeName>
+              <Value>test2.fqdn.com,includeSubdomains</Value>
+            </Node>
+            <Node>
+              <NodeName>Priority</NodeName>
+              <Value>200</Value>
+            </Node>
+            <Node>
+              <NodeName>Country</NodeName>
+              <Value>*</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MinBackhaulThreshold</NodeName>
+          <Node>
+            <NodeName>m001</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>home</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>23412</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>9823</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>m002</NodeName>
+            <Node>
+              <NodeName>NetworkType</NodeName>
+              <Value>roaming</Value>
+            </Node>
+            <Node>
+              <NodeName>DLBandwidth</NodeName>
+              <Value>9271</Value>
+            </Node>
+            <Node>
+              <NodeName>ULBandwidth</NodeName>
+              <Value>2315</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>PolicyUpdate</NodeName>
+          <Node>
+            <NodeName>UpdateInterval</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UpdateMethod</NodeName>
+            <Value>OMA-DM-ClientInitiated</Value>
+          </Node>
+          <Node>
+            <NodeName>Restriction</NodeName>
+            <Value>HomeSP</Value>
+          </Node>
+          <Node>
+            <NodeName>URI</NodeName>
+            <Value>policy.update.com</Value>
+          </Node>
+          <Node>
+            <NodeName>UsernamePassword</NodeName>
+            <Node>
+              <NodeName>Username</NodeName>
+              <Value>updateUser</Value>
+            </Node>
+            <Node>
+              <NodeName>Password</NodeName>
+              <Value>updatePass</Value>
+            </Node>
+          </Node>
+          <Node>
+            <NodeName>TrustRoot</NodeName>
+            <Node>
+              <NodeName>CertURL</NodeName>
+              <Value>update.cert.com</Value>
+            </Node>
+            <Node>
+              <NodeName>CertSHA256Fingerprint</NodeName>
+              <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>SPExclusionList</NodeName>
+          <Node>
+            <NodeName>s001</NodeName>
+            <Node>
+              <NodeName>SSID</NodeName>
+              <Value>excludeSSID</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>RequiredProtoPortTuple</NodeName>
+          <Node>
+            <NodeName>r001</NodeName>
+            <Node>
+              <NodeName>IPProtocol</NodeName>
+              <Value>12</Value>
+            </Node>
+            <Node>
+              <NodeName>PortNumber</NodeName>
+              <Value>34,92,234</Value>
+            </Node>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>MaximumBSSLoadValue</NodeName>
+          <Value>23</Value>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>CredentialPriority</NodeName>
+        <Value>99</Value>
+      </Node>
+      <Node>
+        <NodeName>AAAServerTrustRoot</NodeName>
+        <Node>
+          <NodeName>a001</NodeName>
+          <Node>
+            <NodeName>CertURL</NodeName>
+            <Value>server1.trust.root.com</Value>
+          </Node>
+          <Node>
+            <NodeName>CertSHA256Fingerprint</NodeName>
+            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+          </Node>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>SubscriptionUpdate</NodeName>
+        <Node>
+          <NodeName>UpdateInterval</NodeName>
+          <Value>120</Value>
+        </Node>
+        <Node>
+          <NodeName>UpdateMethod</NodeName>
+          <Value>SSP-ClientInitiated</Value>
+        </Node>
+        <Node>
+          <NodeName>Restriction</NodeName>
+          <Value>RoamingPartner</Value>
+        </Node>
+        <Node>
+          <NodeName>URI</NodeName>
+          <Value>subscription.update.com</Value>
+        </Node>
+        <Node>
+          <NodeName>UsernamePassword</NodeName>
+          <Node>
+            <NodeName>Username</NodeName>
+            <Value>subscriptionUser</Value>
+          </Node>
+          <Node>
+            <NodeName>Password</NodeName>
+            <Value>subscriptionPass</Value>
+          </Node>
+        </Node>
+        <Node>
+          <NodeName>TrustRoot</NodeName>
+          <Node>
+            <NodeName>CertURL</NodeName>
+            <Value>subscription.update.cert.com</Value>
+          </Node>
+          <Node>
+            <NodeName>CertSHA256Fingerprint</NodeName>
+            <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
+          </Node>
+        </Node>
+      </Node>
+      <Node>
+        <NodeName>SubscriptionParameter</NodeName>
+        <Node>
+          <NodeName>CreationDate</NodeName>
+          <Value>2016-02-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>ExpirationDate</NodeName>
+          <Value>2016-03-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>TypeOfSubscription</NodeName>
+          <Value>Gold</Value>
+        </Node>
+        <Node>
+          <NodeName>UsageLimits</NodeName>
+          <Node>
+            <NodeName>DataLimit</NodeName>
+            <Value>921890</Value>
+          </Node>
+          <Node>
+            <NodeName>StartDate</NodeName>
+            <Value>2016-12-01T10:00:00Z</Value>
+          </Node>
+          <Node>
+            <NodeName>TimeLimit</NodeName>
+            <Value>120</Value>
+          </Node>
+          <Node>
+            <NodeName>UsageTimePeriod</NodeName>
+            <Value>99910</Value>
+          </Node>
+        </Node>
+      </Node>
     </Node>
   </Node>
 </MgmtTree>
diff --git a/wifi/tests/src/android/net/wifi/IconInfoTest.java b/wifi/tests/src/android/net/wifi/IconInfoTest.java
new file mode 100644
index 0000000..2fdb484
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/IconInfoTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.IconInfo;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.IconInfo}.
+ */
+@SmallTest
+public class IconInfoTest {
+    private static final String TEST_FILENAME = "testIcon";
+    private static final byte[] TEST_DATA = new byte[] {0x12, 0x23, 0x34, 0x45, 0x56, 0x67};
+
+    /**
+     * Verify parcel write and read consistency for the given {@link IconInfo}
+     *
+     * @param writeIcon the {@link IconInfo} to write and verify
+     * @throws Exception
+     */
+    private static void verifyParcel(IconInfo writeIcon) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeIcon.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        IconInfo readIcon = IconInfo.CREATOR.createFromParcel(parcel);
+        assertEquals(writeIcon, readIcon);
+    }
+
+    /**
+     * Verify parcel serialization for a {@link IconInfo} with null data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithNullData() throws Exception {
+        verifyParcel(new IconInfo(TEST_FILENAME, (byte[]) null));
+    }
+
+    /**
+     * Verify parcel serialization for a {@link IconInfo} with zero length data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithZeroLengthData() throws Exception {
+        verifyParcel(new IconInfo(TEST_FILENAME, new byte[0]));
+    }
+
+    /**
+     * Verify parcel serialization for a {@link IconInfo} with non-zero length data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithNonZeroLengthData() throws Exception {
+        verifyParcel(new IconInfo(TEST_FILENAME, TEST_DATA));
+    }
+
+    /**
+     * Verify parcel serialization for a {@link IconInfo} with a null filename.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithNullFilename() throws Exception {
+        verifyParcel(new IconInfo(null, TEST_DATA));
+    }
+
+    /**
+     * Verify the copy constructor with non-null filename and data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructor() throws Exception {
+        IconInfo source = new IconInfo(TEST_FILENAME, TEST_DATA);
+        assertEquals(source, new IconInfo(source));
+    }
+
+    /**
+     * Verify the copy constructor with null data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructorWithNullData() throws Exception {
+        IconInfo source = new IconInfo(TEST_FILENAME, (byte[]) null);
+        assertEquals(source, new IconInfo(source));
+    }
+
+    /**
+     * Verify the copy constructor with null file name.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructorWithNullFilename() throws Exception {
+        IconInfo source = new IconInfo(null, TEST_DATA);
+        assertEquals(source, new IconInfo(source));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 5f949747..632cfaf 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertEquals;
 
 import android.os.Parcel;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,4 +67,35 @@
 
         assertArrayEquals(bytes, rebytes);
     }
+
+    @Test
+    public void testNetworkSelectionStatusCopy() {
+        NetworkSelectionStatus networkSelectionStatus = new NetworkSelectionStatus();
+        networkSelectionStatus.setNotRecommended(true);
+
+        NetworkSelectionStatus copy = new NetworkSelectionStatus();
+        copy.copy(networkSelectionStatus);
+
+        assertEquals(networkSelectionStatus.isNotRecommended(), copy.isNotRecommended());
+    }
+
+    @Test
+    public void testNetworkSelectionStatusParcel() {
+        NetworkSelectionStatus networkSelectionStatus = new NetworkSelectionStatus();
+        networkSelectionStatus.setNotRecommended(true);
+
+        Parcel parcelW = Parcel.obtain();
+        networkSelectionStatus.writeToParcel(parcelW);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+
+        NetworkSelectionStatus copy = new NetworkSelectionStatus();
+        copy.readFromParcel(parcelR);
+
+        assertEquals(networkSelectionStatus.isNotRecommended(), copy.isNotRecommended());
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 0e503d5..d0aedbad 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -87,6 +87,70 @@
     }
 
     @Test
+    public void testSetClientKeyEntryWithNull() {
+        mEnterpriseConfig.setClientKeyEntry(null, null);
+        assertNull(mEnterpriseConfig.getClientCertificateChain());
+        assertNull(mEnterpriseConfig.getClientCertificate());
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
+        assertNull(mEnterpriseConfig.getClientCertificateChain());
+        assertNull(mEnterpriseConfig.getClientCertificate());
+
+        // Setting the client certificate to null should clear the existing chain.
+        PrivateKey clientKey = FakeKeys.RSA_KEY1;
+        X509Certificate clientCert0 = FakeKeys.CLIENT_CERT;
+        X509Certificate clientCert1 = FakeKeys.CA_CERT1;
+        mEnterpriseConfig.setClientKeyEntry(clientKey, clientCert0);
+        assertNotNull(mEnterpriseConfig.getClientCertificate());
+        mEnterpriseConfig.setClientKeyEntry(null, null);
+        assertNull(mEnterpriseConfig.getClientCertificate());
+        assertNull(mEnterpriseConfig.getClientCertificateChain());
+
+        // Setting the chain to null should clear the existing chain.
+        X509Certificate[] clientChain = new X509Certificate[] {clientCert0, clientCert1};
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        assertNotNull(mEnterpriseConfig.getClientCertificateChain());
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
+        assertNull(mEnterpriseConfig.getClientCertificate());
+        assertNull(mEnterpriseConfig.getClientCertificateChain());
+    }
+
+    @Test
+    public void testSetClientCertificateChain() {
+        PrivateKey clientKey = FakeKeys.RSA_KEY1;
+        X509Certificate cert0 = FakeKeys.CLIENT_CERT;
+        X509Certificate cert1 = FakeKeys.CA_CERT1;
+        X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
+        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
+        assertEquals(result.length, 2);
+        assertTrue(result[0] == cert0 && result[1] == cert1);
+        assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
+    }
+
+    private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
+        boolean exceptionThrown = false;
+        try {
+            PrivateKey clientKey = FakeKeys.RSA_KEY1;
+            mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+        } catch (IllegalArgumentException e) {
+            exceptionThrown = true;
+        }
+        return exceptionThrown;
+    }
+
+    @Test
+    public void testSetInvalidClientCertificateChain() {
+        X509Certificate clientCert = FakeKeys.CLIENT_CERT;
+        X509Certificate caCert = FakeKeys.CA_CERT1;
+        assertTrue("Invalid client certificate",
+                isClientCertificateChainInvalid(new X509Certificate[] {caCert, caCert}));
+        assertTrue("Invalid CA certificate",
+                isClientCertificateChainInvalid(new X509Certificate[] {clientCert, clientCert}));
+        assertTrue("Both certificates invalid",
+                isClientCertificateChainInvalid(new X509Certificate[] {caCert, clientCert}));
+    }
+
+    @Test
     public void testSaveSingleCaCertificateAlias() {
         final String alias = "single_alias 0";
         mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
@@ -237,6 +301,21 @@
         assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
     }
 
+    /** Verfies PEAP/SIM, PEAP/AKA, PEAP/AKA'. */
+    @Test
+    public void peapSimAkaAkaPrime() {
+        mEnterpriseConfig.setEapMethod(Eap.PEAP);
+        mEnterpriseConfig.setPhase2Method(Phase2.SIM);
+        assertEquals("PEAP", getSupplicantEapMethod());
+        assertEquals("\"auth=SIM\"", getSupplicantPhase2Method());
+
+        mEnterpriseConfig.setPhase2Method(Phase2.AKA);
+        assertEquals("\"auth=AKA\"", getSupplicantPhase2Method());
+
+        mEnterpriseConfig.setPhase2Method(Phase2.AKA_PRIME);
+        assertEquals("\"auth=AKA'\"", getSupplicantPhase2Method());
+    }
+
     /** Verfies that the copy constructor preseves the inner method information. */
     @Test
     public void copyConstructor() {
diff --git a/wifi/tests/src/android/net/wifi/WifiSsidTest.java b/wifi/tests/src/android/net/wifi/WifiSsidTest.java
new file mode 100644
index 0000000..c7bdb7b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiSsidTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiSsid}.
+ */
+public class WifiSsidTest {
+
+    private static final byte[] TEST_SSID =
+            new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    /**
+     * Check that createFromByteArray() works.
+     */
+    @Test
+    public void testCreateFromByteArray() {
+        WifiSsid wifiSsid = WifiSsid.createFromByteArray(TEST_SSID);
+        assertTrue(wifiSsid != null);
+        assertEquals(new String(TEST_SSID), wifiSsid.toString());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index a396d87..eceb365 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -100,26 +100,6 @@
      */
 
     /**
-     * Validate pass-through of enableUsage() API.
-     */
-    @Test
-    public void testEnableUsage() throws Exception {
-        mDut.enableUsage();
-
-        verify(mockAwareService).enableUsage();
-    }
-
-    /**
-     * Validate pass-through of disableUsage() API.
-     */
-    @Test
-    public void testDisableUsage() throws Exception {
-        mDut.disableUsage();
-
-        verify(mockAwareService).disableUsage();
-    }
-
-    /**
      * Validate pass-through of isUsageEnabled() API.
      */
     @Test
@@ -566,6 +546,12 @@
         collector.checkThat("mMasterPreference", 0,
                 equalTo(configRequest.mMasterPreference));
         collector.checkThat("mSupport5gBand", false, equalTo(configRequest.mSupport5gBand));
+        collector.checkThat("mDiscoveryWindowInterval.length", 2,
+                equalTo(configRequest.mDiscoveryWindowInterval.length));
+        collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+                equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]));
+        collector.checkThat("mDiscoveryWindowInterval[5Hz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+                equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]));
     }
 
     @Test
@@ -574,10 +560,12 @@
         final int clusterLow = 5;
         final int masterPreference = 55;
         final boolean supportBand5g = true;
+        final int dwWindow5GHz = 3;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
                 .setClusterLow(clusterLow).setMasterPreference(masterPreference)
                 .setSupport5gBand(supportBand5g)
+                .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwWindow5GHz)
                 .build();
 
         collector.checkThat("mClusterHigh", clusterHigh, equalTo(configRequest.mClusterHigh));
@@ -585,6 +573,12 @@
         collector.checkThat("mMasterPreference", masterPreference,
                 equalTo(configRequest.mMasterPreference));
         collector.checkThat("mSupport5gBand", supportBand5g, equalTo(configRequest.mSupport5gBand));
+        collector.checkThat("mDiscoveryWindowInterval.length", 2,
+                equalTo(configRequest.mDiscoveryWindowInterval.length));
+        collector.checkThat("mDiscoveryWindowInterval[2.4GHz]", ConfigRequest.DW_INTERVAL_NOT_INIT,
+                equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]));
+        collector.checkThat("mDiscoveryWindowInterval[5GHz]", dwWindow5GHz,
+                equalTo(configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]));
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -633,16 +627,44 @@
         new ConfigRequest.Builder().setClusterLow(100).setClusterHigh(5).build();
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderDwIntervalInvalidBand() {
+        new ConfigRequest.Builder().setDiscoveryWindowInterval(5, 1).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderDwIntervalInvalidValueZero() {
+        new ConfigRequest.Builder().setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ,
+                0).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderDwIntervalInvalidValueLarge() {
+        new ConfigRequest.Builder().setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ,
+                6).build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderDwIntervalInvalidValueLargeValidate() {
+        ConfigRequest cr = new ConfigRequest.Builder().build();
+        cr.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ] = 6;
+        cr.validate();
+    }
+
     @Test
     public void testConfigRequestParcel() {
         final int clusterHigh = 189;
         final int clusterLow = 25;
         final int masterPreference = 177;
         final boolean supportBand5g = true;
+        final int dwWindow24GHz = 1;
+        final int dwWindow5GHz = 5;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
                 .setClusterLow(clusterLow).setMasterPreference(masterPreference)
                 .setSupport5gBand(supportBand5g)
+                .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ, dwWindow24GHz)
+                .setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, dwWindow5GHz)
                 .build();
 
         Parcel parcelW = Parcel.obtain();
@@ -951,11 +973,12 @@
         final int sessionId = 123;
         final PeerHandle peerHandle = new PeerHandle(123412);
         final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
-        final String token = "Some arbitrary token string - can really be anything";
+        final byte[] pmk = "Some arbitrary byte array".getBytes();
+        final String passphrase = "A really bad password";
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final PublishConfig publishConfig = new PublishConfig.Builder().build();
 
-        String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+        String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
 
         ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
                 WifiAwareSession.class);
@@ -986,9 +1009,8 @@
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
 
-        // (3) request a network specifier from the session
-        String networkSpecifier = publishSession.getValue().createNetworkSpecifier(peerHandle,
-                token.getBytes());
+        // (3) request an open (unencrypted) network specifier from the session
+        String networkSpecifier = publishSession.getValue().createNetworkSpecifierOpen(peerHandle);
 
         // validate format
         JSONObject jsonObject = new JSONObject(networkSpecifier);
@@ -1000,8 +1022,39 @@
                 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("token", tokenB64,
-                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+        // (4) request an encrypted (PMK) network specifier from the session
+        networkSpecifier = 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)));
+
+        // (5) request an encrypted (Passphrase) network specifier from the session
+        networkSpecifier = 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)));
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);
@@ -1017,9 +1070,10 @@
         final ConfigRequest configRequest = new ConfigRequest.Builder().build();
         final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
         final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
-        final String token = "Some arbitrary token string - can really be anything";
+        final byte[] pmk = "Some arbitrary pmk data".getBytes();
+        final String passphrase = "A really bad password";
 
-        String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+        String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
 
         ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
                 WifiAwareSession.class);
@@ -1038,10 +1092,10 @@
         inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
         WifiAwareSession session = sessionCaptor.getValue();
 
-        /* (2) request a direct network specifier*/
-        String networkSpecifier = session.createNetworkSpecifier(role, someMac, token.getBytes());
+        // (2) request an open (unencrypted) direct network specifier
+        String networkSpecifier = session.createNetworkSpecifierOpen(role, someMac);
 
-        /* validate format*/
+        // validate format
         JSONObject jsonObject = new JSONObject(networkSpecifier);
         collector.checkThat("role", role,
                 equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
@@ -1050,8 +1104,36 @@
         collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
                 jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
                 false)));
-        collector.checkThat("token", tokenB64,
-                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+        // (3) request an encrypted (PMK) direct network specifier
+        networkSpecifier = 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)));
+
+        // (4) request an encrypted (Passphrase) direct network specifier
+        networkSpecifier = 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)));
 
         verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
                 mockPublishSession, mockRttListener);
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/ConfigBuilderTest.java b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
similarity index 76%
rename from wifi/tests/src/android/net/wifi/hotspot2/ConfigBuilderTest.java
rename to wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
index 6095929..56bb437 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/ConfigBuilderTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/ConfigParserTest.java
@@ -21,7 +21,7 @@
 
 import android.net.wifi.FakeKeys;
 import android.net.wifi.hotspot2.pps.Credential;
-import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.HomeSp;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.io.BufferedReader;
@@ -33,10 +33,10 @@
 import org.junit.Test;
 
 /**
- * Unit tests for {@link android.net.wifi.hotspot2.ConfigBuilder}.
+ * Unit tests for {@link android.net.wifi.hotspot2.ConfigParser}.
  */
 @SmallTest
-public class ConfigBuilderTest {
+public class ConfigParserTest {
     /**
      * Hotspot 2.0 Release 1 installation file that contains a Passpoint profile and a
      * CA (Certificate Authority) X.509 certificate {@link FakeKeys#CA_CERT0}.
@@ -83,27 +83,33 @@
         PasspointConfiguration config = new PasspointConfiguration();
 
         // HomeSP configuration.
-        config.homeSp = new HomeSP();
-        config.homeSp.friendlyName = "Century House";
-        config.homeSp.fqdn = "mi6.co.uk";
-        config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFriendlyName("Century House");
+        homeSp.setFqdn("mi6.co.uk");
+        homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+        config.setHomeSp(homeSp);
 
         // Credential configuration.
-        config.credential = new Credential();
-        config.credential.realm = "shaken.stirred.com";
-        config.credential.userCredential = new Credential.UserCredential();
-        config.credential.userCredential.username = "james";
-        config.credential.userCredential.password = "Ym9uZDAwNw==";
-        config.credential.userCredential.eapType = 21;
-        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
-        config.credential.certCredential = new Credential.CertificateCredential();
-        config.credential.certCredential.certType = "x509v3";
-        config.credential.certCredential.certSha256FingerPrint = new byte[32];
-        Arrays.fill(config.credential.certCredential.certSha256FingerPrint, (byte)0x1f);
-        config.credential.simCredential = new Credential.SimCredential();
-        config.credential.simCredential.imsi = "imsi";
-        config.credential.simCredential.eapType = 24;
-        config.credential.caCertificate = FakeKeys.CA_CERT0;
+        Credential credential = new Credential();
+        credential.setRealm("shaken.stirred.com");
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername("james");
+        userCredential.setPassword("Ym9uZDAwNw==");
+        userCredential.setEapType(21);
+        userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+        credential.setUserCredential(userCredential);
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType("x509v3");
+        byte[] certSha256Fingerprint = new byte[32];
+        Arrays.fill(certSha256Fingerprint, (byte)0x1f);
+        certCredential.setCertSha256Fingerprint(certSha256Fingerprint);
+        credential.setCertCredential(certCredential);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi("imsi");
+        simCredential.setEapType(24);
+        credential.setSimCredential(simCredential);
+        credential.setCaCertificate(FakeKeys.CA_CERT0);
+        config.setCredential(credential);
         return config;
     }
 
@@ -117,7 +123,7 @@
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT);
         PasspointConfiguration expectedConfig = generateConfigurationFromProfile();
         PasspointConfiguration actualConfig =
-                ConfigBuilder.buildPasspointConfig(
+                ConfigParser.parsePasspointConfig(
                         "application/x-wifi-config", configStr.getBytes());
         assertTrue(actualConfig.equals(expectedConfig));
     }
@@ -130,7 +136,7 @@
     @Test
     public void parseConfigFileWithInvalidMimeType() throws Exception {
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_CA_CERT);
-        assertNull(ConfigBuilder.buildPasspointConfig(
+        assertNull(ConfigParser.parsePasspointConfig(
                 "application/wifi-config", configStr.getBytes()));
     }
 
@@ -142,7 +148,7 @@
     @Test
     public void parseConfigFileWithUnencodedData() throws Exception {
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_UNENCODED_DATA);
-        assertNull(ConfigBuilder.buildPasspointConfig(
+        assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
 
@@ -154,7 +160,7 @@
     @Test
     public void parseConfigFileWithInvalidPart() throws Exception {
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_INVALID_PART);
-        assertNull(ConfigBuilder.buildPasspointConfig(
+        assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
 
@@ -166,7 +172,7 @@
     @Test
     public void parseConfigFileWithMissingBoundary() throws Exception {
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_MISSING_BOUNDARY);
-        assertNull(ConfigBuilder.buildPasspointConfig(
+        assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
 
@@ -179,7 +185,7 @@
     @Test
     public void parseConfigFileWithInvalidContentType() throws Exception {
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITH_INVALID_CONTENT_TYPE);
-        assertNull(ConfigBuilder.buildPasspointConfig(
+        assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
 
@@ -191,7 +197,7 @@
     @Test
     public void parseConfigFileWithoutPasspointProfile() throws Exception {
         String configStr = loadResourceFile(PASSPOINT_INSTALLATION_FILE_WITHOUT_PROFILE);
-        assertNull(ConfigBuilder.buildPasspointConfig(
+        assertNull(ConfigParser.parsePasspointConfig(
                 "application/x-wifi-config", configStr.getBytes()));
     }
 }
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 2350d32..7df4fcf 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -21,28 +21,40 @@
 
 import android.net.wifi.EAPConstants;
 import android.net.wifi.hotspot2.pps.Credential;
-import android.net.wifi.hotspot2.pps.HomeSP;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
 
 import org.junit.Test;
 
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Unit tests for {@link android.net.wifi.hotspot2.PasspointConfiguration}.
  */
 @SmallTest
 public class PasspointConfigurationTest {
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int CERTIFICATE_FINGERPRINT_BYTES = 32;
 
     /**
      * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
      *
      * @return {@link android.net.wifi.hotspot2.pps.HomeSP}
      */
-    private static HomeSP createHomeSp() {
-        HomeSP homeSp = new HomeSP();
-        homeSp.fqdn = "fqdn";
-        homeSp.friendlyName = "friendly name";
-        homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+    private static HomeSp createHomeSp() {
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("friendly name");
+        homeSp.setRoamingConsortiumOis(new long[] {0x55, 0x66});
         return homeSp;
     }
 
@@ -53,19 +65,110 @@
      */
     private static Credential createCredential() {
         Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = null;
-        cred.certCredential = null;
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "1234*";
-        cred.simCredential.eapType = EAPConstants.EAP_SIM;
-        cred.caCertificate = null;
-        cred.clientCertificateChain = null;
-        cred.clientPrivateKey = null;
+        cred.setRealm("realm");
+        cred.setUserCredential(null);
+        cred.setCertCredential(null);
+        cred.setSimCredential(new Credential.SimCredential());
+        cred.getSimCredential().setImsi("1234*");
+        cred.getSimCredential().setEapType(EAPConstants.EAP_SIM);
+        cred.setCaCertificate(null);
+        cred.setClientCertificateChain(null);
+        cred.setClientPrivateKey(null);
         return cred;
     }
 
     /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.setMinHomeDownlinkBandwidth(123);
+        policy.setMinHomeUplinkBandwidth(345);
+        policy.setMinRoamingDownlinkBandwidth(567);
+        policy.setMinRoamingUplinkBandwidth(789);
+        policy.setMaximumBssLoadValue(12);
+        policy.setExcludedSsidList(new String[] {"ssid1", "ssid2"});
+        HashMap<Integer, String> requiredProtoPortMap = new HashMap<>();
+        requiredProtoPortMap.put(12, "23,342,123");
+        requiredProtoPortMap.put(23, "789,372,1235");
+        policy.setRequiredProtoPortMap(requiredProtoPortMap);
+
+        List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.setFqdn("partner1.com");
+        partner1.setFqdnExactMatch(true);
+        partner1.setPriority(12);
+        partner1.setCountries("us,jp");
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.setFqdn("partner2.com");
+        partner2.setFqdnExactMatch(false);
+        partner2.setPriority(42);
+        partner2.setCountries("ca,fr");
+        preferredRoamingPartnerList.add(partner1);
+        preferredRoamingPartnerList.add(partner2);
+        policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+
+        UpdateParameter policyUpdate = new UpdateParameter();
+        policyUpdate.setUpdateIntervalInMinutes(1712);
+        policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+        policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+        policyUpdate.setServerUri("policy.update.com");
+        policyUpdate.setUsername("username");
+        policyUpdate.setBase64EncodedPassword(
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT));
+        policyUpdate.setTrustRootCertUrl("trust.cert.com");
+        policyUpdate.setTrustRootCertSha256Fingerprint(
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        policy.setPolicyUpdate(policyUpdate);
+
+        return policy;
+    }
+
+    private static UpdateParameter createSubscriptionUpdate() {
+        UpdateParameter subUpdate = new UpdateParameter();
+        subUpdate.setUpdateIntervalInMinutes(9021);
+        subUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
+        subUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
+        subUpdate.setServerUri("subscription.update.com");
+        subUpdate.setUsername("subUsername");
+        subUpdate.setBase64EncodedPassword(
+                Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT));
+        subUpdate.setTrustRootCertUrl("subscription.trust.cert.com");
+        subUpdate.setTrustRootCertSha256Fingerprint(new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        return subUpdate;
+    }
+    /**
+     * Helper function for creating a {@link PasspointConfiguration} for testing.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private static PasspointConfiguration createConfig() {
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setHomeSp(createHomeSp());
+        config.setCredential(createCredential());
+        config.setPolicy(createPolicy());
+        config.setSubscriptionUpdate(createSubscriptionUpdate());
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("trustRoot.cert1.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        trustRootCertList.put("trustRoot.cert2.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.setTrustRootCertList(trustRootCertList);
+        config.setUpdateIdentifier(1);
+        config.setCredentialPriority(120);
+        config.setSubscriptionCreationTimeInMs(231200);
+        config.setSubscriptionExpirationTimeInMs(2134232);
+        config.setSubscriptionType("Gold");
+        config.setUsageLimitUsageTimePeriodInMinutes(3600);
+        config.setUsageLimitStartTimeInMs(124214213);
+        config.setUsageLimitDataLimit(14121);
+        config.setUsageLimitTimeLimitInMinutes(78912);
+        return config;
+    }
+
+    /**
      * Verify parcel write and read consistency for the given configuration.
      *
      * @param writeConfig The configuration to verify
@@ -92,39 +195,73 @@
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+     * Verify parcel read/write for a configuration that contained the full configuration.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPAndCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void verifyParcelWithFullConfiguration() throws Exception {
+        verifyParcel(createConfig());
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain HomeSP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutHomeSP() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setHomeSp(null);
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only HomeSP.
+     * Verify parcel read/write for a configuration that doesn't contain Credential.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithHomeSPOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+    public void verifyParcelWithoutCredential() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setCredential(null);
         verifyParcel(config);
     }
 
     /**
-     * Verify parcel read/write for a configuration that contained only Credential.
+     * Verify parcel read/write for a configuration that doesn't contain Policy.
      *
      * @throws Exception
      */
     @Test
-    public void verifyParcelWithCredentialOnly() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+    public void verifyParcelWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setPolicy(null);
+        verifyParcel(config);
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain subscription update.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutSubscriptionUpdate() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setSubscriptionUpdate(null);
+        verifyParcel(config);
+    }
+
+    /**
+     * Verify parcel read/write for a configuration that doesn't contain trust root certificate
+     * list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutTrustRootCertList() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setTrustRootCertList(null);
         verifyParcel(config);
     }
 
@@ -140,43 +277,114 @@
     }
 
     /**
+     * Verify that a configuration contained all fields is valid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateFullConfig() throws Exception {
+        PasspointConfiguration config = createConfig();
+        assertTrue(config.validate());
+    }
+
+    /**
      * Verify that a configuration without Credential is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutCredential() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
+        PasspointConfiguration config = createConfig();
+        config.setCredential(null);
         assertFalse(config.validate());
     }
 
     /**
-     * Verify that a a configuration without HomeSP is invalid.
+     * Verify that a configuration without HomeSP is invalid.
      *
      * @throws Exception
      */
     @Test
     public void validateConfigWithoutHomeSp() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.credential = createCredential();
+        PasspointConfiguration config = createConfig();
+        config.setHomeSp(null);
         assertFalse(config.validate());
     }
 
     /**
-     * Verify a valid configuration.
+     * Verify that a configuration without Policy is valid, since Policy configurations
+     * are optional (applied for Hotspot 2.0 Release only).
      *
      * @throws Exception
      */
     @Test
-    public void validateValidConfig() throws Exception {
-        PasspointConfiguration config = new PasspointConfiguration();
-        config.homeSp = createHomeSp();
-        config.credential = createCredential();
+    public void validateConfigWithoutPolicy() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setPolicy(null);
         assertTrue(config.validate());
     }
 
     /**
+     * Verify that a configuration without subscription update is valid, since subscription
+     * update configurations are optional (applied for Hotspot 2.0 Release only).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithoutSubscriptionUpdate() throws Exception {
+        PasspointConfiguration config = createConfig();
+        config.setSubscriptionUpdate(null);
+        assertTrue(config.validate());
+    }
+
+    /**
+     * Verify that a configuration with a trust root certificate URL exceeding the max size
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithInvalidTrustRootCertUrl() throws Exception {
+        PasspointConfiguration config = createConfig();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.setTrustRootCertList(trustRootCertList);
+        assertFalse(config.validate());
+
+        trustRootCertList = new HashMap<>();
+        trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.setTrustRootCertList(trustRootCertList);
+        assertFalse(config.validate());
+    }
+
+    /**
+     * Verify that a configuration with an invalid trust root certificate fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateConfigWithInvalidTrustRootCertFingerprint() throws Exception {
+        PasspointConfiguration config = createConfig();
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+        config.setTrustRootCertList(trustRootCertList);
+        assertFalse(config.validate());
+
+        trustRootCertList = new HashMap<>();
+        trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+        config.setTrustRootCertList(trustRootCertList);
+        assertFalse(config.validate());
+
+        trustRootCertList = new HashMap<>();
+        trustRootCertList.put("test.cert.com", null);
+        config.setTrustRootCertList(trustRootCertList);
+        assertFalse(config.validate());
+    }
+
+    /**
      * Verify that copy constructor works when pass in a null source.
      *
      * @throws Exception
@@ -195,9 +403,7 @@
      */
     @Test
     public void validateCopyConstructorWithValidSource() throws Exception {
-        PasspointConfiguration sourceConfig = new PasspointConfiguration();
-        sourceConfig.homeSp = createHomeSp();
-        sourceConfig.credential = createCredential();
+        PasspointConfiguration sourceConfig = createConfig();
         PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
         assertTrue(copyConfig.equals(sourceConfig));
     }
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
deleted file mode 100644
index 10b0267..0000000
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.hotspot2.omadm;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.wifi.hotspot2.omadm.PPSMOParser;
-import android.net.wifi.hotspot2.PasspointConfiguration;
-import android.net.wifi.hotspot2.pps.Credential;
-import android.net.wifi.hotspot2.pps.HomeSP;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Test;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.Arrays;
-
-/**
- * Unit tests for {@link android.net.wifi.hotspot2.omadm.PPSMOParser}.
- */
-@SmallTest
-public class PPSMOParserTest {
-    private static final String VALID_PPS_MO_XML_FILE = "assets/pps/PerProviderSubscription.xml";
-    private static final String PPS_MO_XML_FILE_DUPLICATE_HOMESP =
-            "assets/pps/PerProviderSubscription_DuplicateHomeSP.xml";
-    private static final String PPS_MO_XML_FILE_DUPLICATE_VALUE =
-            "assets/pps/PerProviderSubscription_DuplicateValue.xml";
-    private static final String PPS_MO_XML_FILE_MISSING_VALUE =
-            "assets/pps/PerProviderSubscription_MissingValue.xml";
-    private static final String PPS_MO_XML_FILE_MISSING_NAME =
-            "assets/pps/PerProviderSubscription_MissingName.xml";
-    private static final String PPS_MO_XML_FILE_INVALID_NODE =
-            "assets/pps/PerProviderSubscription_InvalidNode.xml";
-    private static final String PPS_MO_XML_FILE_INVALID_NAME =
-            "assets/pps/PerProviderSubscription_InvalidName.xml";
-
-    /**
-     * Read the content of the given resource file into a String.
-     *
-     * @param filename String name of the file
-     * @return String
-     * @throws IOException
-     */
-    private String loadResourceFile(String filename) throws IOException {
-        InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
-        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
-        StringBuilder builder = new StringBuilder();
-        String line;
-        while ((line = reader.readLine()) != null) {
-            builder.append(line).append("\n");
-        }
-
-        return builder.toString();
-    }
-
-    /**
-     * Generate a {@link PasspointConfiguration} that matches the configuration specified in the
-     * XML file {@link #VALID_PPS_MO_XML_FILE}.
-     *
-     * @return {@link PasspointConfiguration}
-     */
-    private PasspointConfiguration generateConfigurationFromPPSMOTree() {
-        PasspointConfiguration config = new PasspointConfiguration();
-
-        // HomeSP configuration.
-        config.homeSp = new HomeSP();
-        config.homeSp.friendlyName = "Century House";
-        config.homeSp.fqdn = "mi6.co.uk";
-        config.homeSp.roamingConsortiumOIs = new long[] {0x112233L, 0x445566L};
-
-        // Credential configuration.
-        config.credential = new Credential();
-        config.credential.realm = "shaken.stirred.com";
-        config.credential.userCredential = new Credential.UserCredential();
-        config.credential.userCredential.username = "james";
-        config.credential.userCredential.password = "Ym9uZDAwNw==";
-        config.credential.userCredential.eapType = 21;
-        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
-        config.credential.certCredential = new Credential.CertificateCredential();
-        config.credential.certCredential.certType = "x509v3";
-        config.credential.certCredential.certSha256FingerPrint = new byte[32];
-        Arrays.fill(config.credential.certCredential.certSha256FingerPrint, (byte)0x1f);
-        config.credential.simCredential = new Credential.SimCredential();
-        config.credential.simCredential.imsi = "imsi";
-        config.credential.simCredential.eapType = 24;
-        return config;
-    }
-
-    /**
-     * Parse and verify all supported fields under PPS MO tree (currently only fields under
-     * HomeSP and Credential subtree).
-     *
-     * @throws Exception
-     */
-    @Test
-    public void parseValidPPSMOTree() throws Exception {
-        String ppsMoTree = loadResourceFile(VALID_PPS_MO_XML_FILE);
-        PasspointConfiguration expectedConfig = generateConfigurationFromPPSMOTree();
-        PasspointConfiguration actualConfig = PPSMOParser.parseMOText(ppsMoTree);
-        assertTrue(actualConfig.equals(expectedConfig));
-    }
-
-    @Test
-    public void parseNullPPSMOTree() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(null));
-    }
-
-    @Test
-    public void parseEmptyPPSMOTree() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(new String()));
-    }
-
-    @Test
-    public void parsePPSMOTreeWithDuplicateHomeSP() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(
-                loadResourceFile(PPS_MO_XML_FILE_DUPLICATE_HOMESP)));
-    }
-
-    @Test
-    public void parsePPSMOTreeWithDuplicateValue() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(
-                loadResourceFile(PPS_MO_XML_FILE_DUPLICATE_VALUE)));
-    }
-
-    @Test
-    public void parsePPSMOTreeWithMissingValue() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(
-                loadResourceFile(PPS_MO_XML_FILE_MISSING_VALUE)));
-    }
-
-    @Test
-    public void parsePPSMOTreeWithMissingName() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(
-                loadResourceFile(PPS_MO_XML_FILE_MISSING_NAME)));
-    }
-
-    @Test
-    public void parsePPSMOTreeWithInvalidNode() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(
-                loadResourceFile(PPS_MO_XML_FILE_INVALID_NODE)));
-    }
-
-    @Test
-    public void parsePPSMOTreeWithInvalidName() throws Exception {
-        assertEquals(null, PPSMOParser.parseMOText(
-                loadResourceFile(PPS_MO_XML_FILE_INVALID_NAME)));
-    }
-}
-
-
-
-
-
-
-
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java
new file mode 100644
index 0000000..7cd72f0
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PpsMoParserTest.java
@@ -0,0 +1,261 @@
+/*
+ * 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.net.wifi.hotspot2.omadm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.wifi.hotspot2.omadm.PpsMoParser;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSp;
+import android.net.wifi.hotspot2.pps.Policy;
+import android.net.wifi.hotspot2.pps.UpdateParameter;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.omadm.PpsMoParser}.
+ */
+@SmallTest
+public class PpsMoParserTest {
+    private static final String VALID_PPS_MO_XML_FILE = "assets/pps/PerProviderSubscription.xml";
+    private static final String PPS_MO_XML_FILE_DUPLICATE_HOMESP =
+            "assets/pps/PerProviderSubscription_DuplicateHomeSP.xml";
+    private static final String PPS_MO_XML_FILE_DUPLICATE_VALUE =
+            "assets/pps/PerProviderSubscription_DuplicateValue.xml";
+    private static final String PPS_MO_XML_FILE_MISSING_VALUE =
+            "assets/pps/PerProviderSubscription_MissingValue.xml";
+    private static final String PPS_MO_XML_FILE_MISSING_NAME =
+            "assets/pps/PerProviderSubscription_MissingName.xml";
+    private static final String PPS_MO_XML_FILE_INVALID_NODE =
+            "assets/pps/PerProviderSubscription_InvalidNode.xml";
+    private static final String PPS_MO_XML_FILE_INVALID_NAME =
+            "assets/pps/PerProviderSubscription_InvalidName.xml";
+
+    /**
+     * Read the content of the given resource file into a String.
+     *
+     * @param filename String name of the file
+     * @return String
+     * @throws IOException
+     */
+    private String loadResourceFile(String filename) throws IOException {
+        InputStream in = getClass().getClassLoader().getResourceAsStream(filename);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+        StringBuilder builder = new StringBuilder();
+        String line;
+        while ((line = reader.readLine()) != null) {
+            builder.append(line).append("\n");
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * Generate a {@link PasspointConfiguration} that matches the configuration specified in the
+     * XML file {@link #VALID_PPS_MO_XML_FILE}.
+     *
+     * @return {@link PasspointConfiguration}
+     */
+    private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+        byte[] certFingerprint = new byte[32];
+        Arrays.fill(certFingerprint, (byte) 0x1f);
+
+        PasspointConfiguration config = new PasspointConfiguration();
+        config.setUpdateIdentifier(12);
+        config.setCredentialPriority(99);
+
+        // AAA Server trust root.
+        Map<String, byte[]> trustRootCertList = new HashMap<>();
+        trustRootCertList.put("server1.trust.root.com", certFingerprint);
+        config.setTrustRootCertList(trustRootCertList);
+
+        // Subscription update.
+        UpdateParameter subscriptionUpdate = new UpdateParameter();
+        subscriptionUpdate.setUpdateIntervalInMinutes(120);
+        subscriptionUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_SSP);
+        subscriptionUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER);
+        subscriptionUpdate.setServerUri("subscription.update.com");
+        subscriptionUpdate.setUsername("subscriptionUser");
+        subscriptionUpdate.setBase64EncodedPassword("subscriptionPass");
+        subscriptionUpdate.setTrustRootCertUrl("subscription.update.cert.com");
+        subscriptionUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+        config.setSubscriptionUpdate(subscriptionUpdate);
+
+        // Subscription parameters.
+        config.setSubscriptionCreationTimeInMs(format.parse("2016-02-01T10:00:00Z").getTime());
+        config.setSubscriptionExpirationTimeInMs(format.parse("2016-03-01T10:00:00Z").getTime());
+        config.setSubscriptionType("Gold");
+        config.setUsageLimitDataLimit(921890);
+        config.setUsageLimitStartTimeInMs(format.parse("2016-12-01T10:00:00Z").getTime());
+        config.setUsageLimitTimeLimitInMinutes(120);
+        config.setUsageLimitUsageTimePeriodInMinutes(99910);
+
+        // HomeSP configuration.
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFriendlyName("Century House");
+        homeSp.setFqdn("mi6.co.uk");
+        homeSp.setRoamingConsortiumOis(new long[] {0x112233L, 0x445566L});
+        homeSp.setIconUrl("icon.test.com");
+        Map<String, Long> homeNetworkIds = new HashMap<>();
+        homeNetworkIds.put("TestSSID", 0x12345678L);
+        homeNetworkIds.put("NullHESSID", null);
+        homeSp.setHomeNetworkIds(homeNetworkIds);
+        homeSp.setMatchAllOis(new long[] {0x11223344});
+        homeSp.setMatchAnyOis(new long[] {0x55667788});
+        homeSp.setOtherHomePartners(new String[] {"other.fqdn.com"});
+        config.setHomeSp(homeSp);
+
+        // Credential configuration.
+        Credential credential = new Credential();
+        credential.setCreationTimeInMs(format.parse("2016-01-01T10:00:00Z").getTime());
+        credential.setExpirationTimeInMs(format.parse("2016-02-01T10:00:00Z").getTime());
+        credential.setRealm("shaken.stirred.com");
+        credential.setCheckAaaServerCertStatus(true);
+        Credential.UserCredential userCredential = new Credential.UserCredential();
+        userCredential.setUsername("james");
+        userCredential.setPassword("Ym9uZDAwNw==");
+        userCredential.setMachineManaged(true);
+        userCredential.setSoftTokenApp("TestApp");
+        userCredential.setAbleToShare(true);
+        userCredential.setEapType(21);
+        userCredential.setNonEapInnerMethod("MS-CHAP-V2");
+        credential.setUserCredential(userCredential);
+        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
+        certCredential.setCertType("x509v3");
+        certCredential.setCertSha256Fingerprint(certFingerprint);
+        credential.setCertCredential(certCredential);
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi("imsi");
+        simCredential.setEapType(24);
+        credential.setSimCredential(simCredential);
+        config.setCredential(credential);
+
+        // Policy configuration.
+        Policy policy = new Policy();
+        List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.setFqdn("test1.fqdn.com");
+        partner1.setFqdnExactMatch(true);
+        partner1.setPriority(127);
+        partner1.setCountries("us,fr");
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.setFqdn("test2.fqdn.com");
+        partner2.setFqdnExactMatch(false);
+        partner2.setPriority(200);
+        partner2.setCountries("*");
+        preferredRoamingPartnerList.add(partner1);
+        preferredRoamingPartnerList.add(partner2);
+        policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+        policy.setMinHomeDownlinkBandwidth(23412);
+        policy.setMinHomeUplinkBandwidth(9823);
+        policy.setMinRoamingDownlinkBandwidth(9271);
+        policy.setMinRoamingUplinkBandwidth(2315);
+        policy.setExcludedSsidList(new String[] {"excludeSSID"});
+        Map<Integer, String> requiredProtoPortMap = new HashMap<>();
+        requiredProtoPortMap.put(12, "34,92,234");
+        policy.setRequiredProtoPortMap(requiredProtoPortMap);
+        policy.setMaximumBssLoadValue(23);
+        UpdateParameter policyUpdate = new UpdateParameter();
+        policyUpdate.setUpdateIntervalInMinutes(120);
+        policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+        policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+        policyUpdate.setServerUri("policy.update.com");
+        policyUpdate.setUsername("updateUser");
+        policyUpdate.setBase64EncodedPassword("updatePass");
+        policyUpdate.setTrustRootCertUrl("update.cert.com");
+        policyUpdate.setTrustRootCertSha256Fingerprint(certFingerprint);
+        policy.setPolicyUpdate(policyUpdate);
+        config.setPolicy(policy);
+        return config;
+    }
+
+    /**
+     * Parse and verify all supported fields under PPS MO tree.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void parseValidPPSMOTree() throws Exception {
+        String ppsMoTree = loadResourceFile(VALID_PPS_MO_XML_FILE);
+        PasspointConfiguration expectedConfig = generateConfigurationFromPPSMOTree();
+        PasspointConfiguration actualConfig = PpsMoParser.parseMoText(ppsMoTree);
+        assertTrue(actualConfig.equals(expectedConfig));
+    }
+
+    @Test
+    public void parseNullPPSMOTree() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(null));
+    }
+
+    @Test
+    public void parseEmptyPPSMOTree() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(new String()));
+    }
+
+    @Test
+    public void parsePPSMOTreeWithDuplicateHomeSP() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(
+                loadResourceFile(PPS_MO_XML_FILE_DUPLICATE_HOMESP)));
+    }
+
+    @Test
+    public void parsePPSMOTreeWithDuplicateValue() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(
+                loadResourceFile(PPS_MO_XML_FILE_DUPLICATE_VALUE)));
+    }
+
+    @Test
+    public void parsePPSMOTreeWithMissingValue() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(
+                loadResourceFile(PPS_MO_XML_FILE_MISSING_VALUE)));
+    }
+
+    @Test
+    public void parsePPSMOTreeWithMissingName() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(
+                loadResourceFile(PPS_MO_XML_FILE_MISSING_NAME)));
+    }
+
+    @Test
+    public void parsePPSMOTreeWithInvalidNode() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(
+                loadResourceFile(PPS_MO_XML_FILE_INVALID_NODE)));
+    }
+
+    @Test
+    public void parsePPSMOTreeWithInvalidName() throws Exception {
+        assertEquals(null, PpsMoParser.parseMoText(
+                loadResourceFile(PPS_MO_XML_FILE_INVALID_NAME)));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 9c8b749..c7ade00 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -23,10 +23,11 @@
 import android.net.wifi.FakeKeys;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
+import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 
@@ -37,6 +38,17 @@
  */
 @SmallTest
 public class CredentialTest {
+    /**
+     * Helper function for generating Credential for testing.
+     *
+     * @param userCred Instance of UserCredential
+     * @param certCred Instance of CertificateCredential
+     * @param simCred Instance of SimCredential
+     * @param caCert CA certificate
+     * @param clientCertificateChain Chain of client certificates
+     * @param clientPrivateKey Client private key
+     * @return {@link Credential}
+     */
     private static Credential createCredential(Credential.UserCredential userCred,
                                                Credential.CertificateCredential certCred,
                                                Credential.SimCredential simCred,
@@ -44,39 +56,61 @@
                                                X509Certificate[] clientCertificateChain,
                                                PrivateKey clientPrivateKey) {
         Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = userCred;
-        cred.certCredential = certCred;
-        cred.simCredential = simCred;
-        cred.caCertificate = caCert;
-        cred.clientCertificateChain = clientCertificateChain;
-        cred.clientPrivateKey = clientPrivateKey;
+        cred.setCreationTimeInMs(123455L);
+        cred.setExpirationTimeInMs(2310093L);
+        cred.setRealm("realm");
+        cred.setCheckAaaServerCertStatus(true);
+        cred.setUserCredential(userCred);
+        cred.setCertCredential(certCred);
+        cred.setSimCredential(simCred);
+        cred.setCaCertificate(caCert);
+        cred.setClientCertificateChain(clientCertificateChain);
+        cred.setClientPrivateKey(clientPrivateKey);
         return cred;
     }
 
-    private static Credential createCredentialWithCertificateCredential() {
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
+    private static Credential createCredentialWithCertificateCredential()
+            throws NoSuchAlgorithmException, CertificateEncodingException {
         Credential.CertificateCredential certCred = new Credential.CertificateCredential();
-        certCred.certType = "x509v3";
-        certCred.certSha256FingerPrint = new byte[32];
+        certCred.setCertType("x509v3");
+        certCred.setCertSha256Fingerprint(
+                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
         return createCredential(null, certCred, null, FakeKeys.CA_CERT0,
                 new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
     }
 
+    /**
+     * Helper function for generating SIM credential for testing.
+     *
+     * @return {@link Credential}
+     */
     private static Credential createCredentialWithSimCredential() {
         Credential.SimCredential simCred = new Credential.SimCredential();
-        simCred.imsi = "1234*";
-        simCred.eapType = EAPConstants.EAP_SIM;
+        simCred.setImsi("1234*");
+        simCred.setEapType(EAPConstants.EAP_SIM);
         return createCredential(null, null, simCred, null, null, null);
     }
 
+    /**
+     * Helper function for generating user credential for testing.
+     *
+     * @return {@link Credential}
+     */
     private static Credential createCredentialWithUserCredential() {
         Credential.UserCredential userCred = new Credential.UserCredential();
-        userCred.username = "username";
-        userCred.password = "password";
-        userCred.eapType = EAPConstants.EAP_TTLS;
-        userCred.nonEapInnerMethod = "MS-CHAP";
-        return createCredential(userCred, null, null, FakeKeys.CA_CERT0,
-                new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
+        userCred.setUsername("username");
+        userCred.setPassword("password");
+        userCred.setMachineManaged(true);
+        userCred.setAbleToShare(true);
+        userCred.setSoftTokenApp("TestApp");
+        userCred.setEapType(EAPConstants.EAP_TTLS);
+        userCred.setNonEapInnerMethod("MS-CHAP");
+        return createCredential(userCred, null, null, FakeKeys.CA_CERT0, null, null);
     }
 
     private static void verifyParcel(Credential writeCred) {
@@ -134,14 +168,7 @@
      */
     @Test
     public void validateUserCredential() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
         assertTrue(cred.validate());
     }
 
@@ -152,13 +179,8 @@
      */
     @Test
     public void validateUserCredentialWithoutCaCert() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
+        Credential cred = createCredentialWithUserCredential();
+        cred.setCaCertificate(null);
         assertFalse(cred.validate());
     }
 
@@ -169,14 +191,8 @@
      */
     @Test
     public void validateUserCredentialWithEapTls() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
+        cred.getUserCredential().setEapType(EAPConstants.EAP_TLS);
         assertFalse(cred.validate());
     }
 
@@ -188,13 +204,8 @@
      */
     @Test
     public void validateUserCredentialWithoutRealm() throws Exception {
-        Credential cred = new Credential();
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
+        cred.setRealm(null);
         assertFalse(cred.validate());
     }
 
@@ -205,13 +216,8 @@
      */
     @Test
     public void validateUserCredentialWithoutUsername() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
+        cred.getUserCredential().setUsername(null);
         assertFalse(cred.validate());
     }
 
@@ -222,13 +228,8 @@
      */
     @Test
     public void validateUserCredentialWithoutPassword() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
+        cred.getUserCredential().setPassword(null);
         assertFalse(cred.validate());
     }
 
@@ -239,13 +240,8 @@
      */
     @Test
     public void validateUserCredentialWithoutAuthMethod() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
+        cred.getUserCredential().setNonEapInnerMethod(null);
         assertFalse(cred.validate());
     }
 
@@ -258,17 +254,7 @@
      */
     @Test
     public void validateCertCredential() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup certificate credential.
-        cred.certCredential = new Credential.CertificateCredential();
-        cred.certCredential.certType = "x509v3";
-        cred.certCredential.certSha256FingerPrint =
-                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
-        // Setup certificates and private key.
-        cred.caCertificate = FakeKeys.CA_CERT0;
-        cred.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
-        cred.clientPrivateKey = FakeKeys.RSA_KEY1;
+        Credential cred = createCredentialWithCertificateCredential();
         assertTrue(cred.validate());
     }
 
@@ -278,16 +264,8 @@
      * @throws Exception
      */
     public void validateCertCredentialWithoutCaCert() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup certificate credential.
-        cred.certCredential = new Credential.CertificateCredential();
-        cred.certCredential.certType = "x509v3";
-        cred.certCredential.certSha256FingerPrint =
-                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
-        // Setup certificates and private key.
-        cred.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
-        cred.clientPrivateKey = FakeKeys.RSA_KEY1;
+        Credential cred = createCredentialWithCertificateCredential();
+        cred.setCaCertificate(null);
         assertFalse(cred.validate());
     }
 
@@ -298,16 +276,8 @@
      */
     @Test
     public void validateCertCredentialWithoutClientCertChain() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup certificate credential.
-        cred.certCredential = new Credential.CertificateCredential();
-        cred.certCredential.certType = "x509v3";
-        cred.certCredential.certSha256FingerPrint =
-                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
-        // Setup certificates and private key.
-        cred.caCertificate = FakeKeys.CA_CERT0;
-        cred.clientPrivateKey = FakeKeys.RSA_KEY1;
+        Credential cred = createCredentialWithCertificateCredential();
+        cred.setClientCertificateChain(null);
         assertFalse(cred.validate());
     }
 
@@ -318,16 +288,8 @@
      */
     @Test
     public void validateCertCredentialWithoutClientPrivateKey() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup certificate credential.
-        cred.certCredential = new Credential.CertificateCredential();
-        cred.certCredential.certType = "x509v3";
-        cred.certCredential.certSha256FingerPrint =
-                MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded());
-        // Setup certificates and private key.
-        cred.caCertificate = FakeKeys.CA_CERT0;
-        cred.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
+        Credential cred = createCredentialWithCertificateCredential();
+        cred.setClientPrivateKey(null);
         assertFalse(cred.validate());
     }
 
@@ -339,17 +301,8 @@
      */
     @Test
     public void validateCertCredentialWithMismatchFingerprint() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup certificate credential.
-        cred.certCredential = new Credential.CertificateCredential();
-        cred.certCredential.certType = "x509v3";
-        cred.certCredential.certSha256FingerPrint = new byte[32];
-        Arrays.fill(cred.certCredential.certSha256FingerPrint, (byte)0);
-        // Setup certificates and private key.
-        cred.caCertificate = FakeKeys.CA_CERT0;
-        cred.clientCertificateChain = new X509Certificate[] {FakeKeys.CLIENT_CERT};
-        cred.clientPrivateKey = FakeKeys.RSA_KEY1;
+        Credential cred = createCredentialWithCertificateCredential();
+        cred.getCertCredential().setCertSha256Fingerprint(new byte[32]);
         assertFalse(cred.validate());
     }
 
@@ -360,12 +313,7 @@
      */
     @Test
     public void validateSimCredentialWithEapSim() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "1234*";
-        cred.simCredential.eapType = EAPConstants.EAP_SIM;
+        Credential cred = createCredentialWithSimCredential();
         assertTrue(cred.validate());
     }
 
@@ -376,12 +324,8 @@
      */
     @Test
     public void validateSimCredentialWithEapAka() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "1234*";
-        cred.simCredential.eapType = EAPConstants.EAP_AKA;
+        Credential cred = createCredentialWithSimCredential();
+        cred.getSimCredential().setEapType(EAPConstants.EAP_AKA);
         assertTrue(cred.validate());
     }
 
@@ -392,12 +336,8 @@
      */
     @Test
     public void validateSimCredentialWithEapAkaPrime() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "1234*";
-        cred.simCredential.eapType = EAPConstants.EAP_AKA_PRIME;
+        Credential cred = createCredentialWithSimCredential();
+        cred.getSimCredential().setEapType(EAPConstants.EAP_AKA_PRIME);
         assertTrue(cred.validate());
     }
 
@@ -408,11 +348,8 @@
      */
     @Test
     public void validateSimCredentialWithoutIMSI() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.eapType = EAPConstants.EAP_SIM;
+        Credential cred = createCredentialWithSimCredential();
+        cred.getSimCredential().setImsi(null);
         assertFalse(cred.validate());
     }
 
@@ -423,12 +360,8 @@
      */
     @Test
     public void validateSimCredentialWithInvalidIMSI() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "dummy";
-        cred.simCredential.eapType = EAPConstants.EAP_SIM;
+        Credential cred = createCredentialWithSimCredential();
+        cred.getSimCredential().setImsi("dummy");
         assertFalse(cred.validate());
     }
 
@@ -439,12 +372,8 @@
      */
     @Test
     public void validateSimCredentialWithEapTls() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "1234*";
-        cred.simCredential.eapType = EAPConstants.EAP_TLS;
+        Credential cred = createCredentialWithSimCredential();
+        cred.getSimCredential().setEapType(EAPConstants.EAP_TLS);
         assertFalse(cred.validate());
     }
 
@@ -455,19 +384,12 @@
      */
     @Test
     public void validateCredentialWithUserAndSimCredential() throws Exception {
-        Credential cred = new Credential();
-        cred.realm = "realm";
-        // Setup user credential with EAP-TTLS.
-        cred.userCredential = new Credential.UserCredential();
-        cred.userCredential.username = "username";
-        cred.userCredential.password = "password";
-        cred.userCredential.eapType = EAPConstants.EAP_TTLS;
-        cred.userCredential.nonEapInnerMethod = "MS-CHAP";
-        cred.caCertificate = FakeKeys.CA_CERT0;
+        Credential cred = createCredentialWithUserCredential();
         // Setup SIM credential.
-        cred.simCredential = new Credential.SimCredential();
-        cred.simCredential.imsi = "1234*";
-        cred.simCredential.eapType = EAPConstants.EAP_SIM;
+        Credential.SimCredential simCredential = new Credential.SimCredential();
+        simCredential.setImsi("1234*");
+        simCredential.setEapType(EAPConstants.EAP_SIM);
+        cred.setSimCredential(simCredential);
         assertFalse(cred.validate());
     }
 
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
deleted file mode 100644
index c707993..0000000
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.net.wifi.hotspot2.pps;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Test;
-
-/**
- * Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSP}.
- */
-@SmallTest
-public class HomeSPTest {
-    private static HomeSP createHomeSp() {
-        HomeSP homeSp = new HomeSP();
-        homeSp.fqdn = "fqdn";
-        homeSp.friendlyName = "friendly name";
-        homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
-        return homeSp;
-    }
-
-    private static void verifyParcel(HomeSP writeHomeSp) throws Exception {
-        Parcel parcel = Parcel.obtain();
-        writeHomeSp.writeToParcel(parcel, 0);
-
-        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
-        HomeSP readHomeSp = HomeSP.CREATOR.createFromParcel(parcel);
-        assertTrue(readHomeSp.equals(writeHomeSp));
-    }
-
-    /**
-     * Verify parcel read/write for an empty HomeSP.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void verifyParcelWithEmptyHomeSP() throws Exception {
-        verifyParcel(new HomeSP());
-    }
-
-    /**
-     * Verify parcel read/write for a valid HomeSP.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void verifyParcelWithValidHomeSP() throws Exception {
-        verifyParcel(createHomeSp());
-    }
-
-    /**
-     * Verify that a HomeSP is valid when both FQDN and Friendly Name
-     * are provided.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void validateValidHomeSP() throws Exception {
-        HomeSP homeSp = new HomeSP();
-        homeSp.fqdn = "fqdn";
-        homeSp.friendlyName = "friendly name";
-        assertTrue(homeSp.validate());
-    }
-
-    /**
-     * Verify that a HomeSP is not valid when FQDN is not provided
-     *
-     * @throws Exception
-     */
-    @Test
-    public void validateHomeSpWithoutFqdn() throws Exception {
-        HomeSP homeSp = new HomeSP();
-        homeSp.friendlyName = "friendly name";
-        assertFalse(homeSp.validate());
-    }
-
-    /**
-     * Verify that a HomeSP is not valid when Friendly Name is not provided
-     *
-     * @throws Exception
-     */
-    @Test
-    public void validateHomeSpWithoutFriendlyName() throws Exception {
-        HomeSP homeSp = new HomeSP();
-        homeSp.fqdn = "fqdn";
-        assertFalse(homeSp.validate());
-    }
-
-    /**
-     * Verify that a HomeSP is valid when the optional Roaming Consortium OIs are
-     * provided.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void validateHomeSpWithRoamingConsoritums() throws Exception {
-        HomeSP homeSp = new HomeSP();
-        homeSp.fqdn = "fqdn";
-        homeSp.friendlyName = "friendly name";
-        homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
-        assertTrue(homeSp.validate());
-    }
-
-    /**
-     * Verify that copy constructor works when pass in a null source.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void validateCopyConstructorFromNullSource() throws Exception {
-        HomeSP copySp = new HomeSP(null);
-        HomeSP defaultSp = new HomeSP();
-        assertTrue(copySp.equals(defaultSp));
-    }
-
-    /**
-     * Verify that copy constructor works when pass in a valid source.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void validateCopyConstructorFromValidSource() throws Exception {
-        HomeSP sourceSp = new HomeSP();
-        sourceSp.fqdn = "fqdn";
-        sourceSp.friendlyName = "friendlyName";
-        sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
-        HomeSP copySp = new HomeSP(sourceSp);
-        assertTrue(copySp.equals(sourceSp));
-    }
-}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java
new file mode 100644
index 0000000..c41c11f
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSpTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.HomeSp}.
+ */
+@SmallTest
+public class HomeSpTest {
+
+    /**
+     * Helper function for creating a map of home network IDs for testing.
+     *
+     * @return Map of home network IDs
+     */
+    private static Map<String, Long> createHomeNetworkIds() {
+        Map<String, Long> homeNetworkIds = new HashMap<>();
+        homeNetworkIds.put("ssid", 0x1234L);
+        homeNetworkIds.put("nullhessid", null);
+        return homeNetworkIds;
+    }
+
+    /**
+     * Helper function for creating a HomeSp for testing.
+     *
+     * @param homeNetworkIds The map of home network IDs associated with HomeSp
+     * @return {@link HomeSp}
+     */
+    private static HomeSp createHomeSp(Map<String, Long> homeNetworkIds) {
+        HomeSp homeSp = new HomeSp();
+        homeSp.setFqdn("fqdn");
+        homeSp.setFriendlyName("friendly name");
+        homeSp.setIconUrl("icon.url");
+        homeSp.setHomeNetworkIds(homeNetworkIds);
+        homeSp.setMatchAllOis(new long[] {0x11L, 0x22L});
+        homeSp.setMatchAnyOis(new long[] {0x33L, 0x44L});
+        homeSp.setOtherHomePartners(new String[] {"partner1", "partner2"});
+        homeSp.setRoamingConsortiumOis(new long[] {0x55, 0x66});
+        return homeSp;
+    }
+
+    /**
+     * Helper function for creating a HomeSp with home network IDs for testing.
+     *
+     * @return {@link HomeSp}
+     */
+    private static HomeSp createHomeSpWithHomeNetworkIds() {
+        return createHomeSp(createHomeNetworkIds());
+    }
+
+    /**
+     * Helper function for creating a HomeSp without home network IDs for testing.
+     *
+     * @return {@link HomeSp}
+     */
+    private static HomeSp createHomeSpWithoutHomeNetworkIds() {
+        return createHomeSp(null);
+    }
+
+    /**
+     * Helper function for verifying HomeSp after parcel write then read.
+     * @param writeHomeSp
+     * @throws Exception
+     */
+    private static void verifyParcel(HomeSp writeHomeSp) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        writeHomeSp.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        HomeSp readHomeSp = HomeSp.CREATOR.createFromParcel(parcel);
+        assertTrue(readHomeSp.equals(writeHomeSp));
+    }
+
+    /**
+     * Verify parcel read/write for an empty HomeSp.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyHomeSp() throws Exception {
+        verifyParcel(new HomeSp());
+    }
+
+    /**
+     * Verify parcel read/write for a HomeSp containing Home Network IDs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithHomeNetworkIds() throws Exception {
+        verifyParcel(createHomeSpWithHomeNetworkIds());
+    }
+
+    /**
+     * Verify parcel read/write for a HomeSp without Home Network IDs.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutHomeNetworkIds() throws Exception {
+        verifyParcel(createHomeSpWithoutHomeNetworkIds());
+    }
+
+    /**
+     * Verify that a HomeSp is valid when both FQDN and Friendly Name
+     * are provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateValidHomeSp() throws Exception {
+        HomeSp homeSp = createHomeSpWithHomeNetworkIds();
+        assertTrue(homeSp.validate());
+    }
+
+    /**
+     * Verify that a HomeSp is not valid when FQDN is not provided
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithoutFqdn() throws Exception {
+        HomeSp homeSp = createHomeSpWithHomeNetworkIds();
+        homeSp.setFqdn(null);
+        assertFalse(homeSp.validate());
+    }
+
+    /**
+     * Verify that a HomeSp is not valid when Friendly Name is not provided
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithoutFriendlyName() throws Exception {
+        HomeSp homeSp = createHomeSpWithHomeNetworkIds();
+        homeSp.setFriendlyName(null);
+        assertFalse(homeSp.validate());
+    }
+
+    /**
+     * Verify that a HomeSp is valid when the optional Home Network IDs are
+     * not provided.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithoutHomeNetworkIds() throws Exception {
+        HomeSp homeSp = createHomeSpWithoutHomeNetworkIds();
+        assertTrue(homeSp.validate());
+    }
+
+    /**
+     * Verify that a HomeSp is invalid when the optional Home Network IDs
+     * contained an invalid SSID (exceeding maximum number of bytes).
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateHomeSpWithInvalidHomeNetworkIds() throws Exception {
+        HomeSp homeSp = createHomeSpWithoutHomeNetworkIds();
+        // HomeNetworkID with SSID exceeding the maximum length.
+        Map<String, Long> homeNetworkIds = new HashMap<>();
+        byte[] rawSsidBytes = new byte[33];
+        Arrays.fill(rawSsidBytes, (byte) 'a');
+        homeNetworkIds.put(
+                StringFactory.newStringFromBytes(rawSsidBytes, StandardCharsets.UTF_8), 0x1234L);
+        homeSp.setHomeNetworkIds(homeNetworkIds);
+        assertFalse(homeSp.validate());
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorFromNullSource() throws Exception {
+        HomeSp copySp = new HomeSp(null);
+        HomeSp defaultSp = new HomeSp();
+        assertTrue(copySp.equals(defaultSp));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a valid source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorFromValidSource() throws Exception {
+        HomeSp sourceSp = createHomeSpWithHomeNetworkIds();
+        HomeSp copySp = new HomeSp(sourceSp);
+        assertTrue(copySp.equals(sourceSp));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
new file mode 100644
index 0000000..2a36764
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
@@ -0,0 +1,312 @@
+/*
+ * 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.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.Policy}.
+ */
+@SmallTest
+public class PolicyTest {
+    private static final int MAX_NUMBER_OF_EXCLUDED_SSIDS = 128;
+    private static final int MAX_SSID_BYTES = 32;
+    private static final int MAX_PORT_STRING_BYTES = 64;
+
+    /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.setMinHomeDownlinkBandwidth(123);
+        policy.setMinHomeUplinkBandwidth(345);
+        policy.setMinRoamingDownlinkBandwidth(567);
+        policy.setMinRoamingUplinkBandwidth(789);
+        policy.setExcludedSsidList(new String[] {"ssid1", "ssid2"});
+        Map<Integer, String> requiredProtoPortMap = new HashMap<>();
+        requiredProtoPortMap.put(12, "23,342,123");
+        requiredProtoPortMap.put(23, "789,372,1235");
+        policy.setRequiredProtoPortMap(requiredProtoPortMap);
+        policy.setMaximumBssLoadValue(12);
+
+        List<Policy.RoamingPartner> preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.setFqdn("partner1.com");
+        partner1.setFqdnExactMatch(true);
+        partner1.setPriority(12);
+        partner1.setCountries("us,jp");
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.setFqdn("partner2.com");
+        partner2.setFqdnExactMatch(false);
+        partner2.setPriority(42);
+        partner2.setCountries("ca,fr");
+        preferredRoamingPartnerList.add(partner1);
+        preferredRoamingPartnerList.add(partner2);
+        policy.setPreferredRoamingPartnerList(preferredRoamingPartnerList);
+
+        UpdateParameter policyUpdate = new UpdateParameter();
+        policyUpdate.setUpdateIntervalInMinutes(1712);
+        policyUpdate.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+        policyUpdate.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+        policyUpdate.setServerUri("policy.update.com");
+        policyUpdate.setUsername("username");
+        policyUpdate.setBase64EncodedPassword(
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT));
+        policyUpdate.setTrustRootCertUrl("trust.cert.com");
+        policyUpdate.setTrustRootCertSha256Fingerprint(new byte[32]);
+        policy.setPolicyUpdate(policyUpdate);
+
+        return policy;
+    }
+
+    /**
+     * Helper function for verifying Policy after parcel write then read.
+     * @param policyToWrite
+     * @throws Exception
+     */
+    private static void verifyParcel(Policy policyToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        policyToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        Policy policyFromRead = Policy.CREATOR.createFromParcel(parcel);
+        assertTrue(policyFromRead.equals(policyToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty Policy.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyPolicy() throws Exception {
+        verifyParcel(new Policy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullPolicy() throws Exception {
+        verifyParcel(createPolicy());
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without protocol port map.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        policy.setRequiredProtoPortMap(null);
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without preferred roaming partner list.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPreferredRoamingPartnerList() throws Exception {
+        Policy policy = createPolicy();
+        policy.setPreferredRoamingPartnerList(null);
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify parcel read/write for a Policy without policy update parameters.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.setPolicyUpdate(null);
+        verifyParcel(policy);
+    }
+
+    /**
+     * Verify that policy created using copy constructor with null source should be the same
+     * as the policy created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        Policy copyPolicy = new Policy(null);
+        Policy defaultPolicy = new Policy();
+        assertTrue(defaultPolicy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that policy created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullPolicy() throws Exception {
+        Policy policy = createPolicy();
+        Policy copyPolicy = new Policy(policy);
+        assertTrue(policy.equals(copyPolicy));
+    }
+
+    /**
+     * Verify that a default policy (with no informatio) is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithDefault() throws Exception {
+        Policy policy = new Policy();
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy created using {@link #createPolicy} is valid, since all fields are
+     * filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithFullPolicy() throws Exception {
+        assertTrue(createPolicy().validate());
+    }
+
+    /**
+     * Verify that a policy without policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithoutPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.setPolicyUpdate(null);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with invalid policy update parameters is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPolicyUpdate() throws Exception {
+        Policy policy = createPolicy();
+        policy.setPolicyUpdate(new UpdateParameter());
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with FQDN not specified is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutFQDN() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.setFqdnExactMatch(true);
+        partner.setPriority(12);
+        partner.setCountries("us,jp");
+        policy.getPreferredRoamingPartnerList().add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a preferred roaming partner with countries not specified is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithRoamingPartnerWithoutCountries() throws Exception {
+        Policy policy = createPolicy();
+        Policy.RoamingPartner partner = new Policy.RoamingPartner();
+        partner.setFqdn("test.com");
+        partner.setFqdnExactMatch(true);
+        partner.setPriority(12);
+        policy.getPreferredRoamingPartnerList().add(partner);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with a proto-port tuple that contains an invalid port string is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidPortStringInProtoPortMap() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawPortBytes = new byte[MAX_PORT_STRING_BYTES + 1];
+        policy.getRequiredProtoPortMap().put(
+                324, new String(rawPortBytes, StandardCharsets.UTF_8));
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with number of excluded SSIDs exceeded the max is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithSsidExclusionListSizeExceededMax() throws Exception {
+        Policy policy = createPolicy();
+        String[] excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+        Arrays.fill(excludedSsidList, "ssid");
+        policy.setExcludedSsidList(excludedSsidList);
+        assertFalse(policy.validate());
+    }
+
+    /**
+     * Verify that a policy with an invalid SSID in the excluded SSID list is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidSsid() throws Exception {
+        Policy policy = createPolicy();
+        byte[] rawSsidBytes = new byte[MAX_SSID_BYTES + 1];
+        Arrays.fill(rawSsidBytes, (byte) 'a');
+        String[] excludedSsidList = new String[] {
+                new String(rawSsidBytes, StandardCharsets.UTF_8)};
+        policy.setExcludedSsidList(excludedSsidList);
+        assertFalse(policy.validate());
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.java
new file mode 100644
index 0000000..551ed43
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/UpdateParameterTest.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 android.net.wifi.hotspot2.pps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link android.net.wifi.hotspot2.pps.UpdateParameter}.
+ */
+@SmallTest
+public class UpdateParameterTest {
+    private static final int MAX_URI_BYTES = 1023;
+    private static final int MAX_URL_BYTES = 1023;
+    private static final int MAX_USERNAME_BYTES = 63;
+    private static final int MAX_PASSWORD_BYTES = 255;
+    private static final int CERTIFICATE_SHA256_BYTES = 32;
+
+    /**
+     * Helper function for creating a {@link UpdateParameter} for testing.
+     *
+     * @return {@link UpdateParameter}
+     */
+    private static UpdateParameter createUpdateParameter() {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.setUpdateIntervalInMinutes(1712);
+        updateParam.setUpdateMethod(UpdateParameter.UPDATE_METHOD_OMADM);
+        updateParam.setRestriction(UpdateParameter.UPDATE_RESTRICTION_HOMESP);
+        updateParam.setServerUri("server.pdate.com");
+        updateParam.setUsername("username");
+        updateParam.setBase64EncodedPassword(
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT));
+        updateParam.setTrustRootCertUrl("trust.cert.com");
+        updateParam.setTrustRootCertSha256Fingerprint(new byte[32]);
+        return updateParam;
+    }
+
+    /**
+     * Helper function for verifying UpdateParameter after parcel write then read.
+     * @param paramToWrite The UpdateParamter to verify
+     * @throws Exception
+     */
+    private static void verifyParcel(UpdateParameter paramToWrite) throws Exception {
+        Parcel parcel = Parcel.obtain();
+        paramToWrite.writeToParcel(parcel, 0);
+
+        parcel.setDataPosition(0);    // Rewind data position back to the beginning for read.
+        UpdateParameter paramFromRead = UpdateParameter.CREATOR.createFromParcel(parcel);
+        assertTrue(paramFromRead.equals(paramToWrite));
+    }
+
+    /**
+     * Verify parcel read/write for an empty UpdateParameter.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithEmptyUpdateParameter() throws Exception {
+        verifyParcel(new UpdateParameter());
+    }
+
+    /**
+     * Verify parcel read/write for a UpdateParameter with all fields set.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyParcelWithFullUpdateParameter() throws Exception {
+        verifyParcel(createUpdateParameter());
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with null source should be the
+     * same as the UpdateParameter created using default constructor.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithNullSource() throws Exception {
+        UpdateParameter copyParam = new UpdateParameter(null);
+        UpdateParameter defaultParam = new UpdateParameter();
+        assertTrue(defaultParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that UpdateParameter created using copy constructor with a valid source should be the
+     * same as the source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void verifyCopyConstructionWithFullUpdateParameter() throws Exception {
+        UpdateParameter origParam = createUpdateParameter();
+        UpdateParameter copyParam = new UpdateParameter(origParam);
+        assertTrue(origParam.equals(copyParam));
+    }
+
+    /**
+     * Verify that a default UpdateParameter is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithDefault() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter created using {@link #createUpdateParameter} is valid,
+     * since all fields are filled in with valid values.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithFullPolicy() throws Exception {
+        assertTrue(createUpdateParameter().validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown update method is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowMethod() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setUpdateMethod("adsfasd");
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an unknown restriction is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUnknowRestriction() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setRestriction("adsfasd");
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an username exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithUsernameExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUsernameBytes = new byte[MAX_USERNAME_BYTES + 1];
+        Arrays.fill(rawUsernameBytes, (byte) 'a');
+        updateParam.setUsername(new String(rawUsernameBytes, StandardCharsets.UTF_8));
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty username is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyUsername() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setUsername(null);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a password exceeding maximum size is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordExceedingMaxSize() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawPasswordBytes = new byte[MAX_PASSWORD_BYTES + 1];
+        Arrays.fill(rawPasswordBytes, (byte) 'a');
+        updateParam.setBase64EncodedPassword(new String(rawPasswordBytes, StandardCharsets.UTF_8));
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an empty password is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithEmptyPassword() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setBase64EncodedPassword(null);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with a Base64 encoded password that contained invalid padding
+     * is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithPasswordContainedInvalidPadding() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setBase64EncodedPassword(updateParam.getBase64EncodedPassword() + "=");
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setTrustRootCertUrl(null);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with invalid trust root certificate URL is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidTrustRootCertUrl() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUrlBytes = new byte[MAX_URL_BYTES + 1];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        updateParam.setTrustRootCertUrl(new String(rawUrlBytes, StandardCharsets.UTF_8));
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without trust root certificate SHA-256 fingerprint is
+     * invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithouttrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setTrustRootCertSha256Fingerprint(null);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an incorrect size trust root certificate SHA-256
+     * fingerprint is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithInvalidtrustRootCertSha256Fingerprint() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setTrustRootCertSha256Fingerprint(new byte[CERTIFICATE_SHA256_BYTES + 1]);
+        assertFalse(updateParam.validate());
+
+        updateParam.setTrustRootCertSha256Fingerprint(new byte[CERTIFICATE_SHA256_BYTES - 1]);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter without server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setServerUri(null);
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with an invalid server URI is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validatePolicyWithInvalidServerUri() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        byte[] rawUriBytes = new byte[MAX_URI_BYTES + 1];
+        Arrays.fill(rawUriBytes, (byte) 'a');
+        updateParam.setServerUri(new String(rawUriBytes, StandardCharsets.UTF_8));
+        assertFalse(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with update interval set to "never" will not perform
+     * validation on other parameters, since update is not applicable in this case.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithNoServerCheck() throws Exception {
+        UpdateParameter updateParam = new UpdateParameter();
+        updateParam.setUpdateIntervalInMinutes(UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER);
+        updateParam.setUsername(null);
+        updateParam.setBase64EncodedPassword(null);
+        updateParam.setUpdateMethod(null);
+        updateParam.setRestriction(null);
+        updateParam.setServerUri(null);
+        updateParam.setTrustRootCertUrl(null);
+        updateParam.setTrustRootCertSha256Fingerprint(null);
+        assertTrue(updateParam.validate());
+    }
+
+    /**
+     * Verify that an UpdateParameter with unset update interval is invalid.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateUpdateParameterWithoutUpdateInterval() throws Exception {
+        UpdateParameter updateParam = createUpdateParameter();
+        updateParam.setUpdateIntervalInMinutes(Long.MIN_VALUE);
+        assertFalse(updateParam.validate());
+    }
+}