Merge "use apex_test for test apexes"
diff --git a/api/current.txt b/api/current.txt
index 2cd3969..7f10d09 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -27825,6 +27825,7 @@
     field public static final String COLUMN_DESCRIPTION = "description";
     field public static final String COLUMN_DISPLAY_NAME = "display_name";
     field public static final String COLUMN_DISPLAY_NUMBER = "display_number";
+    field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
     field public static final String COLUMN_INPUT_ID = "input_id";
     field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
@@ -27850,6 +27851,7 @@
     field public static final String SERVICE_TYPE_AUDIO_VIDEO = "SERVICE_TYPE_AUDIO_VIDEO";
     field public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER";
     field public static final String TYPE_1SEG = "TYPE_1SEG";
+    field public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T";
     field public static final String TYPE_ATSC_C = "TYPE_ATSC_C";
     field public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H";
     field public static final String TYPE_ATSC_T = "TYPE_ATSC_T";
@@ -27943,6 +27945,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
@@ -27990,6 +27993,8 @@
     field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
     field @Deprecated public static final String COLUMN_EPISODE_NUMBER = "episode_number";
     field public static final String COLUMN_EPISODE_TITLE = "episode_title";
+    field public static final String COLUMN_EVENT_ID = "event_id";
+    field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
     field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
@@ -28006,6 +28011,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -28071,6 +28077,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -28131,6 +28138,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
index 8ba204c..d802177 100644
--- a/api/module-lib-current.txt
+++ b/api/module-lib-current.txt
@@ -1,148 +1 @@
 // Signature format: 2.0
-package android.app.timedetector {
-
-  public final class PhoneTimeSuggestion implements android.os.Parcelable {
-    method public void addDebugInfo(@NonNull String);
-    method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
-    method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getDebugInfo();
-    method public int getSlotIndex();
-    method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR;
-  }
-
-  public static final class PhoneTimeSuggestion.Builder {
-    ctor public PhoneTimeSuggestion.Builder(int);
-    method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String);
-    method @NonNull public android.app.timedetector.PhoneTimeSuggestion build();
-    method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>);
-  }
-
-  public interface TimeDetector {
-    method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion);
-  }
-
-}
-
-package android.app.timezonedetector {
-
-  public final class PhoneTimeZoneSuggestion implements android.os.Parcelable {
-    method public void addDebugInfo(@NonNull String);
-    method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
-    method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String);
-    method public int describeContents();
-    method @NonNull public java.util.List<java.lang.String> getDebugInfo();
-    method public int getMatchType();
-    method public int getQuality();
-    method public int getSlotIndex();
-    method @Nullable public String getZoneId();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR;
-    field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4
-    field public static final int MATCH_TYPE_NA = 0; // 0x0
-    field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3
-    field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2
-    field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5
-    field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3
-    field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2
-    field public static final int QUALITY_NA = 0; // 0x0
-    field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1
-  }
-
-  public static final class PhoneTimeZoneSuggestion.Builder {
-    ctor public PhoneTimeZoneSuggestion.Builder(int);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build();
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int);
-    method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String);
-  }
-
-  public interface TimeZoneDetector {
-    method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion);
-  }
-
-}
-
-package android.os {
-
-  public final class TimestampedValue<T> implements android.os.Parcelable {
-    ctor public TimestampedValue(long, @Nullable T);
-    method public int describeContents();
-    method public long getReferenceTimeMillis();
-    method @Nullable public T getValue();
-    method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>);
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR;
-  }
-
-}
-
-package android.timezone {
-
-  public final class CountryTimeZones {
-    method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
-    method @Nullable public String getDefaultTimeZoneId();
-    method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
-    method public boolean hasUtcZone(long);
-    method public boolean isDefaultTimeZoneBoosted();
-    method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
-    method public boolean matchesCountryCode(@NonNull String);
-  }
-
-  public static final class CountryTimeZones.OffsetResult {
-    ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
-    method @NonNull public android.icu.util.TimeZone getTimeZone();
-    method public boolean isOnlyMatch();
-  }
-
-  public static final class CountryTimeZones.TimeZoneMapping {
-    method @NonNull public android.icu.util.TimeZone getTimeZone();
-    method @NonNull public String getTimeZoneId();
-  }
-
-  public final class TelephonyLookup {
-    method @NonNull public static android.timezone.TelephonyLookup getInstance();
-    method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
-  }
-
-  public final class TelephonyNetwork {
-    method @NonNull public String getCountryIsoCode();
-    method @NonNull public String getMcc();
-    method @NonNull public String getMnc();
-  }
-
-  public final class TelephonyNetworkFinder {
-    method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
-  }
-
-  public final class TimeZoneFinder {
-    method @Nullable public String getIanaVersion();
-    method @NonNull public static android.timezone.TimeZoneFinder getInstance();
-    method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
-  }
-
-  public final class TzDataSetVersion {
-    method public static int currentFormatMajorVersion();
-    method public static int currentFormatMinorVersion();
-    method public int getFormatMajorVersion();
-    method public int getFormatMinorVersion();
-    method public int getRevision();
-    method @NonNull public String getRulesVersion();
-    method public static boolean isCompatibleWithThisDevice(android.timezone.TzDataSetVersion);
-    method @NonNull public static android.timezone.TzDataSetVersion read() throws java.io.IOException, android.timezone.TzDataSetVersion.TzDataSetException;
-  }
-
-  public static final class TzDataSetVersion.TzDataSetException extends java.lang.Exception {
-    ctor public TzDataSetVersion.TzDataSetException(String);
-    ctor public TzDataSetVersion.TzDataSetException(String, Throwable);
-  }
-
-  public final class ZoneInfoDb {
-    method @NonNull public static android.timezone.ZoneInfoDb getInstance();
-    method @NonNull public String getVersion();
-  }
-
-}
-
diff --git a/api/system-current.txt b/api/system-current.txt
index 7aaac7f..e24ed55 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -201,7 +201,6 @@
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
     field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
-    field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
     field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
@@ -4603,7 +4602,9 @@
   }
 
   public class NetworkPolicyManager {
+    method @NonNull public android.telephony.SubscriptionPlan[] getSubscriptionPlans(int, @NonNull String);
     method public void setSubscriptionOverride(int, int, int, long, @NonNull String);
+    method public void setSubscriptionPlans(int, @NonNull android.telephony.SubscriptionPlan[], @NonNull String);
     field public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 2; // 0x2
     field public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1; // 0x1
   }
@@ -7220,7 +7221,7 @@
     field public static final String SERIAL_NUMBER = "serial_number";
     field public static final String SERVICE_CATEGORY = "service_category";
     field public static final String SLOT_INDEX = "slot_index";
-    field public static final String SUB_ID = "sub_id";
+    field public static final String SUBSCRIPTION_ID = "sub_id";
   }
 
   public static final class Telephony.CellBroadcasts.Preference {
@@ -8468,6 +8469,7 @@
 
   public final class CallQuality implements android.os.Parcelable {
     ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+    ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
     method public int describeContents();
     method public int getAverageRelativeJitter();
     method public int getAverageRoundTripTime();
@@ -8480,6 +8482,9 @@
     method public int getNumRtpPacketsTransmitted();
     method public int getNumRtpPacketsTransmittedLost();
     method public int getUplinkCallQualityLevel();
+    method public boolean isIncomingSilenceDetected();
+    method public boolean isOutgoingSilenceDetected();
+    method public boolean isRtpInactivityDetected();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CALL_QUALITY_BAD = 4; // 0x4
     field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -8554,10 +8559,12 @@
 
   public class CellBroadcastIntents {
     method public static void sendSmsCbReceivedBroadcast(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull android.telephony.SmsCbMessage, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, int);
+    field public static final String ACTION_AREA_INFO_UPDATED = "android.telephony.action.AREA_INFO_UPDATED";
   }
 
   public abstract class CellBroadcastService extends android.app.Service {
     ctor public CellBroadcastService();
+    method @NonNull @WorkerThread public abstract CharSequence getCellBroadcastAreaInfo(int);
     method @CallSuper @NonNull public android.os.IBinder onBind(@Nullable android.content.Intent);
     method public abstract void onCdmaCellBroadcastSms(int, @NonNull byte[], int);
     method public abstract void onCdmaScpMessage(int, @NonNull java.util.List<android.telephony.cdma.CdmaSmsCbProgramData>, @NonNull String, @NonNull java.util.function.Consumer<android.os.Bundle>);
@@ -9654,6 +9661,11 @@
     method public void updateServiceLocation();
     method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
+    field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+    field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+    field public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+    field public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+    field public static final String ACTION_CARRIER_SIGNAL_RESET = "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
     field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
     field public static final String ACTION_SERVICE_PROVIDERS_UPDATED = "android.telephony.action.SERVICE_PROVIDERS_UPDATED";
     field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
@@ -9672,8 +9684,17 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
     field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
     field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
+    field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
+    field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+    field @Deprecated public static final String EXTRA_APN_TYPE = "apnType";
+    field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
     field public static final String EXTRA_DATA_SPN = "android.telephony.extra.DATA_SPN";
+    field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
+    field public static final String EXTRA_ERROR_CODE = "errorCode";
+    field public static final String EXTRA_PCO_ID = "pcoId";
+    field public static final String EXTRA_PCO_VALUE = "pcoValue";
     field public static final String EXTRA_PLMN = "android.telephony.extra.PLMN";
+    field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
     field public static final String EXTRA_SHOW_PLMN = "android.telephony.extra.SHOW_PLMN";
     field public static final String EXTRA_SHOW_SPN = "android.telephony.extra.SHOW_SPN";
     field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
@@ -10002,6 +10023,11 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getSupportedCountries();
+    method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public java.util.List<java.lang.String> getUnsupportedCountries();
+    method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public boolean isSupportedCountry(@NonNull String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setSupportedCountries(@NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void setUnsupportedCountries(@NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.telephony.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED";
     field @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public static final String ACTION_OTA_STATUS_CHANGED = "android.telephony.euicc.action.OTA_STATUS_CHANGED";
     field public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
diff --git a/api/test-current.txt b/api/test-current.txt
index 6b3b3a8..1e5b7b8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2580,7 +2580,7 @@
     field public static final String SERIAL_NUMBER = "serial_number";
     field public static final String SERVICE_CATEGORY = "service_category";
     field public static final String SLOT_INDEX = "slot_index";
-    field public static final String SUB_ID = "sub_id";
+    field public static final String SUBSCRIPTION_ID = "sub_id";
   }
 
   public static final class Telephony.Sms.Intents {
@@ -3048,6 +3048,7 @@
 
   public final class CallQuality implements android.os.Parcelable {
     ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+    ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
     method public int describeContents();
     method public int getAverageRelativeJitter();
     method public int getAverageRoundTripTime();
@@ -3060,6 +3061,9 @@
     method public int getNumRtpPacketsTransmitted();
     method public int getNumRtpPacketsTransmittedLost();
     method public int getUplinkCallQualityLevel();
+    method public boolean isIncomingSilenceDetected();
+    method public boolean isOutgoingSilenceDetected();
+    method public boolean isRtpInactivityDetected();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CALL_QUALITY_BAD = 4; // 0x4
     field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index 16288e8..0133a44 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.TimestampedValue;
@@ -51,11 +50,9 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class PhoneTimeSuggestion implements Parcelable {
 
     /** @hide */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
             new Parcelable.Creator<PhoneTimeSuggestion>() {
                 public PhoneTimeSuggestion createFromParcel(Parcel in) {
@@ -188,7 +185,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
         private final int mSlotIndex;
         @Nullable private TimestampedValue<Long> mUtcTime;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 2412fb3..df4f513 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.SystemClock;
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.TIME_DETECTOR_SERVICE)
 public interface TimeDetector {
 
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
index 0544ccd..9147b44 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -57,11 +56,9 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class PhoneTimeZoneSuggestion implements Parcelable {
 
     /** @hide */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @NonNull
     public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
             new Creator<PhoneTimeZoneSuggestion>() {
@@ -297,7 +294,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Builder {
         private final int mSlotIndex;
         @Nullable private String mZoneId;
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index b4f6087..6a3953e 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
 
@@ -27,7 +26,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
 public interface TimeZoneDetector {
 
@@ -49,7 +47,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
     void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion);
 
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index 140363c..b128ea7 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -676,7 +676,8 @@
         }
 
         try {
-            mService.registerConnectivityDiagnosticsCallback(binder, request);
+            mService.registerConnectivityDiagnosticsCallback(
+                    binder, request, mContext.getOpPackageName());
         } catch (RemoteException exception) {
             exception.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 06b18a6..c871c45 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -221,7 +221,7 @@
     boolean isCallerCurrentAlwaysOnVpnLockdownApp();
 
     void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback,
-            in NetworkRequest request);
+            in NetworkRequest request, String callingPackageName);
     void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback);
 
     IBinder startOrGetTestNetworkService();
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index f94bdb7..38f7390 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -858,8 +858,8 @@
      *
      * <p>In general, user-supplied networks (such as WiFi networks) do not have an administrator.
      *
-     * <p>An app is granted owner privileges over Networks that it supplies. Owner privileges
-     * implicitly include administrator privileges.
+     * <p>An app is granted owner privileges over Networks that it supplies. The owner UID MUST
+     * always be included in administratorUids.
      *
      * @param administratorUids the UIDs to be set as administrators of this Network.
      * @hide
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 32e6a6d..0f66c79 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -337,7 +337,6 @@
      *            requested state until explicitly cleared, or the next reboot,
      *            whichever happens first
      * @param callingPackage the name of the package making the call.
-     *
      */
     public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask,
             @SubscriptionOverrideMask int overrideValue, long timeoutMillis,
@@ -351,6 +350,37 @@
     }
 
     /**
+     * Set the subscription plans for a specific subscriber.
+     *
+     * @param subId the subscriber this relationship applies to.
+     * @param plans the list of plans.
+     * @param callingPackage the name of the package making the call
+     */
+    public void setSubscriptionPlans(int subId, @NonNull SubscriptionPlan[] plans,
+            @NonNull String callingPackage) {
+        try {
+            mService.setSubscriptionPlans(subId, plans, callingPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get subscription plans for the given subscription id.
+     *
+     * @param subId the subscriber to get the subscription plans for.
+     * @param callingPackage the name of the package making the call.
+     */
+    @NonNull
+    public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) {
+        try {
+            return mService.getSubscriptionPlans(subId, callingPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Resets network policy settings back to factory defaults.
      *
      * @hide
diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
index f4c87ac..4c4335b 100644
--- a/core/java/android/os/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -36,7 +35,6 @@
  * @param <T> the type of the value with an associated timestamp
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TimestampedValue<T> implements Parcelable {
     private final long mReferenceTimeMillis;
     @Nullable
@@ -96,7 +94,6 @@
     }
 
     /** @hide */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
             new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
 
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index c2b0ace..a897a8e 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4166,7 +4166,7 @@
          * The subscription which received this cell broadcast message.
          * <P>Type: INTEGER</P>
          */
-        public static final String SUB_ID = "sub_id";
+        public static final String SUBSCRIPTION_ID = "sub_id";
 
         /**
          * The slot which received this cell broadcast message.
@@ -4447,7 +4447,7 @@
         public static final String[] QUERY_COLUMNS_FWK = {
                 _ID,
                 SLOT_INDEX,
-                SUB_ID,
+                SUBSCRIPTION_ID,
                 GEOGRAPHICAL_SCOPE,
                 PLMN,
                 LAC,
diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java
index 2e08108..32d330e 100644
--- a/core/java/android/telephony/CellBroadcastIntents.java
+++ b/core/java/android/telephony/CellBroadcastIntents.java
@@ -19,6 +19,8 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
@@ -43,6 +45,14 @@
     private static final String EXTRA_MESSAGE = "message";
 
     /**
+     * Broadcast intent action for notifying area information has been updated. The information
+     * can be retrieved by {@link CellBroadcastService#getCellBroadcastAreaInfo(int)}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AREA_INFO_UPDATED =
+            "android.telephony.action.AREA_INFO_UPDATED";
+
+    /**
      * @hide
      */
     private CellBroadcastIntents() {
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
index 970acd0..ab2c4fc 100644
--- a/core/java/android/timezone/CountryTimeZones.java
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
 import android.icu.util.TimeZone;
 
 import java.util.ArrayList;
@@ -32,7 +31,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class CountryTimeZones {
 
     /**
@@ -40,7 +38,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class TimeZoneMapping {
 
         @NonNull
@@ -97,7 +94,6 @@
      *
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class OffsetResult {
 
         private final TimeZone mTimeZone;
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
index 8a5864e..a4c3fbd 100644
--- a/core/java/android/timezone/TelephonyLookup.java
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TelephonyLookup {
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
index 487b3f2..823cd25 100644
--- a/core/java/android/timezone/TelephonyNetwork.java
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -17,7 +17,6 @@
 package android.timezone;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -26,7 +25,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TelephonyNetwork {
 
     @NonNull
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
index 2ddd3d9..4bfeff8 100644
--- a/core/java/android/timezone/TelephonyNetworkFinder.java
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import java.util.Objects;
 
@@ -27,7 +26,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TelephonyNetworkFinder {
 
     @NonNull
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
index c76bb1d..03f5013 100644
--- a/core/java/android/timezone/TimeZoneFinder.java
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TimeZoneFinder {
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java
index efe50a0..f993012 100644
--- a/core/java/android/timezone/TzDataSetVersion.java
+++ b/core/java/android/timezone/TzDataSetVersion.java
@@ -17,7 +17,6 @@
 package android.timezone;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -45,7 +44,6 @@
  * @hide
  */
 @VisibleForTesting
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class TzDataSetVersion {
 
     /**
@@ -88,7 +86,6 @@
      * A checked exception used in connection with time zone data sets.
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class TzDataSetException extends Exception {
 
         /** Creates an instance with a message. */
diff --git a/core/java/android/timezone/ZoneInfoDb.java b/core/java/android/timezone/ZoneInfoDb.java
index 4612a56..9354a69 100644
--- a/core/java/android/timezone/ZoneInfoDb.java
+++ b/core/java/android/timezone/ZoneInfoDb.java
@@ -17,7 +17,6 @@
 package android.timezone;
 
 import android.annotation.NonNull;
-import android.annotation.SystemApi;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -29,7 +28,6 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
 public final class ZoneInfoDb {
 
     private static final Object sLock = new Object();
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index f5a19fe..6d2d735 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -52,6 +52,7 @@
 
     public static final String DIALOGS_PACKAGE = "com.android.vpndialogs";
 
+    // TODO: Rename this to something that encompasses Settings-based Platform VPNs as well.
     public static final String LEGACY_VPN = "[Legacy VPN]";
 
     public static Intent getIntentForConfirmation() {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index cbba5bb..9a27e71 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -675,8 +675,6 @@
     char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
-    char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
-    char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
     char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
@@ -926,88 +924,45 @@
     bool skip_compilation = ((strcmp(voldDecryptBuf, "trigger_restart_min_framework") == 0) ||
                              (strcmp(voldDecryptBuf, "1") == 0));
 
-    // Extra options for boot.art/boot.oat image generation.
-    parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf,
-                               "-Xms", "-Ximage-compiler-option");
-    parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf,
-                               "-Xmx", "-Ximage-compiler-option");
-    if (skip_compilation) {
-        addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=assume-verified");
-    } else {
-        parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
-                            "--compiler-filter=", "-Ximage-compiler-option");
-    }
-
-    // If there is a boot profile, it takes precedence over the image and preloaded classes.
-    if (hasFile("/system/etc/boot-image.prof")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--profile-file=/system/etc/boot-image.prof");
-        addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=speed-profile");
-    } else {
-        ALOGE("Missing boot-image.prof file, /system/etc/boot-image.prof not found: %s\n",
-              strerror(errno));
-        return -1;
-    }
-
-
-    // If there is a dirty-image-objects file, push it.
-    if (hasFile("/system/etc/dirty-image-objects")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
-    }
-
-    property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
-    parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option");
-
-    // Extra options for DexClassLoader.
-    parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xms", dex2oatXmsFlagsBuf,
-                               "-Xms", "-Xcompiler-option");
-    parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xmx", dex2oatXmxFlagsBuf,
-                               "-Xmx", "-Xcompiler-option");
+    // Extra options for JIT.
     if (skip_compilation) {
         addOption("-Xcompiler-option");
         addOption("--compiler-filter=assume-verified");
-
-        // We skip compilation when a minimal runtime is brought up for decryption. In that case
-        // /data is temporarily backed by a tmpfs, which is usually small.
-        // If the system image contains prebuilts, they will be relocated into the tmpfs. In this
-        // specific situation it is acceptable to *not* relocate and run out of the prebuilts
-        // directly instead.
-        addOption("--runtime-arg");
-        addOption("-Xnorelocate");
     } else {
         parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf,
                             "--compiler-filter=", "-Xcompiler-option");
     }
     parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option");
-    parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
-                        "-Ximage-compiler-option");
     parseCompilerOption("dalvik.vm.dex2oat-cpu-set", dex2oatCpuSetBuf, "--cpu-set=",
                         "-Xcompiler-option");
-    parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
-                        "-Ximage-compiler-option");
-
-    // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to
-    // pass the instruction-set-features/variant as an image-compiler-option.
-    // Note: it is OK to reuse the buffer, as the values are exactly the same between
-    //       * compiler-option, used for runtime compilation (DexClassLoader)
-    //       * image-compiler-option, used for boot-image compilation on device
 
     // Copy the variant.
     sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", ABI_STRING);
     parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
-                        "--instruction-set-variant=", "-Ximage-compiler-option");
-    parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
                         "--instruction-set-variant=", "-Xcompiler-option");
     // Copy the features.
     sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", ABI_STRING);
     parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
-                        "--instruction-set-features=", "-Ximage-compiler-option");
-    parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
                         "--instruction-set-features=", "-Xcompiler-option");
 
+    /*
+     * When running with debug.generate-debug-info, add --generate-debug-info to
+     * the compiler options so that both JITted code and the boot image extension,
+     * if it is compiled on device, will include native debugging information.
+     */
+    property_get("debug.generate-debug-info", propBuf, "");
+    bool generate_debug_info = (strcmp(propBuf, "true") == 0);
+    if (generate_debug_info) {
+        addOption("-Xcompiler-option");
+        addOption("--generate-debug-info");
+    }
+
+    // The mini-debug-info makes it possible to backtrace through compiled code.
+    bool generate_mini_debug_info = property_get_bool("dalvik.vm.minidebuginfo", 0);
+    if (generate_mini_debug_info) {
+        addOption("-Xcompiler-option");
+        addOption("--generate-mini-debug-info");
+    }
 
     property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, "");
     parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option");
@@ -1016,6 +971,53 @@
     property_get("dalvik.vm.extra-opts", extraOptsBuf, "");
     parseExtraOpts(extraOptsBuf, NULL);
 
+    // Extra options for boot image extension generation.
+    if (skip_compilation) {
+        addOption("-Xnoimage-dex2oat");
+    } else {
+        parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf,
+                                   "-Xms", "-Ximage-compiler-option");
+        parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf,
+                                   "-Xmx", "-Ximage-compiler-option");
+
+        parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
+                            "--compiler-filter=", "-Ximage-compiler-option");
+
+        // If there is a dirty-image-objects file, push it.
+        if (hasFile("/system/etc/dirty-image-objects")) {
+            addOption("-Ximage-compiler-option");
+            addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
+        }
+
+        parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
+                            "-Ximage-compiler-option");
+        parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
+                            "-Ximage-compiler-option");
+
+        // The runtime may compile a boot image extension, when necessary, not using installd.
+        // Thus, we need to pass the instruction-set-features/variant as an image-compiler-option.
+        // Note: it is OK to reuse the buffer, as the values are exactly the same between
+        //       * compiler-option, used for runtime compilation (DexClassLoader)
+        //       * image-compiler-option, used for boot-image compilation on device
+        parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
+                            "--instruction-set-variant=", "-Ximage-compiler-option");
+        parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
+                            "--instruction-set-features=", "-Ximage-compiler-option");
+
+        if (generate_debug_info) {
+            addOption("-Ximage-compiler-option");
+            addOption("--generate-debug-info");
+        }
+
+        if (generate_mini_debug_info) {
+            addOption("-Ximage-compiler-option");
+            addOption("--generate-mini-debug-info");
+        }
+
+        property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
+        parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option");
+    }
+
     /* Set the properties for locale */
     {
         strcpy(localeOption, "-Duser.locale=");
@@ -1073,25 +1075,6 @@
     parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf,
                        "-Xzygote-max-boot-retry=");
 
-    /*
-     * When running with debug.generate-debug-info, add --generate-debug-info to
-     * the compiler options so that the boot image, if it is compiled on device,
-     * will include native debugging information.
-     */
-    property_get("debug.generate-debug-info", propBuf, "");
-    if (strcmp(propBuf, "true") == 0) {
-        addOption("-Xcompiler-option");
-        addOption("--generate-debug-info");
-        addOption("-Ximage-compiler-option");
-        addOption("--generate-debug-info");
-    }
-
-    // The mini-debug-info makes it possible to backtrace through JIT code.
-    if (property_get_bool("dalvik.vm.minidebuginfo", 0)) {
-        addOption("-Xcompiler-option");
-        addOption("--generate-mini-debug-info");
-    }
-
     // If set, the property below can be used to enable core platform API violation reporting.
     property_get("persist.debug.dalvik.vm.core_platform_api_policy", propBuf, "");
     if (propBuf[0] != '\0') {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2a0e78..d6097d8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2557,7 +2557,7 @@
 
     <!-- Allows telephony to suggest the time / time zone.
          <p>Not for use by third-party applications.
-         @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide
+         @hide
      -->
     <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
         android:protectionLevel="signature|telephony" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 2d1c61c..0d7ac08 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -39,6 +39,9 @@
     <!-- Albania: 5 digits, known short codes listed -->
     <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
 
+    <!-- Argentia: 5 digits, known short codes listed -->
+    <shortcode country="ar" pattern="\\d{5}" free="11711|28291" />
+
     <!-- Armenia: 3-4 digits, emergency numbers 10[123] -->
     <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" />
 
@@ -80,7 +83,7 @@
     <shortcode country="cn" premium="1066.*" free="1065.*" />
 
     <!-- Colombia: 1-6 digits (not confirmed) -->
-    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160" />
+    <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960" />
 
     <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
     <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index dcc6b95..1290633 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -38,6 +38,10 @@
         ICredentialStoreFactory storeFactory =
                 ICredentialStoreFactory.Stub.asInterface(
                     ServiceManager.getService("android.security.identity"));
+        if (storeFactory == null) {
+            // This can happen if credstore is not running or not installed.
+            return null;
+        }
 
         ICredentialStore credStore = null;
         try {
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 09b7559..433c622 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1109,6 +1109,24 @@
          * <p>Type: TEXT
          */
         String COLUMN_SERIES_ID = "series_id";
+
+        /**
+         * The split ID of this TV program for multi-part content, as a URI.
+         *
+         * <p>A content may consist of multiple programs within the same channel or over several
+         * channels. For example, a film might be divided into two parts interrupted by a news in
+         * the middle or a longer sport event might be split into several parts over several
+         * channels. The split ID is used to identify all the programs in the same multi-part
+         * content. Suitable URIs include
+         * <ul>
+         * <li>{@code crid://<CRIDauthority>/<data>#<IMI>} from ETSI TS 102 323
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        String COLUMN_SPLIT_ID = "split_id";
     }
 
     /**
@@ -1677,6 +1695,7 @@
                 TYPE_ATSC_T,
                 TYPE_ATSC_C,
                 TYPE_ATSC_M_H,
+                TYPE_ATSC3_T,
                 TYPE_ISDB_T,
                 TYPE_ISDB_TB,
                 TYPE_ISDB_S,
@@ -1801,6 +1820,13 @@
         public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H";
 
         /**
+         * The channel type for ATSC3.0 (terrestrial).
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T";
+
+        /**
          * The channel type for ISDB-T (terrestrial).
          *
          * @see #COLUMN_TYPE
@@ -2022,6 +2048,7 @@
          * {@link #TYPE_ATSC_C},
          * {@link #TYPE_ATSC_M_H},
          * {@link #TYPE_ATSC_T},
+         * {@link #TYPE_ATSC3_T},
          * {@link #TYPE_CMMB},
          * {@link #TYPE_DTMB},
          * {@link #TYPE_DVB_C},
@@ -2407,6 +2434,22 @@
          */
         public static final String COLUMN_TRANSIENT = "transient";
 
+        /**
+         * The global content ID of this TV channel, as a URI.
+         *
+         * <p>A globally unique URI that identifies this TV channel, if applicable. Suitable URIs
+         * include
+         * <ul>
+         * <li>{@code globalServiceId} from ATSC A/331. ex {@code https://doi.org/10.5239/7E4E-B472}
+         * <li>Other broadcast ID provider. ex {@code http://example.com/tv_channel/1234}
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+
         private Channels() {}
 
         /**
@@ -2562,6 +2605,37 @@
          */
         public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
 
+        /**
+         * The event ID of this TV program.
+         *
+         * <p>It is used to identify the current TV program in the same channel, if applicable.
+         * Use the same coding for {@code event_id} in the underlying broadcast standard if it
+         * is defined there (e.g. ATSC A/65, ETSI EN 300 468 and ARIB STD-B10).
+         *
+         * <p>This is a required field only if the underlying broadcast standard defines the same
+         * name field. Otherwise, leave empty.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_EVENT_ID = "event_id";
+
+        /**
+         * The global content ID of this TV program, as a URI.
+         *
+         * <p>A globally unique ID that identifies this TV program, if applicable. Suitable URIs
+         * include
+         * <ul>
+         * <li>{@code crid://<CRIDauthority>/<data>} from ETSI TS 102 323
+         * <li>{@code globalContentId} from ATSC A/332
+         * <li>Other broadcast ID provider. ex {@code http://example.com/tv_program/1234}
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+
         private Programs() {}
 
         /** Canonical genres for TV programs. */
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 642dc82..41a9b24 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -49,7 +50,6 @@
 import android.widget.TextView;
 
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.TrafficStatsConstants;
 
@@ -203,7 +203,7 @@
     }
 
     private URL getUrlForCaptivePortal() {
-        String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+        String url = getIntent().getStringExtra(TelephonyManager.EXTRA_REDIRECTION_URL);
         if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
         final CarrierConfigManager configManager = getApplicationContext()
                 .getSystemService(CarrierConfigManager.class);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 46b1d5f..c7f5e9a 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,10 +19,10 @@
 import android.content.Intent;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -50,7 +50,7 @@
      * @param intent passing signal for config match
      * @return a list of carrier action for the given signal based on the carrier config.
      *
-     *  Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+     *  Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
      *  This intent allows fined-grained matching based on both intent type & extra values:
      *  apnType and errorCode.
      *  apnType read from passing intent is "default" and errorCode is 0x26 for example and
@@ -78,25 +78,25 @@
             String arg1 = null;
             String arg2 = null;
             switch (intent.getAction()) {
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY);
                     break;
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY);
-                    arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY);
-                    arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY);
+                    arg1 = intent.getStringExtra(TelephonyManager.EXTRA_APN_TYPE);
+                    arg2 = intent.getStringExtra(TelephonyManager.EXTRA_ERROR_CODE);
                     break;
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
                     break;
-                case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
+                case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
                     configs = b.getStringArray(CarrierConfigManager
                             .KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE);
-                    arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents
-                            .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
+                    arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager
+                            .EXTRA_DEFAULT_NETWORK_AVAILABLE, false));
                     break;
                 default:
                     Log.e(TAG, "load carrier config failure with un-configured key: "
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
index 3e34f0a..78a02d7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -27,10 +27,9 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.provider.Settings;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
-import com.android.internal.telephony.TelephonyIntents;
-
 /**
  * Service to run {@link android.app.job.JobScheduler} job.
  * Service to monitor when there is a change to conent URI
@@ -93,7 +92,7 @@
         }
         int jobId;
         switch(intent.getAction()) {
-            case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+            case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
                 jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
                 break;
             default:
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
index 1928ad9..086a287 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.carrierdefaultapp;
 
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -25,7 +30,6 @@
 import android.test.InstrumentationTestCase;
 
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
 
 import org.junit.After;
 import org.junit.Before;
@@ -34,10 +38,6 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 
 public class CarrierDefaultReceiverTest extends InstrumentationTestCase {
     @Mock
@@ -87,7 +87,7 @@
                 .KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"});
         doReturn(b).when(mCarrierConfigMgr).getConfig();
 
-        Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
+        Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         mReceiver.onReceive(mContext, intent);
 
diff --git a/packages/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml
index 9bd5be7..7595d2b 100644
--- a/packages/DynamicSystemInstallationService/res/values/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values/strings.xml
@@ -35,4 +35,7 @@
     <!-- Toast when we fail to launch into Dynamic System [CHAR LIMIT=64] -->
     <string name="toast_failed_to_reboot_to_dynsystem">Can\u2019t restart or load dynamic system</string>
 
+    <!-- URL of Dynamic System Key Revocation List [DO NOT TRANSLATE] -->
+    <string name="key_revocation_list_url" translatable="false">https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json</string>
+
 </resources>
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9ccb837..9bae223 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -46,6 +46,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.net.http.HttpResponseCache;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -60,6 +61,8 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
@@ -146,10 +149,26 @@
         prepareNotification();
 
         mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
+
+        // Install an HttpResponseCache in the application cache directory so we can cache
+        // gsi key revocation list. The http(s) protocol handler uses this cache transparently.
+        // The cache size is chosen heuristically. Since we don't have too much traffic right now,
+        // a moderate size of 1MiB should be enough.
+        try {
+            File httpCacheDir = new File(getCacheDir(), "httpCache");
+            long httpCacheSize = 1 * 1024 * 1024; // 1 MiB
+            HttpResponseCache.install(httpCacheDir, httpCacheSize);
+        } catch (IOException e) {
+            Log.d(TAG, "HttpResponseCache.install() failed: " + e);
+        }
     }
 
     @Override
     public void onDestroy() {
+        HttpResponseCache cache = HttpResponseCache.getInstalled();
+        if (cache != null) {
+            cache.flush();
+        }
         // Cancel the persistent notification.
         mNM.cancel(NOTIFICATION_ID);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 9aea0e7..438c435 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 import android.webkit.URLUtil;
 
+import org.json.JSONException;
+
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.IOException;
@@ -100,7 +102,9 @@
     private final Context mContext;
     private final DynamicSystemManager mDynSystem;
     private final ProgressListener mListener;
+    private final boolean mIsNetworkUrl;
     private DynamicSystemManager.Session mInstallationSession;
+    private KeyRevocationList mKeyRevocationList;
 
     private boolean mIsZip;
     private boolean mIsCompleted;
@@ -123,6 +127,7 @@
         mContext = context;
         mDynSystem = dynSystem;
         mListener = listener;
+        mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl);
     }
 
     @Override
@@ -152,9 +157,11 @@
                 return null;
             }
 
+            // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk)
+
             mDynSystem.finishInstallation();
         } catch (Exception e) {
-            e.printStackTrace();
+            Log.e(TAG, e.toString(), e);
             mDynSystem.remove();
             return e;
         } finally {
@@ -220,7 +227,7 @@
                 String.format(Locale.US, "Unsupported file format: %s", mUrl));
         }
 
-        if (URLUtil.isNetworkUrl(mUrl)) {
+        if (mIsNetworkUrl) {
             mStream = new URL(mUrl).openStream();
         } else if (URLUtil.isFileUrl(mUrl)) {
             if (mIsZip) {
@@ -234,6 +241,25 @@
             throw new UnsupportedUrlException(
                     String.format(Locale.US, "Unsupported URL: %s", mUrl));
         }
+
+        // TODO(yochiang): Bypass this check if device is unlocked
+        try {
+            String listUrl = mContext.getString(R.string.key_revocation_list_url);
+            mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl));
+        } catch (IOException | JSONException e) {
+            Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List");
+            mKeyRevocationList = new KeyRevocationList();
+            keyRevocationThrowOrWarning(e);
+        }
+    }
+
+    private void keyRevocationThrowOrWarning(Exception e) throws Exception {
+        if (mIsNetworkUrl) {
+            throw e;
+        } else {
+            // If DSU is being installed from a local file URI, then be permissive
+            Log.w(TAG, e.toString());
+        }
     }
 
     private void installUserdata() throws Exception {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
new file mode 100644
index 0000000..522bc54
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dynsystem;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+
+class KeyRevocationList {
+
+    private static final String TAG = "KeyRevocationList";
+
+    private static final String JSON_ENTRIES = "entries";
+    private static final String JSON_PUBLIC_KEY = "public_key";
+    private static final String JSON_STATUS = "status";
+    private static final String JSON_REASON = "reason";
+
+    private static final String STATUS_REVOKED = "REVOKED";
+
+    @VisibleForTesting
+    HashMap<String, RevocationStatus> mEntries;
+
+    static class RevocationStatus {
+        final String mStatus;
+        final String mReason;
+
+        RevocationStatus(String status, String reason) {
+            mStatus = status;
+            mReason = reason;
+        }
+    }
+
+    KeyRevocationList() {
+        mEntries = new HashMap<String, RevocationStatus>();
+    }
+
+    /**
+     * Returns the revocation status of a public key.
+     *
+     * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist.
+     */
+    RevocationStatus getRevocationStatusForKey(String publicKey) {
+        return mEntries.get(publicKey);
+    }
+
+    /** Test if a public key is revoked or not. */
+    boolean isRevoked(String publicKey) {
+        RevocationStatus entry = getRevocationStatusForKey(publicKey);
+        return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED);
+    }
+
+    @VisibleForTesting
+    void addEntry(String publicKey, String status, String reason) {
+        mEntries.put(publicKey, new RevocationStatus(status, reason));
+    }
+
+    /**
+     * Creates a KeyRevocationList from a JSON String.
+     *
+     * @param jsonString the revocation list, for example:
+     *     <pre>{@code
+     *      {
+     *        "entries": [
+     *          {
+     *            "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5",
+     *            "status": "REVOKED",
+     *            "reason": "Revocation Reason"
+     *          }
+     *        ]
+     *      }
+     *     }</pre>
+     *
+     * @throws JSONException if |jsonString| is malformed.
+     */
+    static KeyRevocationList fromJsonString(String jsonString) throws JSONException {
+        JSONObject jsonObject = new JSONObject(jsonString);
+        KeyRevocationList list = new KeyRevocationList();
+        Log.d(TAG, "Begin of revocation list");
+        if (jsonObject.has(JSON_ENTRIES)) {
+            JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES);
+            for (int i = 0; i < entries.length(); ++i) {
+                JSONObject entry = entries.getJSONObject(i);
+                String publicKey = entry.getString(JSON_PUBLIC_KEY);
+                String status = entry.getString(JSON_STATUS);
+                String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : "";
+                list.addEntry(publicKey, status, reason);
+                Log.d(TAG, "Revocation entry: " + entry.toString());
+            }
+        }
+        Log.d(TAG, "End of revocation list");
+        return list;
+    }
+
+    /**
+     * Creates a KeyRevocationList from a URL.
+     *
+     * @throws IOException if |url| is inaccessible.
+     * @throws JSONException if fetched content is malformed.
+     */
+    static KeyRevocationList fromUrl(URL url) throws IOException, JSONException {
+        Log.d(TAG, "Fetch from URL: " + url.toString());
+        // Force "conditional GET"
+        // Force validate the cached result with server each time, and use the cached result
+        // only if it is validated by server, else fetch new data from server.
+        // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response
+        URLConnection connection = url.openConnection();
+        connection.setUseCaches(true);
+        connection.addRequestProperty("Cache-Control", "max-age=0");
+        try (InputStream stream = connection.getInputStream()) {
+            return fromJsonString(readFully(stream));
+        }
+    }
+
+    private static String readFully(InputStream in) throws IOException {
+        int n;
+        byte[] buffer = new byte[4096];
+        StringBuilder builder = new StringBuilder();
+        while ((n = in.read(buffer, 0, 4096)) > -1) {
+            builder.append(new String(buffer, 0, n));
+        }
+        return builder.toString();
+    }
+}
diff --git a/packages/DynamicSystemInstallationService/tests/Android.bp b/packages/DynamicSystemInstallationService/tests/Android.bp
new file mode 100644
index 0000000..3bdf829
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/Android.bp
@@ -0,0 +1,15 @@
+android_test {
+    name: "DynamicSystemInstallationServiceTests",
+
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+
+    resource_dirs: ["res"],
+    platform_apis: true,
+    instrumentation_for: "DynamicSystemInstallationService",
+    certificate: "platform",
+}
diff --git a/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f5f0ae6
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.dynsystem.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.dynsystem"
+        android:label="Tests for DynamicSystemInstallationService" />
+
+</manifest>
diff --git a/packages/DynamicSystemInstallationService/tests/res/values/strings.xml b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml
new file mode 100644
index 0000000..fdb620b
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- testFromJsonString -->
+    <string name="blacklist_json_string" translatable="false">
+      {
+        \"entries\":[
+          {
+            \"public_key\":\"00fa2c6637c399afa893fe83d85f3569998707d5\",
+            \"status\":\"REVOKED\",
+            \"reason\":\"Key revocation test key\"
+          },
+          {
+            \"public_key\":\"key2\",
+            \"status\":\"REVOKED\"
+          }
+        ]
+      }
+    </string>
+</resources>
diff --git a/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java
new file mode 100644
index 0000000..82ce542
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.dynsystem;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.json.JSONException;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * A test for KeyRevocationList.java
+ */
+@RunWith(AndroidJUnit4.class)
+public class KeyRevocationListTest {
+
+    private static final String TAG = "KeyRevocationListTest";
+
+    private static Context sContext;
+
+    private static String sBlacklistJsonString;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        sContext = InstrumentationRegistry.getInstrumentation().getContext();
+        sBlacklistJsonString =
+                sContext.getString(com.android.dynsystem.tests.R.string.blacklist_json_string);
+    }
+
+    @Test
+    @SmallTest
+    public void testFromJsonString() throws JSONException {
+        KeyRevocationList blacklist;
+        blacklist = KeyRevocationList.fromJsonString(sBlacklistJsonString);
+        Assert.assertNotNull(blacklist);
+        Assert.assertFalse(blacklist.mEntries.isEmpty());
+        blacklist = KeyRevocationList.fromJsonString("{}");
+        Assert.assertNotNull(blacklist);
+        Assert.assertTrue(blacklist.mEntries.isEmpty());
+    }
+
+    @Test
+    @SmallTest
+    public void testFromUrl() throws IOException, JSONException {
+        URLConnection mockConnection = mock(URLConnection.class);
+        doReturn(new ByteArrayInputStream(sBlacklistJsonString.getBytes()))
+                .when(mockConnection).getInputStream();
+        URL mockUrl = new URL(
+                "http",     // protocol
+                "foo.bar",  // host
+                80,         // port
+                "baz",      // file
+                new URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) {
+                        return mockConnection;
+                    }
+                });
+        URL mockBadUrl = new URL(
+                "http",     // protocol
+                "foo.bar",  // host
+                80,         // port
+                "baz",      // file
+                new URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) throws IOException {
+                        throw new IOException();
+                    }
+                });
+
+        KeyRevocationList blacklist = KeyRevocationList.fromUrl(mockUrl);
+        Assert.assertNotNull(blacklist);
+        Assert.assertFalse(blacklist.mEntries.isEmpty());
+
+        blacklist = null;
+        try {
+            blacklist = KeyRevocationList.fromUrl(mockBadUrl);
+            // Up should throw, down should be unreachable
+            Assert.fail("Expected IOException not thrown");
+        } catch (IOException e) {
+            // This is expected, do nothing
+        }
+        Assert.assertNull(blacklist);
+    }
+
+    @Test
+    @SmallTest
+    public void testIsRevoked() {
+        KeyRevocationList blacklist = new KeyRevocationList();
+        blacklist.addEntry("key1", "REVOKED", "reason for key1");
+
+        KeyRevocationList.RevocationStatus revocationStatus =
+                blacklist.getRevocationStatusForKey("key1");
+        Assert.assertNotNull(revocationStatus);
+        Assert.assertEquals(revocationStatus.mReason, "reason for key1");
+
+        revocationStatus = blacklist.getRevocationStatusForKey("key2");
+        Assert.assertNull(revocationStatus);
+
+        Assert.assertTrue(blacklist.isRevoked("key1"));
+        Assert.assertFalse(blacklist.isRevoked("key2"));
+    }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 7785d9c9..71f751c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -50,8 +50,11 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
+import static java.util.Map.Entry;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -64,6 +67,7 @@
 import android.database.ContentObserver;
 import android.net.CaptivePortal;
 import android.net.ConnectionInfo;
+import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import android.net.ConnectivityManager;
 import android.net.ICaptivePortal;
 import android.net.IConnectivityDiagnosticsCallback;
@@ -132,6 +136,7 @@
 import android.os.Messenger;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -172,6 +177,7 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.LocationPermissionChecker;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
@@ -494,9 +500,9 @@
      /**
       * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
       * been tested.
-      * obj = String representing URL that Internet probe was redirect to, if it was redirected.
-      * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
-      * arg2 = NetID.
+      * obj = {@link NetworkTestedResults} representing information sent from NetworkMonitor.
+      * data = PersistableBundle of extras passed from NetworkMonitor. If {@link
+      * NetworkMonitorCallbacks#notifyNetworkTested} is called, this will be null.
       */
     private static final int EVENT_NETWORK_TESTED = 41;
 
@@ -598,6 +604,9 @@
     private Set<String> mWolSupportedInterfaces;
 
     private TelephonyManager mTelephonyManager;
+    private final AppOpsManager mAppOpsManager;
+
+    private final LocationPermissionChecker mLocationPermissionChecker;
 
     private KeepaliveTracker mKeepaliveTracker;
     private NetworkNotificationManager mNotifier;
@@ -994,6 +1003,8 @@
         mNetd = netd;
         mKeyStore = KeyStore.getInstance();
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mLocationPermissionChecker = new LocationPermissionChecker(mContext);
 
         // To ensure uid rules are synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -2094,6 +2105,12 @@
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
     }
 
+    private boolean checkNetworkStackPermission(int pid, int uid) {
+        return checkAnyPermissionOf(pid, uid,
+                android.Manifest.permission.NETWORK_STACK,
+                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
+    }
+
     private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) {
         return checkAnyPermissionOf(pid, uid,
                 android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP,
@@ -2740,88 +2757,21 @@
                     break;
                 }
                 case EVENT_NETWORK_TESTED: {
-                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
+                    final NetworkTestedResults results = (NetworkTestedResults) msg.obj;
+
+                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(results.mNetId);
                     if (nai == null) break;
 
-                    final boolean wasPartial = nai.partialConnectivity;
-                    nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
-                    final boolean partialConnectivityChanged =
-                            (wasPartial != nai.partialConnectivity);
+                    handleNetworkTested(nai, results.mTestResult,
+                            (results.mRedirectUrl == null) ? "" : results.mRedirectUrl);
 
-                    final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
-                    final boolean wasValidated = nai.lastValidated;
-                    final boolean wasDefault = isDefaultNetwork(nai);
-                    // Only show a connected notification if the network is pending validation
-                    // after the captive portal app was open, and it has now validated.
-                    if (nai.captivePortalValidationPending && valid) {
-                        // User is now logged in, network validated.
-                        nai.captivePortalValidationPending = false;
-                        showNetworkNotification(nai, NotificationType.LOGGED_IN);
-                    }
-
-                    final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : "";
-
-                    if (DBG) {
-                        final String logMsg = !TextUtils.isEmpty(redirectUrl)
-                                 ? " with redirect to " + redirectUrl
-                                 : "";
-                        log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
-                    }
-                    if (valid != nai.lastValidated) {
-                        if (wasDefault) {
-                            mDeps.getMetricsLogger()
-                                    .defaultNetworkMetrics().logDefaultNetworkValidity(
-                                            SystemClock.elapsedRealtime(), valid);
-                        }
-                        final int oldScore = nai.getCurrentScore();
-                        nai.lastValidated = valid;
-                        nai.everValidated |= valid;
-                        updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                        // If score has changed, rebroadcast to NetworkProviders. b/17726566
-                        if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
-                        if (valid) {
-                            handleFreshlyValidatedNetwork(nai);
-                            // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
-                            // LOST_INTERNET notifications if network becomes valid.
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.NO_INTERNET);
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.LOST_INTERNET);
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.PARTIAL_CONNECTIVITY);
-                            mNotifier.clearNotification(nai.network.netId,
-                                    NotificationType.PRIVATE_DNS_BROKEN);
-                            // If network becomes valid, the hasShownBroken should be reset for
-                            // that network so that the notification will be fired when the private
-                            // DNS is broken again.
-                            nai.networkAgentConfig.hasShownBroken = false;
-                        }
-                    } else if (partialConnectivityChanged) {
-                        updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
-                    }
-                    updateInetCondition(nai);
-                    // Let the NetworkAgent know the state of its network
-                    Bundle redirectUrlBundle = new Bundle();
-                    redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
-                    // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
-                    nai.asyncChannel.sendMessage(
-                            NetworkAgent.CMD_REPORT_NETWORK_STATUS,
-                            (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
-                            0, redirectUrlBundle);
-
-                    // If NetworkMonitor detects partial connectivity before
-                    // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
-                    // immediately. Re-notify partial connectivity silently if no internet
-                    // notification already there.
-                    if (!wasPartial && nai.partialConnectivity) {
-                        // Remove delayed message if there is a pending message.
-                        mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
-                        handlePromptUnvalidated(nai.network);
-                    }
-
-                    if (wasValidated && !nai.lastValidated) {
-                        handleNetworkUnvalidated(nai);
-                    }
+                    // Invoke ConnectivityReport generation for this Network test event.
+                    final Message m =
+                            mConnectivityDiagnosticsHandler.obtainMessage(
+                                    ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED,
+                                    new ConnectivityReportEvent(results.mTimestampMillis, nai));
+                    m.setData(msg.getData());
+                    mConnectivityDiagnosticsHandler.sendMessage(m);
                     break;
                 }
                 case EVENT_PROVISIONING_NOTIFICATION: {
@@ -2872,6 +2822,87 @@
             return true;
         }
 
+        private void handleNetworkTested(
+                @NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
+            final boolean wasPartial = nai.partialConnectivity;
+            nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
+            final boolean partialConnectivityChanged =
+                    (wasPartial != nai.partialConnectivity);
+
+            final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0);
+            final boolean wasValidated = nai.lastValidated;
+            final boolean wasDefault = isDefaultNetwork(nai);
+            // Only show a connected notification if the network is pending validation
+            // after the captive portal app was open, and it has now validated.
+            if (nai.captivePortalValidationPending && valid) {
+                // User is now logged in, network validated.
+                nai.captivePortalValidationPending = false;
+                showNetworkNotification(nai, NotificationType.LOGGED_IN);
+            }
+
+            if (DBG) {
+                final String logMsg = !TextUtils.isEmpty(redirectUrl)
+                        ? " with redirect to " + redirectUrl
+                        : "";
+                log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg);
+            }
+            if (valid != nai.lastValidated) {
+                if (wasDefault) {
+                    mDeps.getMetricsLogger()
+                            .defaultNetworkMetrics().logDefaultNetworkValidity(
+                            SystemClock.elapsedRealtime(), valid);
+                }
+                final int oldScore = nai.getCurrentScore();
+                nai.lastValidated = valid;
+                nai.everValidated |= valid;
+                updateCapabilities(oldScore, nai, nai.networkCapabilities);
+                // If score has changed, rebroadcast to NetworkProviders. b/17726566
+                if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
+                if (valid) {
+                    handleFreshlyValidatedNetwork(nai);
+                    // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
+                    // LOST_INTERNET notifications if network becomes valid.
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.NO_INTERNET);
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.LOST_INTERNET);
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.PARTIAL_CONNECTIVITY);
+                    mNotifier.clearNotification(nai.network.netId,
+                            NotificationType.PRIVATE_DNS_BROKEN);
+                    // If network becomes valid, the hasShownBroken should be reset for
+                    // that network so that the notification will be fired when the private
+                    // DNS is broken again.
+                    nai.networkAgentConfig.hasShownBroken = false;
+                }
+            } else if (partialConnectivityChanged) {
+                updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
+            }
+            updateInetCondition(nai);
+            // Let the NetworkAgent know the state of its network
+            Bundle redirectUrlBundle = new Bundle();
+            redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
+            // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
+            nai.asyncChannel.sendMessage(
+                    NetworkAgent.CMD_REPORT_NETWORK_STATUS,
+                    (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
+                    0, redirectUrlBundle);
+
+            // If NetworkMonitor detects partial connectivity before
+            // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification
+            // immediately. Re-notify partial connectivity silently if no internet
+            // notification already there.
+            if (!wasPartial && nai.partialConnectivity) {
+                // Remove delayed message if there is a pending message.
+                mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network);
+                handlePromptUnvalidated(nai.network);
+            }
+
+            if (wasValidated && !nai.lastValidated) {
+                handleNetworkUnvalidated(nai);
+            }
+        }
+
         private int getCaptivePortalMode() {
             return Settings.Global.getInt(mContext.getContentResolver(),
                     Settings.Global.CAPTIVE_PORTAL_MODE,
@@ -2920,8 +2951,23 @@
 
         @Override
         public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
-            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
-                    testResult, mNetId, redirectUrl));
+            notifyNetworkTestedWithExtras(testResult, redirectUrl, SystemClock.elapsedRealtime(),
+                    PersistableBundle.EMPTY);
+        }
+
+        @Override
+        public void notifyNetworkTestedWithExtras(
+                int testResult,
+                @Nullable String redirectUrl,
+                long timestampMillis,
+                @NonNull PersistableBundle extras) {
+            final Message msg =
+                    mTrackerHandler.obtainMessage(
+                            EVENT_NETWORK_TESTED,
+                            new NetworkTestedResults(
+                                    mNetId, testResult, timestampMillis, redirectUrl));
+            msg.setData(new Bundle(extras));
+            mTrackerHandler.sendMessage(msg);
         }
 
         @Override
@@ -7382,7 +7428,11 @@
 
     @GuardedBy("mVpns")
     private Vpn getVpnIfOwner() {
-        final int uid = Binder.getCallingUid();
+        return getVpnIfOwner(Binder.getCallingUid());
+    }
+
+    @GuardedBy("mVpns")
+    private Vpn getVpnIfOwner(int uid) {
         final int user = UserHandle.getUserId(uid);
 
         final Vpn vpn = mVpns.get(user);
@@ -7494,6 +7544,17 @@
          */
         private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2;
 
+        /**
+         * Event for {@link NetworkStateTrackerHandler} to trigger ConnectivityReport callbacks
+         * after processing {@link #EVENT_NETWORK_TESTED} events.
+         * obj = {@link ConnectivityReportEvent} representing ConnectivityReport info reported from
+         * NetworkMonitor.
+         * data = PersistableBundle of extras passed from NetworkMonitor.
+         *
+         * <p>See {@link ConnectivityService#EVENT_NETWORK_TESTED}.
+         */
+        private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED;
+
         private ConnectivityDiagnosticsHandler(Looper looper) {
             super(looper);
         }
@@ -7511,6 +7572,19 @@
                             (IConnectivityDiagnosticsCallback) msg.obj, msg.arg1);
                     break;
                 }
+                case EVENT_NETWORK_TESTED: {
+                    final ConnectivityReportEvent reportEvent =
+                            (ConnectivityReportEvent) msg.obj;
+
+                    // This is safe because {@link
+                    // NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} receives a
+                    // PersistableBundle and converts it to the Bundle in the incoming Message. If
+                    // {@link NetworkMonitorCallbacks#notifyNetworkTested} is called, msg.data will
+                    // not be set. This is also safe, as msg.getData() will return an empty Bundle.
+                    final PersistableBundle extras = new PersistableBundle(msg.getData());
+                    handleNetworkTestedWithExtras(reportEvent, extras);
+                    break;
+                }
             }
         }
     }
@@ -7520,12 +7594,16 @@
     class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient {
         @NonNull private final IConnectivityDiagnosticsCallback mCb;
         @NonNull private final NetworkRequestInfo mRequestInfo;
+        @NonNull private final String mCallingPackageName;
 
         @VisibleForTesting
         ConnectivityDiagnosticsCallbackInfo(
-                @NonNull IConnectivityDiagnosticsCallback cb, @NonNull NetworkRequestInfo nri) {
+                @NonNull IConnectivityDiagnosticsCallback cb,
+                @NonNull NetworkRequestInfo nri,
+                @NonNull String callingPackageName) {
             mCb = cb;
             mRequestInfo = nri;
+            mCallingPackageName = callingPackageName;
         }
 
         @Override
@@ -7535,6 +7613,39 @@
         }
     }
 
+    /**
+     * Class used for sending information from {@link
+     * NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} to the handler for processing it.
+     */
+    private static class NetworkTestedResults {
+        private final int mNetId;
+        private final int mTestResult;
+        private final long mTimestampMillis;
+        @Nullable private final String mRedirectUrl;
+
+        private NetworkTestedResults(
+                int netId, int testResult, long timestampMillis, @Nullable String redirectUrl) {
+            mNetId = netId;
+            mTestResult = testResult;
+            mTimestampMillis = timestampMillis;
+            mRedirectUrl = redirectUrl;
+        }
+    }
+
+    /**
+     * Class used for sending information from {@link NetworkStateTrackerHandler} to {@link
+     * ConnectivityDiagnosticsHandler}.
+     */
+    private static class ConnectivityReportEvent {
+        private final long mTimestampMillis;
+        @NonNull private final NetworkAgentInfo mNai;
+
+        private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai) {
+            mTimestampMillis = timestampMillis;
+            mNai = nai;
+        }
+    }
+
     private void handleRegisterConnectivityDiagnosticsCallback(
             @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) {
         ensureRunningOnConnectivityServiceThread();
@@ -7582,13 +7693,80 @@
         cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0);
     }
 
+    private void handleNetworkTestedWithExtras(
+            @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) {
+        final NetworkAgentInfo nai = reportEvent.mNai;
+        final ConnectivityReport report =
+                new ConnectivityReport(
+                        reportEvent.mNai.network,
+                        reportEvent.mTimestampMillis,
+                        nai.linkProperties,
+                        nai.networkCapabilities,
+                        extras);
+        final List<IConnectivityDiagnosticsCallback> results =
+                getMatchingPermissionedCallbacks(nai);
+        for (final IConnectivityDiagnosticsCallback cb : results) {
+            try {
+                cb.onConnectivityReport(report);
+            } catch (RemoteException ex) {
+                loge("Error invoking onConnectivityReport", ex);
+            }
+        }
+    }
+
+    private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
+            @NonNull NetworkAgentInfo nai) {
+        final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
+        for (Entry<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo> entry :
+                mConnectivityDiagnosticsCallbacks.entrySet()) {
+            final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue();
+            final NetworkRequestInfo nri = cbInfo.mRequestInfo;
+            if (nai.satisfies(nri.request)) {
+                if (checkConnectivityDiagnosticsPermissions(
+                        nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) {
+                    results.add(entry.getKey());
+                }
+            }
+        }
+        return results;
+    }
+
+    @VisibleForTesting
+    boolean checkConnectivityDiagnosticsPermissions(
+            int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) {
+        if (checkNetworkStackPermission(callbackPid, callbackUid)) {
+            return true;
+        }
+
+        if (!mLocationPermissionChecker.checkLocationPermission(
+                callbackPackageName, null /* featureId */, callbackUid, null /* message */)) {
+            return false;
+        }
+
+        synchronized (mVpns) {
+            if (getVpnIfOwner(callbackUid) != null) {
+                return true;
+            }
+        }
+
+        // Administrator UIDs also contains the Owner UID
+        if (nai.networkCapabilities.getAdministratorUids().contains(callbackUid)) {
+            return true;
+        }
+
+        return false;
+    }
+
     @Override
     public void registerConnectivityDiagnosticsCallback(
-            @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) {
+            @NonNull IConnectivityDiagnosticsCallback callback,
+            @NonNull NetworkRequest request,
+            @NonNull String callingPackageName) {
         if (request.legacyType != TYPE_NONE) {
             throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated."
                     + " Please use NetworkCapabilities instead.");
         }
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName);
 
         // This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid
         // and administrator uids to be safe.
@@ -7606,7 +7784,7 @@
         // callback's binder death.
         final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId);
         final ConnectivityDiagnosticsCallbackInfo cbInfo =
-                new ConnectivityDiagnosticsCallbackInfo(callback, nri);
+                new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName);
 
         mConnectivityDiagnosticsHandler.sendMessage(
                 mConnectivityDiagnosticsHandler.obtainMessage(
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 86dc9c4..9956022 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -49,6 +49,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.IpPrefix;
+import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
@@ -180,7 +181,10 @@
     private boolean mIsPackageTargetingAtLeastQ;
     private String mInterface;
     private Connection mConnection;
-    private LegacyVpnRunner mLegacyVpnRunner;
+
+    /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
+    private VpnRunner mVpnRunner;
+
     private PendingIntent mStatusIntent;
     private volatile boolean mEnableTeardown = true;
     private final INetworkManagementService mNetd;
@@ -762,7 +766,7 @@
                 mNetworkCapabilities.setUids(null);
             }
 
-            // Revoke the connection or stop LegacyVpnRunner.
+            // Revoke the connection or stop the VpnRunner.
             if (mConnection != null) {
                 try {
                     mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
@@ -772,9 +776,9 @@
                 }
                 mContext.unbindService(mConnection);
                 mConnection = null;
-            } else if (mLegacyVpnRunner != null) {
-                mLegacyVpnRunner.exit();
-                mLegacyVpnRunner = null;
+            } else if (mVpnRunner != null) {
+                mVpnRunner.exit();
+                mVpnRunner = null;
             }
 
             try {
@@ -1506,8 +1510,8 @@
         @Override
         public void interfaceStatusChanged(String interfaze, boolean up) {
             synchronized (Vpn.this) {
-                if (!up && mLegacyVpnRunner != null) {
-                    mLegacyVpnRunner.check(interfaze);
+                if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) {
+                    ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze);
                 }
             }
         }
@@ -1524,9 +1528,10 @@
                         mContext.unbindService(mConnection);
                         mConnection = null;
                         agentDisconnect();
-                    } else if (mLegacyVpnRunner != null) {
-                        mLegacyVpnRunner.exit();
-                        mLegacyVpnRunner = null;
+                    } else if (mVpnRunner != null) {
+                        // agentDisconnect must be called from mVpnRunner.exit()
+                        mVpnRunner.exit();
+                        mVpnRunner = null;
                     }
                 }
             }
@@ -1909,23 +1914,40 @@
 
     private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
             VpnProfile profile) {
-        stopLegacyVpnPrivileged();
+        stopVpnRunnerPrivileged();
 
         // Prepare for the new request.
         prepareInternal(VpnConfig.LEGACY_VPN);
         updateState(DetailedState.CONNECTING, "startLegacyVpn");
 
         // Start a new LegacyVpnRunner and we are done!
-        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
-        mLegacyVpnRunner.start();
+        mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
+        mVpnRunner.start();
     }
 
-    /** Stop legacy VPN. Permissions must be checked by callers. */
-    public synchronized void stopLegacyVpnPrivileged() {
-        if (mLegacyVpnRunner != null) {
-            mLegacyVpnRunner.exit();
-            mLegacyVpnRunner = null;
+    /**
+     * Checks if this the currently running VPN (if any) was started by the Settings app
+     *
+     * <p>This includes both Legacy VPNs and Platform VPNs.
+     */
+    private boolean isSettingsVpnLocked() {
+        return mVpnRunner != null && VpnConfig.LEGACY_VPN.equals(mPackage);
+    }
 
+    /** Stop VPN runner. Permissions must be checked by callers. */
+    public synchronized void stopVpnRunnerPrivileged() {
+        if (!isSettingsVpnLocked()) {
+            return;
+        }
+
+        final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
+
+        mVpnRunner.exit();
+        mVpnRunner = null;
+
+        // LegacyVpn uses daemons that must be shut down before new ones are brought up.
+        // The same limitation does not apply to Platform VPNs.
+        if (isLegacyVpn) {
             synchronized (LegacyVpnRunner.TAG) {
                 // wait for old thread to completely finish before spinning up
                 // new instance, otherwise state updates can be out of order.
@@ -1947,7 +1969,7 @@
      * Callers are responsible for checking permissions if needed.
      */
     private synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() {
-        if (mLegacyVpnRunner == null) return null;
+        if (!isSettingsVpnLocked()) return null;
 
         final LegacyVpnInfo info = new LegacyVpnInfo();
         info.key = mConfig.user;
@@ -1958,14 +1980,53 @@
         return info;
     }
 
-    public VpnConfig getLegacyVpnConfig() {
-        if (mLegacyVpnRunner != null) {
+    public synchronized VpnConfig getLegacyVpnConfig() {
+        if (isSettingsVpnLocked()) {
             return mConfig;
         } else {
             return null;
         }
     }
 
+    /** This class represents the common interface for all VPN runners. */
+    private abstract class VpnRunner extends Thread {
+
+        protected VpnRunner(String name) {
+            super(name);
+        }
+
+        public abstract void run();
+
+        protected abstract void exit();
+    }
+
+    private class IkeV2VpnRunner extends VpnRunner {
+        private static final String TAG = "IkeV2VpnRunner";
+
+        private final IpSecManager mIpSecManager;
+        private final VpnProfile mProfile;
+
+        IkeV2VpnRunner(VpnProfile profile) {
+            super(TAG);
+            mProfile = profile;
+
+            // TODO: move this to startVpnRunnerPrivileged()
+            mConfig = new VpnConfig();
+            mIpSecManager = mContext.getSystemService(IpSecManager.class);
+        }
+
+        @Override
+        public void run() {
+            // TODO: Build IKE config, start IKE session
+        }
+
+        @Override
+        public void exit() {
+            // TODO: Teardown IKE session & any resources.
+            agentDisconnect();
+        }
+    }
+
     /**
      * Bringing up a VPN connection takes time, and that is all this thread
      * does. Here we have plenty of time. The only thing we need to take
@@ -1973,7 +2034,7 @@
      * requests will pile up. This could be done in a Handler as a state
      * machine, but it is much easier to read in the current form.
      */
-    private class LegacyVpnRunner extends Thread {
+    private class LegacyVpnRunner extends VpnRunner {
         private static final String TAG = "LegacyVpnRunner";
 
         private final String[] mDaemons;
@@ -2043,13 +2104,21 @@
             mContext.registerReceiver(mBroadcastReceiver, filter);
         }
 
-        public void check(String interfaze) {
+        /**
+         * Checks if the parameter matches the underlying interface
+         *
+         * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has
+         * no ability to migrate between interfaces (or Networks).
+         */
+        public void exitIfOuterInterfaceIs(String interfaze) {
             if (interfaze.equals(mOuterInterface)) {
                 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
                 exit();
             }
         }
 
+        /** Tears down this LegacyVpn connection */
+        @Override
         public void exit() {
             // We assume that everything is reset after stopping the daemons.
             interrupt();
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index ef8f647..3cafaff 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -138,7 +138,7 @@
 
         if (egressDisconnected || egressChanged) {
             mAcceptedEgressIface = null;
-            mVpn.stopLegacyVpnPrivileged();
+            mVpn.stopVpnRunnerPrivileged();
         }
         if (egressDisconnected) {
             hideNotification();
@@ -218,7 +218,7 @@
         mAcceptedEgressIface = null;
         mErrorCount = 0;
 
-        mVpn.stopLegacyVpnPrivileged();
+        mVpn.stopVpnRunnerPrivileged();
         mVpn.setLockdown(false);
         hideNotification();
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index b7d6360..0bb0f94 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -37,6 +37,9 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 
+/**
+ * The implementation of ITimeDetectorService.aidl.
+ */
 public final class TimeDetectorService extends ITimeDetectorService.Stub {
     private static final String TAG = "TimeDetectorService";
 
@@ -75,7 +78,7 @@
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                 new ContentObserver(handler) {
                     public void onChange(boolean selfChange) {
-                        timeDetectorService.handleAutoTimeDetectionToggle();
+                        timeDetectorService.handleAutoTimeDetectionChanged();
                     }
                 });
 
@@ -114,8 +117,9 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
     }
 
+    /** Internal method for handling the auto time setting being changed. */
     @VisibleForTesting
-    public void handleAutoTimeDetectionToggle() {
+    public void handleAutoTimeDetectionChanged() {
         mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged);
     }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 468b806..a7c3b4d 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -26,8 +26,8 @@
 import java.io.PrintWriter;
 
 /**
- * The interface for classes that implement the time detection algorithm used by the
- * TimeDetectorService.
+ * The interface for the class that implements the time detection algorithm used by the
+ * {@link TimeDetectorService}.
  *
  * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
  * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index a1e643f..19435ee 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -38,7 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
+ * An implementation of {@link TimeDetectorStrategy} that passes phone and manual suggestions to
  * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used
  * unless the data becomes too stale.
  *
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index adf6d7e..2520316 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -24,9 +24,9 @@
 import android.provider.Settings;
 
 /**
- * The real implementation of {@link TimeZoneDetectorStrategy.Callback}.
+ * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}.
  */
-public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback {
+public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 9a1fe65..381ee10 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -67,19 +67,21 @@
 
     private static TimeZoneDetectorService create(@NonNull Context context) {
         final TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                TimeZoneDetectorStrategy.create(context);
+                TimeZoneDetectorStrategyImpl.create(context);
 
         Handler handler = FgThread.getHandler();
+        TimeZoneDetectorService service =
+                new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+
         ContentResolver contentResolver = context.getContentResolver();
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
                 new ContentObserver(handler) {
                     public void onChange(boolean selfChange) {
-                        timeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
+                        service.handleAutoTimeZoneDetectionChanged();
                     }
                 });
-
-        return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+        return service;
     }
 
     @VisibleForTesting
@@ -111,17 +113,25 @@
             @Nullable String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        mTimeZoneDetectorStrategy.dumpState(pw, args);
+        mTimeZoneDetectorStrategy.dump(pw, args);
+    }
+
+    /** Internal method for handling the auto time zone setting being changed. */
+    @VisibleForTesting
+    public void handleAutoTimeZoneDetectionChanged() {
+        mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneDetectionChanged);
     }
 
     private void enforceSuggestPhoneTimeZonePermission() {
         mContext.enforceCallingPermission(
-                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+                android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE,
+                "suggest phone time and time zone");
     }
 
     private void enforceSuggestManualTimeZonePermission() {
         mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+                android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE,
+                "suggest manual time and time zone");
     }
 }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b0e0069..1d439e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -15,192 +15,26 @@
  */
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.PhoneTimeZoneSuggestion;
-import android.content.Context;
-import android.util.LocalLog;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 
 /**
- * A singleton, stateful time zone detection strategy that is aware of user (manual) suggestions and
- * suggestions from multiple phone devices. Suggestions are acted on or ignored as needed, dependent
- * on the current "auto time zone detection" setting.
+ * The interface for the class that implement the time detection algorithm used by the
+ * {@link TimeZoneDetectorService}.
  *
- * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses
- * the best suggestion based on a scoring algorithm. If several phones provide the same score then
- * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer
- * possible to be confident about the time zone, phones must submit an empty suggestion in order to
- * "withdraw" their previous suggestion.
+ * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
+ * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must
+ * handle thread safety.
+ *
+ * @hide
  */
-public class TimeZoneDetectorStrategy {
+public interface TimeZoneDetectorStrategy {
 
-    /**
-     * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be
-     * faked for tests.
-     *
-     * <p>Note: Because the system properties-derived values like
-     * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
-     * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
-     * processes!), their use are prone to race conditions. That will be true until the
-     * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategy}.
-     */
-    @VisibleForTesting
-    public interface Callback {
-
-        /**
-         * Returns true if automatic time zone detection is enabled in settings.
-         */
-        boolean isAutoTimeZoneDetectionEnabled();
-
-        /**
-         * Returns true if the device has had an explicit time zone set.
-         */
-        boolean isDeviceTimeZoneInitialized();
-
-        /**
-         * Returns the device's currently configured time zone.
-         */
-        String getDeviceTimeZone();
-
-        /**
-         * Sets the device's time zone.
-         */
-        void setDeviceTimeZone(@NonNull String zoneId);
-    }
-
-    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
-    private static final boolean DBG = false;
-
-    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Origin {}
-
-    /** Used when a time value originated from a telephony signal. */
-    @Origin
-    private static final int ORIGIN_PHONE = 1;
-
-    /** Used when a time value originated from a user / manual settings. */
-    @Origin
-    private static final int ORIGIN_MANUAL = 2;
-
-    /**
-     * The abstract score for an empty or invalid phone suggestion.
-     *
-     * Used to score phone suggestions where there is no zone.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_NONE = 0;
-
-    /**
-     * The abstract score for a low quality phone suggestion.
-     *
-     * Used to score suggestions where:
-     * The suggested zone ID is one of several possibilities, and the possibilities have different
-     * offsets.
-     *
-     * You would have to be quite desperate to want to use this choice.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_LOW = 1;
-
-    /**
-     * The abstract score for a medium quality phone suggestion.
-     *
-     * Used for:
-     * The suggested zone ID is one of several possibilities but at least the possibilities have the
-     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
-     * switch to DST at the wrong time and (for example) their calendar events.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_MEDIUM = 2;
-
-    /**
-     * The abstract score for a high quality phone suggestion.
-     *
-     * Used for:
-     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
-     * the info available.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_HIGH = 3;
-
-    /**
-     * The abstract score for a highest quality phone suggestion.
-     *
-     * Used for:
-     * Suggestions that must "win" because they constitute test or emulator zone ID.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_HIGHEST = 4;
-
-    /**
-     * The threshold at which phone suggestions are good enough to use to set the device's time
-     * zone.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM;
-
-    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
-    private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30;
-
-    @NonNull
-    private final Callback mCallback;
-
-    /**
-     * A log that records the decisions / decision metadata that affected the device's time zone
-     * (for use during debugging).
-     */
-    @NonNull
-    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
-    /**
-     * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two
-     * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to
-     * be stable.
-     */
-    @GuardedBy("this")
-    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex =
-            new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
-
-    /**
-     * Creates a new instance of {@link TimeZoneDetectorStrategy}.
-     */
-    public static TimeZoneDetectorStrategy create(Context context) {
-        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
-        return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper);
-    }
-
-    @VisibleForTesting
-    public TimeZoneDetectorStrategy(Callback callback) {
-        mCallback = Objects.requireNonNull(callback);
-    }
-
-    /** Process the suggested manually- / user-entered time zone. */
-    public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
-        Objects.requireNonNull(suggestion);
-
-        String timeZoneId = suggestion.getZoneId();
-        String cause = "Manual time suggestion received: suggestion=" + suggestion;
-        setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
-    }
+    /** Process the suggested manually-entered (i.e. user sourced) time zone. */
+    void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion);
 
     /**
      * Suggests a time zone for the device, or withdraws a previous suggestion if
@@ -210,312 +44,15 @@
      * suggestion. The strategy uses suggestions to decide whether to modify the device's time zone
      * setting and what to set it to.
      */
-    public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
-        if (DBG) {
-            Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion);
-        }
-        Objects.requireNonNull(suggestion);
-
-        // Score the suggestion.
-        int score = scorePhoneSuggestion(suggestion);
-        QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
-
-        // Store the suggestion against the correct slotIndex.
-        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
-
-        // Now perform auto time zone detection. The new suggestion may be used to modify the time
-        // zone setting.
-        String reason = "New phone time suggested. suggestion=" + suggestion;
-        doAutoTimeZoneDetection(reason);
-    }
-
-    private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
-        int score;
-        if (suggestion.getZoneId() == null) {
-            score = PHONE_SCORE_NONE;
-        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
-                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
-            // Handle emulator / test cases : These suggestions should always just be used.
-            score = PHONE_SCORE_HIGHEST;
-        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
-            score = PHONE_SCORE_HIGH;
-        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
-            // The suggestion may be wrong, but at least the offset should be correct.
-            score = PHONE_SCORE_MEDIUM;
-        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
-            // The suggestion has a good chance of being wrong.
-            score = PHONE_SCORE_LOW;
-        } else {
-            throw new AssertionError();
-        }
-        return score;
-    }
-
-    /**
-     * Finds the best available time zone suggestion from all phones. If it is high-enough quality
-     * and automatic time zone detection is enabled then it will be set on the device. The outcome
-     * can be that this strategy becomes / remains un-opinionated and nothing is set.
-     */
-    @GuardedBy("this")
-    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
-        if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
-            // Avoid doing unnecessary work with this (race-prone) check.
-            return;
-        }
-
-        QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
-
-        // Work out what to do with the best suggestion.
-        if (bestPhoneSuggestion == null) {
-            // There is no phone suggestion available at all. Become un-opinionated.
-            if (DBG) {
-                Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion."
-                        + " detectionReason=" + detectionReason);
-            }
-            return;
-        }
-
-        // Special case handling for uninitialized devices. This should only happen once.
-        String newZoneId = bestPhoneSuggestion.suggestion.getZoneId();
-        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
-            String cause = "Device has no time zone set. Attempting to set the device to the best"
-                    + " available suggestion."
-                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                    + ", detectionReason=" + detectionReason;
-            Slog.i(LOG_TAG, cause);
-            setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause);
-            return;
-        }
-
-        boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD;
-        if (!suggestionGoodEnough) {
-            if (DBG) {
-                Slog.d(LOG_TAG, "Best suggestion not good enough."
-                        + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                        + ", detectionReason=" + detectionReason);
-            }
-            return;
-        }
-
-        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
-        // zone ID.
-        if (newZoneId == null) {
-            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
-                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                    + " detectionReason=" + detectionReason);
-            return;
-        }
-
-        String zoneId = bestPhoneSuggestion.suggestion.getZoneId();
-        String cause = "Found good suggestion."
-                + ", bestPhoneSuggestion=" + bestPhoneSuggestion
-                + ", detectionReason=" + detectionReason;
-        setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause);
-    }
-
-    @GuardedBy("this")
-    private void setDeviceTimeZoneIfRequired(
-            @Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
-        Objects.requireNonNull(newZoneId);
-        Objects.requireNonNull(cause);
-
-        boolean isOriginAutomatic = isOriginAutomatic(origin);
-        if (isOriginAutomatic) {
-            if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
-                            + " origin=" + origin
-                            + ", newZoneId=" + newZoneId
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        } else {
-            if (mCallback.isAutoTimeZoneDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time zone detection is enabled."
-                            + " origin=" + origin
-                            + ", newZoneId=" + newZoneId
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        }
-
-        String currentZoneId = mCallback.getDeviceTimeZone();
-
-        // Avoid unnecessary changes / intents.
-        if (newZoneId.equals(currentZoneId)) {
-            // No need to set the device time zone - the setting is already what we would be
-            // suggesting.
-            if (DBG) {
-                Slog.d(LOG_TAG, "No need to change the time zone;"
-                        + " device is already set to the suggested zone."
-                        + " origin=" + origin
-                        + ", newZoneId=" + newZoneId
-                        + ", cause=" + cause);
-            }
-            return;
-        }
-
-        mCallback.setDeviceTimeZone(newZoneId);
-        String msg = "Set device time zone."
-                + " origin=" + origin
-                + ", currentZoneId=" + currentZoneId
-                + ", newZoneId=" + newZoneId
-                + ", cause=" + cause;
-        if (DBG) {
-            Slog.d(LOG_TAG, msg);
-        }
-        mTimeZoneChangesLog.log(msg);
-    }
-
-    private static boolean isOriginAutomatic(@Origin int origin) {
-        return origin != ORIGIN_MANUAL;
-    }
-
-    @GuardedBy("this")
-    @Nullable
-    private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() {
-        QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
-
-        // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
-        // and find the best. Note that we deliberately do not look at age: the caller can
-        // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
-        // expected to withdraw suggestions they no longer have confidence in.
-        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
-            QualifiedPhoneTimeZoneSuggestion candidateSuggestion =
-                    mSuggestionBySlotIndex.valueAt(i);
-            if (candidateSuggestion == null) {
-                // Unexpected
-                continue;
-            }
-
-            if (bestSuggestion == null) {
-                bestSuggestion = candidateSuggestion;
-            } else if (candidateSuggestion.score > bestSuggestion.score) {
-                bestSuggestion = candidateSuggestion;
-            } else if (candidateSuggestion.score == bestSuggestion.score) {
-                // Tie! Use the suggestion with the lowest slotIndex.
-                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
-                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
-                if (candidateSlotIndex < bestSlotIndex) {
-                    bestSuggestion = candidateSuggestion;
-                }
-            }
-        }
-        return bestSuggestion;
-    }
-
-    /**
-     * Returns the current best phone suggestion. Not intended for general use: it is used during
-     * tests to check strategy behavior.
-     */
-    @VisibleForTesting
-    @Nullable
-    public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() {
-        return findBestPhoneSuggestion();
-    }
+    void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion);
 
     /**
      * Called when there has been a change to the automatic time zone detection setting.
      */
-    @VisibleForTesting
-    public synchronized void handleAutoTimeZoneDetectionChange() {
-        if (DBG) {
-            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
-        }
-        if (mCallback.isAutoTimeZoneDetectionEnabled()) {
-            // When the user enabled time zone detection, run the time zone detection and change the
-            // device time zone if possible.
-            String reason = "Auto time zone detection setting enabled.";
-            doAutoTimeZoneDetection(reason);
-        }
-    }
+    void handleAutoTimeZoneDetectionChanged();
 
     /**
      * Dumps internal state such as field values.
      */
-    public synchronized void dumpState(PrintWriter pw, String[] args) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
-        ipw.println("TimeZoneDetectorStrategy:");
-
-        ipw.increaseIndent(); // level 1
-        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
-                + mCallback.isAutoTimeZoneDetectionEnabled());
-        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
-                + mCallback.isDeviceTimeZoneInitialized());
-        ipw.println("mCallback.getDeviceTimeZone()="
-                + mCallback.getDeviceTimeZone());
-
-        ipw.println("Time zone change log:");
-        ipw.increaseIndent(); // level 2
-        mTimeZoneChangesLog.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-
-        ipw.println("Phone suggestion history:");
-        ipw.increaseIndent(); // level 2
-        mSuggestionBySlotIndex.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-        ipw.decreaseIndent(); // level 1
-        ipw.flush();
-    }
-
-    /**
-     * A method used to inspect strategy state during tests. Not intended for general use.
-     */
-    @VisibleForTesting
-    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) {
-        return mSuggestionBySlotIndex.get(slotIndex);
-    }
-
-    /**
-     * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
-     */
-    @VisibleForTesting
-    public static class QualifiedPhoneTimeZoneSuggestion {
-
-        @VisibleForTesting
-        public final PhoneTimeZoneSuggestion suggestion;
-
-        /**
-         * The score the suggestion has been given. This can be used to rank against other
-         * suggestions of the same type.
-         */
-        @VisibleForTesting
-        public final int score;
-
-        @VisibleForTesting
-        public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
-            this.suggestion = suggestion;
-            this.score = score;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
-            return score == that.score
-                    && suggestion.equals(that.suggestion);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(score, suggestion);
-        }
-
-        @Override
-        public String toString() {
-            return "QualifiedPhoneTimeZoneSuggestion{"
-                    + "suggestion=" + suggestion
-                    + ", score=" + score
-                    + '}';
-        }
-    }
+    void dump(PrintWriter pw, String[] args);
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
new file mode 100644
index 0000000..f85f9fe
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.timezonedetector;
+
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.Context;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
+ * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
+ * zone detection" setting.
+ *
+ * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses
+ * the best suggestion based on a scoring algorithm. If several phones provide the same score then
+ * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer
+ * possible to be confident about the time zone, phones must submit an empty suggestion in order to
+ * "withdraw" their previous suggestion.
+ *
+ * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
+ */
+public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
+
+    /**
+     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with the surrounding service. It can
+     * be faked for tests.
+     *
+     * <p>Note: Because the system properties-derived values like
+     * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
+     * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
+     * processes!), their use are prone to race conditions. That will be true until the
+     * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategyImpl}.
+     */
+    @VisibleForTesting
+    public interface Callback {
+
+        /**
+         * Returns true if automatic time zone detection is enabled in settings.
+         */
+        boolean isAutoTimeZoneDetectionEnabled();
+
+        /**
+         * Returns true if the device has had an explicit time zone set.
+         */
+        boolean isDeviceTimeZoneInitialized();
+
+        /**
+         * Returns the device's currently configured time zone.
+         */
+        String getDeviceTimeZone();
+
+        /**
+         * Sets the device's time zone.
+         */
+        void setDeviceTimeZone(@NonNull String zoneId);
+    }
+
+    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
+    private static final boolean DBG = false;
+
+    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Origin {}
+
+    /** Used when a time value originated from a telephony signal. */
+    @Origin
+    private static final int ORIGIN_PHONE = 1;
+
+    /** Used when a time value originated from a user / manual settings. */
+    @Origin
+    private static final int ORIGIN_MANUAL = 2;
+
+    /**
+     * The abstract score for an empty or invalid phone suggestion.
+     *
+     * Used to score phone suggestions where there is no zone.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_NONE = 0;
+
+    /**
+     * The abstract score for a low quality phone suggestion.
+     *
+     * Used to score suggestions where:
+     * The suggested zone ID is one of several possibilities, and the possibilities have different
+     * offsets.
+     *
+     * You would have to be quite desperate to want to use this choice.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_LOW = 1;
+
+    /**
+     * The abstract score for a medium quality phone suggestion.
+     *
+     * Used for:
+     * The suggested zone ID is one of several possibilities but at least the possibilities have the
+     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
+     * switch to DST at the wrong time and (for example) their calendar events.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_MEDIUM = 2;
+
+    /**
+     * The abstract score for a high quality phone suggestion.
+     *
+     * Used for:
+     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
+     * the info available.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_HIGH = 3;
+
+    /**
+     * The abstract score for a highest quality phone suggestion.
+     *
+     * Used for:
+     * Suggestions that must "win" because they constitute test or emulator zone ID.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_HIGHEST = 4;
+
+    /**
+     * The threshold at which phone suggestions are good enough to use to set the device's time
+     * zone.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM;
+
+    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+    private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30;
+
+    @NonNull
+    private final Callback mCallback;
+
+    /**
+     * A log that records the decisions / decision metadata that affected the device's time zone
+     * (for use during debugging).
+     */
+    @NonNull
+    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
+
+    /**
+     * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two
+     * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to
+     * be stable.
+     */
+    @GuardedBy("this")
+    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex =
+            new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
+
+    /**
+     * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
+     */
+    public static TimeZoneDetectorStrategyImpl create(Context context) {
+        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
+        return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
+    }
+
+    @VisibleForTesting
+    public TimeZoneDetectorStrategyImpl(Callback callback) {
+        mCallback = Objects.requireNonNull(callback);
+    }
+
+    @Override
+    public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
+        Objects.requireNonNull(suggestion);
+
+        String timeZoneId = suggestion.getZoneId();
+        String cause = "Manual time suggestion received: suggestion=" + suggestion;
+        setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
+    }
+
+    @Override
+    public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
+        if (DBG) {
+            Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion);
+        }
+        Objects.requireNonNull(suggestion);
+
+        // Score the suggestion.
+        int score = scorePhoneSuggestion(suggestion);
+        QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
+
+        // Store the suggestion against the correct slotIndex.
+        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
+
+        // Now perform auto time zone detection. The new suggestion may be used to modify the time
+        // zone setting.
+        String reason = "New phone time suggested. suggestion=" + suggestion;
+        doAutoTimeZoneDetection(reason);
+    }
+
+    private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
+        int score;
+        if (suggestion.getZoneId() == null) {
+            score = PHONE_SCORE_NONE;
+        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
+                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
+            // Handle emulator / test cases : These suggestions should always just be used.
+            score = PHONE_SCORE_HIGHEST;
+        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
+            score = PHONE_SCORE_HIGH;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
+            // The suggestion may be wrong, but at least the offset should be correct.
+            score = PHONE_SCORE_MEDIUM;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
+            // The suggestion has a good chance of being wrong.
+            score = PHONE_SCORE_LOW;
+        } else {
+            throw new AssertionError();
+        }
+        return score;
+    }
+
+    /**
+     * Finds the best available time zone suggestion from all phones. If it is high-enough quality
+     * and automatic time zone detection is enabled then it will be set on the device. The outcome
+     * can be that this strategy becomes / remains un-opinionated and nothing is set.
+     */
+    @GuardedBy("this")
+    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
+        if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
+            // Avoid doing unnecessary work with this (race-prone) check.
+            return;
+        }
+
+        QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
+
+        // Work out what to do with the best suggestion.
+        if (bestPhoneSuggestion == null) {
+            // There is no phone suggestion available at all. Become un-opinionated.
+            if (DBG) {
+                Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion."
+                        + " detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        // Special case handling for uninitialized devices. This should only happen once.
+        String newZoneId = bestPhoneSuggestion.suggestion.getZoneId();
+        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
+            String cause = "Device has no time zone set. Attempting to set the device to the best"
+                    + " available suggestion."
+                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
+                    + ", detectionReason=" + detectionReason;
+            Slog.i(LOG_TAG, cause);
+            setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause);
+            return;
+        }
+
+        boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD;
+        if (!suggestionGoodEnough) {
+            if (DBG) {
+                Slog.d(LOG_TAG, "Best suggestion not good enough."
+                        + " bestPhoneSuggestion=" + bestPhoneSuggestion
+                        + ", detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
+        // zone ID.
+        if (newZoneId == null) {
+            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
+                    + " detectionReason=" + detectionReason);
+            return;
+        }
+
+        String zoneId = bestPhoneSuggestion.suggestion.getZoneId();
+        String cause = "Found good suggestion."
+                + ", bestPhoneSuggestion=" + bestPhoneSuggestion
+                + ", detectionReason=" + detectionReason;
+        setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause);
+    }
+
+    @GuardedBy("this")
+    private void setDeviceTimeZoneIfRequired(
+            @Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
+        Objects.requireNonNull(newZoneId);
+        Objects.requireNonNull(cause);
+
+        boolean isOriginAutomatic = isOriginAutomatic(origin);
+        if (isOriginAutomatic) {
+            if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
+                            + " origin=" + origin
+                            + ", newZoneId=" + newZoneId
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        } else {
+            if (mCallback.isAutoTimeZoneDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time zone detection is enabled."
+                            + " origin=" + origin
+                            + ", newZoneId=" + newZoneId
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        }
+
+        String currentZoneId = mCallback.getDeviceTimeZone();
+
+        // Avoid unnecessary changes / intents.
+        if (newZoneId.equals(currentZoneId)) {
+            // No need to set the device time zone - the setting is already what we would be
+            // suggesting.
+            if (DBG) {
+                Slog.d(LOG_TAG, "No need to change the time zone;"
+                        + " device is already set to the suggested zone."
+                        + " origin=" + origin
+                        + ", newZoneId=" + newZoneId
+                        + ", cause=" + cause);
+            }
+            return;
+        }
+
+        mCallback.setDeviceTimeZone(newZoneId);
+        String msg = "Set device time zone."
+                + " origin=" + origin
+                + ", currentZoneId=" + currentZoneId
+                + ", newZoneId=" + newZoneId
+                + ", cause=" + cause;
+        if (DBG) {
+            Slog.d(LOG_TAG, msg);
+        }
+        mTimeZoneChangesLog.log(msg);
+    }
+
+    private static boolean isOriginAutomatic(@Origin int origin) {
+        return origin != ORIGIN_MANUAL;
+    }
+
+    @GuardedBy("this")
+    @Nullable
+    private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() {
+        QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
+
+        // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
+        // and find the best. Note that we deliberately do not look at age: the caller can
+        // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
+        // expected to withdraw suggestions they no longer have confidence in.
+        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+            QualifiedPhoneTimeZoneSuggestion candidateSuggestion =
+                    mSuggestionBySlotIndex.valueAt(i);
+            if (candidateSuggestion == null) {
+                // Unexpected
+                continue;
+            }
+
+            if (bestSuggestion == null) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score > bestSuggestion.score) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score == bestSuggestion.score) {
+                // Tie! Use the suggestion with the lowest slotIndex.
+                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
+                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
+                if (candidateSlotIndex < bestSlotIndex) {
+                    bestSuggestion = candidateSuggestion;
+                }
+            }
+        }
+        return bestSuggestion;
+    }
+
+    /**
+     * Returns the current best phone suggestion. Not intended for general use: it is used during
+     * tests to check strategy behavior.
+     */
+    @VisibleForTesting
+    @Nullable
+    public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() {
+        return findBestPhoneSuggestion();
+    }
+
+    @Override
+    public synchronized void handleAutoTimeZoneDetectionChanged() {
+        if (DBG) {
+            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+        }
+        if (mCallback.isAutoTimeZoneDetectionEnabled()) {
+            // When the user enabled time zone detection, run the time zone detection and change the
+            // device time zone if possible.
+            String reason = "Auto time zone detection setting enabled.";
+            doAutoTimeZoneDetection(reason);
+        }
+    }
+
+    /**
+     * Dumps internal state such as field values.
+     */
+    @Override
+    public synchronized void dump(PrintWriter pw, String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+        ipw.println("TimeZoneDetectorStrategy:");
+
+        ipw.increaseIndent(); // level 1
+        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
+                + mCallback.isAutoTimeZoneDetectionEnabled());
+        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+                + mCallback.isDeviceTimeZoneInitialized());
+        ipw.println("mCallback.getDeviceTimeZone()="
+                + mCallback.getDeviceTimeZone());
+
+        ipw.println("Time zone change log:");
+        ipw.increaseIndent(); // level 2
+        mTimeZoneChangesLog.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
+        ipw.println("Phone suggestion history:");
+        ipw.increaseIndent(); // level 2
+        mSuggestionBySlotIndex.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+        ipw.decreaseIndent(); // level 1
+        ipw.flush();
+    }
+
+    /**
+     * A method used to inspect strategy state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) {
+        return mSuggestionBySlotIndex.get(slotIndex);
+    }
+
+    /**
+     * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
+     */
+    @VisibleForTesting
+    public static class QualifiedPhoneTimeZoneSuggestion {
+
+        @VisibleForTesting
+        public final PhoneTimeZoneSuggestion suggestion;
+
+        /**
+         * The score the suggestion has been given. This can be used to rank against other
+         * suggestions of the same type.
+         */
+        @VisibleForTesting
+        public final int score;
+
+        @VisibleForTesting
+        public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
+            this.suggestion = suggestion;
+            this.score = score;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
+            return score == that.score
+                    && suggestion.equals(that.suggestion);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(score, suggestion);
+        }
+
+        @Override
+        public String toString() {
+            return "QualifiedPhoneTimeZoneSuggestion{"
+                    + "suggestion=" + suggestion
+                    + ", score=" + score
+                    + '}';
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index ae53692..218f43c 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -33,14 +33,13 @@
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
 import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.timezonedetector.TestHandler;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -108,7 +107,7 @@
                 eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
                 anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion);
     }
 
@@ -140,7 +139,7 @@
                 eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
                 anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion);
     }
 
@@ -170,7 +169,7 @@
         verify(mMockContext).enforceCallingOrSelfPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion);
     }
 
@@ -187,21 +186,23 @@
 
     @Test
     public void testAutoTimeDetectionToggle() throws Exception {
-        mTimeDetectorService.handleAutoTimeDetectionToggle();
+        mTimeDetectorService.handleAutoTimeDetectionChanged();
         mTestHandler.assertTotalMessagesEnqueued(1);
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled();
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
 
-        mTimeDetectorService.handleAutoTimeDetectionToggle();
+        mStubbedTimeDetectorStrategy.resetCallTracking();
+
+        mTimeDetectorService.handleAutoTimeDetectionChanged();
         mTestHandler.assertTotalMessagesEnqueued(2);
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled();
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
     }
 
     private static PhoneTimeSuggestion createPhoneTimeSuggestion() {
-        int phoneId = 1234;
+        int slotIndex = 1234;
         TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
-        return new PhoneTimeSuggestion.Builder(phoneId)
+        return new PhoneTimeSuggestion.Builder(slotIndex)
                 .setUtcTime(timeValue)
                 .build();
     }
@@ -222,7 +223,7 @@
         private PhoneTimeSuggestion mLastPhoneSuggestion;
         private ManualTimeSuggestion mLastManualSuggestion;
         private NetworkTimeSuggestion mLastNetworkSuggestion;
-        private boolean mLastAutoTimeDetectionToggleCalled;
+        private boolean mHandleAutoTimeDetectionChangedCalled;
         private boolean mDumpCalled;
 
         @Override
@@ -231,31 +232,26 @@
 
         @Override
         public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastPhoneSuggestion = timeSuggestion;
         }
 
         @Override
         public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastManualSuggestion = timeSuggestion;
         }
 
         @Override
         public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastNetworkSuggestion = timeSuggestion;
         }
 
         @Override
         public void handleAutoTimeDetectionChanged() {
-            resetCallTracking();
-            mLastAutoTimeDetectionToggleCalled = true;
+            mHandleAutoTimeDetectionChangedCalled = true;
         }
 
         @Override
         public void dump(PrintWriter pw, String[] args) {
-            resetCallTracking();
             mDumpCalled = true;
         }
 
@@ -263,7 +259,7 @@
             mLastPhoneSuggestion = null;
             mLastManualSuggestion = null;
             mLastNetworkSuggestion = null;
-            mLastAutoTimeDetectionToggleCalled = false;
+            mHandleAutoTimeDetectionChangedCalled = false;
             mDumpCalled = false;
         }
 
@@ -279,45 +275,12 @@
             assertEquals(expectedSuggestion, mLastNetworkSuggestion);
         }
 
-        void verifyHandleAutoTimeDetectionToggleCalled() {
-            assertTrue(mLastAutoTimeDetectionToggleCalled);
+        void verifyHandleAutoTimeDetectionChangedCalled() {
+            assertTrue(mHandleAutoTimeDetectionChangedCalled);
         }
 
         void verifyDumpCalled() {
             assertTrue(mDumpCalled);
         }
     }
-
-    /**
-     * A Handler that can track posts/sends and wait for work to be completed.
-     */
-    private static class TestHandler extends Handler {
-
-        private int mMessagesSent;
-
-        TestHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            mMessagesSent++;
-            return super.sendMessageAtTime(msg, uptimeMillis);
-        }
-
-        /** Asserts the number of messages posted or sent is as expected. */
-        void assertTotalMessagesEnqueued(int expected) {
-            assertEquals(expected, mMessagesSent);
-        }
-
-        /**
-         * Waits for all currently enqueued work due to be processed to be completed before
-         * returning.
-         */
-        void waitForEmptyQueue() throws InterruptedException {
-            while (!getLooper().getQueue().isIdle()) {
-                Thread.sleep(100);
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
new file mode 100644
index 0000000..21c9685
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * A Handler that can track posts/sends and wait for them to be completed.
+ */
+public class TestHandler extends Handler {
+
+    private final Object mMonitor = new Object();
+    private int mMessagesProcessed = 0;
+    private int mMessagesSent = 0;
+
+    public TestHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+        synchronized (mMonitor) {
+            mMessagesSent++;
+        }
+
+        Runnable callback = msg.getCallback();
+        // Have the callback increment the mMessagesProcessed when it is done. It will notify
+        // any threads waiting for all messages to be processed if appropriate.
+        Runnable newCallback = () -> {
+            callback.run();
+            synchronized (mMonitor) {
+                mMessagesProcessed++;
+                if (mMessagesSent == mMessagesProcessed) {
+                    mMonitor.notifyAll();
+                }
+            }
+        };
+        msg.setCallback(newCallback);
+        return super.sendMessageAtTime(msg, uptimeMillis);
+    }
+
+    /** Asserts the number of messages posted or sent is as expected. */
+    public void assertTotalMessagesEnqueued(int expected) {
+        synchronized (mMonitor) {
+            assertEquals(expected, mMessagesSent);
+        }
+    }
+
+    /**
+     * Waits for all enqueued work to be completed before returning.
+     */
+    public void waitForMessagesToBeProcessed() throws InterruptedException {
+        synchronized (mMonitor) {
+            if (mMessagesSent != mMessagesProcessed) {
+                mMonitor.wait();
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
new file mode 100644
index 0000000..3e7d40a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.HandlerThread;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeZoneDetectorServiceTest {
+
+    private Context mMockContext;
+    private StubbedTimeZoneDetectorStrategy mStubbedTimeZoneDetectorStrategy;
+
+    private TimeZoneDetectorService mTimeZoneDetectorService;
+    private HandlerThread mHandlerThread;
+    private TestHandler mTestHandler;
+
+
+    @Before
+    public void setUp() {
+        mMockContext = mock(Context.class);
+
+        // Create a thread + handler for processing the work that the service posts.
+        mHandlerThread = new HandlerThread("TimeZoneDetectorServiceTest");
+        mHandlerThread.start();
+        mTestHandler = new TestHandler(mHandlerThread.getLooper());
+
+        mStubbedTimeZoneDetectorStrategy = new StubbedTimeZoneDetectorStrategy();
+
+        mTimeZoneDetectorService = new TimeZoneDetectorService(
+                mMockContext, mTestHandler, mStubbedTimeZoneDetectorStrategy);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSuggestPhoneTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+        PhoneTimeZoneSuggestion timeZoneSuggestion = createPhoneTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingPermission(
+                    eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestPhoneTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        PhoneTimeZoneSuggestion timeZoneSuggestion = createPhoneTimeZoneSuggestion();
+        mTimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifySuggestPhoneTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSuggestManualTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestManualTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+        mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test
+    public void testDump() {
+        when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mTimeZoneDetectorService.dump(null, null, null);
+
+        verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP));
+        mStubbedTimeZoneDetectorStrategy.verifyDumpCalled();
+    }
+
+    @Test
+    public void testAutoTimeZoneDetectionChanged() throws Exception {
+        mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged();
+        mTestHandler.assertTotalMessagesEnqueued(1);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled();
+
+        mStubbedTimeZoneDetectorStrategy.resetCallTracking();
+
+        mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged();
+        mTestHandler.assertTotalMessagesEnqueued(2);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled();
+    }
+
+    private static PhoneTimeZoneSuggestion createPhoneTimeZoneSuggestion() {
+        int slotIndex = 1234;
+        return new PhoneTimeZoneSuggestion.Builder(slotIndex)
+                .setZoneId("TestZoneId")
+                .setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                .setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                .build();
+    }
+
+    private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
+        return new ManualTimeZoneSuggestion("TestZoneId");
+    }
+
+    private static class StubbedTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
+
+        // Call tracking.
+        private PhoneTimeZoneSuggestion mLastPhoneSuggestion;
+        private ManualTimeZoneSuggestion mLastManualSuggestion;
+        private boolean mHandleAutoTimeZoneDetectionChangedCalled;
+        private boolean mDumpCalled;
+
+        @Override
+        public void suggestPhoneTimeZone(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+            mLastPhoneSuggestion = timeZoneSuggestion;
+        }
+
+        @Override
+        public void suggestManualTimeZone(ManualTimeZoneSuggestion timeZoneSuggestion) {
+            mLastManualSuggestion = timeZoneSuggestion;
+        }
+
+        @Override
+        public void handleAutoTimeZoneDetectionChanged() {
+            mHandleAutoTimeZoneDetectionChangedCalled = true;
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String[] args) {
+            mDumpCalled = true;
+        }
+
+        void resetCallTracking() {
+            mLastPhoneSuggestion = null;
+            mLastManualSuggestion = null;
+            mHandleAutoTimeZoneDetectionChangedCalled = false;
+            mDumpCalled = false;
+        }
+
+        void verifySuggestPhoneTimeZoneCalled(PhoneTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastPhoneSuggestion);
+        }
+
+        public void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastManualSuggestion);
+        }
+
+        void verifyHandleAutoTimeZoneDetectionChangedCalled() {
+            assertTrue(mHandleAutoTimeZoneDetectionChangedCalled);
+        }
+
+        void verifyDumpCalled() {
+            assertTrue(mDumpCalled);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 2429cfc..1e38711 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -24,12 +24,12 @@
 import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
 
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGH;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGHEST;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_LOW;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_MEDIUM;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_NONE;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_USAGE_THRESHOLD;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_HIGH;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_HIGHEST;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_LOW;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_MEDIUM;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_NONE;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_USAGE_THRESHOLD;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -41,7 +41,7 @@
 import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality;
 
-import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion;
+import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedPhoneTimeZoneSuggestion;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,9 +52,9 @@
 import java.util.LinkedList;
 
 /**
- * White-box unit tests for {@link TimeZoneDetectorStrategy}.
+ * White-box unit tests for {@link TimeZoneDetectorStrategyImpl}.
  */
-public class TimeZoneDetectorStrategyTest {
+public class TimeZoneDetectorStrategyImplTest {
 
     /** A time zone used for initialization that does not occur elsewhere in tests. */
     private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
@@ -78,14 +78,14 @@
             newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGHEST),
     };
 
-    private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
     private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback;
 
     @Before
     public void setUp() {
         mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback();
         mTimeZoneDetectorStrategy =
-                new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback);
+                new TimeZoneDetectorStrategyImpl(mFakeTimeZoneDetectorStrategyCallback);
     }
 
     @Test
@@ -364,7 +364,7 @@
     }
 
     /**
-     * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
      * zone is actually necessary. This test proves that the service doesn't assume it knows the
      * current setting.
      */
@@ -441,7 +441,8 @@
         return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build();
     }
 
-    static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
+    static class FakeTimeZoneDetectorStrategyCallback
+            implements TimeZoneDetectorStrategyImpl.Callback {
 
         private boolean mAutoTimeZoneDetectionEnabled;
         private TestState<String> mTimeZoneId = new TestState<>();
@@ -560,7 +561,7 @@
 
         Script autoTimeZoneDetectionEnabled(boolean enabled) {
             mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled);
-            mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
+            mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChanged();
             return this;
         }
 
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index e01deb2..1e1cdba 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -80,6 +80,9 @@
     private int mMaxRelativeJitter;
     private int mAverageRoundTripTime;
     private int mCodecType;
+    private boolean mRtpInactivityDetected;
+    private boolean mRxSilenceDetected;
+    private boolean mTxSilenceDetected;
 
     /** @hide **/
     public CallQuality(Parcel in) {
@@ -94,6 +97,9 @@
         mMaxRelativeJitter = in.readInt();
         mAverageRoundTripTime = in.readInt();
         mCodecType = in.readInt();
+        mRtpInactivityDetected = in.readBoolean();
+        mRxSilenceDetected = in.readBoolean();
+        mTxSilenceDetected = in.readBoolean();
     }
 
     /** @hide **/
@@ -109,7 +115,7 @@
      * @param numRtpPacketsReceived RTP packets received from network
      * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
      * transmitted
-     * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved
+     * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
      * @param averageRelativeJitter average relative jitter in milliseconds
      * @param maxRelativeJitter maximum relative jitter in milliseconds
      * @param averageRoundTripTime average round trip delay in milliseconds
@@ -127,6 +133,48 @@
             int maxRelativeJitter,
             int averageRoundTripTime,
             int codecType) {
+        this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration,
+            numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost,
+            numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter,
+            averageRoundTripTime, codecType, false, false, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param callQualityLevel the call quality level (see #CallQualityLevel)
+     * @param callDuration the call duration in milliseconds
+     * @param numRtpPacketsTransmitted RTP packets sent to network
+     * @param numRtpPacketsReceived RTP packets received from network
+     * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
+     * transmitted
+     * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
+     * @param averageRelativeJitter average relative jitter in milliseconds
+     * @param maxRelativeJitter maximum relative jitter in milliseconds
+     * @param averageRoundTripTime average round trip delay in milliseconds
+     * @param codecType the codec type
+     * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of
+     * 4 seconds
+     * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds
+     * immediately after call is connected
+     * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately
+     * after call is connected
+     */
+    public CallQuality(
+            @CallQualityLevel int downlinkCallQualityLevel,
+            @CallQualityLevel int uplinkCallQualityLevel,
+            int callDuration,
+            int numRtpPacketsTransmitted,
+            int numRtpPacketsReceived,
+            int numRtpPacketsTransmittedLost,
+            int numRtpPacketsNotReceived,
+            int averageRelativeJitter,
+            int maxRelativeJitter,
+            int averageRoundTripTime,
+            int codecType,
+            boolean rtpInactivityDetected,
+            boolean rxSilenceDetected,
+            boolean txSilenceDetected) {
         this.mDownlinkCallQualityLevel = downlinkCallQualityLevel;
         this.mUplinkCallQualityLevel = uplinkCallQualityLevel;
         this.mCallDuration = callDuration;
@@ -138,6 +186,9 @@
         this.mMaxRelativeJitter = maxRelativeJitter;
         this.mAverageRoundTripTime = averageRoundTripTime;
         this.mCodecType = codecType;
+        this.mRtpInactivityDetected = rtpInactivityDetected;
+        this.mRxSilenceDetected = rxSilenceDetected;
+        this.mTxSilenceDetected = txSilenceDetected;
     }
 
     // getters
@@ -226,6 +277,29 @@
     }
 
     /**
+     * Returns true if no rtp packets are received continuously for the last 4 seconds
+     */
+    public boolean isRtpInactivityDetected() {
+        return mRtpInactivityDetected;
+    }
+
+    /**
+     * Returns true if only silence rtp packets are received for a duration of 20 seconds starting
+     * at call setup
+     */
+    public boolean isIncomingSilenceDetected() {
+        return mRxSilenceDetected;
+    }
+
+    /**
+      * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at
+      * call setup
+      */
+    public boolean isOutgoingSilenceDetected() {
+        return mTxSilenceDetected;
+    }
+
+    /**
      * Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in
      * {@link ImsStreamMediaProfile}.
      *
@@ -270,6 +344,9 @@
                 + " maxRelativeJitter=" + mMaxRelativeJitter
                 + " averageRoundTripTime=" + mAverageRoundTripTime
                 + " codecType=" + mCodecType
+                + " rtpInactivityDetected=" + mRtpInactivityDetected
+                + " txSilenceDetected=" + mRxSilenceDetected
+                + " rxSilenceDetected=" + mTxSilenceDetected
                 + "}";
     }
 
@@ -286,7 +363,10 @@
                 mAverageRelativeJitter,
                 mMaxRelativeJitter,
                 mAverageRoundTripTime,
-                mCodecType);
+                mCodecType,
+                mRtpInactivityDetected,
+                mRxSilenceDetected,
+                mTxSilenceDetected);
     }
 
     @Override
@@ -311,7 +391,10 @@
                 && mAverageRelativeJitter == s.mAverageRelativeJitter
                 && mMaxRelativeJitter == s.mMaxRelativeJitter
                 && mAverageRoundTripTime == s.mAverageRoundTripTime
-                && mCodecType == s.mCodecType);
+                && mCodecType == s.mCodecType
+                && mRtpInactivityDetected == s.mRtpInactivityDetected
+                && mRxSilenceDetected == s.mRxSilenceDetected
+                && mTxSilenceDetected == s.mTxSilenceDetected);
     }
 
     /**
@@ -336,6 +419,9 @@
         dest.writeInt(mMaxRelativeJitter);
         dest.writeInt(mAverageRoundTripTime);
         dest.writeInt(mCodecType);
+        dest.writeBoolean(mRtpInactivityDetected);
+        dest.writeBoolean(mRxSilenceDetected);
+        dest.writeBoolean(mTxSilenceDetected);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() {
diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java
index f841df2..1fe80f4 100644
--- a/telephony/java/android/telephony/CellBroadcastService.java
+++ b/telephony/java/android/telephony/CellBroadcastService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Bundle;
@@ -102,6 +103,17 @@
             @NonNull String originatingAddress, @NonNull Consumer<Bundle> callback);
 
     /**
+     * Get broadcasted area information.
+     *
+     * @param slotIndex the index of the slot which received the area information.
+     *
+     * @return The area information string sent from the network. This is usually the human readable
+     * string shown in Setting app's SIM status page.
+     */
+    @WorkerThread
+    public abstract @NonNull CharSequence getCellBroadcastAreaInfo(int slotIndex);
+
+    /**
      * If overriding this method, call through to the super method for any unknown actions.
      * {@inheritDoc}
      */
@@ -163,5 +175,17 @@
             CellBroadcastService.this.onCdmaScpMessage(slotIndex, smsCbProgramData,
                     originatingAddress, consumer);
         }
+
+        /**
+         * Get broadcasted area information
+         *
+         * @param slotIndex         the index of the slot which received the message
+         *
+         * @return The area information
+         */
+        @Override
+        public @NonNull CharSequence getCellBroadcastAreaInfo(int slotIndex) {
+            return CellBroadcastService.this.getCellBroadcastAreaInfo(slotIndex);
+        }
     }
 }
diff --git a/telephony/java/android/telephony/ICellBroadcastService.aidl b/telephony/java/android/telephony/ICellBroadcastService.aidl
index 11263d9..4f20ed6 100644
--- a/telephony/java/android/telephony/ICellBroadcastService.aidl
+++ b/telephony/java/android/telephony/ICellBroadcastService.aidl
@@ -36,4 +36,7 @@
     /** @see android.telephony.CellBroadcastService#onCdmaScpMessage */
     oneway void handleCdmaScpMessage(int slotId, in List<CdmaSmsCbProgramData> programData,
             String originatingAddress, in RemoteCallback callback);
+
+    /** @see android.telephony.CellBroadcastService#getCellBroadcastAreaInfo */
+    CharSequence getCellBroadcastAreaInfo(int slotIndex);
 }
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 09e87c0..c0dfec9 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -572,7 +572,7 @@
     public ContentValues getContentValues() {
         ContentValues cv = new ContentValues(16);
         cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex);
-        cv.put(CellBroadcasts.SUB_ID, mSubId);
+        cv.put(CellBroadcasts.SUBSCRIPTION_ID, mSubId);
         cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
         if (mLocation.getPlmn() != null) {
             cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
@@ -636,7 +636,7 @@
         int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
         int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
         int slotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX));
-        int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUB_ID));
+        int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUBSCRIPTION_ID));
 
         String plmn;
         int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ffd40d0..1cc3f19 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1515,6 +1515,193 @@
     public static final String EXTRA_SIM_COMBINATION_NAMES =
             "android.telephony.extra.SIM_COMBINATION_NAMES";
 
+    /**
+     * <p>Broadcast Action: when data connections get redirected with validation failure.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+     *   <li>{@link #EXTRA_REDIRECTION_URL}</li><dd>redirection url string</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
+            "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+
+    /**
+     * <p>Broadcast Action: when data connections setup fails.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+     *   <li>{@link #EXTRA_ERROR_CODE}</li><dd>A integer with dataFailCause.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
+            "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+
+    /**
+     * <p>Broadcast Action: when pco value is available.
+     * intended for sim/account status checks and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+     *   <li>{@link #EXTRA_APN_PROTOCOL}</li><dd>A string with the protocol of the apn connection
+     *      (IP,IPV6, IPV4V6)</dd>
+     *   <li>{@link #EXTRA_APN_PROTOCOL_INT}</li><dd>A integer with the protocol of the apn
+     *      connection (IP,IPV6, IPV4V6)</dd>
+     *   <li>{@link #EXTRA_PCO_ID}</li><dd>An integer indicating the pco id for the data.</dd>
+     *   <li>{@link #EXTRA_PCO_VALUE}</li><dd>A byte array of pco data read from modem.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the data connection.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
+            "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+
+    /**
+     * <p>Broadcast Action: when system default network available/unavailable with
+     * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
+     * other network becomes system default network, Wi-Fi for example.
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>{@link #EXTRA_DEFAULT_NETWORK_AVAILABLE}</li>
+     *   <dd>A boolean indicates default network available.</dd>
+     *   <li>subId</li><dd>Sub Id which associated the default data.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system. </p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
+            "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+
+    /**
+     * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
+     * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+     * </ul>
+     * <p class="note">This is a protected intent that can only be sent by the system.</p>
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String ACTION_CARRIER_SIGNAL_RESET =
+            "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+
+    // CARRIER_SIGNAL_ACTION extra keys
+    /**
+     *  An string extra of redirected url upon {@link #ACTION_CARRIER_SIGNAL_REDIRECTED}.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
+
+    /**
+     *  An integer extra of error code upon {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}.
+     *  Check {@link DataFailCause} for all possible values.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_ERROR_CODE = "errorCode";
+
+    /**
+     *  An string extra of corresponding apn type upon
+     *  {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+     *  {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @deprecated This is kept for backward compatibility reason. Use {@link #EXTRA_APN_TYPE_INT}
+     *  instead.
+     *
+     *  @hide
+     */
+    @SystemApi
+    @Deprecated
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_TYPE = "apnType";
+
+    /**
+     *  An string integer of corresponding apn type upon
+     *  {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+     *  {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  Check {@link ApnSetting} TYPE_* for its values.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+
+    /**
+     *  An string extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @deprecated This is kept for backward compatibility reason.
+     *  Use {@link #EXTRA_APN_PROTOCOL_INT} instead.
+     *
+     *  @hide
+     */
+    @SystemApi
+    @Deprecated
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_PROTOCOL = "apnProto";
+
+    /**
+     *  An integer extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  Check {@link ApnSetting} PROTOCOL_* for its values.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+
+    /**
+     *  An integer extra indicating the pco id for the data upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_PCO_ID = "pcoId";
+
+    /**
+     *  An extra of byte array of pco data read from modem upon
+     *  {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_PCO_VALUE = "pcoValue";
+
+    /**
+     *  An boolean extra indicating default network available upon
+     *  {@link #ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE} broadcasts.
+     *  @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
+
     //
     //
     // Device Info
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cb66a96..ccd28f4 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -38,6 +38,9 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs.
@@ -939,6 +942,138 @@
     }
 
     /**
+     * Sets the supported countries for eUICC.
+     *
+     * <p>Requires that the calling app has the
+     * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * <p>The supported country list will be replaced by {@code supportedCountries}. For how we
+     * determine whether a country is supported please check {@link #isSupportedCountry}.
+     *
+     * @param supportedCountries is a list of strings contains country ISO codes in uppercase.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setSupportedCountries(@NonNull List<String> supportedCountries) {
+        if (!isEnabled()) {
+            return;
+        }
+        try {
+            getIEuiccController().setSupportedCountries(
+                    true /* isSupported */,
+                    supportedCountries.stream()
+                        .map(String::toUpperCase).collect(Collectors.toList()));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the unsupported countries for eUICC.
+     *
+     * <p>Requires that the calling app has the
+     * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * <p>The unsupported country list will be replaced by {@code unsupportedCountries}. For how we
+     * determine whether a country is supported please check {@link #isSupportedCountry}.
+     *
+     * @param unsupportedCountries is a list of strings contains country ISO codes in uppercase.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public void setUnsupportedCountries(@NonNull List<String> unsupportedCountries) {
+        if (!isEnabled()) {
+            return;
+        }
+        try {
+            getIEuiccController().setSupportedCountries(
+                    false /* isSupported */,
+                    unsupportedCountries.stream()
+                        .map(String::toUpperCase).collect(Collectors.toList()));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the supported countries for eUICC.
+     *
+     * <p>Requires that the calling app has the
+     * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * @return list of strings contains country ISO codes in uppercase.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    @NonNull
+    public List<String> getSupportedCountries() {
+        if (!isEnabled()) {
+            return Collections.emptyList();
+        }
+        try {
+            return getIEuiccController().getSupportedCountries(true /* isSupported */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the unsupported countries for eUICC.
+     *
+     * <p>Requires that the calling app has the
+     * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+     *
+     * @return list of strings contains country ISO codes in uppercase.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    @NonNull
+    public List<String> getUnsupportedCountries() {
+        if (!isEnabled()) {
+            return Collections.emptyList();
+        }
+        try {
+            return getIEuiccController().getSupportedCountries(false /* isSupported */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the given country supports eUICC.
+     *
+     * <p>Supported country list has a higher prority than unsupported country list. If the
+     * supported country list is not empty, {@code countryIso} will be considered as supported when
+     * it exists in the supported country list. Otherwise {@code countryIso} is not supported. If
+     * the supported country list is empty, {@code countryIso} will be considered as supported if it
+     * does not exist in the unsupported country list. Otherwise {@code countryIso} is not
+     * supported. If both supported and unsupported country lists are empty, then all countries are
+     * consider be supported. For how to set supported and unsupported country list, please check
+     * {@link #setSupportedCountries} and {@link #setUnsupportedCountries}.
+     *
+     * @param countryIso should be the ISO-3166 country code is provided in uppercase 2 character
+     * format.
+     * @return whether the given country supports eUICC or not.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    public boolean isSupportedCountry(@NonNull String countryIso) {
+        if (!isEnabled()) {
+            return false;
+        }
+        try {
+            return getIEuiccController().isSupportedCountry(countryIso.toUpperCase());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Refreshes the cardId if its uninitialized, and returns whether we should continue the
      * operation.
      * <p>
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index eef96c4..4dda066 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -351,85 +351,6 @@
             "android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED";
 
     /**
-     * <p>Broadcast Action: when data connections get redirected with validation failure.
-     * intended for sim/account status checks and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>apnType</li><dd>A string with the apn type.</dd>
-     *   <li>redirectionUrl</li><dd>redirection url string</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system.</p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
-            "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
-    /**
-     * <p>Broadcast Action: when data connections setup fails.
-     * intended for sim/account status checks and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>apnType</li><dd>A string with the apn type.</dd>
-     *   <li>errorCode</li><dd>A integer with dataFailCause.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system. </p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
-            "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
-
-    /**
-     * <p>Broadcast Action: when pco value is available.
-     * intended for sim/account status checks and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>apnType</li><dd>A string with the apn type.</dd>
-     *   <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6,
-     *                        IPV4V6)</dd>
-     *   <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
-     *   <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the data connection.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system. </p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
-            "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
-
-    /**
-     * <p>Broadcast Action: when system default network available/unavailable with
-     * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
-     * other network becomes system default network, Wi-Fi for example.
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd>
-     *   <li>subId</li><dd>Sub Id which associated the default data.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system. </p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
-            "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
-
-    /**
-     * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
-     * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
-     * </ul>
-     * <p class="note">This is a protected intent that can only be sent by the system.</p>
-     */
-    public static final String ACTION_CARRIER_SIGNAL_RESET =
-            "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
-
-    // CARRIER_SIGNAL_ACTION extra keys
-    public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
-    public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
-    public static final String EXTRA_APN_TYPE_KEY = "apnType";
-    public static final String EXTRA_APN_PROTO_KEY = "apnProto";
-    public static final String EXTRA_PCO_ID_KEY = "pcoId";
-    public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
-    public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable";
-
-    /**
      * Broadcast action to trigger CI OMA-DM Session.
      */
     public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 7422863..35e8a12 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
+import java.util.List;
 
 /** @hide */
 interface IEuiccController {
@@ -47,4 +48,7 @@
     oneway void eraseSubscriptionsWithOptions(
         int cardId, int options, in PendingIntent callbackIntent);
     oneway void retainSubscriptionsForFactoryReset(int cardId, in PendingIntent callbackIntent);
+    void setSupportedCountries(boolean isSupported, in List<String> countriesList);
+    List<String> getSupportedCountries(boolean isSupported);
+    boolean isSupportedCountry(String countryIso);
 }
diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 2d5df4f..0628691 100644
--- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -38,6 +38,8 @@
 import android.content.Context;
 import android.os.PersistableBundle;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -58,21 +60,26 @@
 
     private static final Executor INLINE_EXECUTOR = x -> x.run();
 
-    @Mock private Context mContext;
     @Mock private IConnectivityManager mService;
     @Mock private ConnectivityDiagnosticsCallback mCb;
 
+    private Context mContext;
     private ConnectivityDiagnosticsBinder mBinder;
     private ConnectivityDiagnosticsManager mManager;
 
+    private String mPackageName;
+
     @Before
     public void setUp() {
-        mContext = mock(Context.class);
+        mContext = InstrumentationRegistry.getContext();
+
         mService = mock(IConnectivityManager.class);
         mCb = mock(ConnectivityDiagnosticsCallback.class);
 
         mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR);
         mManager = new ConnectivityDiagnosticsManager(mContext, mService);
+
+        mPackageName = mContext.getOpPackageName();
     }
 
     @After
@@ -271,7 +278,7 @@
         mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
 
         verify(mService).registerConnectivityDiagnosticsCallback(
-                any(ConnectivityDiagnosticsBinder.class), eq(request));
+                any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName));
         assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
     }
 
@@ -302,7 +309,7 @@
         // verify that re-registering is successful
         mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb);
         verify(mService, times(2)).registerConnectivityDiagnosticsCallback(
-                any(ConnectivityDiagnosticsBinder.class), eq(request));
+                any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName));
         assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb));
     }
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 072d336..8162843 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -23,6 +23,7 @@
 import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
@@ -119,6 +120,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -132,6 +134,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
+import android.location.LocationManager;
 import android.net.CaptivePortalData;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
@@ -177,6 +180,7 @@
 import android.net.util.MultinetworkPolicyTracker;
 import android.os.BadParcelableException;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -187,6 +191,7 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -218,6 +223,7 @@
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.Vpn;
@@ -292,6 +298,8 @@
 
     private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000;
 
+    private static final long TIMESTAMP = 1234L;
+
     private static final String CLAT_PREFIX = "v4-";
     private static final String MOBILE_IFNAME = "test_rmnet_data0";
     private static final String WIFI_IFNAME = "test_wlan0";
@@ -327,6 +335,8 @@
     @Mock AlarmManager mAlarmManager;
     @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;
     @Mock IBinder mIBinder;
+    @Mock LocationManager mLocationManager;
+    @Mock AppOpsManager mAppOpsManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -412,6 +422,8 @@
             if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
             if (Context.USER_SERVICE.equals(name)) return mUserManager;
             if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager;
+            if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager;
+            if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager;
             return super.getSystemService(name);
         }
 
@@ -564,6 +576,7 @@
         private int mProbesCompleted;
         private int mProbesSucceeded;
         private String mNmValidationRedirectUrl = null;
+        private PersistableBundle mValidationExtras = PersistableBundle.EMPTY;
         private boolean mNmProvNotificationRequested = false;
 
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
@@ -631,8 +644,8 @@
             }
 
             mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded);
-            mNmCallbacks.notifyNetworkTested(
-                    mNmValidationResult, mNmValidationRedirectUrl);
+            mNmCallbacks.notifyNetworkTestedWithExtras(
+                    mNmValidationResult, mNmValidationRedirectUrl, TIMESTAMP, mValidationExtras);
 
             if (mNmValidationRedirectUrl != null) {
                 mNmCallbacks.showProvisioningNotification(
@@ -970,6 +983,8 @@
         // not inherit from NetworkAgent.
         private TestNetworkAgentWrapper mMockNetworkAgent;
 
+        private VpnInfo mVpnInfo;
+
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
                     userId);
@@ -1041,6 +1056,17 @@
             mConnected = false;
             mConfig = null;
         }
+
+        @Override
+        public synchronized VpnInfo getVpnInfo() {
+            if (mVpnInfo != null) return mVpnInfo;
+
+            return super.getVpnInfo();
+        }
+
+        private void setVpnInfo(VpnInfo vpnInfo) {
+            mVpnInfo = vpnInfo;
+        }
     }
 
     private void mockVpn(int uid) {
@@ -6400,7 +6426,7 @@
                         new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE);
         try {
             mService.registerConnectivityDiagnosticsCallback(
-                    mConnectivityDiagnosticsCallback, request);
+                    mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
             fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest");
         } catch (IllegalArgumentException expected) {
         }
@@ -6414,7 +6440,7 @@
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
 
         mService.registerConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback, wifiRequest);
+                mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         verify(mIBinder, timeout(TIMEOUT_MS))
                 .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
@@ -6438,7 +6464,7 @@
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
 
         mService.registerConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback, wifiRequest);
+                mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         verify(mIBinder, timeout(TIMEOUT_MS))
                 .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt());
@@ -6449,7 +6475,7 @@
 
         // Register the same callback again
         mService.registerConnectivityDiagnosticsCallback(
-                mConnectivityDiagnosticsCallback, wifiRequest);
+                mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName());
 
         // Block until all other events are done processing.
         HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
@@ -6458,4 +6484,145 @@
                 mService.mConnectivityDiagnosticsCallbacks.containsKey(
                         mConnectivityDiagnosticsCallback));
     }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception {
+        final NetworkAgentInfo naiWithoutUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, new NetworkCapabilities(), null,
+                        mServiceContext, null, null, mService, null, null, null, 0);
+
+        mServiceContext.setPermission(
+                android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+        assertTrue(
+                "NetworkStack permission not applied",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception {
+        final NetworkAgentInfo naiWithoutUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, new NetworkCapabilities(), null,
+                        mServiceContext, null, null, mService, null, null, null, 0);
+
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        assertFalse(
+                "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception {
+        final NetworkAgentInfo naiWithoutUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, new NetworkCapabilities(), null,
+                        mServiceContext, null, null, mService, null, null, null, 0);
+
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be
+        // active
+        final VpnInfo info = new VpnInfo();
+        info.ownerUid = Process.myUid();
+        info.vpnIface = "interface";
+        mMockVpn.setVpnInfo(info);
+        assertTrue(
+                "Active VPN permission not applied",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithoutUid,
+                        mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.setAdministratorUids(Arrays.asList(Process.myUid()));
+        final NetworkAgentInfo naiWithUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, nc, null, mServiceContext, null, null,
+                        mService, null, null, null, 0);
+
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested
+        mMockVpn.disconnect();
+        assertTrue(
+                "NetworkCapabilities administrator uid permission not applied",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName()));
+    }
+
+    @Test
+    public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.setOwnerUid(Process.myUid());
+        nc.setAdministratorUids(Arrays.asList(Process.myUid()));
+        final NetworkAgentInfo naiWithUid =
+                new NetworkAgentInfo(
+                        null, null, null, null, null, nc, null, mServiceContext, null, null,
+                        mService, null, null, null, 0);
+
+        setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+
+        // Use wrong pid and uid
+        assertFalse(
+                "Permissions allowed when they shouldn't be granted",
+                mService.checkConnectivityDiagnosticsPermissions(
+                        Process.myPid() + 1, Process.myUid() + 1, naiWithUid,
+                        mContext.getOpPackageName()));
+    }
+
+    private void setupLocationPermissions(
+            int targetSdk, boolean locationToggle, String op, String perm) throws Exception {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = targetSdk;
+        when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any()))
+                .thenReturn(applicationInfo);
+
+        when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle);
+
+        when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName())))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+
+        mServiceContext.setPermission(perm, PERMISSION_GRANTED);
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+        final NetworkRequest request = new NetworkRequest.Builder().build();
+        when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
+
+        mServiceContext.setPermission(
+                android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+
+        mService.registerConnectivityDiagnosticsCallback(
+                mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
+
+        // Block until all other events are done processing.
+        HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
+
+        // Connect the cell agent verify that it notifies TestNetworkCallback that it is available
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        mCm.registerDefaultNetworkCallback(callback);
+        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+        mCellNetworkAgent.connect(true);
+        callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+        callback.assertNoCallback();
+
+        // Wait for onConnectivityReport to fire
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+                .onConnectivityReport(any(ConnectivityReport.class));
+    }
 }