Add a client chain to WifiEnterpriseConfig
am: 291ddaef78

Change-Id: Ic8451631732bd3bfce7ff08f9f37b18745cda357
diff --git a/Android.mk b/Android.mk
index 5dfa58a..a798a31 100644
--- a/Android.mk
+++ b/Android.mk
@@ -437,6 +437,8 @@
         telephony/java/com/android/ims/internal/IImsExternalCallStateListener.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 \
diff --git a/api/current.txt b/api/current.txt
index 1d2b2a9..42275de 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24786,6 +24786,7 @@
     method public boolean isTdlsSupported();
     method public boolean isWifiEnabled();
     method public boolean pingSupplicant();
+    method public void queryPasspointIcon(long, java.lang.String);
     method public boolean reassociate();
     method public boolean reconnect();
     method public boolean removeNetwork(int);
@@ -24796,6 +24797,10 @@
     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
@@ -24803,6 +24808,18 @@
     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_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+    field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
     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_SUPPLICANT_CONNECTED = "connected";
@@ -37092,6 +37109,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";
@@ -60706,6 +60724,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>);
@@ -60716,7 +60737,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);
@@ -60745,12 +60770,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 e3d2b94..6ebbee2 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";
@@ -27169,6 +27170,7 @@
     method public boolean isWifiEnabled();
     method public boolean isWifiScannerSupported();
     method public boolean pingSupplicant();
+    method public void queryPasspointIcon(long, java.lang.String);
     method public boolean reassociate();
     method public boolean reconnect();
     method public boolean removeNetwork(int);
@@ -27183,6 +27185,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
@@ -27196,6 +27202,18 @@
     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_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+    field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
     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";
@@ -40261,6 +40279,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";
@@ -41138,6 +41157,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 {
@@ -64317,6 +64345,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>);
@@ -64327,7 +64358,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);
@@ -64356,12 +64391,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 2730881..40085a2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -24859,6 +24859,7 @@
     method public boolean isTdlsSupported();
     method public boolean isWifiEnabled();
     method public boolean pingSupplicant();
+    method public void queryPasspointIcon(long, java.lang.String);
     method public boolean reassociate();
     method public boolean reconnect();
     method public boolean removeNetwork(int);
@@ -24869,6 +24870,10 @@
     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
@@ -24876,6 +24881,18 @@
     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_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+    field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+    field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+    field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+    field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
     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_SUPPLICANT_CONNECTED = "connected";
@@ -37174,6 +37191,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";
@@ -60797,6 +60815,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>);
@@ -60807,7 +60828,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);
@@ -60836,12 +60861,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/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..cae4be6 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);
@@ -5109,6 +5112,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/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/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 52cd2de..a37a0b3 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -51,9 +51,10 @@
     // 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_INVALID = 1000 * 1000;
 
diff --git a/core/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl
index d36b766..8f634bb 100644
--- a/core/java/android/net/IIpConnectivityMetrics.aidl
+++ b/core/java/android/net/IIpConnectivityMetrics.aidl
@@ -23,8 +23,7 @@
 interface IIpConnectivityMetrics {
 
     /**
-     * @return the number of remaining available slots in buffer,
-     * or -1 if the event was dropped due to rate limiting.
+     * @return number of remaining available slots in buffer.
      */
     int logEvent(in ConnectivityMetricsEvent event);
 }
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..bd32314 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) {
+                        throw new FormatException("expected non-zero type length in first chunk");
+                    }
                     chunks.clear();
                     chunkTnf = tnf;
                 }
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 175d883..210ddb6 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);
     }
 
     /**
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/SystemProperties.java b/core/java/android/os/SystemProperties.java
index e47c238..6a751e8 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,45 @@
  *
  * {@hide}
  */
-public class SystemProperties
-{
+public class SystemProperties {
+    private static final String TAG = "SystemProperties";
+    private static final boolean TRACK_KEY_ACCESS = false;
+
     public static final int PROP_NAME_MAX = 31;
     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);
@@ -50,6 +88,7 @@
         if (key.length() > PROP_NAME_MAX) {
             throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
         }
+        if (TRACK_KEY_ACCESS) onKeyAccess(key);
         return native_get(key);
     }
 
@@ -62,6 +101,7 @@
         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);
     }
 
@@ -77,6 +117,7 @@
         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);
     }
 
@@ -92,6 +133,7 @@
         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);
     }
 
@@ -112,6 +154,7 @@
         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);
     }
 
@@ -128,6 +171,7 @@
             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/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/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/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/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index ec80303..763d392 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;
@@ -642,13 +643,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(
@@ -678,7 +676,7 @@
      * @param args non-null; zygote spawner args
      */
     public static void applyDebuggerSystemProperty(Arguments args) {
-        if ("1".equals(SystemProperties.get("ro.debuggable"))) {
+        if (RoSystemProperties.DEBUGGABLE) {
             args.debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
         }
     }
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/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index a32dbad..516ab38 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -247,6 +247,11 @@
 
 static void DropCapabilitiesBoundingSet(JNIEnv* env) {
   for (int i = 0; prctl(PR_CAPBSET_READ, i, 0, 0, 0) >= 0; i++) {
+    // Keep CAP_SYS_PTRACE in our bounding set so crash_dump can gain it.
+    if (i == CAP_SYS_PTRACE) {
+      continue;
+    }
+
     int rc = prctl(PR_CAPBSET_DROP, i, 0, 0, 0);
     if (rc == -1) {
       if (errno == EINVAL) {
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/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/config.xml b/core/res/res/values/config.xml
index dbc4324..fbde9c0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2267,6 +2267,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">true</bool>
+
     <bool name="config_networkSamplingWakesDevice">true</bool>
 
     <string-array translatable="false" name="config_cdma_home_system" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9db131b..21df50e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -252,6 +252,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" />
@@ -2723,6 +2725,7 @@
 
   <java-symbol type="bool" name="config_permissionReviewRequired" />
 
+
   <java-symbol type="drawable" name="ic_restart" />
 
   <java-symbol type="drawable" name="emergency_icon" />
diff --git a/core/tests/coretests/src/android/util/TokenBucketTest.java b/core/tests/coretests/src/android/util/TokenBucketTest.java
index f7ac20c..a053ad3 100644
--- a/core/tests/coretests/src/android/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/android/util/TokenBucketTest.java
@@ -177,3 +177,4 @@
 
     interface Fn { void call(); }
 }
+
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/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/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 4f43eac..78b33e5 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;
@@ -217,6 +220,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 +235,7 @@
                   // when from system.
                 }
             }
+            updateOppLauncherComponentState(bluetoothDisallowed);
         }
     };
 
@@ -751,8 +760,9 @@
             mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                     "Need BLUETOOTH ADMIN permission");
 
-            if (!isEnabled() && mPermissionReviewRequired) {
-                startConsentUi(packageName, callingUid, BluetoothAdapter.ACTION_REQUEST_ENABLE);
+            if (!isEnabled() && mPermissionReviewRequired
+                    && startConsentUiIfNeeded(packageName, callingUid,
+                            BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
                 return false;
             }
         }
@@ -786,8 +796,9 @@
             mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                     "Need BLUETOOTH ADMIN permission");
 
-            if (isEnabled() && mPermissionReviewRequired) {
-                startConsentUi(packageName, callingUid, BluetoothAdapter.ACTION_REQUEST_DISABLE);
+            if (isEnabled() && mPermissionReviewRequired
+                    && startConsentUiIfNeeded(packageName, callingUid,
+                            BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
                 return false;
             }
         }
@@ -807,8 +818,8 @@
         return true;
     }
 
-    private void startConsentUi(String packageName, int callingUid, String intentAction)
-            throws RemoteException {
+    private boolean startConsentUiIfNeeded(String packageName,
+            int callingUid, String intentAction) throws RemoteException {
         try {
             // Validate the package only if we are going to use it
             ApplicationInfo applicationInfo = mContext.getPackageManager()
@@ -820,12 +831,16 @@
                         + " not in uid " + callingUid);
             }
 
-            // Permission review mode, trigger a user prompt
-            Intent intent = new Intent(intentAction);
-            mContext.startActivity(intent);
+            // Legacy apps in permission review mode trigger a user prompt
+            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                Intent intent = new Intent(intentAction);
+                mContext.startActivity(intent);
+                return true;
+            }
         } catch (PackageManager.NameNotFoundException e) {
             throw new RemoteException(e.getMessage());
         }
+        return false;
     }
 
     public void unbindAndFinish() {
@@ -938,7 +953,13 @@
         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()) {
@@ -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 8c38ed6..803c2db 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -690,11 +690,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 +710,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());
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 97f913e..4405c1b 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -49,6 +49,7 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IDeviceIdleController;
 import android.os.IMaintenanceActivityListener;
 import android.os.Looper;
@@ -1237,7 +1238,7 @@
         }
     }
 
-    public class LocalService {
+    public final class LocalService {
         public void addPowerSaveTempWhitelistAppDirect(int appId, long duration, boolean sync,
                 String reason) {
             addPowerSaveTempWhitelistAppDirectInternal(0, appId, duration, sync, reason);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index ae1aef6..10a5388 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -369,7 +369,7 @@
         // we do not start the service and launch a review activity if the calling app
         // is in the foreground passing it a pending intent to start the service when
         // review is completed.
-        if (mAm.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r, callingPackage,
                     callingUid, service, callerFg, userId)) {
                 return null;
@@ -913,7 +913,7 @@
         // we schedule binding to the service but do not start its process, then
         // we launch a review activity to which is passed a callback to invoke
         // when done to start the bound service's process to completing the binding.
-        if (mAm.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
             if (mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                     s.packageName, s.userId)) {
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a2963b8..a9ddb63 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;
@@ -1575,8 +1576,6 @@
     // being called for multiwindow assist in a single session.
     private int mViSessionId = 1000;
 
-    final boolean mPermissionReviewRequired;
-
     final class KillHandler extends Handler {
         static final int KILL_PROCESS_GROUP_MSG = 4000;
 
@@ -2624,9 +2623,6 @@
 
         Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass());
 
-        mPermissionReviewRequired = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_permissionReviewRequired);
-
         mHandlerThread = new ServiceThread(TAG,
                 android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
         mHandlerThread.start();
@@ -6571,12 +6567,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 +6604,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,
@@ -10839,7 +10838,7 @@
                     // If permissions need a review before any of the app components can run,
                     // we return no provider and launch a review activity if the calling app
                     // is in the foreground.
-                    if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
+                    if (Build.PERMISSIONS_REVIEW_REQUIRED) {
                         if (!requestTargetProviderPermissionsReviewIfNeededLocked(cpi, r, userId)) {
                             return null;
                         }
@@ -12039,6 +12038,7 @@
             mProfileFd = profilerInfo.profileFd;
             mSamplingInterval = profilerInfo.samplingInterval;
             mAutoStopProfiler = profilerInfo.autoStopProfiler;
+            mStreamingOutput = profilerInfo.streamingOutput;
             mProfileType = 0;
         }
     }
@@ -14920,7 +14920,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);
             }
         }
@@ -21439,6 +21439,7 @@
         mProfileFile = null;
         mProfileType = 0;
         mAutoStopProfiler = false;
+        mStreamingOutput = false;
         mSamplingInterval = 0;
     }
 
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/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 907394e..115971f 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -416,8 +416,7 @@
         // If permissions need a review before any of the app components can run, we
         // launch the review activity and pass a pending intent to start the activity
         // we are to launching now after the review is completed.
-        if ((mService.mPermissionReviewRequired
-                || Build.PERMISSIONS_REVIEW_REQUIRED) && aInfo != null) {
+        if (Build.PERMISSIONS_REVIEW_REQUIRED && aInfo != null) {
             if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired(
                     aInfo.packageName, userId)) {
                 IIntentSender target = mService.getIntentSenderLocked(
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index ea901ce..362a347 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -626,7 +626,7 @@
         // the broadcast and if the calling app is in the foreground and the broadcast is
         // explicit we launch the review UI passing it a pending intent to send the skipped
         // broadcast.
-        if (mService.mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
             if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
                     filter.owningUserId)) {
                 r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
@@ -1132,8 +1132,7 @@
             // the broadcast and if the calling app is in the foreground and the broadcast is
             // explicit we launch the review UI passing it a pending intent to send the skipped
             // broadcast.
-            if ((mService.mPermissionReviewRequired
-                    || Build.PERMISSIONS_REVIEW_REQUIRED) && !skip) {
+            if (Build.PERMISSIONS_REVIEW_REQUIRED && !skip) {
                 if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                         info.activityInfo.packageName, UserHandle.getUserId(
                                 info.activityInfo.applicationInfo.uid))) {
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index be68173..642f2e0 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -19,19 +19,15 @@
 import android.content.Context;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
-import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.os.IBinder;
 import android.os.Parcelable;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.Log;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.TokenBucket;
 import com.android.server.SystemService;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -60,8 +56,6 @@
     // Maximum size of the event buffer.
     private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
 
-    private static final int ERROR_RATE_LIMITED = -1;
-
     // Lock ensuring that concurrent manipulations of the event buffer are correct.
     // There are three concurrent operations to synchronize:
     //  - appending events to the buffer.
@@ -79,8 +73,6 @@
     private int mDropped;
     @GuardedBy("mLock")
     private int mCapacity;
-    @GuardedBy("mLock")
-    private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
 
     private final ToIntFunction<Context> mCapacityGetter;
 
@@ -130,10 +122,6 @@
             if (event == null) {
                 return left;
             }
-            if (isRateLimited(event)) {
-                // Do not count as a dropped event. TODO: consider adding separate counter
-                return ERROR_RATE_LIMITED;
-            }
             if (left == 0) {
                 mDropped++;
                 return 0;
@@ -143,11 +131,6 @@
         }
     }
 
-    private boolean isRateLimited(ConnectivityMetricsEvent event) {
-        TokenBucket tb = mBuckets.get(event.data.getClass());
-        return (tb != null) && !tb.get();
-    }
-
     private String flushEncodedOutput() {
         final ArrayList<ConnectivityMetricsEvent> events;
         final int dropped;
@@ -273,11 +256,4 @@
         }
         return Math.min(size, MAXIMUM_BUFFER_SIZE);
     };
-
-    private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() {
-        ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>();
-        // one token every minute, 50 tokens max: burst of ~50 events every hour.
-        map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
-        return map;
-    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 9ffa40b..c6bf4c5 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,8 +37,7 @@
 
     public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH };
 
-    @VisibleForTesting
-    static final String NOTIFICATION_ID = "Connectivity.Notification";
+    private 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/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 23481dc..08a3332 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -40,7 +40,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 +50,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,15 +66,20 @@
     public static final int EVENT_ON_LINKPROPERTIES = 3;
     public static final int EVENT_ON_LOST           = 4;
 
+    private static final int LISTEN_ALL = 1;
+    private static final int TRACK_DEFAULT = 2;
+    private static final int MOBILE_REQUEST = 3;
+
     private final Context mContext;
     private final StateMachine mTarget;
     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;
@@ -85,16 +96,13 @@
     public void start() {
         stop();
 
-        mDefaultNetworkCallback = new UpstreamNetworkCallback();
-        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
+        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
+                .clearCapabilities().build();
+        mListenAllCallback = new UpstreamNetworkCallback(LISTEN_ALL);
+        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback);
 
-        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(TRACK_DEFAULT);
+        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);
     }
 
     public void stop() {
@@ -103,8 +111,8 @@
         releaseCallback(mDefaultNetworkCallback);
         mDefaultNetworkCallback = null;
 
-        releaseCallback(mDunTetheringCallback);
-        mDunTetheringCallback = null;
+        releaseCallback(mListenAllCallback);
+        mListenAllCallback = null;
 
         mNetworkMap.clear();
     }
@@ -140,7 +148,7 @@
 
         // The existing default network and DUN callbacks will be notified.
         // Therefore, to avoid duplicate notifications, we only register a no-op.
-        mMobileNetworkCallback = new NetworkCallback();
+        mMobileNetworkCallback = new UpstreamNetworkCallback(MOBILE_REQUEST);
 
         // TODO: Change the timeout from 0 (no onUnavailable callback) to some
         // moderate callback timeout. This might be useful for updating some UI.
@@ -165,86 +173,117 @@
         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();
-
-        if (mDefaultNetworkCallback != null) {
+        // 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.
+        final NetworkCallback cb =
+                (callbackType == TRACK_DEFAULT) ? mDefaultNetworkCallback :
+                (callbackType == MOBILE_REQUEST) ? mMobileNetworkCallback : null;
+        if (cb != null) {
+            final ConnectivityManager cm = cm();
             cm.requestNetworkCapabilities(mDefaultNetworkCallback);
             cm.requestLinkProperties(mDefaultNetworkCallback);
         }
 
-        // 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.
+        if (callbackType == TRACK_DEFAULT) {
+            mCurrentDefault = network;
+        }
 
+        // 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 == 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));
     }
 
@@ -261,9 +300,15 @@
      * tethering master state machine thread for subsequent processing.
      */
     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));
+            mTarget.getHandler().post(() -> handleAvailable(mCallbackType, network));
         }
 
         @Override
@@ -278,7 +323,7 @@
 
         @Override
         public void onLost(Network network) {
-            mTarget.getHandler().post(() -> handleLost(network));
+            mTarget.getHandler().post(() -> handleLost(mCallbackType, network));
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 07dbb17..0e696b3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1148,8 +1148,6 @@
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
-    final boolean mPermissionReviewRequired;
-
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
 
@@ -2080,10 +2078,6 @@
         }
 
         mContext = context;
-
-        mPermissionReviewRequired = context.getResources().getBoolean(
-                R.bool.config_permissionReviewRequired);
-
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
@@ -4057,7 +4051,7 @@
             // their permissions as always granted runtime ones since we need
             // to keep the review required permission flag per user while an
             // install permission's state is shared across all users.
-            if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
+            if (Build.PERMISSIONS_REVIEW_REQUIRED
                     && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
                     && bp.isRuntime()) {
                 return;
@@ -4168,7 +4162,7 @@
             // their permissions as always granted runtime ones since we need
             // to keep the review required permission flag per user while an
             // install permission's state is shared across all users.
-            if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
+            if (Build.PERMISSIONS_REVIEW_REQUIRED
                     && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
                     && bp.isRuntime()) {
                 return;
@@ -10061,8 +10055,7 @@
                     // their permissions as always granted runtime ones since we need
                     // to keep the review required permission flag per user while an
                     // install permission's state is shared across all users.
-                    if (!appSupportsRuntimePermissions && !mPermissionReviewRequired
-                            && !Build.PERMISSIONS_REVIEW_REQUIRED) {
+                    if (!appSupportsRuntimePermissions && !Build.PERMISSIONS_REVIEW_REQUIRED) {
                         // For legacy apps dangerous permissions are install time ones.
                         grant = GRANT_INSTALL;
                     } else if (origPermissions.hasInstallPermission(bp.name)) {
@@ -10148,7 +10141,7 @@
                                             changedRuntimePermissionUserIds, userId);
                                 }
                                 // If the app supports runtime permissions no need for a review.
-                                if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
+                                if (Build.PERMISSIONS_REVIEW_REQUIRED
                                         && appSupportsRuntimePermissions
                                         && (flags & PackageManager
                                                 .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
@@ -10157,8 +10150,7 @@
                                     changedRuntimePermissionUserIds = ArrayUtils.appendInt(
                                             changedRuntimePermissionUserIds, userId);
                                 }
-                            } else if ((mPermissionReviewRequired
-                                        || Build.PERMISSIONS_REVIEW_REQUIRED)
+                            } else if (Build.PERMISSIONS_REVIEW_REQUIRED
                                     && !appSupportsRuntimePermissions) {
                                 // For legacy apps that need a permission review, every new
                                 // runtime permission is granted but it is pending a review.
@@ -16753,7 +16745,7 @@
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
-            if ((mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED)
+            if (Build.PERMISSIONS_REVIEW_REQUIRED
                     && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
             }
@@ -20623,7 +20615,7 @@
         // permissions to keep per user flag state whether review is needed.
         // Hence, if a new user is added we have to propagate dangerous
         // permission grants for these legacy apps.
-        if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
+        if (Build.PERMISSIONS_REVIEW_REQUIRED) {
             updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
                     | UPDATE_PERMISSIONS_REPLACE_ALL);
         }
@@ -21077,7 +21069,7 @@
         public boolean isPermissionsReviewRequired(String packageName, int userId) {
             synchronized (mPackages) {
                 // If we do not support permission review, done.
-                if (!mPermissionReviewRequired && !Build.PERMISSIONS_REVIEW_REQUIRED) {
+                if (!Build.PERMISSIONS_REVIEW_REQUIRED) {
                     return false;
                 }
 
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index a545af9..aeeca79 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -16,12 +16,17 @@
 
 package com.android.server;
 
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.NetworkPolicy.CYCLE_NONE;
 import static android.net.NetworkPolicy.LIMIT_DISABLED;
 import static android.net.NetworkPolicy.WARNING_DISABLED;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.computeNextCycleBoundary;
 import static android.net.TrafficStats.KB_IN_BYTES;
@@ -29,42 +34,28 @@
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.Time.TIMEZONE_UTC;
-
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
 import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
+import static org.easymock.EasyMock.anyInt;
+import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.aryEq;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
-import android.app.IUidObserver;
+import android.app.IProcessObserver;
 import android.app.Notification;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkPolicyListener;
@@ -78,48 +69,40 @@
 import android.net.NetworkTemplate;
 import android.os.Binder;
 import android.os.INetworkManagementService;
-import android.os.PowerManagerInternal;
+import android.os.MessageQueue.IdleHandler;
 import android.os.UserHandle;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.test.mock.MockPackageManager;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
 import android.text.format.Time;
-import android.util.Log;
 import android.util.TrustedTime;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.net.NetworkPolicyManagerService;
-
-import libcore.io.IoUtils;
-
 import com.google.common.util.concurrent.AbstractFuture;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
 
 import java.io.File;
-import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.TimeZone;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.logging.Handler;
+
+import libcore.io.IoUtils;
 
 /**
  * Tests for {@link NetworkPolicyManagerService}.
  */
-@RunWith(AndroidJUnit4.class)
-public class NetworkPolicyManagerServiceTest {
+@LargeTest
+public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
     private static final String TAG = "NetworkPolicyManagerServiceTest";
 
     private static final long TEST_START = 1194220800000L;
@@ -131,19 +114,19 @@
     private BroadcastInterceptingContext mServiceContext;
     private File mPolicyDir;
 
-    private @Mock IActivityManager mActivityManager;
-    private @Mock INetworkStatsService mStatsService;
-    private @Mock INetworkManagementService mNetworkManager;
-    private @Mock TrustedTime mTime;
-    private @Mock IConnectivityManager mConnManager;
-    private @Mock INotificationManager mNotifManager;
-    private @Mock PackageManager mPackageManager;
+    private IActivityManager mActivityManager;
+    private INetworkStatsService mStatsService;
+    private INetworkManagementService mNetworkManager;
+    private INetworkPolicyListener mPolicyListener;
+    private TrustedTime mTime;
+    private IConnectivityManager mConnManager;
+    private INotificationManager mNotifManager;
 
-    private IUidObserver mUidObserver;
+    private NetworkPolicyManagerService mService;
+    private IProcessObserver mProcessObserver;
     private INetworkManagementEventObserver mNetworkObserver;
 
-    private NetworkPolicyListenerAnswer mPolicyListener;
-    private NetworkPolicyManagerService mService;
+    private Binder mStubBinder = new Binder();
 
     private long mStartTime;
     private long mElapsedRealtime;
@@ -156,30 +139,39 @@
     private static final int UID_A = UserHandle.getUid(USER_ID, APP_ID_A);
     private static final int UID_B = UserHandle.getUid(USER_ID, APP_ID_B);
 
-    private static final String PKG_NAME_A = "name.is.A,pkg.A";
+    private static final int PID_1 = 400;
+    private static final int PID_2 = 401;
+    private static final int PID_3 = 402;
 
-    @BeforeClass
-    public static void registerLocalServices() {
-        addLocalServiceMock(PowerManagerInternal.class);
-        addLocalServiceMock(DeviceIdleController.LocalService.class);
-        final UsageStatsManagerInternal usageStats =
-                addLocalServiceMock(UsageStatsManagerInternal.class);
-        when(usageStats.getIdleUidsForUser(anyInt())).thenReturn(new int[]{});
-    }
-
-    @Before
-    public void callSystemReady() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        final Context context = InstrumentationRegistry.getContext();
+    public void _setUp() throws Exception {
+        super.setUp();
 
         setCurrentTimeMillis(TEST_START);
 
         // intercept various broadcasts, and pretend that uids have packages
-        mServiceContext = new BroadcastInterceptingContext(context) {
+        mServiceContext = new BroadcastInterceptingContext(getContext()) {
             @Override
             public PackageManager getPackageManager() {
-                return mPackageManager;
+                return new MockPackageManager() {
+                    @Override
+                    public String[] getPackagesForUid(int uid) {
+                        return new String[] { "com.example" };
+                    }
+
+                    @Override
+                    public PackageInfo getPackageInfo(String packageName, int flags) {
+                        final PackageInfo info = new PackageInfo();
+                        final Signature signature;
+                        if ("android".equals(packageName)) {
+                            signature = new Signature("F00D");
+                        } else {
+                            signature = new Signature("DEAD");
+                        }
+                        info.signatures = new Signature[] { signature };
+                        return info;
+                    }
+
+                };
             }
 
             @Override
@@ -188,112 +180,229 @@
             }
         };
 
-        mPolicyDir = context.getFilesDir();
+        mPolicyDir = getContext().getFilesDir();
         if (mPolicyDir.exists()) {
             IoUtils.deleteContents(mPolicyDir);
         }
 
-        doAnswer(new Answer<Void>() {
+        mActivityManager = createMock(IActivityManager.class);
+        mStatsService = createMock(INetworkStatsService.class);
+        mNetworkManager = createMock(INetworkManagementService.class);
+        mPolicyListener = createMock(INetworkPolicyListener.class);
+        mTime = createMock(TrustedTime.class);
+        mConnManager = createMock(IConnectivityManager.class);
+        mNotifManager = createMock(INotificationManager.class);
 
-            @Override
-            public Void answer(InvocationOnMock invocation) throws Throwable {
-                mUidObserver = (IUidObserver) invocation.getArguments()[0];
-                Log.d(TAG, "set mUidObserver to " + mUidObserver);
-                return null;
-            }
-        }).when(mActivityManager).registerUidObserver(any(), anyInt());
-
-        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, mStatsService,
-                mNetworkManager, mTime, mPolicyDir, true);
+        mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager,
+                mStatsService, mNetworkManager, mTime, mPolicyDir, true);
         mService.bindConnectivityManager(mConnManager);
         mService.bindNotificationManager(mNotifManager);
-        mPolicyListener = new NetworkPolicyListenerAnswer(mService);
 
-        // Sets some common expectations.
-        when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenAnswer(
-                new Answer<PackageInfo>() {
+        // RemoteCallbackList needs a binder to use as key
+        expect(mPolicyListener.asBinder()).andReturn(mStubBinder).atLeastOnce();
+        replay();
+        mService.registerListener(mPolicyListener);
+        verifyAndReset();
 
-                    @Override
-                    public PackageInfo answer(InvocationOnMock invocation) throws Throwable {
-                        final String packageName = (String) invocation.getArguments()[0];
-                        final PackageInfo info = new PackageInfo();
-                        final Signature signature;
-                        if ("android".equals(packageName)) {
-                            signature = new Signature("F00D");
-                        } else {
-                            signature = new Signature("DEAD");
-                        }
-                        info.signatures = new Signature[] {
-                            signature
-                        };
-                        return info;
-                    }
-                });
-        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenReturn(new ApplicationInfo());
-        when(mPackageManager.getPackagesForUid(UID_A)).thenReturn(new String[] {PKG_NAME_A});
-        when(mNetworkManager.isBandwidthControlEnabled()).thenReturn(true);
-        expectCurrentTime();
-
-        // Prepare NPMS.
-        mService.systemReady();
+        // catch IProcessObserver during systemReady()
+        final Capture<IProcessObserver> processObserver = new Capture<IProcessObserver>();
+        mActivityManager.registerProcessObserver(capture(processObserver));
+        expectLastCall().atLeastOnce();
 
         // catch INetworkManagementEventObserver during systemReady()
-        ArgumentCaptor<INetworkManagementEventObserver> networkObserver =
-              ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
-        verify(mNetworkManager).registerObserver(networkObserver.capture());
+        final Capture<INetworkManagementEventObserver> networkObserver = new Capture<
+                INetworkManagementEventObserver>();
+        mNetworkManager.registerObserver(capture(networkObserver));
+        expectLastCall().atLeastOnce();
+
+        expect(mNetworkManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce();
+        expectCurrentTime();
+
+        replay();
+        mService.systemReady();
+        verifyAndReset();
+
+        mProcessObserver = processObserver.getValue();
         mNetworkObserver = networkObserver.getValue();
+
     }
 
-    @After
-    public void removeFiles() throws Exception {
+    public void _tearDown() throws Exception {
         for (File file : mPolicyDir.listFiles()) {
             file.delete();
         }
+
+        mServiceContext = null;
+        mPolicyDir = null;
+
+        mActivityManager = null;
+        mStatsService = null;
+        mPolicyListener = null;
+        mTime = null;
+
+        mService = null;
+        mProcessObserver = null;
+
+        super.tearDown();
     }
 
-    @After
-    public void unregisterLocalServices() throws Exception {
-        // Registered by NetworkPolicyManagerService's constructor.
-        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
-    }
-
-    // NOTE: testPolicyChangeTriggersListener() and testUidForeground() are too superficial, they
-    // don't check for side-effects (like calls to NetworkManagementService) neither cover all
-    // different modes (Data Saver, Battery Saver, Doze, App idle, etc...).
-    // These scenarios are extensively tested on CTS' HostsideRestrictBackgroundNetworkTests.
-
-    @Test
-    public void testPolicyChangeTriggersListener() throws Exception {
-        mPolicyListener.expect().onRestrictBackgroundBlacklistChanged(anyInt(), anyBoolean());
-
+    @Suppress
+    public void testPolicyChangeTriggersBroadcast() throws Exception {
         mService.setUidPolicy(APP_ID_A, POLICY_NONE);
+
+        // change background policy and expect broadcast
+        final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
+                ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+
         mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
 
-        mPolicyListener.waitAndVerify().onRestrictBackgroundBlacklistChanged(APP_ID_A, true);
+        backgroundChanged.get();
     }
 
-    @Test
-    public void testUidForeground() throws Exception {
-        // push all uids into background
-        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE);
-        mUidObserver.onUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_SERVICE);
+    @Suppress
+    public void testPidForegroundCombined() throws Exception {
+        IdleFuture idle;
+
+        // push all uid into background
+        idle = expectIdle();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false);
+        idle.get();
         assertFalse(mService.isUidForeground(UID_A));
         assertFalse(mService.isUidForeground(UID_B));
 
-        // push one of the uids into foreground
-        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_TOP);
+        // push one of the shared pids into foreground
+        idle = expectIdle();
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+        idle.get();
         assertTrue(mService.isUidForeground(UID_A));
         assertFalse(mService.isUidForeground(UID_B));
 
         // and swap another uid into foreground
-        mUidObserver.onUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE);
-        mUidObserver.onUidStateChanged(UID_B, ActivityManager.PROCESS_STATE_TOP);
+        idle = expectIdle();
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true);
+        idle.get();
         assertFalse(mService.isUidForeground(UID_A));
         assertTrue(mService.isUidForeground(UID_B));
+
+        // push both pid into foreground
+        idle = expectIdle();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true);
+        idle.get();
+        assertTrue(mService.isUidForeground(UID_A));
+
+        // pull one out, should still be foreground
+        idle = expectIdle();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        idle.get();
+        assertTrue(mService.isUidForeground(UID_A));
+
+        // pull final pid out, should now be background
+        idle = expectIdle();
+        mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false);
+        idle.get();
+        assertFalse(mService.isUidForeground(UID_A));
     }
 
-    @Test
+    @Suppress
+    public void testPolicyNone() throws Exception {
+        Future<Void> future;
+
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, true);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        future.get();
+        verifyAndReset();
+
+        // POLICY_NONE should RULE_ALLOW in foreground
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, true);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mService.setUidPolicy(APP_ID_A, POLICY_NONE);
+        future.get();
+        verifyAndReset();
+
+        // POLICY_NONE should RULE_ALLOW in background
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        future.get();
+        verifyAndReset();
+    }
+
+    @Suppress
+    public void testPolicyReject() throws Exception {
+        Future<Void> future;
+
+        // POLICY_REJECT should RULE_ALLOW in background
+        expectSetUidMeteredNetworkBlacklist(UID_A, true);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        replay();
+        mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
+        verifyAndReset();
+
+        // POLICY_REJECT should RULE_ALLOW in foreground
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, true);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+        future.get();
+        verifyAndReset();
+
+        // POLICY_REJECT should RULE_REJECT in background
+        expectSetUidMeteredNetworkBlacklist(UID_A, true);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        future.get();
+        verifyAndReset();
+    }
+
+    @Suppress
+    public void testPolicyRejectAddRemove() throws Exception {
+        Future<Void> future;
+
+        // POLICY_NONE should have RULE_ALLOW in background
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+        mService.setUidPolicy(APP_ID_A, POLICY_NONE);
+        future.get();
+        verifyAndReset();
+
+        // adding POLICY_REJECT should cause RULE_REJECT
+        expectSetUidMeteredNetworkBlacklist(UID_A, true);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        replay();
+        mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
+        verifyAndReset();
+
+        // removing POLICY_REJECT should return us to RULE_ALLOW
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        mService.setUidPolicy(APP_ID_A, POLICY_NONE);
+        future.get();
+        verifyAndReset();
+    }
+
     public void testLastCycleBoundaryThisMonth() throws Exception {
         // assume cycle day of "5th", which should be in same month
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
@@ -305,7 +414,6 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Test
     public void testLastCycleBoundaryLastMonth() throws Exception {
         // assume cycle day of "20th", which should be in last month
         final long currentTime = parseTime("2007-11-14T00:00:00.000Z");
@@ -317,7 +425,6 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Test
     public void testLastCycleBoundaryThisMonthFebruary() throws Exception {
         // assume cycle day of "30th" in february; should go to january
         final long currentTime = parseTime("2007-02-14T00:00:00.000Z");
@@ -329,7 +436,6 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Test
     public void testLastCycleBoundaryLastMonthFebruary() throws Exception {
         // assume cycle day of "30th" in february, which should clamp
         final long currentTime = parseTime("2007-03-14T00:00:00.000Z");
@@ -341,7 +447,6 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Test
     public void testCycleBoundaryLeapYear() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 29, TIMEZONE_UTC, 1024L, 1024L, false);
@@ -365,7 +470,6 @@
                 computeNextCycleBoundary(parseTime("2007-03-14T00:00:00.000Z"), policy));
     }
 
-    @Test
     public void testNextCycleTimezoneAfterUtc() throws Exception {
         // US/Central is UTC-6
         final NetworkPolicy policy = new NetworkPolicy(
@@ -374,7 +478,6 @@
                 computeNextCycleBoundary(parseTime("2012-01-05T00:00:00.000Z"), policy));
     }
 
-    @Test
     public void testNextCycleTimezoneBeforeUtc() throws Exception {
         // Israel is UTC+2
         final NetworkPolicy policy = new NetworkPolicy(
@@ -383,7 +486,6 @@
                 computeNextCycleBoundary(parseTime("2012-01-05T00:00:00.000Z"), policy));
     }
 
-    @Test
     public void testNextCycleSane() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 31, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED, false);
@@ -399,7 +501,6 @@
         }
     }
 
-    @Test
     public void testLastCycleSane() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 31, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED, false);
@@ -415,7 +516,6 @@
         }
     }
 
-    @Test
     public void testCycleTodayJanuary() throws Exception {
         final NetworkPolicy policy = new NetworkPolicy(
                 sTemplateWifi, 14, "US/Pacific", 1024L, 1024L, false);
@@ -435,7 +535,6 @@
                 computeLastCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy));
     }
 
-    @Test
     public void testLastCycleBoundaryDST() throws Exception {
         final long currentTime = parseTime("1989-01-02T07:30:00.000");
         final long expectedCycle = parseTime("1988-12-03T02:00:00.000Z");
@@ -446,7 +545,6 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Test
     public void testLastCycleBoundaryJanuaryDST() throws Exception {
         final long currentTime = parseTime("1989-01-26T21:00:00.000Z");
         final long expectedCycle = parseTime("1989-01-01T01:59:59.000Z");
@@ -457,10 +555,11 @@
         assertTimeEquals(expectedCycle, actualCycle);
     }
 
-    @Test
+    @Suppress
     public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
+        Future<Void> future;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -471,40 +570,75 @@
         // first, pretend that wifi network comes online. no policy active,
         // which means we shouldn't push limit to interface.
         state = new NetworkState[] { buildWifi() };
-        when(mConnManager.getAllNetworkState()).thenReturn(state);
+        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
         expectCurrentTime();
+        expectClearNotifications();
+        expectAdvisePersistThreshold();
+        future = expectMeteredIfacesChanged();
 
-        mPolicyListener.expect().onMeteredIfacesChanged(any());
+        replay();
         mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-        mPolicyListener.waitAndVerify().onMeteredIfacesChanged(any());
+        future.get();
+        verifyAndReset();
 
         // now change cycle to be on 15th, and test in early march, to verify we
         // pick cycle day in previous month.
-        when(mConnManager.getAllNetworkState()).thenReturn(state);
+        expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
         expectCurrentTime();
 
         // pretend that 512 bytes total have happened
         stats = new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 256L, 2L, 256L, 2L);
-        when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
-                .thenReturn(stats.getTotalBytes());
-
-        mPolicyListener.expect().onMeteredIfacesChanged(any());
-        setNetworkPolicies(new NetworkPolicy(
-                sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
-        mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
+        expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, TIME_MAR_10))
+                .andReturn(stats.getTotalBytes()).atLeastOnce();
+        expectPolicyDataEnable(TYPE_WIFI, true);
 
         // TODO: consider making strongly ordered mock
-        verifyPolicyDataEnable(TYPE_WIFI, true);
-        verifyRemoveInterfaceQuota(TEST_IFACE);
-        verifySetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
+        expectRemoveInterfaceQuota(TEST_IFACE);
+        expectSetInterfaceQuota(TEST_IFACE, (2 * MB_IN_BYTES) - 512);
+
+        expectClearNotifications();
+        expectAdvisePersistThreshold();
+        future = expectMeteredIfacesChanged(TEST_IFACE);
+
+        replay();
+        setNetworkPolicies(new NetworkPolicy(
+                sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
+        future.get();
+        verifyAndReset();
     }
 
-    @Test
+    @Suppress
+    public void testUidRemovedPolicyCleared() throws Exception {
+        Future<Void> future;
+
+        // POLICY_REJECT should RULE_REJECT in background
+        expectSetUidMeteredNetworkBlacklist(UID_A, true);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        replay();
+        mService.setUidPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
+        future.get();
+        verifyAndReset();
+
+        // uninstall should clear RULE_REJECT
+        expectSetUidMeteredNetworkBlacklist(UID_A, false);
+        expectSetUidForeground(UID_A, false);
+        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        replay();
+        final Intent intent = new Intent(ACTION_UID_REMOVED);
+        intent.putExtra(EXTRA_UID, UID_A);
+        mServiceContext.sendBroadcast(intent);
+        future.get();
+        verifyAndReset();
+    }
+
+    @Suppress
     public void testOverWarningLimitNotification() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
-        Future<String> tagFuture = null;
+        Future<Void> future;
+        Future<String> tagFuture;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -519,15 +653,20 @@
 
         {
             expectCurrentTime();
-            when(mConnManager.getAllNetworkState()).thenReturn(state);
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
+            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats.getTotalBytes()).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, true);
 
-            mPolicyListener.expect().onMeteredIfacesChanged(any());
+            expectClearNotifications();
+            expectAdvisePersistThreshold();
+            future = expectMeteredIfacesChanged();
+
+            replay();
             setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1
                     * MB_IN_BYTES, 2 * MB_IN_BYTES, false));
-            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(any());
-            verifyPolicyDataEnable(TYPE_WIFI, true);
+            future.get();
+            verifyAndReset();
         }
 
         // bring up wifi network
@@ -538,17 +677,22 @@
 
         {
             expectCurrentTime();
-            when(mConnManager.getAllNetworkState()).thenReturn(state);
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
+            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats.getTotalBytes()).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, true);
 
-            mPolicyListener.expect().onMeteredIfacesChanged(any());
+            expectRemoveInterfaceQuota(TEST_IFACE);
+            expectSetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
+
+            expectClearNotifications();
+            expectAdvisePersistThreshold();
+            future = expectMeteredIfacesChanged(TEST_IFACE);
+
+            replay();
             mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
-            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
-
-            verifyPolicyDataEnable(TYPE_WIFI, true);
-            verifyRemoveInterfaceQuota(TEST_IFACE);
-            verifySetInterfaceQuota(TEST_IFACE, 2 * MB_IN_BYTES);
+            future.get();
+            verifyAndReset();
         }
 
         // go over warning, which should kick notification
@@ -558,15 +702,18 @@
 
         {
             expectCurrentTime();
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
+            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats.getTotalBytes()).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, true);
+
+            expectForceUpdate();
+            expectClearNotifications();
             tagFuture = expectEnqueueNotification();
 
+            replay();
             mNetworkObserver.limitReached(null, TEST_IFACE);
-
             assertNotificationType(TYPE_WARNING, tagFuture.get());
-            verifyPolicyDataEnable(TYPE_WIFI, true);
-
+            verifyAndReset();
         }
 
         // go over limit, which should kick notification and dialog
@@ -576,14 +723,18 @@
 
         {
             expectCurrentTime();
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
+            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats.getTotalBytes()).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, false);
+
+            expectForceUpdate();
+            expectClearNotifications();
             tagFuture = expectEnqueueNotification();
 
+            replay();
             mNetworkObserver.limitReached(null, TEST_IFACE);
-
             assertNotificationType(TYPE_LIMIT, tagFuture.get());
-            verifyPolicyDataEnable(TYPE_WIFI, false);
+            verifyAndReset();
         }
 
         // now snooze policy, which should remove quota
@@ -591,28 +742,35 @@
 
         {
             expectCurrentTime();
-            when(mConnManager.getAllNetworkState()).thenReturn(state);
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
-            tagFuture = expectEnqueueNotification();
+            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats.getTotalBytes()).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, true);
 
-            mPolicyListener.expect().onMeteredIfacesChanged(any());
-            mService.snoozeLimit(sTemplateWifi);
-            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
-
-            assertNotificationType(TYPE_LIMIT_SNOOZED, tagFuture.get());
             // snoozed interface still has high quota so background data is
             // still restricted.
-            verifyRemoveInterfaceQuota(TEST_IFACE);
-            verifySetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
-            verifyPolicyDataEnable(TYPE_WIFI, true);
+            expectRemoveInterfaceQuota(TEST_IFACE);
+            expectSetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
+            expectAdvisePersistThreshold();
+            expectMeteredIfacesChanged(TEST_IFACE);
+
+            future = expectClearNotifications();
+            tagFuture = expectEnqueueNotification();
+
+            replay();
+            mService.snoozeLimit(sTemplateWifi);
+            assertNotificationType(TYPE_LIMIT_SNOOZED, tagFuture.get());
+            future.get();
+            verifyAndReset();
         }
     }
 
-    @Test
+    @Suppress
     public void testMeteredNetworkWithoutLimit() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
+        Future<Void> future;
+        Future<String> tagFuture;
 
         final long TIME_FEB_15 = 1171497600000L;
         final long TIME_MAR_10 = 1173484800000L;
@@ -627,19 +785,24 @@
 
         {
             expectCurrentTime();
-            when(mConnManager.getAllNetworkState()).thenReturn(state);
-            when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15,
-                    currentTimeMillis())).thenReturn(stats.getTotalBytes());
+            expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+            expect(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, currentTimeMillis()))
+                    .andReturn(stats.getTotalBytes()).atLeastOnce();
+            expectPolicyDataEnable(TYPE_WIFI, true);
 
-            mPolicyListener.expect().onMeteredIfacesChanged(any());
+            expectRemoveInterfaceQuota(TEST_IFACE);
+            expectSetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
+
+            expectClearNotifications();
+            expectAdvisePersistThreshold();
+            future = expectMeteredIfacesChanged(TEST_IFACE);
+
+            replay();
             setNetworkPolicies(new NetworkPolicy(
                     sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, LIMIT_DISABLED,
                     true));
-            mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE}));
-
-            verifyPolicyDataEnable(TYPE_WIFI, true);
-            verifyRemoveInterfaceQuota(TEST_IFACE);
-            verifySetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE);
+            future.get();
+            verifyAndReset();
         }
     }
 
@@ -662,36 +825,87 @@
     }
 
     private void expectCurrentTime() throws Exception {
-        when(mTime.forceRefresh()).thenReturn(false);
-        when(mTime.hasCache()).thenReturn(true);
-        when(mTime.currentTimeMillis()).thenReturn(currentTimeMillis());
-        when(mTime.getCacheAge()).thenReturn(0L);
-        when(mTime.getCacheCertainty()).thenReturn(0L);
+        expect(mTime.forceRefresh()).andReturn(false).anyTimes();
+        expect(mTime.hasCache()).andReturn(true).anyTimes();
+        expect(mTime.currentTimeMillis()).andReturn(currentTimeMillis()).anyTimes();
+        expect(mTime.getCacheAge()).andReturn(0L).anyTimes();
+        expect(mTime.getCacheCertainty()).andReturn(0L).anyTimes();
+    }
+
+    private void expectForceUpdate() throws Exception {
+        mStatsService.forceUpdate();
+        expectLastCall().atLeastOnce();
+    }
+
+    private Future<Void> expectClearNotifications() throws Exception {
+        final FutureAnswer future = new FutureAnswer();
+        mNotifManager.cancelNotificationWithTag(
+                isA(String.class), isA(String.class), anyInt(), anyInt());
+        expectLastCall().andAnswer(future).anyTimes();
+        return future;
     }
 
     private Future<String> expectEnqueueNotification() throws Exception {
-        final FutureAnswer<String> futureAnswer = new FutureAnswer<String>(2);
-        doAnswer(futureAnswer).when(mNotifManager).enqueueNotificationWithTag(
-                anyString(), anyString(), anyString() /* capture here (index 2)*/,
-                anyInt(), isA(Notification.class), isA(int[].class), anyInt());
-        return futureAnswer;
+        final FutureCapture<String> tag = new FutureCapture<String>();
+        mNotifManager.enqueueNotificationWithTag(isA(String.class), isA(String.class),
+                capture(tag.capture), anyInt(),
+                isA(Notification.class), isA(int[].class), UserHandle.myUserId());
+        return tag;
     }
 
-    private void verifySetInterfaceQuota(String iface, long quotaBytes) throws Exception {
-        verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(iface, quotaBytes);
+    private void expectSetInterfaceQuota(String iface, long quotaBytes) throws Exception {
+        mNetworkManager.setInterfaceQuota(iface, quotaBytes);
+        expectLastCall().atLeastOnce();
     }
 
-    private void verifyRemoveInterfaceQuota(String iface) throws Exception {
-        verify(mNetworkManager, atLeastOnce()).removeInterfaceQuota(iface);
+    private void expectRemoveInterfaceQuota(String iface) throws Exception {
+        mNetworkManager.removeInterfaceQuota(iface);
+        expectLastCall().atLeastOnce();
     }
 
-    private Future<Void> verifyPolicyDataEnable(int type, boolean enabled) throws Exception {
+    private void expectSetInterfaceAlert(String iface, long alertBytes) throws Exception {
+        mNetworkManager.setInterfaceAlert(iface, alertBytes);
+        expectLastCall().atLeastOnce();
+    }
+
+    private void expectRemoveInterfaceAlert(String iface) throws Exception {
+        mNetworkManager.removeInterfaceAlert(iface);
+        expectLastCall().atLeastOnce();
+    }
+
+    private void expectSetUidMeteredNetworkBlacklist(int uid, boolean rejectOnQuotaInterfaces)
+            throws Exception {
+        mNetworkManager.setUidMeteredNetworkBlacklist(uid, rejectOnQuotaInterfaces);
+        expectLastCall().atLeastOnce();
+    }
+
+    private void expectSetUidForeground(int uid, boolean uidForeground) throws Exception {
+        mStatsService.setUidForeground(uid, uidForeground);
+        expectLastCall().atLeastOnce();
+    }
+
+    private Future<Void> expectRulesChanged(int uid, int policy) throws Exception {
+        final FutureAnswer future = new FutureAnswer();
+        mPolicyListener.onUidRulesChanged(eq(uid), eq(policy));
+        expectLastCall().andAnswer(future);
+        return future;
+    }
+
+    private Future<Void> expectMeteredIfacesChanged(String... ifaces) throws Exception {
+        final FutureAnswer future = new FutureAnswer();
+        mPolicyListener.onMeteredIfacesChanged(aryEq(ifaces));
+        expectLastCall().andAnswer(future);
+        return future;
+    }
+
+    private Future<Void> expectPolicyDataEnable(int type, boolean enabled) throws Exception {
         // TODO: bring back this test
         return null;
     }
 
-    private void verifyAdvisePersistThreshold() throws Exception {
-        verify(mStatsService).advisePersistThreshold(anyLong());
+    private void expectAdvisePersistThreshold() throws Exception {
+        mStatsService.advisePersistThreshold(anyLong());
+        expectLastCall().anyTimes();
     }
 
     private static class TestAbstractFuture<T> extends AbstractFuture<T> {
@@ -705,21 +919,50 @@
         }
     }
 
-    private static class FutureAnswer<T> extends TestAbstractFuture<T> implements Answer<Void> {
-        private final int index;
-
-        FutureAnswer(int index) {
-            this.index = index;
-        }
+    private static class FutureAnswer extends TestAbstractFuture<Void> implements IAnswer<Void> {
         @Override
-        public Void answer(InvocationOnMock invocation) throws Throwable {
-            @SuppressWarnings("unchecked")
-            T captured = (T) invocation.getArguments()[index];
-            set(captured);
+        public Void answer() {
+            set(null);
             return null;
         }
     }
 
+    private static class FutureCapture<T> extends TestAbstractFuture<T> {
+        public Capture<T> capture = new Capture<T>() {
+            @Override
+            public void setValue(T value) {
+                super.setValue(value);
+                set(value);
+            }
+        };
+    }
+
+    private static class IdleFuture extends AbstractFuture<Void> implements IdleHandler {
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            try {
+                return get(5, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public boolean queueIdle() {
+            set(null);
+            return false;
+        }
+    }
+
+    /**
+     * Wait until {@link #mService} internal {@link Handler} is idle.
+     */
+    private IdleFuture expectIdle() {
+        final IdleFuture future = new IdleFuture();
+        mService.addIdleHandler(future);
+        return future;
+    }
+
     private static void assertTimeEquals(long expected, long actual) {
         if (expected != actual) {
             fail("expected " + formatTime(expected) + " but was actually " + formatTime(actual));
@@ -747,7 +990,7 @@
     }
 
     private static void assertNotificationType(int expected, String actualTag) {
-        assertEquals("notification type mismatch for '" + actualTag +"'",
+        assertEquals(
                 Integer.toString(expected), actualTag.substring(actualTag.lastIndexOf(':') + 1));
     }
 
@@ -768,59 +1011,15 @@
         mElapsedRealtime += duration;
     }
 
-    /**
-     * Creates a mock and registers it to {@link LocalServices}.
-     */
-    private static <T> T addLocalServiceMock(Class<T> clazz) {
-        final T mock = mock(clazz);
-        LocalServices.addService(clazz, mock);
-        return mock;
+    private void replay() {
+        EasyMock.replay(mActivityManager, mStatsService, mPolicyListener, mNetworkManager, mTime,
+                mConnManager, mNotifManager);
     }
 
-    /**
-     * Custom Mockito answer used to verify async {@link INetworkPolicyListener} calls.
-     *
-     * <p>Typical usage:
-     * <pre><code>
-     *    mPolicyListener.expect().someCallback(any());
-     *    // do something on objects under test
-     *    mPolicyListener.waitAndVerify().someCallback(eq(expectedValue));
-     * </code></pre>
-     */
-    final class NetworkPolicyListenerAnswer implements Answer<Void> {
-        private CountDownLatch latch;
-        private final INetworkPolicyListener listener;
-
-        NetworkPolicyListenerAnswer(NetworkPolicyManagerService service) {
-            this.listener = mock(INetworkPolicyListener.class);
-            // RemoteCallbackList needs a binder to use as key
-            when(listener.asBinder()).thenReturn(new Binder());
-            service.registerListener(listener);
-        }
-
-        @Override
-        public Void answer(InvocationOnMock invocation) throws Throwable {
-            Log.d(TAG,"counting down on answer: " + invocation);
-            latch.countDown();
-            return null;
-        }
-
-        INetworkPolicyListener expect() {
-            assertNull("expect() called before waitAndVerify()", latch);
-            latch = new CountDownLatch(1);
-            return doAnswer(this).when(listener);
-        }
-
-        INetworkPolicyListener waitAndVerify() {
-            assertNotNull("waitAndVerify() called before expect()", latch);
-            try {
-                assertTrue("callback not called in 5 seconds", latch.await(5, TimeUnit.SECONDS));
-            } catch (InterruptedException e) {
-                fail("Thread interrupted before callback called");
-            } finally {
-                latch = null;
-            }
-            return verify(listener, atLeastOnce());
-        }
+    private void verifyAndReset() {
+        EasyMock.verify(mActivityManager, mStatsService, mPolicyListener, mNetworkManager, mTime,
+                mConnManager, mNotifManager);
+        EasyMock.reset(mActivityManager, mStatsService, mPolicyListener, mNetworkManager, mTime,
+                mConnManager, mNotifManager);
     }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 69f9133..d7cd07a 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.
      */
@@ -1215,6 +1221,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);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 307dc1a..1554868 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;
@@ -39,8 +40,11 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 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 +57,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;
@@ -4041,6 +4047,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
      *
diff --git a/telephony/java/android/telephony/ims/ImsServiceBase.java b/telephony/java/android/telephony/ims/ImsServiceBase.java
new file mode 100644
index 0000000..0b50eca
--- /dev/null
+++ b/telephony/java/android/telephony/ims/ImsServiceBase.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;
+
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base ImsService Implementation, which is used by the ImsResolver to bind.
+ * @hide
+ */
+@SystemApi
+public class ImsServiceBase extends Service {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
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..0509d60
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telephony.ims.feature;
+
+/**
+ * Base class for all IMS features that are supported by the framework.
+ * @hide
+ */
+public class ImsFeature {
+
+    // Invalid feature value
+    public static final int INVALID = -1;
+    // ImsFeatures that are defined in the Manifests
+    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;
+}
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..fa86a43
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * {@hide}
+ */
+interface IImsServiceController {
+    void createImsFeature(int slotId, int feature);
+    void removeImsFeature(int slotId, int feature);
+}
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..0a36b6b
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims.internal;
+
+/**
+ * {@hide}
+ */
+oneway interface IImsServiceFeatureListener {
+    void imsFeatureCreated(int slotId, int feature);
+    void imsFeatureRemoved(int slotId, int feature);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a8eaf36..d406cb0 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -31,6 +31,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;
 
@@ -715,6 +717,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.
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 46b6403..2d7a68f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -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,32 @@
     }
 
     /**
-     * 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();
-
+    private static void waitForIdleHandler(HandlerThread handler, int timeoutMs) {
+        final ConditionVariable cv = new ConditionVariable();
+        final MessageQueue queue = handler.getLooper().getQueue();
+        final IdleHandler idleHandler = () -> {
             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);
+                cv.open();
+                return false; // Remove the idleHandler.
             }
-
-            if (!cv.block(timeoutMs)) {
-                fail("HandlerThread " + getName() +
-                        " did not become idle after " + timeoutMs + " ms");
-                queue.removeIdleHandler(mIdleHandler);
+        };
+        synchronized (queue) {
+            if (queue.isIdle()) {
+                return;
             }
+            queue.addIdleHandler(idleHandler);
+        }
+        if (!cv.block(timeoutMs)) {
+            fail("HandlerThread " + handler.getName() +
+                    " did not become idle after " + timeoutMs + " ms");
+            queue.removeIdleHandler(idleHandler);
         }
     }
 
-    // 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 +202,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 +231,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 +263,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 +303,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            mHandlerThread.waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -648,11 +630,6 @@
         }
 
         @Override
-        protected HandlerThread createHandlerThread() {
-            return new IdleableHandlerThread("WrappedConnectivityService");
-        }
-
-        @Override
         protected int getDefaultTcpRwnd() {
             // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
             return 0;
@@ -710,7 +687,7 @@
         }
 
         public void waitForIdle(int timeoutMs) {
-            ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs);
+            waitForIdleHandler(mHandlerThread, timeoutMs);
         }
 
         public void waitForIdle() {
@@ -1135,7 +1112,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<>();
 
@@ -1487,8 +1464,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();
@@ -1977,7 +1954,9 @@
         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);
+        mService.waitForIdle();
+        int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
+        fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
         callback.assertNoCallback();
         assertFalse(isForegroundNetwork(mCellNetworkAgent));
         assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -2003,6 +1982,7 @@
 
         // Disconnect wifi and check that cell is foreground again.
         mWiFiNetworkAgent.disconnect();
+        mService.waitForIdle();
         callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
         fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
@@ -2339,14 +2319,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.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, timeoutMs);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        sleepFor(15);
         networkCallback.assertNoCallback();
     }
 
@@ -2359,17 +2339,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.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, 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 +2365,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 +2386,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,13 +2404,13 @@
         networkCallback.assertNoCallback();
     }
 
-    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
+    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) {
         long start = SystemClock.elapsedRealtime();
         while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
             if (fn.getAsBoolean()) {
                 return;
             }
-            Thread.sleep(10);
+            sleepFor(15);
         }
         assertTrue(fn.getAsBoolean());
     }
@@ -2594,7 +2578,9 @@
         callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK);
 
         // ... and that stopping it after that has no adverse effects.
-        assertNull(mCm.getNetworkCapabilities(myNet));
+        // TODO: investigate assertEventuallyTrue is needed and waitForIdle() is not enough
+        final Network myNetAlias = myNet;
+        assertEventuallyTrue(() -> mCm.getNetworkCapabilities(myNetAlias) == null, 100);
         ka.stop();
 
         // Reconnect.
@@ -2838,11 +2824,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/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index aa491bb..14b5cbe 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
-import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.DhcpClientEvent;
@@ -113,27 +112,6 @@
         assertEquals("", output3);
     }
 
-    public void testRateLimiting() {
-        final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
-        final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
-        final long fakeTimestamp = 1;
-
-        int attempt = 100; // More than burst quota, but less than buffer size.
-        for (int i = 0; i < attempt; i++) {
-            logger.log(ev);
-        }
-
-        String output1 = getdump("flush");
-        assertFalse("".equals(output1));
-
-        for (int i = 0; i < attempt; i++) {
-            assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
-        }
-
-        String output2 = getdump("flush");
-        assertEquals("", output2);
-    }
-
     public void testEndToEndLogging() {
         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
 
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
deleted file mode 100644
index 813e928..0000000
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ /dev/null
@@ -1,142 +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.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/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 1e67769..b8c739b 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -91,12 +91,12 @@
     }
 
     @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.hasNoCallbacks());
@@ -197,9 +197,12 @@
                    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;
                 }
             }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 3b6e76f..ab725e2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -120,11 +120,7 @@
     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";
 
     /**
      * Broadcast intent action indicating that the a Passpoint release
@@ -159,6 +155,127 @@
     public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay";
 
     /**
+     * Broadcast intent action indicating that a Passpoint provider icon has been received.
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     */
+    public static final String ACTION_PASSPOINT_ICON =
+            "android.net.wifi.action.PASSPOINT_ICON";
+    /**
+     * BSSID of the sender.
+     *
+     * Type: long
+     */
+    public static final String EXTRA_PASSPOINT_ICON_BSSID =
+            "android.net.wifi.extra.PASSPOINT_ICON_BSSID";
+    /**
+     * Filename of the icon.
+     *
+     * Type: String
+     */
+    public static final String EXTRA_PASSPOINT_ICON_FILENAME =
+            "android.net.wifi.extra.PASSPOINT_ICON_FILENAME";
+    /**
+     * Binary blob of the icon.
+     *
+     * Type: byte[]
+     */
+    public static final String EXTRA_PASSPOINT_ICON_DATA =
+            "android.net.wifi.extra.PASSPOINT_ICON_DATA";
+
+    /**
+     * Broadcast intent action indicating a Passpoint OSU Providers List element has been received.
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     */
+    public static final String ACTION_PASSPOINT_OSU_PROVIDERS_LIST =
+            "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST";
+    /**
+     * BSSID of the sender.
+     *
+     * Type: long
+     */
+    public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID =
+            "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID";
+    /**
+     * Raw data of OSU Providers List ANQP element.  Refer to Section 4.8 of Hotspot 2.0 Release 2
+     * Technical Specification for the exact data format.
+     *
+     * Type: byte[]
+     */
+    public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA =
+            "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA";
+
+    /**
+     * Broadcast intent action indicating that a Passpoint Deauth Imminent frame has been received.
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     */
+    public static final String ACTION_PASSPOINT_DEAUTH_IMMINENT =
+            "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT";
+    /**
+     * The BSSID of the sender.
+     *
+     * Type: long
+     */
+    public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID =
+            "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID";
+    /**
+     * Flag indicating failure at BSS (Basic Service Set) or ESS (Extended Service Set) level.
+     *
+     * Type: boolean
+     */
+    public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS =
+            "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS";
+    /**
+     * Delay in seconds that a device shall wait before attempting re-association to the same BSS
+     * or ESS (as indicated by {@link #EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS}.
+     *
+     * Type: int
+     */
+    public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY =
+            "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY";
+    /**
+     * URL that provides a webpage explaining the deauth reason.
+     *
+     * Type: String
+     */
+    public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL =
+            "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL";
+
+    /**
+     * Broadcast intent action indicating a Passpoint subscription remediation frame has been
+     * received.
+     *
+     * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE
+     */
+    public static final String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION =
+            "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION";
+    /**
+     * The BSSID of the sender.
+     *
+     * Type: long
+     */
+    public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID =
+            "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID";
+    /**
+     * The protocol supported by the subscription remediation server. The possible values are:
+     * 0 - OMA DM
+     * 1 - SOAP XML SPP
+     *
+     * Type: int
+     */
+    public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD =
+            "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD";
+    /**
+     * URL of the subscription remediation server.
+     *
+     * Type: String
+     */
+    public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL =
+            "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL";
+
+    /**
      * Broadcast intent action indicating that Wi-Fi has been enabled, disabled,
      * enabling, disabling, or unknown. One extra provides this state as an int.
      * Another extra provides the previous state, if available.
@@ -898,10 +1015,10 @@
     }
 
     /**
-     * Query for a Hotspot 2.0 release 2 OSU icon
+     * Query for a Hotspot 2.0 release 2 OSU icon file.
+     *
      * @param bssid The BSSID of the AP
-     * @param fileName Icon file name
-     * @hide
+     * @param fileName File name of the icon to query
      */
     public void queryPasspointIcon(long bssid, String fileName) {
         try {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 643753a..ca4d121 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -18,9 +18,19 @@
 
 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.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.HashMap;
+import java.util.Map;
+
 /**
  * Class representing Passpoint configuration.  This contains configurations specified in
  * PerProviderSubscription (PPS) Management Object (MO) tree.
@@ -28,13 +38,108 @@
  * 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 {
+    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;
+
     public HomeSP homeSp = null;
     public Credential credential = null;
+    public Policy policy = null;
+
+    /**
+     * Meta data for performing subscription update.
+     */
+    public UpdateParameter subscriptionUpdate = null;
+
+    /**
+     * 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.
+     */
+    public Map<String, byte[]> trustRootCertList = null;
+
+    /**
+     * Set by the subscription server, updated every time the configuration is updated by
+     * the subscription server.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    public int updateIdentifier = Integer.MIN_VALUE;
+
+    /**
+     * The priority of the credential.
+     *
+     * Use Integer.MIN_VALUE to indicate unset value.
+     */
+    public int credentialPriority = Integer.MIN_VALUE;
+
+    /**
+     * 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.
+     */
+    public long subscriptionCreationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * 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.
+     */
+    public long subscriptionExpirationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * The type of the subscription.  This is defined by the provider and the value is provider
+     * specific.
+     */
+    public String subscriptionType = null;
+
+    /**
+     * 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.
+     */
+    public long usageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
+
+    /**
+     * 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.
+     */
+    public long usageLimitStartTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * 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.
+     */
+    public long usageLimitDataLimit = Long.MIN_VALUE;
+
+    /**
+     * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
+     * A value of zero indicate unlimited time usage.
+     */
+    public long usageLimitTimeLimitInMinutes = Long.MIN_VALUE;
+
 
     /**
      * Constructor for creating PasspointConfiguration with default values.
@@ -47,14 +152,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.homeSp != null) {
+            homeSp = new HomeSP(source.homeSp);
+        }
+        if (source.credential != null) {
+            credential = new Credential(source.credential);
+        }
+        if (source.policy != null) {
+            policy = new Policy(source.policy);
+        }
+        if (source.trustRootCertList != null) {
+            trustRootCertList = Collections.unmodifiableMap(source.trustRootCertList);
+        }
+        if (source.subscriptionUpdate != null) {
+            subscriptionUpdate = new UpdateParameter(source.subscriptionUpdate);
+        }
+        updateIdentifier = source.updateIdentifier;
+        credentialPriority = source.credentialPriority;
+        subscriptionCreationTimeInMs = source.subscriptionCreationTimeInMs;
+        subscriptionExpirationTimeInMs = source.subscriptionExpirationTimeInMs;
+        subscriptionType = source.subscriptionType;
+        usageLimitDataLimit = source.usageLimitDataLimit;
+        usageLimitStartTimeInMs = source.usageLimitStartTimeInMs;
+        usageLimitTimeLimitInMinutes = source.usageLimitTimeLimitInMinutes;
+        usageLimitUsageTimePeriodInMinutes = source.usageLimitUsageTimePeriodInMinutes;
     }
 
     @Override
@@ -66,6 +191,18 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(homeSp, flags);
         dest.writeParcelable(credential, flags);
+        dest.writeParcelable(policy, flags);
+        dest.writeParcelable(subscriptionUpdate, flags);
+        writeTrustRootCerts(dest, trustRootCertList);
+        dest.writeInt(updateIdentifier);
+        dest.writeInt(credentialPriority);
+        dest.writeLong(subscriptionCreationTimeInMs);
+        dest.writeLong(subscriptionExpirationTimeInMs);
+        dest.writeString(subscriptionType);
+        dest.writeLong(usageLimitUsageTimePeriodInMinutes);
+        dest.writeLong(usageLimitStartTimeInMs);
+        dest.writeLong(usageLimitDataLimit);
+        dest.writeLong(usageLimitTimeLimitInMinutes);
     }
 
     @Override
@@ -77,9 +214,22 @@
             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 (homeSp == null ? that.homeSp == null : homeSp.equals(that.homeSp))
+                && (credential == null ? that.credential == null
+                        : credential.equals(that.credential))
+                && (policy == null) ? that.policy == null : policy.equals(that.policy)
+                && (subscriptionUpdate == null) ? that.subscriptionUpdate == null
+                        : subscriptionUpdate.equals(that.subscriptionUpdate)
+                && isTrustRootCertListEquals(trustRootCertList, that.trustRootCertList)
+                && updateIdentifier == that.updateIdentifier
+                && credentialPriority == that.credentialPriority
+                && subscriptionCreationTimeInMs == that.subscriptionCreationTimeInMs
+                && subscriptionExpirationTimeInMs == that.subscriptionExpirationTimeInMs
+                && TextUtils.equals(subscriptionType, that.subscriptionType)
+                && usageLimitUsageTimePeriodInMinutes == that.usageLimitUsageTimePeriodInMinutes
+                && usageLimitStartTimeInMs == that.usageLimitStartTimeInMs
+                && usageLimitDataLimit == that.usageLimitDataLimit
+                && usageLimitTimeLimitInMinutes == that .usageLimitTimeLimitInMinutes;
     }
 
     /**
@@ -94,6 +244,37 @@
         if (credential == null || !credential.validate()) {
             return false;
         }
+        if (policy != null && !policy.validate()) {
+            return false;
+        }
+        if (subscriptionUpdate != null && !subscriptionUpdate.validate()) {
+            return false;
+        }
+        if (trustRootCertList != null) {
+            for (Map.Entry<String, byte[]> entry : trustRootCertList.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;
     }
 
@@ -104,11 +285,88 @@
                 PasspointConfiguration config = new PasspointConfiguration();
                 config.homeSp = in.readParcelable(null);
                 config.credential = in.readParcelable(null);
+                config.policy = in.readParcelable(null);
+                config.subscriptionUpdate = in.readParcelable(null);
+                config.trustRootCertList = readTrustRootCerts(in);
+                config.updateIdentifier = in.readInt();
+                config.credentialPriority = in.readInt();
+                config.subscriptionCreationTimeInMs = in.readLong();
+                config.subscriptionExpirationTimeInMs = in.readLong();
+                config.subscriptionType = in.readString();
+                config.usageLimitUsageTimePeriodInMinutes = in.readLong();
+                config.usageLimitStartTimeInMs = in.readLong();
+                config.usageLimitDataLimit = in.readLong();
+                config.usageLimitTimeLimitInMinutes = 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
index 2731f428..22b0f97 100644
--- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
+++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
@@ -19,11 +19,16 @@
 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;
@@ -128,6 +133,20 @@
     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";
@@ -147,18 +166,56 @@
      * 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_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.
@@ -335,6 +392,10 @@
      *     ...
      *   </RTPProperties>
      *   <Node>
+     *     <NodeName>UpdateIdentifier</NodeName>
+     *     <Value>...</Value>
+     *   </Node>
+     *   <Node>
      *     ...
      *   </Node>
      * </Node>
@@ -347,11 +408,12 @@
             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("Duplicant NodeName: " + child.getText());
+                        throw new ParsingException("Duplicate NodeName: " + child.getText());
                     }
                     nodeName = child.getText();
                     if (!TextUtils.equals(nodeName, NODE_PER_PROVIDER_SUBSCRIPTION)) {
@@ -359,13 +421,22 @@
                     }
                     break;
                 case TAG_NODE:
-                    // Only one PerProviderSubscription instance is expected and allowed.
-                    if (config != null) {
-                        throw new ParsingException("Multiple PPS instance");
+                    // 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);
                     }
-                    // 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.
@@ -378,6 +449,9 @@
                     throw new ParsingException("Unknown tag under PPS node: " + child.getTag());
             }
         }
+        if (config != null && updateIdentifier != Integer.MIN_VALUE) {
+            config.updateIdentifier = updateIdentifier;
+        }
         return config;
     }
 
@@ -537,6 +611,21 @@
                 case NODE_CREDENTIAL:
                     config.credential = parseCredential(child);
                     break;
+                case NODE_POLICY:
+                    config.policy = parsePolicy(child);
+                    break;
+                case NODE_AAA_SERVER_TRUST_ROOT:
+                    config.trustRootCertList = parseAAAServerTrustRootList(child);
+                    break;
+                case NODE_SUBSCRIPTION_UPDATE:
+                    config.subscriptionUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SUBSCRIPTION_PARAMETER:
+                    parseSubscriptionParameter(child, config);
+                    break;
+                case NODE_CREDENTIAL_PRIORITY:
+                    config.credentialPriority = parseInteger(getPpsNodeValue(child));
+                    break;
                 default:
                     throw new ParsingException("Unknown node: " + child.getName());
             }
@@ -602,11 +691,7 @@
         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]);
-            }
+            oiArray[i] = parseLong(oiStrArray[i], 16);
         }
         return oiArray;
     }
@@ -657,11 +742,7 @@
                     ssid = getPpsNodeValue(child);
                     break;
                 case NODE_HESSID:
-                    try {
-                        hessid = Long.parseLong(getPpsNodeValue(child), 16);
-                    } catch (NumberFormatException e) {
-                        throw new ParsingException("Invalid HESSID: " + getPpsNodeValue(child));
-                    }
+                    hessid = parseLong(getPpsNodeValue(child), 16);
                     break;
                 default:
                     throw new ParsingException("Unknown node under NetworkID instance: " +
@@ -812,6 +893,12 @@
         Credential credential = new Credential();
         for (PPSNode child: node.getChildren()) {
             switch (child.getName()) {
+                case NODE_CREATION_DATE:
+                    credential.creationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    credential.expirationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
                 case NODE_USERNAME_PASSWORD:
                     credential.userCredential = parseUserCredential(child);
                     break;
@@ -821,6 +908,10 @@
                 case NODE_REALM:
                     credential.realm = getPpsNodeValue(child);
                     break;
+                case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+                    credential.checkAAAServerCertStatus =
+                            Boolean.parseBoolean(getPpsNodeValue(child));
+                    break;
                 case NODE_SIM:
                     credential.simCredential = parseSimCredential(child);
                     break;
@@ -855,6 +946,15 @@
                 case NODE_PASSWORD:
                     userCred.password = getPpsNodeValue(child);
                     break;
+                case NODE_MACHINE_MANAGED:
+                    userCred.machineManaged = Boolean.parseBoolean(getPpsNodeValue(child));
+                    break;
+                case NODE_SOFT_TOKEN_APP:
+                    userCred.softTokenApp = getPpsNodeValue(child);
+                    break;
+                case NODE_ABLE_TO_SHARE:
+                    userCred.ableToShare = Boolean.parseBoolean(getPpsNodeValue(child));
+                    break;
                 case NODE_EAP_METHOD:
                     parseEAPMethod(child, userCred);
                     break;
@@ -889,6 +989,15 @@
                 case NODE_INNER_METHOD:
                     userCred.nonEapInnerMethod = 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());
             }
@@ -957,6 +1066,503 @@
     }
 
     /**
+     * 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.preferredRoamingPartnerList = parsePreferredRoamingPartnerList(child);
+                    break;
+                case NODE_MIN_BACKHAUL_THRESHOLD:
+                    parseMinBackhaulThreshold(child, policy);
+                    break;
+                case NODE_POLICY_UPDATE:
+                    policy.policyUpdate = parseUpdateParameter(child);
+                    break;
+                case NODE_SP_EXCLUSION_LIST:
+                    policy.excludedSsidList = parseSpExclusionList(child);
+                    break;
+                case NODE_REQUIRED_PROTO_PORT_TUPLE:
+                    policy.requiredProtoPortMap = parseRequiredProtoPortTuple(child);
+                    break;
+                case NODE_MAXIMUM_BSS_LOAD_VALUE:
+                    policy.maximumBssLoadValue = 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.fqdn = fqdnMatchArray[0];
+                    if (TextUtils.equals(fqdnMatchArray[1], "exactMatch")) {
+                        roamingPartner.fqdnExactMatch = true;
+                    } else if (TextUtils.equals(fqdnMatchArray[1], "includeSubdomains")) {
+                        roamingPartner.fqdnExactMatch = false;
+                    } else {
+                        throw new ParsingException("Invalid FQDN_Match: " + fqdnMatch);
+                    }
+                    break;
+                case NODE_PRIORITY:
+                    roamingPartner.priority = parseInteger(getPpsNodeValue(child));
+                    break;
+                case NODE_COUNTRY:
+                    roamingPartner.countries = 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.minHomeDownlinkBandwidth = downlinkBandwidth;
+            policy.minHomeUplinkBandwidth = uplinkBandwidth;
+        } else if (TextUtils.equals(networkType, "roaming")) {
+            policy.minRoamingDownlinkBandwidth = downlinkBandwidth;
+            policy.minRoamingUplinkBandwidth = 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.updateIntervalInMinutes = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_UPDATE_METHOD:
+                    updateParam.updateMethod = getPpsNodeValue(child);
+                    break;
+                case NODE_RESTRICTION:
+                    updateParam.restriction = getPpsNodeValue(child);
+                    break;
+                case NODE_URI:
+                    updateParam.serverUri = getPpsNodeValue(child);
+                    break;
+                case NODE_USERNAME_PASSWORD:
+                    Pair<String, String> usernamePassword = parseUpdateUserCredential(child);
+                    updateParam.username = usernamePassword.first;
+                    updateParam.base64EncodedPassword = usernamePassword.second;
+                    break;
+                case NODE_TRUST_ROOT:
+                    Pair<String, byte[]> trustRoot = parseTrustRoot(child);
+                    updateParam.trustRootCertUrl = trustRoot.first;
+                    updateParam.trustRootCertSha256Fingerprint = 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.subscriptionCreationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_EXPIRATION_DATE:
+                    config.subscriptionExpirationTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_TYPE_OF_SUBSCRIPTION:
+                    config.subscriptionType = 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.usageLimitDataLimit = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_START_DATE:
+                    config.usageLimitStartTimeInMs = parseDate(getPpsNodeValue(child));
+                    break;
+                case NODE_TIME_LIMIT:
+                    config.usageLimitTimeLimitInMinutes = parseLong(getPpsNodeValue(child), 10);
+                    break;
+                case NODE_USAGE_TIME_PERIOD:
+                    config.usageLimitUsageTimePeriodInMinutes =
+                            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
@@ -981,6 +1587,22 @@
     }
 
     /**
+     * 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
@@ -996,6 +1618,21 @@
     }
 
     /**
+     * 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
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 790dfaf..3374f42d 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -23,6 +23,7 @@
 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;
@@ -41,8 +42,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 {
@@ -52,7 +51,21 @@
      * 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.
+     */
+    public long creationTimeInMs = Long.MIN_VALUE;
+
+    /**
+     * 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.
+     */
+    public long expirationTimeInMs = Long.MIN_VALUE;
 
     /**
      * The realm associated with this credential.  It will be used to determine
@@ -62,6 +75,13 @@
     public String realm = null;
 
     /**
+     * When set to true, the device should check AAA (Authentication, Authorization,
+     * and Accounting) server's certificate during EAP (Extensible Authentication
+     * Protocol) authentication.
+     */
+    public boolean checkAAAServerCertStatus = false;
+
+    /**
      * Username-password based credential.
      * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
      */
@@ -70,13 +90,13 @@
          * 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 Non-EAP inner methods.  Refer to
@@ -97,6 +117,21 @@
         public String password = null;
 
         /**
+         * Flag indicating if the password is machine managed.
+         */
+        public boolean machineManaged = false;
+
+        /**
+         * The name of the application used to generate the password.
+         */
+        public String softTokenApp = null;
+
+        /**
+         * Flag indicating if this credential is usable on other mobile devices as well.
+         */
+        public boolean ableToShare = false;
+
+        /**
          * EAP (Extensible Authentication Protocol) method type.
          * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
          * for valid values.
@@ -123,6 +158,9 @@
             if (source != null) {
                 username = source.username;
                 password = source.password;
+                machineManaged = source.machineManaged;
+                softTokenApp = source.softTokenApp;
+                ableToShare = source.ableToShare;
                 eapType = source.eapType;
                 nonEapInnerMethod = source.nonEapInnerMethod;
             }
@@ -137,6 +175,9 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeString(username);
             dest.writeString(password);
+            dest.writeInt(machineManaged ? 1 : 0);
+            dest.writeString(softTokenApp);
+            dest.writeInt(ableToShare ? 1 : 0);
             dest.writeInt(eapType);
             dest.writeString(nonEapInnerMethod);
         }
@@ -151,10 +192,13 @@
             }
 
             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(username, that.username)
+                    && TextUtils.equals(password, that.password)
+                    && machineManaged == that.machineManaged
+                    && TextUtils.equals(softTokenApp, that.softTokenApp)
+                    && ableToShare == that.ableToShare
+                    && eapType == that.eapType
+                    && TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
         }
 
         /**
@@ -167,8 +211,9 @@
                 Log.d(TAG, "Missing username");
                 return false;
             }
-            if (username.length() > MAX_USERNAME_LENGTH) {
-                Log.d(TAG, "username exceeding maximum length: " + username.length());
+            if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+                Log.d(TAG, "username exceeding maximum length: "
+                        + username.getBytes(StandardCharsets.UTF_8).length);
                 return false;
             }
 
@@ -176,8 +221,9 @@
                 Log.d(TAG, "Missing password");
                 return false;
             }
-            if (password.length() > MAX_PASSWORD_LENGTH) {
-                Log.d(TAG, "password exceeding maximum length: " + password.length());
+            if (password.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+                Log.d(TAG, "password exceeding maximum length: "
+                        + password.getBytes(StandardCharsets.UTF_8).length);
                 return false;
             }
 
@@ -202,6 +248,9 @@
                     UserCredential userCredential = new UserCredential();
                     userCredential.username = in.readString();
                     userCredential.password = in.readString();
+                    userCredential.machineManaged = in.readInt() != 0;
+                    userCredential.softTokenApp = in.readString();
+                    userCredential.ableToShare = in.readInt() != 0;
                     userCredential.eapType = in.readInt();
                     userCredential.nonEapInnerMethod = in.readString();
                     return userCredential;
@@ -281,8 +330,8 @@
             }
 
             CertificateCredential that = (CertificateCredential) thatObject;
-            return TextUtils.equals(certType, that.certType) &&
-                    Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+            return TextUtils.equals(certType, that.certType)
+                    && Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
         }
 
         /**
@@ -295,8 +344,8 @@
                 Log.d(TAG, "Unsupported certificate type: " + certType);
                 return false;
             }
-            if (certSha256FingerPrint == null ||
-                    certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+            if (certSha256FingerPrint == null
+                    || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
                 Log.d(TAG, "Invalid SHA-256 fingerprint");
                 return false;
             }
@@ -378,8 +427,8 @@
             }
 
             SimCredential that = (SimCredential) thatObject;
-            return TextUtils.equals(imsi, that.imsi) &&
-                    eapType == that.eapType;
+            return TextUtils.equals(imsi, that.imsi)
+                    && eapType == that.eapType;
         }
 
         @Override
@@ -400,8 +449,8 @@
             if (!verifyImsi()) {
                 return false;
             }
-            if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA &&
-                    eapType != EAPConstants.EAP_AKA_PRIME) {
+            if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA
+                    && eapType != EAPConstants.EAP_AKA_PRIME) {
                 Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType);
                 return false;
             }
@@ -490,7 +539,10 @@
      */
     public Credential(Credential source) {
         if (source != null) {
+            creationTimeInMs = source.creationTimeInMs;
+            expirationTimeInMs = source.expirationTimeInMs;
             realm = source.realm;
+            checkAAAServerCertStatus = source.checkAAAServerCertStatus;
             if (source.userCredential != null) {
                 userCredential = new UserCredential(source.userCredential);
             }
@@ -516,7 +568,10 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(creationTimeInMs);
+        dest.writeLong(expirationTimeInMs);
         dest.writeString(realm);
+        dest.writeInt(checkAAAServerCertStatus ? 1 : 0);
         dest.writeParcelable(userCredential, flags);
         dest.writeParcelable(certCredential, flags);
         dest.writeParcelable(simCredential, flags);
@@ -535,16 +590,19 @@
         }
 
         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(realm, that.realm)
+                && creationTimeInMs == that.creationTimeInMs
+                && expirationTimeInMs == that.expirationTimeInMs
+                && checkAAAServerCertStatus == that.checkAAAServerCertStatus
+                && (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);
     }
 
     /**
@@ -557,8 +615,9 @@
             Log.d(TAG, "Missing realm");
             return false;
         }
-        if (realm.length() > MAX_REALM_LENGTH) {
-            Log.d(TAG, "realm exceeding maximum length: " + realm.length());
+        if (realm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+            Log.d(TAG, "realm exceeding maximum length: "
+                    + realm.getBytes(StandardCharsets.UTF_8).length);
             return false;
         }
 
@@ -588,7 +647,10 @@
             @Override
             public Credential createFromParcel(Parcel in) {
                 Credential credential = new Credential();
+                credential.creationTimeInMs = in.readLong();
+                credential.expirationTimeInMs = in.readLong();
                 credential.realm = in.readString();
+                credential.checkAAAServerCertStatus = in.readInt() != 0;
                 credential.userCredential = in.readParcelable(null);
                 credential.certCredential = in.readParcelable(null);
                 credential.simCredential = in.readParcelable(null);
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
new file mode 100644
index 0000000..e923f1f
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+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..b2583d3
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java
@@ -0,0 +1,452 @@
+/**
+ * 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;
+
+/**
+ * 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.
+ *
+ * @hide
+ */
+public final class Policy implements Parcelable {
+    private static final String TAG = "Policy";
+
+    /**
+     * Default priority for preferred roaming partner.
+     */
+    public static final int PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY = 128;
+
+    /**
+     * 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.
+     */
+    public long minHomeDownlinkBandwidth = Long.MIN_VALUE;
+    public long minHomeUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * 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.
+     */
+    public long minRoamingDownlinkBandwidth = Long.MIN_VALUE;
+    public long minRoamingUplinkBandwidth = Long.MIN_VALUE;
+
+    /**
+     * List of SSIDs that are not preferred by the Home SP.
+     */
+    public String[] excludedSsidList = null;
+
+    /**
+     * 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 ",".
+     */
+    public Map<Integer, String> requiredProtoPortMap = null;
+
+    /**
+     * 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.
+     */
+    public int maximumBssLoadValue = Integer.MIN_VALUE;
+
+    /**
+     * 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.
+         */
+        public String fqdn = null;
+
+        /**
+         * 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.
+         */
+        public boolean fqdnExactMatch = false;
+
+        /**
+         * Priority associated with this roaming partner policy.
+         */
+        public int priority = PREFERRED_ROAMING_PARTNER_DEFAULT_PRIORITY;
+
+        /**
+         * A string contained One or more, comma delimited (i.e., ",") ISO/IEC 3166-1 two
+         * character country strings or the country-independent value, "*".
+         */
+        public String countries = null;
+
+        public RoamingPartner() {}
+
+        public RoamingPartner(RoamingPartner source) {
+            if (source != null) {
+                fqdn = source.fqdn;
+                fqdnExactMatch = source.fqdnExactMatch;
+                priority = source.priority;
+                countries = source.countries;
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(fqdn);
+            dest.writeInt(fqdnExactMatch ? 1 : 0);
+            dest.writeInt(priority);
+            dest.writeString(countries);
+        }
+
+        @Override
+        public boolean equals(Object thatObject) {
+            if (this == thatObject) {
+                return true;
+            }
+            if (!(thatObject instanceof RoamingPartner)) {
+                return false;
+            }
+
+            RoamingPartner that = (RoamingPartner) thatObject;
+            return TextUtils.equals(fqdn, that.fqdn)
+                    && fqdnExactMatch == that.fqdnExactMatch
+                    && priority == that.priority
+                    && TextUtils.equals(countries, that.countries);
+        }
+
+        /**
+         * Validate RoamingParnter data.
+         *
+         * @return true on success
+         */
+        public boolean validate() {
+            if (TextUtils.isEmpty(fqdn)) {
+                Log.d(TAG, "Missing FQDN");
+                return false;
+            }
+            if (TextUtils.isEmpty(countries)) {
+                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.fqdn = in.readString();
+                    roamingPartner.fqdnExactMatch = in.readInt() != 0;
+                    roamingPartner.priority = in.readInt();
+                    roamingPartner.countries = in.readString();
+                    return roamingPartner;
+                }
+
+                @Override
+                public RoamingPartner[] newArray(int size) {
+                    return new RoamingPartner[size];
+                }
+            };
+    }
+    public List<RoamingPartner> preferredRoamingPartnerList = null;
+
+    /**
+     * Meta data used for policy update.
+     */
+    public UpdateParameter policyUpdate = null;
+
+    /**
+     * 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;
+        }
+        minHomeDownlinkBandwidth = source.minHomeDownlinkBandwidth;
+        minHomeUplinkBandwidth = source.minHomeUplinkBandwidth;
+        minRoamingDownlinkBandwidth = source.minRoamingDownlinkBandwidth;
+        minRoamingUplinkBandwidth = source.minRoamingUplinkBandwidth;
+        maximumBssLoadValue = source.maximumBssLoadValue;
+        if (source.excludedSsidList != null) {
+            excludedSsidList = Arrays.copyOf(source.excludedSsidList,
+                    source.excludedSsidList.length);
+        }
+        if (source.requiredProtoPortMap != null) {
+            requiredProtoPortMap = Collections.unmodifiableMap(source.requiredProtoPortMap);
+        }
+        if (source.preferredRoamingPartnerList != null) {
+            preferredRoamingPartnerList = Collections.unmodifiableList(
+                    source.preferredRoamingPartnerList);
+        }
+        if (source.policyUpdate != null) {
+            policyUpdate = new UpdateParameter(source.policyUpdate);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(minHomeDownlinkBandwidth);
+        dest.writeLong(minHomeUplinkBandwidth);
+        dest.writeLong(minRoamingDownlinkBandwidth);
+        dest.writeLong(minRoamingUplinkBandwidth);
+        dest.writeStringArray(excludedSsidList);
+        writeProtoPortMap(dest, requiredProtoPortMap);
+        dest.writeInt(maximumBssLoadValue);
+        writeRoamingPartnerList(dest, flags, preferredRoamingPartnerList);
+        dest.writeParcelable(policyUpdate, flags);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof Policy)) {
+            return false;
+        }
+        Policy that = (Policy) thatObject;
+
+        return minHomeDownlinkBandwidth == that.minHomeDownlinkBandwidth
+                && minHomeUplinkBandwidth == that.minHomeUplinkBandwidth
+                && minRoamingDownlinkBandwidth == that.minRoamingDownlinkBandwidth
+                && minRoamingUplinkBandwidth == that.minRoamingUplinkBandwidth
+                && Arrays.equals(excludedSsidList, that.excludedSsidList)
+                && (requiredProtoPortMap == null) ? that.requiredProtoPortMap == null
+                        : requiredProtoPortMap.equals(that.requiredProtoPortMap)
+                && maximumBssLoadValue == that.maximumBssLoadValue
+                && (preferredRoamingPartnerList == null) ? that.preferredRoamingPartnerList == null
+                        : preferredRoamingPartnerList.equals(that.preferredRoamingPartnerList)
+                && (policyUpdate == null) ? that.policyUpdate == null
+                        : policyUpdate.equals(that.policyUpdate);
+    }
+
+    /**
+     * Validate Policy data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (policyUpdate == null) {
+            Log.d(TAG, "PolicyUpdate not specified");
+            return false;
+        }
+        if (!policyUpdate.validate()) {
+            return false;
+        }
+
+        // Validate SSID exclusion list.
+        if (excludedSsidList != null) {
+            if (excludedSsidList.length > MAX_EXCLUSION_SSIDS) {
+                Log.d(TAG, "SSID exclusion list size exceeded the max: "
+                        + excludedSsidList.length);
+                return false;
+            }
+            for (String ssid : excludedSsidList) {
+                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 (requiredProtoPortMap != null) {
+            for (Map.Entry<Integer, String> entry : requiredProtoPortMap.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 (preferredRoamingPartnerList != null) {
+            for (RoamingPartner partner : preferredRoamingPartnerList) {
+                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.minHomeDownlinkBandwidth = in.readLong();
+                policy.minHomeUplinkBandwidth = in.readLong();
+                policy.minRoamingDownlinkBandwidth = in.readLong();
+                policy.minRoamingUplinkBandwidth = in.readLong();
+                policy.excludedSsidList = in.createStringArray();
+                policy.requiredProtoPortMap = readProtoPortMap(in);
+                policy.maximumBssLoadValue = in.readInt();
+                policy.preferredRoamingPartnerList = readRoamingPartnerList(in);
+                policy.policyUpdate = 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/UpdateParameter.aidl b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
new file mode 100644
index 0000000..701db47
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2.pps;
+
+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..a390df7
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java
@@ -0,0 +1,303 @@
+/**
+ * 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;
+
+/**
+ * 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.
+ *
+ * @hide
+ */
+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.
+     */
+    public long updateIntervalInMinutes = Long.MIN_VALUE;
+
+    /**
+     * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
+     * and "SPP-ClientInitiated".
+     */
+    public String updateMethod = null;
+
+    /**
+     * This specifies the hotspots at which the subscription update is permitted.  Permitted
+     * values are "HomeSP", "RoamingPartner", or "Unrestricted";
+     */
+    public String restriction = null;
+
+    /**
+     * The URI of the update server.
+     */
+    public String serverUri = null;
+
+    /**
+     * Username used to authenticate with the policy server.
+     */
+    public String username = null;
+
+    /**
+     * Base64 encoded password used to authenticate with the policy server.
+     */
+    public String base64EncodedPassword = null;
+
+    /**
+     * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
+     * policy server's identity.
+     */
+    public String trustRootCertUrl = null;
+
+    /**
+     * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
+     */
+    public byte[] trustRootCertSha256Fingerprint = null;
+
+    /**
+     * 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;
+        }
+        updateIntervalInMinutes = source.updateIntervalInMinutes;
+        updateMethod = source.updateMethod;
+        restriction = source.restriction;
+        serverUri = source.serverUri;
+        username = source.username;
+        base64EncodedPassword = source.base64EncodedPassword;
+        trustRootCertUrl = source.trustRootCertUrl;
+        if (source.trustRootCertSha256Fingerprint != null) {
+            trustRootCertSha256Fingerprint = Arrays.copyOf(source.trustRootCertSha256Fingerprint,
+                    source.trustRootCertSha256Fingerprint.length);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeLong(updateIntervalInMinutes);
+        dest.writeString(updateMethod);
+        dest.writeString(restriction);
+        dest.writeString(serverUri);
+        dest.writeString(username);
+        dest.writeString(base64EncodedPassword);
+        dest.writeString(trustRootCertUrl);
+        dest.writeByteArray(trustRootCertSha256Fingerprint);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof UpdateParameter)) {
+            return false;
+        }
+        UpdateParameter that = (UpdateParameter) thatObject;
+
+        return updateIntervalInMinutes == that.updateIntervalInMinutes
+                && TextUtils.equals(updateMethod, that.updateMethod)
+                && TextUtils.equals(restriction, that.restriction)
+                && TextUtils.equals(serverUri, that.serverUri)
+                && TextUtils.equals(username, that.username)
+                && TextUtils.equals(base64EncodedPassword, that.base64EncodedPassword)
+                && TextUtils.equals(trustRootCertUrl, that.trustRootCertUrl)
+                && Arrays.equals(trustRootCertSha256Fingerprint,
+                        that.trustRootCertSha256Fingerprint);
+    }
+
+    /**
+     * Validate UpdateParameter data.
+     *
+     * @return true on success
+     */
+    public boolean validate() {
+        if (updateIntervalInMinutes == Long.MIN_VALUE) {
+            Log.d(TAG, "Update interval not specified");
+            return false;
+        }
+        // Update not applicable.
+        if (updateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
+            return true;
+        }
+
+        if (!TextUtils.equals(updateMethod, UPDATE_METHOD_OMADM)
+                && !TextUtils.equals(updateMethod, UPDATE_METHOD_SSP)) {
+            Log.d(TAG, "Unknown update method: " + updateMethod);
+            return false;
+        }
+
+        if (!TextUtils.equals(restriction, UPDATE_RESTRICTION_HOMESP)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
+                && !TextUtils.equals(restriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
+            Log.d(TAG, "Unknown restriction: " + restriction);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(serverUri)) {
+            Log.d(TAG, "Missing update server URI");
+            return false;
+        }
+        if (serverUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
+            Log.d(TAG, "URI bytes exceeded the max: "
+                    + serverUri.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(username)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+            Log.d(TAG, "Username bytes exceeded the max: "
+                    + username.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(base64EncodedPassword)) {
+            Log.d(TAG, "Missing username");
+            return false;
+        }
+        if (base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+            Log.d(TAG, "Password bytes exceeded the max: "
+                    + base64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+        try {
+            Base64.decode(base64EncodedPassword, Base64.DEFAULT);
+        } catch (IllegalArgumentException e) {
+            Log.d(TAG, "Invalid encoding for password: " + base64EncodedPassword);
+            return false;
+        }
+
+        if (TextUtils.isEmpty(trustRootCertUrl)) {
+            Log.d(TAG, "Missing trust root certificate URL");
+            return false;
+        }
+        if (trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
+            Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
+                    + trustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
+            return false;
+        }
+
+        if (trustRootCertSha256Fingerprint == null) {
+            Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
+            return false;
+        }
+        if (trustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
+            Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
+                    + trustRootCertSha256Fingerprint.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.updateIntervalInMinutes = in.readLong();
+                updateParam.updateMethod = in.readString();
+                updateParam.restriction = in.readString();
+                updateParam.serverUri = in.readString();
+                updateParam.username = in.readString();
+                updateParam.base64EncodedPassword = in.readString();
+                updateParam.trustRootCertUrl = in.readString();
+                updateParam.trustRootCertSha256Fingerprint = 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 07ef7b3..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>
@@ -87,10 +91,22 @@
       <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>
@@ -101,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>
@@ -119,7 +147,7 @@
             <Value>x509v3</Value>
           </Node>
           <Node>
-            <NodeName>CertSHA256FingerPrint</NodeName>
+            <NodeName>CertSHA256Fingerprint</NodeName>
             <Value>1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f</Value>
           </Node>
         </Node>
@@ -135,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/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index 2350d32..1eb08e0 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -22,16 +22,26 @@
 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.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;
+
 /**
  * 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}.
@@ -66,6 +76,93 @@
     }
 
     /**
+     * Helper function for creating a {@link Policy} for testing.
+     *
+     * @return {@link Policy}
+     */
+    private static Policy createPolicy() {
+        Policy policy = new Policy();
+        policy.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.maximumBssLoadValue = 12;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint =
+                new byte[CERTIFICATE_FINGERPRINT_BYTES];
+
+        return policy;
+    }
+
+    private static UpdateParameter createSubscriptionUpdate() {
+        UpdateParameter subUpdate = new UpdateParameter();
+        subUpdate.updateIntervalInMinutes = 9021;
+        subUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+        subUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+        subUpdate.serverUri = "subscription.update.com";
+        subUpdate.username = "subUsername";
+        subUpdate.base64EncodedPassword =
+                Base64.encodeToString("subPassword".getBytes(), Base64.DEFAULT);
+        subUpdate.trustRootCertUrl = "subscription.trust.cert.com";
+        subUpdate.trustRootCertSha256Fingerprint = 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.homeSp = createHomeSp();
+        config.credential = createCredential();
+        config.policy = createPolicy();
+        config.subscriptionUpdate = createSubscriptionUpdate();
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("trustRoot.cert1.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.trustRootCertList.put("trustRoot.cert2.com",
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        config.updateIdentifier = 1;
+        config.credentialPriority = 120;
+        config.subscriptionCreationTimeInMs = 231200;
+        config.subscriptionExpirationTimeInMs = 2134232;
+        config.subscriptionType = "Gold";
+        config.usageLimitUsageTimePeriodInMinutes = 3600;
+        config.usageLimitStartTimeInMs = 124214213;
+        config.usageLimitDataLimit = 14121;
+        config.usageLimitTimeLimitInMinutes = 78912;
+        return config;
+    }
+
+    /**
      * Verify parcel write and read consistency for the given configuration.
      *
      * @param writeConfig The configuration to verify
@@ -92,39 +189,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.homeSp = 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.credential = 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.policy = 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.subscriptionUpdate = 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.trustRootCertList = null;
         verifyParcel(config);
     }
 
@@ -140,43 +271,108 @@
     }
 
     /**
+     * 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.credential = 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.homeSp = 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.policy = 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.subscriptionUpdate = 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];
+        Arrays.fill(rawUrlBytes, (byte) 'a');
+        config.trustRootCertList.put(new String(rawUrlBytes, StandardCharsets.UTF_8),
+                new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put(null, new byte[CERTIFICATE_FINGERPRINT_BYTES]);
+        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();
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES + 1]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", new byte[CERTIFICATE_FINGERPRINT_BYTES - 1]);
+        assertFalse(config.validate());
+
+        config.trustRootCertList = new HashMap<>();
+        config.trustRootCertList.put("test.cert.com", null);
+        assertFalse(config.validate());
+    }
+
+    /**
      * Verify that copy constructor works when pass in a null source.
      *
      * @throws Exception
@@ -195,9 +391,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
index e553705..055204c 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
@@ -23,7 +23,10 @@
 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;
 
@@ -31,6 +34,9 @@
 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;
 
@@ -78,8 +84,39 @@
      *
      * @return {@link PasspointConfiguration}
      */
-    private PasspointConfiguration generateConfigurationFromPPSMOTree() {
+    private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception {
+        DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+
         PasspointConfiguration config = new PasspointConfiguration();
+        config.updateIdentifier = 12;
+        config.credentialPriority = 99;
+
+        // AAA Server trust root.
+        config.trustRootCertList = new HashMap<>();
+        byte[] certFingerprint = new byte[32];
+        Arrays.fill(certFingerprint, (byte) 0x1f);
+        config.trustRootCertList.put("server1.trust.root.com", certFingerprint);
+
+        // Subscription update.
+        config.subscriptionUpdate = new UpdateParameter();
+        config.subscriptionUpdate.updateIntervalInMinutes = 120;
+        config.subscriptionUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_SSP;
+        config.subscriptionUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_ROAMING_PARTNER;
+        config.subscriptionUpdate.serverUri = "subscription.update.com";
+        config.subscriptionUpdate.username = "subscriptionUser";
+        config.subscriptionUpdate.base64EncodedPassword = "subscriptionPass";
+        config.subscriptionUpdate.trustRootCertUrl = "subscription.update.cert.com";
+        config.subscriptionUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.subscriptionUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
+        // Subscription parameters.
+        config.subscriptionCreationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
+        config.subscriptionExpirationTimeInMs = format.parse("2016-03-01T10:00:00Z").getTime();
+        config.subscriptionType = "Gold";
+        config.usageLimitDataLimit = 921890;
+        config.usageLimitStartTimeInMs = format.parse("2016-12-01T10:00:00Z").getTime();
+        config.usageLimitTimeLimitInMinutes = 120;
+        config.usageLimitUsageTimePeriodInMinutes = 99910;
 
         // HomeSP configuration.
         config.homeSp = new HomeSP();
@@ -96,10 +133,16 @@
 
         // Credential configuration.
         config.credential = new Credential();
+        config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime();
+        config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime();
         config.credential.realm = "shaken.stirred.com";
+        config.credential.checkAAAServerCertStatus = true;
         config.credential.userCredential = new Credential.UserCredential();
         config.credential.userCredential.username = "james";
         config.credential.userCredential.password = "Ym9uZDAwNw==";
+        config.credential.userCredential.machineManaged = true;
+        config.credential.userCredential.softTokenApp = "TestApp";
+        config.credential.userCredential.ableToShare = true;
         config.credential.userCredential.eapType = 21;
         config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2";
         config.credential.certCredential = new Credential.CertificateCredential();
@@ -109,12 +152,46 @@
         config.credential.simCredential = new Credential.SimCredential();
         config.credential.simCredential.imsi = "imsi";
         config.credential.simCredential.eapType = 24;
+
+        // Policy configuration.
+        config.policy = new Policy();
+        config.policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "test1.fqdn.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 127;
+        partner1.countries = "us,fr";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "test2.fqdn.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 200;
+        partner2.countries = "*";
+        config.policy.preferredRoamingPartnerList.add(partner1);
+        config.policy.preferredRoamingPartnerList.add(partner2);
+        config.policy.minHomeDownlinkBandwidth = 23412;
+        config.policy.minHomeUplinkBandwidth = 9823;
+        config.policy.minRoamingDownlinkBandwidth = 9271;
+        config.policy.minRoamingUplinkBandwidth = 2315;
+        config.policy.excludedSsidList = new String[] {"excludeSSID"};
+        config.policy.requiredProtoPortMap = new HashMap<>();
+        config.policy.requiredProtoPortMap.put(12, "34,92,234");
+        config.policy.maximumBssLoadValue = 23;
+        config.policy.policyUpdate = new UpdateParameter();
+        config.policy.policyUpdate.updateIntervalInMinutes = 120;
+        config.policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        config.policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        config.policy.policyUpdate.serverUri = "policy.update.com";
+        config.policy.policyUpdate.username = "updateUser";
+        config.policy.policyUpdate.base64EncodedPassword = "updatePass";
+        config.policy.policyUpdate.trustRootCertUrl = "update.cert.com";
+        config.policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+        Arrays.fill(config.policy.policyUpdate.trustRootCertSha256Fingerprint, (byte) 0x1f);
+
         return config;
     }
 
     /**
-     * Parse and verify all supported fields under PPS MO tree (currently only fields under
-     * HomeSP and Credential subtree).
+     * Parse and verify all supported fields under PPS MO tree.
      *
      * @throws Exception
      */
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..f571c7fc 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -37,6 +37,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,7 +55,10 @@
                                                X509Certificate[] clientCertificateChain,
                                                PrivateKey clientPrivateKey) {
         Credential cred = new Credential();
+        cred.creationTimeInMs = 123455L;
+        cred.expirationTimeInMs = 2310093L;
         cred.realm = "realm";
+        cred.checkAAAServerCertStatus = true;
         cred.userCredential = userCred;
         cred.certCredential = certCred;
         cred.simCredential = simCred;
@@ -54,6 +68,11 @@
         return cred;
     }
 
+    /**
+     * Helper function for generating certificate credential for testing.
+     *
+     * @return {@link Credential}
+     */
     private static Credential createCredentialWithCertificateCredential() {
         Credential.CertificateCredential certCred = new Credential.CertificateCredential();
         certCred.certType = "x509v3";
@@ -62,6 +81,11 @@
                 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*";
@@ -69,10 +93,18 @@
         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.machineManaged = true;
+        userCred.ableToShare = true;
+        userCred.softTokenApp = "TestApp";
         userCred.eapType = EAPConstants.EAP_TTLS;
         userCred.nonEapInnerMethod = "MS-CHAP";
         return createCredential(userCred, null, null, FakeKeys.CA_CERT0,
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..c371c49
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/PolicyTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.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.minHomeDownlinkBandwidth = 123;
+        policy.minHomeUplinkBandwidth = 345;
+        policy.minRoamingDownlinkBandwidth = 567;
+        policy.minRoamingUplinkBandwidth = 789;
+        policy.excludedSsidList = new String[] {"ssid1", "ssid2"};
+        policy.requiredProtoPortMap = new HashMap<>();
+        policy.requiredProtoPortMap.put(12, "23,342,123");
+        policy.requiredProtoPortMap.put(23, "789,372,1235");
+        policy.maximumBssLoadValue = 12;
+
+        policy.preferredRoamingPartnerList = new ArrayList<>();
+        Policy.RoamingPartner partner1 = new Policy.RoamingPartner();
+        partner1.fqdn = "partner1.com";
+        partner1.fqdnExactMatch = true;
+        partner1.priority = 12;
+        partner1.countries = "us,jp";
+        Policy.RoamingPartner partner2 = new Policy.RoamingPartner();
+        partner2.fqdn = "partner2.com";
+        partner2.fqdnExactMatch = false;
+        partner2.priority = 42;
+        partner2.countries = "ca,fr";
+        policy.preferredRoamingPartnerList.add(partner1);
+        policy.preferredRoamingPartnerList.add(partner2);
+
+        policy.policyUpdate = new UpdateParameter();
+        policy.policyUpdate.updateIntervalInMinutes = 1712;
+        policy.policyUpdate.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        policy.policyUpdate.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        policy.policyUpdate.serverUri = "policy.update.com";
+        policy.policyUpdate.username = "username";
+        policy.policyUpdate.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        policy.policyUpdate.trustRootCertUrl = "trust.cert.com";
+        policy.policyUpdate.trustRootCertSha256Fingerprint = new byte[32];
+
+        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.requiredProtoPortMap = 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.preferredRoamingPartnerList = 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.policyUpdate = 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.policyUpdate = 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.policyUpdate = 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.fqdnExactMatch = true;
+        partner.priority = 12;
+        partner.countries = "us,jp";
+        policy.preferredRoamingPartnerList.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.fqdn = "test.com";
+        partner.fqdnExactMatch = true;
+        partner.priority = 12;
+        policy.preferredRoamingPartnerList.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.requiredProtoPortMap.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();
+        policy.excludedSsidList = new String[MAX_NUMBER_OF_EXCLUDED_SSIDS + 1];
+        Arrays.fill(policy.excludedSsidList, "ssid");
+        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');
+        policy.excludedSsidList = new String[] {new String(rawSsidBytes, StandardCharsets.UTF_8)};
+        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..6bf0db1b
--- /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.updateIntervalInMinutes = 1712;
+        updateParam.updateMethod = UpdateParameter.UPDATE_METHOD_OMADM;
+        updateParam.restriction = UpdateParameter.UPDATE_RESTRICTION_HOMESP;
+        updateParam.serverUri = "server.pdate.com";
+        updateParam.username = "username";
+        updateParam.base64EncodedPassword =
+                Base64.encodeToString("password".getBytes(), Base64.DEFAULT);
+        updateParam.trustRootCertUrl = "trust.cert.com";
+        updateParam.trustRootCertSha256Fingerprint = 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.updateMethod = "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.restriction = "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.username = 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.username = 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.base64EncodedPassword = 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.base64EncodedPassword = 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.base64EncodedPassword = updateParam.base64EncodedPassword + "=";
+        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.trustRootCertUrl = 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.trustRootCertUrl = 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.trustRootCertSha256Fingerprint = 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.trustRootCertSha256Fingerprint = new byte[CERTIFICATE_SHA256_BYTES + 1];
+        assertFalse(updateParam.validate());
+
+        updateParam.trustRootCertSha256Fingerprint = 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.serverUri = 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.serverUri = 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.updateIntervalInMinutes = UpdateParameter.UPDATE_CHECK_INTERVAL_NEVER;
+        updateParam.username = null;
+        updateParam.base64EncodedPassword = null;
+        updateParam.updateMethod = null;
+        updateParam.restriction = null;
+        updateParam.serverUri = null;
+        updateParam.trustRootCertUrl = null;
+        updateParam.trustRootCertSha256Fingerprint = 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.updateIntervalInMinutes = Long.MIN_VALUE;
+        assertFalse(updateParam.validate());
+    }
+}