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));
+ }
}