diff options
98 files changed, 3644 insertions, 1868 deletions
diff --git a/ApiDocs.bp b/ApiDocs.bp index c40004cf8e5c..04ddc50a94c4 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -176,6 +176,7 @@ droiddoc { hdf: [ "android.whichdoc offline", ], + compat_config: ":global-compat-config", proofread_file: "offline-sdk-docs-proofrerad.txt", args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"", static_doc_index_redirect: "docs/docs-preview-index.html", diff --git a/StubLibraries.bp b/StubLibraries.bp index 442df6fc994e..232a7cfd4702 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -109,6 +109,16 @@ droidstubs { jdiff_enabled: true, } +priv_apps = " " + + "--show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + + "\\) " + +module_libs = " " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + + "\\) " + droidstubs { name: "system-api-stubs-docs", defaults: ["metalava-api-stubs-default"], @@ -120,10 +130,7 @@ droidstubs { arg_files: [ "core/res/AndroidManifest.xml", ], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\)", + args: metalava_framework_docs_args + priv_apps, check_api: { current: { api_file: "api/system-current.txt", @@ -171,49 +178,17 @@ droidstubs { // @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES) ///////////////////////////////////////////////////////////////////// -// TODO(b/146727827) remove the *-api modules when we can teach metalava +// TODO(b/146727827) remove the *-api module when we can teach metalava // about the relationship among the API surfaces. Currently, these modules are only to generate // the API signature files and ensure that the APIs evolve in a backwards compatible manner. // They however are NOT used for building the API stub. -droidstubs { - name: "module-app-api", - defaults: ["metalava-non-updatable-api-stubs-default"], - libs: ["framework-all"], - arg_files: ["core/res/AndroidManifest.xml"], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\)", - check_api: { - current: { - api_file: "api/module-app-current.txt", - removed_api_file: "api/module-app-removed.txt", - }, - // TODO(b/147559833) enable the compatibility check against the last release API - // and the API lint - //last_released: { - // api_file: ":last-released-module-app-api", - // removed_api_file: "api/module-app-removed.txt", - // baseline_file: ":module-app-api-incompatibilities-with-last-released" - //}, - //api_lint: { - // enabled: true, - // new_since: ":last-released-module-app-api", - // baseline_file: "api/module-app-lint-baseline.txt", - //}, - }, - //jdiff_enabled: true, -} droidstubs { name: "module-lib-api", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: ["metalava-api-stubs-default"], libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + - "process=android.annotation.SystemApi.Process.ALL\\)", + args: metalava_framework_docs_args + module_libs, check_api: { current: { api_file: "api/module-lib-current.txt", @@ -235,39 +210,17 @@ droidstubs { //jdiff_enabled: true, } -// The following two droidstubs modules generate source files for the API stub libraries for -// modules. Note that they not only include their own APIs but also other APIs that have -// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just -// the ones with 'client=MODULE_LIBRARIES'. -droidstubs { - name: "module-app-api-stubs-docs", - defaults: ["metalava-non-updatable-api-stubs-default"], - libs: ["framework-all"], - arg_files: ["core/res/AndroidManifest.xml"], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\)" + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\)", -} + +// The following droidstub module generates source files for the API stub library for +// modules. Note that it not only includes its own APIs but also other APIs that have +// narrower scope (all @SystemApis, not just the ones with 'client=MODULE_LIBRARIES'). droidstubs { name: "module-lib-api-stubs-docs", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: ["metalava-api-stubs-default"], libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], - args: metalava_framework_docs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\)" + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\)" + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + - "process=android.annotation.SystemApi.Process.ALL\\)", + args: metalava_framework_docs_args + priv_apps + module_libs, } ///////////////////////////////////////////////////////////////////// @@ -336,21 +289,6 @@ java_library_static { } java_library_static { - name: "android_module_app_stubs_current", - srcs: [ - ":module-app-api-stubs-docs", - ], - libs: [ - "stub-annotations", - "framework-all", - ], - static_libs: [ - "private-stub-annotations-jar", - ], - defaults: ["framework-stubs-default"], -} - -java_library_static { name: "android_module_lib_stubs_current", srcs: [ ":module-lib-api-stubs-docs", @@ -397,7 +335,7 @@ droidstubs { merge_annotations_dirs: [ "metalava-manual", ], - args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)", + args: priv_apps, } java_library_static { @@ -423,7 +361,7 @@ droidstubs { removed_dex_api_filename: "removed-dex.txt", args: metalava_framework_docs_args + " --show-unannotated " + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " + + priv_apps + " --show-annotation android.annotation.TestApi ", } @@ -442,7 +380,7 @@ droidstubs { " --hide ReferencesHidden " + " --hide UnhiddenSystemApi " + " --show-unannotated " + - " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " + + priv_apps + " --show-annotation android.annotation.TestApi ", } diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index 245a96b99148..86f4ab7c1128 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -44,34 +44,68 @@ java_library { ], } +stubs_defaults { + name: "framework-sdkextensions-stubs-defaults", + srcs: [ ":framework-sdkextensions-sources" ], + libs: [ "framework-annotations-lib" ], + sdk_version: "system_current", +} + droidstubs { - name: "framework-sdkextensions-droidstubs-publicapi", + name: "framework-sdkextensions-stubs-srcs-publicapi", defaults: [ - "framework-sdkextensions-stubs-defaults", "framework-module-stubs-defaults-publicapi", + "framework-sdkextensions-stubs-defaults", ] } droidstubs { - name: "framework-sdkextensions-droidstubs-systemapi", + name: "framework-sdkextensions-stubs-srcs-systemapi", defaults: [ - "framework-sdkextensions-stubs-defaults", "framework-module-stubs-defaults-systemapi", + "framework-sdkextensions-stubs-defaults", ] } -stubs_defaults { - name: "framework-sdkextensions-stubs-defaults", - srcs: [ - ":framework-sdkextensions-sources", - ":framework-annotations", - ], - sdk_version: "system_current", +droidstubs { + name: "framework-sdkextensions-api-module_libs_api", + defaults: [ + "framework-module-api-defaults-module_libs_api", + "framework-sdkextensions-stubs-defaults", + ] +} + +droidstubs { + name: "framework-sdkextensions-stubs-srcs-module_libs_api", + defaults: [ + "framework-module-stubs-defaults-module_libs_api", + "framework-sdkextensions-stubs-defaults", + ] +} + +java_library { + name: "framework-sdkextensions-stubs-publicapi", + srcs: [":framework-sdkextensions-stubs-srcs-publicapi"], + sdk_version: "current", + visibility: [ + "//frameworks/base", // Framework + "//frameworks/base/apex/sdkextensions", // sdkextensions SDK + ] } java_library { name: "framework-sdkextensions-stubs-systemapi", - srcs: [":framework-sdkextensions-droidstubs-systemapi"], + srcs: [":framework-sdkextensions-stubs-srcs-systemapi"], + sdk_version: "system_current", + visibility: [ + "//frameworks/base", // Framework + "//frameworks/base/apex/sdkextensions", // sdkextensions SDK + ] +} + +java_library { + name: "framework-sdkextensions-stubs-module_libs_api", + srcs: [":framework-sdkextensions-stubs-srcs-module_libs_api"], sdk_version: "system_current", visibility: [ "//frameworks/base", // Framework diff --git a/api/current.txt b/api/current.txt index 2cd396907bc7..7f10d09aa107 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27825,6 +27825,7 @@ package android.media.tv { 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 @@ package android.media.tv { 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 @@ package android.media.tv { 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 @@ package android.media.tv { 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 @@ package android.media.tv { 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 @@ package android.media.tv { 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 @@ package android.media.tv { 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-app-current.txt b/api/module-app-current.txt deleted file mode 100644 index 4307e675e431..000000000000 --- a/api/module-app-current.txt +++ /dev/null @@ -1,9 +0,0 @@ -// Signature format: 2.0 -package android.app { - - public final class NotificationChannel implements android.os.Parcelable { - method public void setBlockableSystem(boolean); - } - -} - diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt deleted file mode 100644 index d802177e249b..000000000000 --- a/api/module-app-removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 278a18654e03..d802177e249b 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 public boolean isForCountryCode(@NonNull String); - method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone); - } - - 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 @Nullable 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 f821d4e40352..726d645a4e26 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -201,7 +201,6 @@ package android { 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"; @@ -553,6 +552,7 @@ package android.app { method public int getUserLockedFields(); method public boolean isDeleted(); method public void populateFromXml(org.xmlpull.v1.XmlPullParser); + method public void setBlockableSystem(boolean); method public org.json.JSONObject toJson() throws org.json.JSONException; method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException; } @@ -1305,8 +1305,8 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeActiveDevice(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setScanMode(int, int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setScanMode(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setScanMode(int, long); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setScanMode(int); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2 @@ -4601,6 +4601,14 @@ package android.net { field public final android.net.WifiKey wifiKey; } + 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 + } + public class NetworkProvider { ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); @@ -7191,6 +7199,7 @@ package android.provider { field public static final String CMAS_SEVERITY = "cmas_severity"; field public static final String CMAS_URGENCY = "cmas_urgency"; field @NonNull public static final android.net.Uri CONTENT_URI; + field public static final String DATA_CODING_SCHEME = "dcs"; field public static final String DEFAULT_SORT_ORDER = "date DESC"; field public static final String DELIVERY_TIME = "date"; field public static final String ETWS_WARNING_TYPE = "etws_warning_type"; @@ -7198,9 +7207,11 @@ package android.provider { field public static final String GEOMETRIES = "geometries"; field public static final String LAC = "lac"; field public static final String LANGUAGE_CODE = "language"; + field public static final String LOCATION_CHECK_TIME = "location_check_time"; field public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time"; field public static final String MESSAGE_BODY = "body"; field public static final String MESSAGE_BROADCASTED = "message_broadcasted"; + field public static final String MESSAGE_DISPLAYED = "message_displayed"; field public static final String MESSAGE_FORMAT = "format"; field @NonNull @RequiresPermission(android.Manifest.permission.READ_CELL_BROADCASTS) public static final android.net.Uri MESSAGE_HISTORY_URI; field public static final String MESSAGE_PRIORITY = "priority"; @@ -7210,7 +7221,7 @@ package android.provider { 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 { @@ -7723,6 +7734,7 @@ package android.service.euicc { public abstract class EuiccService extends android.app.Service { ctor public EuiccService(); + method public void dump(@NonNull java.io.PrintWriter); method @CallSuper public android.os.IBinder onBind(android.content.Intent); method public abstract int onDeleteSubscription(int, String); method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle); @@ -8547,10 +8559,12 @@ package android.telephony { 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>); @@ -9404,11 +9418,12 @@ package android.telephony { } public final class SmsCbMessage implements android.os.Parcelable { - ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo, int, @Nullable java.util.List<android.telephony.CbGeoUtils.Geometry>, long, int, int); + ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, int, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo, int, @Nullable java.util.List<android.telephony.CbGeoUtils.Geometry>, long, int, int); method @NonNull public static android.telephony.SmsCbMessage createFromCursor(@NonNull android.database.Cursor); method public int describeContents(); method @Nullable public android.telephony.SmsCbCmasInfo getCmasWarningInfo(); method @NonNull public android.content.ContentValues getContentValues(); + method public int getDataCodingScheme(); method @Nullable public android.telephony.SmsCbEtwsInfo getEtwsWarningInfo(); method public int getGeographicalScope(); method @NonNull public java.util.List<android.telephony.CbGeoUtils.Geometry> getGeometries(); @@ -9616,6 +9631,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings(); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAllowedNetworkTypes(long); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setAlwaysReportSignalStrength(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCallForwarding(@NonNull android.telephony.CallForwardingInfo); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCallWaitingStatus(boolean); diff --git a/api/test-current.txt b/api/test-current.txt index d7d5b8deb7c3..1e5b7b82a916 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2558,6 +2558,7 @@ package android.provider { field public static final String CMAS_SEVERITY = "cmas_severity"; field public static final String CMAS_URGENCY = "cmas_urgency"; field @NonNull public static final android.net.Uri CONTENT_URI; + field public static final String DATA_CODING_SCHEME = "dcs"; field public static final String DEFAULT_SORT_ORDER = "date DESC"; field public static final String DELIVERY_TIME = "date"; field public static final String ETWS_WARNING_TYPE = "etws_warning_type"; @@ -2565,9 +2566,11 @@ package android.provider { field public static final String GEOMETRIES = "geometries"; field public static final String LAC = "lac"; field public static final String LANGUAGE_CODE = "language"; + field public static final String LOCATION_CHECK_TIME = "location_check_time"; field public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time"; field public static final String MESSAGE_BODY = "body"; field public static final String MESSAGE_BROADCASTED = "message_broadcasted"; + field public static final String MESSAGE_DISPLAYED = "message_displayed"; field public static final String MESSAGE_FORMAT = "format"; field @NonNull @RequiresPermission(android.Manifest.permission.READ_CELL_BROADCASTS) public static final android.net.Uri MESSAGE_HISTORY_URI; field public static final String MESSAGE_PRIORITY = "priority"; @@ -2577,7 +2580,7 @@ package android.provider { 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 { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 777bea040944..d1fb2db67c9f 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -351,6 +351,7 @@ message Atom { 228 [(allow_from_any_uid) = true]; PerfettoUploaded perfetto_uploaded = 229 [(log_from_module) = "perfetto"]; + UserspaceRebootReported userspace_reboot_reported = 243; } // Pulled events will start at field 10000. @@ -6987,3 +6988,43 @@ message UpdateEngineSuccessfulUpdateReported { // The number of reboot of the device during a successful update. optional int32 reboot_count = 7; } + +/* + * Logs userspace reboot outcome and duration. + * + * Logged from: + * frameworks/base/core/java/com/android/server/BootReceiver.java + */ +message UserspaceRebootReported { + // Possible outcomes of userspace reboot. + enum Outcome { + // Default value in case platform failed to determine the outcome. + OUTCOME_UNKNOWN = 0; + // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot). + SUCCESS = 1; + // Userspace reboot shutdown sequence was aborted. + FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2; + // Remounting userdata into checkpointing mode failed. + FAILED_USERDATA_REMOUNT = 3; + // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard + // reboot. + FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4; + } + // Outcome of userspace reboot. Always set. + optional Outcome outcome = 1; + // Duration of userspace reboot in case it has a successful outcome. + // Duration is measured as time between userspace reboot was initiated and until boot completed + // (e.g. sys.boot_completed=1). + optional int64 duration_millis = 2; + // State of primary user's (user0) credential encryption storage. + enum UserEncryptionState { + // Default value. + USER_ENCRYPTION_STATE_UNKNOWN = 0; + // Credential encrypted storage is unlocked. + UNLOCKED = 1; + // Credential encrypted storage is locked. + LOCKED = 2; + } + // State of primary user's encryption storage at the moment boot completed. Always set. + optional UserEncryptionState user_encryption_state = 3; +} diff --git a/core/java/android/annotation/SystemApi.java b/core/java/android/annotation/SystemApi.java index ecbfed96cefd..4ac00983af13 100644 --- a/core/java/android/annotation/SystemApi.java +++ b/core/java/android/annotation/SystemApi.java @@ -47,22 +47,14 @@ public @interface SystemApi { /** * Specifies that the intended clients of a SystemApi are privileged apps. * This is the default value for {@link #client}. - * TODO Update the javadoc according to the final spec */ PRIVILEGED_APPS, /** - * DO NOT USE. Use PRIVILEGED_APPS instead. - * (This would provide no further protection over PRIVILEGED_APPS; do not rely on it) - * @deprecated Use #PRIVILEGED_APPS instead - */ - @Deprecated - MODULE_APPS, - - /** - * Specifies that the intended clients of a SystemApi are modules implemented - * as libraries, like the conscrypt.jar in the conscrypt APEX. - * TODO Update the javadoc according to the final spec + * Specifies that the intended clients of a SystemApi are used by classes in + * <pre>BOOTCLASSPATH</pre> in mainline modules. Mainline modules can also expose + * this type of system APIs too when they're used only by the non-updatable + * platform code. */ MODULE_LIBRARIES, @@ -70,19 +62,6 @@ public @interface SystemApi { * Specifies that the system API is available only in the system server process. * Use this to expose APIs from code loaded by the system server process <em>but</em> * not in <pre>BOOTCLASSPATH</pre>. - * TODO(b/148177503) Update "services-stubs" and actually use it. - */ - SYSTEM_SERVER - } - - /** @deprecated do not use */ - @Deprecated - enum Process { - /** @deprecated do not use */ - ALL, - - /** - * @deprecated use Client#SYSTEM_SERVER instead */ SYSTEM_SERVER } @@ -93,13 +72,6 @@ public @interface SystemApi { Client client() default android.annotation.SystemApi.Client.PRIVILEGED_APPS; /** - * @deprecated use Client#SYSTEM_SERVER instead for system_server APIs - */ - @Deprecated - Process process() default android.annotation.SystemApi.Process.ALL; - - - /** * Container for {@link SystemApi} that allows it to be applied repeatedly to types. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 8ad2b36ae0da..b08f0354327c 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -15,8 +15,6 @@ */ package android.app; -import static android.annotation.SystemApi.Client.MODULE_APPS; - import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -325,7 +323,7 @@ public final class NotificationChannel implements Parcelable { * @param blockableSystem if {@code true}, allows users to block notifications on this channel. * @hide */ - @SystemApi(client = MODULE_APPS) + @SystemApi @TestApi public void setBlockableSystem(boolean blockableSystem) { mBlockableSystem = blockableSystem; diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java index 16288e82d452..0133a4472686 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java @@ -18,7 +18,6 @@ package android.app.timedetector; 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 @@ import java.util.Objects; * * @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 @@ public final class PhoneTimeSuggestion implements Parcelable { * * @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 2412fb3994ed..df4f513ab095 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -18,7 +18,6 @@ package android.app.timedetector; 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 @@ import android.os.TimestampedValue; * * @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 0544ccd3f4c5..9147b4462492 100644 --- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java +++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java @@ -19,7 +19,6 @@ package android.app.timezonedetector; 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 @@ import java.util.Objects; * * @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 @@ public final class PhoneTimeZoneSuggestion implements Parcelable { * * @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 b4f608787d4a..6a3953eccb2d 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -18,7 +18,6 @@ package android.app.timezonedetector; import android.annotation.NonNull; import android.annotation.RequiresPermission; -import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; @@ -27,7 +26,6 @@ import android.content.Context; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) public interface TimeZoneDetector { @@ -49,7 +47,6 @@ public interface TimeZoneDetector { * * @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/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 3bc83dbcd04e..56424c9f5a7d 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1485,9 +1485,8 @@ public final class BluetoothAdapter { * <p>The Bluetooth scan mode determines if the local adapter is * connectable and/or discoverable from remote Bluetooth devices. * <p>For privacy reasons, discoverable mode is automatically turned off - * after <code>duration</code> seconds. For example, 120 seconds should be - * enough for a remote device to initiate and complete its discovery - * process. + * after <code>durationMillis</code> milliseconds. For example, 120000 milliseconds should be + * enough for a remote device to initiate and complete its discovery process. * <p>Valid scan mode values are: * {@link #SCAN_MODE_NONE}, * {@link #SCAN_MODE_CONNECTABLE}, @@ -1502,24 +1501,29 @@ public final class BluetoothAdapter { * </code>instead. * * @param mode valid scan mode - * @param duration time in seconds to apply scan mode, only used for {@link + * @param durationMillis time in milliseconds to apply scan mode, only used for {@link * #SCAN_MODE_CONNECTABLE_DISCOVERABLE} * @return true if the scan mode was set, false otherwise * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) - public boolean setScanMode(@ScanMode int mode, int duration) { + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setScanMode(@ScanMode int mode, long durationMillis) { if (getState() != STATE_ON) { return false; } try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.setScanMode(mode, duration); + int durationSeconds = Math.toIntExact(durationMillis / 1000); + return mService.setScanMode(mode, durationSeconds); } } catch (RemoteException e) { Log.e(TAG, "", e); + } catch (ArithmeticException ex) { + Log.e(TAG, "setScanMode: Duration in seconds outside of the bounds of an int"); + throw new IllegalArgumentException("Duration not in bounds. In seconds, the " + + "durationMillis must be in the range of an int"); } finally { mServiceLock.readLock().unlock(); } @@ -1552,13 +1556,22 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setScanMode(@ScanMode int mode) { if (getState() != STATE_ON) { return false; } - /* getDiscoverableTimeout() to use the latest from NV than use 0 */ - return setScanMode(mode, getDiscoverableTimeout()); + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.setScanMode(mode, getDiscoverableTimeout()); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + mServiceLock.readLock().unlock(); + } + return false; } /** @hide */ diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 140363c48227..b128ea7f3e39 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -676,7 +676,8 @@ public class ConnectivityDiagnosticsManager { } 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 3e9e7faccb02..c871c456dc66 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -116,10 +116,18 @@ interface IConnectivityManager boolean prepareVpn(String oldPackage, String newPackage, int userId); - void setVpnPackageAuthorization(String packageName, int userId, boolean authorized); + void setVpnPackageAuthorization(String packageName, int userId, int vpnType); ParcelFileDescriptor establishVpn(in VpnConfig config); + boolean provisionVpnProfile(in VpnProfile profile, String packageName); + + void deleteVpnProfile(String packageName); + + void startVpnProfile(String packageName); + + void stopVpnProfile(String packageName); + VpnConfig getVpnConfig(int userId); @UnsupportedAppUsage @@ -213,7 +221,7 @@ interface IConnectivityManager 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 f94bdb767c02..38f7390abffd 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -858,8 +858,8 @@ public final class NetworkCapabilities implements Parcelable { * * <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 d1c13290338f..0f66c794ea50 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -18,6 +18,9 @@ package android.net; import static android.content.pm.PackageManager.GET_SIGNATURES; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; @@ -38,6 +41,8 @@ import android.util.Range; import com.google.android.collect.Sets; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.time.ZonedDateTime; import java.util.HashSet; import java.util.Iterator; @@ -48,14 +53,24 @@ import java.util.Iterator; * {@hide} */ @SystemService(Context.NETWORK_POLICY_SERVICE) +@SystemApi public class NetworkPolicyManager { /* POLICY_* are masks and can be ORed, although currently they are not.*/ - /** No specific network policy, use system default. */ + /** + * No specific network policy, use system default. + * @hide + */ public static final int POLICY_NONE = 0x0; - /** Reject network usage on metered networks when application in background. */ + /** + * Reject network usage on metered networks when application in background. + * @hide + */ public static final int POLICY_REJECT_METERED_BACKGROUND = 0x1; - /** Allow metered network use in the background even when in data usage save mode. */ + /** + * Allow metered network use in the background even when in data usage save mode. + * @hide + */ public static final int POLICY_ALLOW_METERED_BACKGROUND = 0x4; /* @@ -74,49 +89,98 @@ public class NetworkPolicyManager { * * See network-policy-restrictions.md for more info. */ - /** No specific rule was set */ + /** + * No specific rule was set + * @hide + */ public static final int RULE_NONE = 0; - /** Allow traffic on metered networks. */ + /** + * Allow traffic on metered networks. + * @hide + */ public static final int RULE_ALLOW_METERED = 1 << 0; - /** Temporarily allow traffic on metered networks because app is on foreground. */ + /** + * Temporarily allow traffic on metered networks because app is on foreground. + * @hide + */ public static final int RULE_TEMPORARY_ALLOW_METERED = 1 << 1; - /** Reject traffic on metered networks. */ + /** + * Reject traffic on metered networks. + * @hide + */ public static final int RULE_REJECT_METERED = 1 << 2; - /** Network traffic should be allowed on all networks (metered or non-metered), although - * metered-network restrictions could still apply. */ + /** + * Network traffic should be allowed on all networks (metered or non-metered), although + * metered-network restrictions could still apply. + * @hide + */ public static final int RULE_ALLOW_ALL = 1 << 5; - /** Reject traffic on all networks. */ + /** + * Reject traffic on all networks. + * @hide + */ public static final int RULE_REJECT_ALL = 1 << 6; - /** Mask used to get the {@code RULE_xxx_METERED} rules */ + /** + * Mask used to get the {@code RULE_xxx_METERED} rules + * @hide + */ public static final int MASK_METERED_NETWORKS = 0b00001111; - /** Mask used to get the {@code RULE_xxx_ALL} rules */ + /** + * Mask used to get the {@code RULE_xxx_ALL} rules + * @hide + */ public static final int MASK_ALL_NETWORKS = 0b11110000; + /** @hide */ public static final int FIREWALL_RULE_DEFAULT = 0; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_NONE = "none"; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_DOZABLE = "dozable"; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_STANDBY = "standby"; + /** @hide */ public static final String FIREWALL_CHAIN_NAME_POWERSAVE = "powersave"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; + /** @hide */ public static final int FOREGROUND_THRESHOLD_STATE = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; /** * {@link Intent} extra that indicates which {@link NetworkTemplate} rule it * applies to. + * @hide */ public static final String EXTRA_NETWORK_TEMPLATE = "android.net.NETWORK_TEMPLATE"; - public static final int OVERRIDE_UNMETERED = 1 << 0; - public static final int OVERRIDE_CONGESTED = 1 << 1; + /** + * Mask used to check if an override value is marked as unmetered. + */ + public static final int SUBSCRIPTION_OVERRIDE_UNMETERED = 1 << 0; + + /** + * Mask used to check if an override value is marked as congested. + */ + public static final int SUBSCRIPTION_OVERRIDE_CONGESTED = 1 << 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "SUBSCRIPTION_OVERRIDE_" }, value = { + SUBSCRIPTION_OVERRIDE_UNMETERED, + SUBSCRIPTION_OVERRIDE_CONGESTED + }) + public @interface SubscriptionOverrideMask {} private final Context mContext; @UnsupportedAppUsage private INetworkPolicyManager mService; + /** @hide */ public NetworkPolicyManager(Context context, INetworkPolicyManager service) { if (service == null) { throw new IllegalArgumentException("missing INetworkPolicyManager"); @@ -125,6 +189,7 @@ public class NetworkPolicyManager { mService = service; } + /** @hide */ @UnsupportedAppUsage public static NetworkPolicyManager from(Context context) { return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE); @@ -135,6 +200,7 @@ public class NetworkPolicyManager { * * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags, * although it is not validated. + * @hide */ @UnsupportedAppUsage public void setUidPolicy(int uid, int policy) { @@ -152,6 +218,7 @@ public class NetworkPolicyManager { * * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags, * although it is not validated. + * @hide */ public void addUidPolicy(int uid, int policy) { try { @@ -168,6 +235,7 @@ public class NetworkPolicyManager { * * @param policy should be {@link #POLICY_NONE} or any combination of {@code POLICY_} flags, * although it is not validated. + * @hide */ public void removeUidPolicy(int uid, int policy) { try { @@ -177,6 +245,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public int getUidPolicy(int uid) { try { @@ -186,6 +255,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public int[] getUidsWithPolicy(int policy) { try { @@ -195,6 +265,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void registerListener(INetworkPolicyListener listener) { try { @@ -204,6 +275,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public void unregisterListener(INetworkPolicyListener listener) { try { @@ -213,6 +285,7 @@ public class NetworkPolicyManager { } } + /** @hide */ public void setNetworkPolicies(NetworkPolicy[] policies) { try { mService.setNetworkPolicies(policies); @@ -221,6 +294,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public NetworkPolicy[] getNetworkPolicies() { try { @@ -230,6 +304,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public void setRestrictBackground(boolean restrictBackground) { try { @@ -239,6 +314,7 @@ public class NetworkPolicyManager { } } + /** @hide */ @UnsupportedAppUsage public boolean getRestrictBackground() { try { @@ -249,6 +325,62 @@ public class NetworkPolicyManager { } /** + * Override connections to be temporarily marked as either unmetered or congested, + * along with automatic timeouts if desired. + * + * @param subId the subscriber ID this override applies to. + * @param overrideMask the bitmask that specifies which of the overrides is being + * set or cleared. + * @param overrideValue the override values to set or clear. + * @param timeoutMillis the timeout after which the requested override will + * be automatically cleared, or {@code 0} to leave in the + * 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, + @NonNull String callingPackage) { + try { + mService.setSubscriptionOverride(subId, overrideMask, overrideValue, timeoutMillis, + callingPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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 @@ -286,6 +418,7 @@ public class NetworkPolicyManager { /** * Check if given UID can have a {@link #setUidPolicy(int, int)} defined, * usually to protect critical system services. + * @hide */ @Deprecated public static boolean isUidValidForPolicy(Context context, int uid) { @@ -353,6 +486,7 @@ public class NetworkPolicyManager { /** * Returns true if {@param procState} is considered foreground and as such will be allowed * to access network when the device is idle or in battery saver mode. Otherwise, false. + * @hide */ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) { return procState <= FOREGROUND_THRESHOLD_STATE; @@ -361,16 +495,19 @@ public class NetworkPolicyManager { /** * Returns true if {@param procState} is considered foreground and as such will be allowed * to access network when the device is in data saver mode. Otherwise, false. + * @hide */ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) { return procState <= FOREGROUND_THRESHOLD_STATE; } + /** @hide */ public static String resolveNetworkId(WifiConfiguration config) { return WifiInfo.sanitizeSsid(config.isPasspoint() ? config.providerFriendlyName : config.SSID); } + /** @hide */ public static String resolveNetworkId(String ssid) { return WifiInfo.sanitizeSsid(ssid); } diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index f95807a14f00..f19ba0f5ef51 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -18,10 +18,22 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; +import android.os.RemoteException; + +import com.android.internal.net.VpnProfile; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.GeneralSecurityException; /** * This class provides an interface for apps to manage platform VPN profiles @@ -38,9 +50,30 @@ import android.content.Intent; * @see Ikev2VpnProfile */ public class VpnManager { + /** Type representing a lack of VPN @hide */ + public static final int TYPE_VPN_NONE = -1; + /** VPN service type code @hide */ + public static final int TYPE_VPN_SERVICE = 1; + /** Platform VPN type code @hide */ + public static final int TYPE_VPN_PLATFORM = 2; + + /** @hide */ + @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM}) + @Retention(RetentionPolicy.SOURCE) + public @interface VpnType {} + @NonNull private final Context mContext; @NonNull private final IConnectivityManager mService; + private static Intent getIntentForConfirmation() { + final Intent intent = new Intent(); + final ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString( + com.android.internal.R.string.config_platformVpnConfirmDialogComponent)); + intent.setComponent(componentName); + return intent; + } + /** * Create an instance of the VpnManger with the given context. * @@ -57,18 +90,49 @@ public class VpnManager { /** * Install a VpnProfile configuration keyed on the calling app's package name. * - * @param profile the PlatformVpnProfile provided by this package. Will override any previous - * PlatformVpnProfile stored for this package. - * @return an intent to request user consent if needed (null otherwise). + * <p>This method returns {@code null} if user consent has already been granted, or an {@link + * Intent} to a system activity. If an intent is returned, the application should launch the + * activity using {@link Activity#startActivityForResult} to request user consent. The activity + * may pop up a dialog to require user action, and the result will come back via its {@link + * Activity#onActivityResult}. If the result is {@link Activity#RESULT_OK}, the user has + * consented, and the VPN profile can be started. + * + * @param profile the VpnProfile provided by this package. Will override any previous VpnProfile + * stored for this package. + * @return an Intent requesting user consent to start the VPN, or null if consent is not + * required based on privileges or previous user consent. */ @Nullable public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) { - throw new UnsupportedOperationException("Not yet implemented"); + final VpnProfile internalProfile; + + try { + internalProfile = profile.toVpnProfile(); + } catch (GeneralSecurityException | IOException e) { + // Conversion to VpnProfile failed; this is an invalid profile. Both of these exceptions + // indicate a failure to convert a PrivateKey or X509Certificate to a Base64 encoded + // string as required by the VpnProfile. + throw new IllegalArgumentException("Failed to serialize PlatformVpnProfile", e); + } + + try { + // Profile can never be null; it either gets set, or an exception is thrown. + if (mService.provisionVpnProfile(internalProfile, mContext.getOpPackageName())) { + return null; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return getIntentForConfirmation(); } /** Delete the VPN profile configuration that was provisioned by the calling app */ public void deleteProvisionedVpnProfile() { - throw new UnsupportedOperationException("Not yet implemented"); + try { + mService.deleteVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -78,11 +142,19 @@ public class VpnManager { * setup, or if user consent has not been granted */ public void startProvisionedVpnProfile() { - throw new UnsupportedOperationException("Not yet implemented"); + try { + mService.startVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** Tear down the VPN provided by the calling app (if any) */ public void stopProvisionedVpnProfile() { - throw new UnsupportedOperationException("Not yet implemented"); + try { + mService.stopVpnProfile(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java index 4b804b097d59..63e510733907 100644 --- a/core/java/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -234,7 +234,7 @@ public class VpnService extends Service { if (!cm.prepareVpn(packageName, null, userId)) { cm.prepareVpn(null, packageName, userId); } - cm.setVpnPackageAuthorization(packageName, userId, true); + cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); } catch (RemoteException e) { // ignore } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 9c4a36f1c965..2fa75f16a546 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -913,6 +913,18 @@ public class Binder implements IBinder { resultReceiver.send(0, null); } + /** @hide */ + @Override + public final native @Nullable IBinder getExtension(); + + /** + * Set the binder extension. + * This should be called immediately when the object is created. + * + * @hide + */ + public final native void setExtension(@Nullable IBinder extension); + /** * Default implementation rewinds the parcels and calls onTransact. On * the remote side, transact calls into the binder to do the IPC. diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index c35b05f10498..a26bd39eed9d 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -454,6 +454,10 @@ public final class BinderProxy implements IBinder { return null; } + /** @hide */ + @Override + public native @Nullable IBinder getExtension() throws RemoteException; + /** * Perform a binder transaction on a proxy. * diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index d9ed32799ea5..c43644dbd960 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -244,6 +244,18 @@ public interface IBinder { @NonNull ResultReceiver resultReceiver) throws RemoteException; /** + * Get the binder extension of this binder interface. + * This allows one to customize an interface without having to modify the original interface. + * + * @return null if don't have binder extension + * @throws RemoteException + * @hide + */ + public default @Nullable IBinder getExtension() throws RemoteException { + throw new IllegalStateException("Method is not implemented"); + } + + /** * Perform a generic operation with the object. * * @param code The action to perform. This should diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 6f4f30c4981a..10b4e5ddcd55 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -99,6 +99,12 @@ public class Process { public static final int KEYSTORE_UID = 1017; /** + * Defines the UID/GID for credstore. + * @hide + */ + public static final int CREDSTORE_UID = 1076; + + /** * Defines the UID/GID for the NFC service process. * @hide */ diff --git a/core/java/android/os/TimestampedValue.java b/core/java/android/os/TimestampedValue.java index f4c87ac9dfc9..4c4335bc7867 100644 --- a/core/java/android/os/TimestampedValue.java +++ b/core/java/android/os/TimestampedValue.java @@ -18,7 +18,6 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import java.util.Objects; @@ -36,7 +35,6 @@ import java.util.Objects; * @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 @@ public final class TimestampedValue<T> implements Parcelable { } /** @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 1611d28b1be2..a897a8e4994a 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4166,7 +4166,7 @@ public final class Telephony { * 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. @@ -4245,6 +4245,15 @@ public final class Telephony { public static final String LANGUAGE_CODE = "language"; /** + * Dats coding scheme of the message. + * <p> + * The data coding scheme (dcs) value defined in 3GPP TS 23.038 section 4 + * </p> + * <P>Type: INTEGER</P> + */ + public static final String DATA_CODING_SCHEME = "dcs"; + + /** * Message body. * <P>Type: TEXT</P> */ @@ -4332,18 +4341,32 @@ public final class Telephony { public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC"; /** - * The timestamp in millisecond of when the device received the message. + * The timestamp in millisecond, reported by {@link System#currentTimeMillis()}, when the + * device received the message. * <P>Type: BIGINT</P> */ public static final String RECEIVED_TIME = "received_time"; /** + * The timestamp in millisecond, reported by {@link System#currentTimeMillis()}, when + * location was checked last time. Note this is only applicable to geo-targeting message. + * For non geo-targeting message. the field will be set to -1. + * <P>Type: BIGINT</P> + */ + public static final String LOCATION_CHECK_TIME = "location_check_time"; + /** * Indicates that whether the message has been broadcasted to the application. * <P>Type: BOOLEAN</P> */ public static final String MESSAGE_BROADCASTED = "message_broadcasted"; /** + * Indicates that whether the message has been displayed to the user. + * <P>Type: BOOLEAN</P> + */ + public static final String MESSAGE_DISPLAYED = "message_displayed"; + + /** * The Warning Area Coordinates Elements. This element is used for geo-fencing purpose. * * The geometry and its coordinates are separated vertical bar, the first item is the @@ -4424,7 +4447,7 @@ public final class Telephony { public static final String[] QUERY_COLUMNS_FWK = { _ID, SLOT_INDEX, - SUB_ID, + SUBSCRIPTION_ID, GEOGRAPHICAL_SCOPE, PLMN, LAC, diff --git a/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl b/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl new file mode 100644 index 000000000000..ea55ebbadd88 --- /dev/null +++ b/core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.euicc; + +/** @hide */ +oneway interface IEuiccServiceDumpResultCallback { + void onComplete(in String logs); +}
\ No newline at end of file diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java index 2e0810835a52..32d330e1a47f 100644 --- a/core/java/android/telephony/CellBroadcastIntents.java +++ b/core/java/android/telephony/CellBroadcastIntents.java @@ -19,6 +19,8 @@ package android.telephony; 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 @@ public class CellBroadcastIntents { 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 5875761bb1c9..ab2c4fc1bf23 100644 --- a/core/java/android/timezone/CountryTimeZones.java +++ b/core/java/android/timezone/CountryTimeZones.java @@ -19,7 +19,6 @@ package android.timezone; 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 @@ import java.util.Objects; * * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class CountryTimeZones { /** @@ -40,7 +38,6 @@ public final class CountryTimeZones { * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class TimeZoneMapping { @NonNull @@ -51,8 +48,10 @@ public final class CountryTimeZones { } /** - * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the - * ID is unrecognized. + * Returns the ID for this mapping. The ID is a tzdb time zone identifier like + * "America/Los_Angeles" that can be used with methods such as {@link + * TimeZone#getFrozenTimeZone(String)}. See {@link #getTimeZone()} which returns a frozen + * {@link TimeZone} object. */ @NonNull public String getTimeZoneId() { @@ -60,10 +59,9 @@ public final class CountryTimeZones { } /** - * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is - * unrecognized. + * Returns a frozen {@link TimeZone} object for this mapping. */ - @Nullable + @NonNull public TimeZone getTimeZone() { return mDelegate.getTimeZone(); } @@ -96,7 +94,6 @@ public final class CountryTimeZones { * * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final class OffsetResult { private final TimeZone mTimeZone; @@ -158,9 +155,10 @@ public final class CountryTimeZones { } /** - * Returns true if the ISO code for the country is a match for the one specified. + * Returns true if the ISO code for the country is a case-insensitive match for the one + * supplied. */ - public boolean isForCountryCode(@NonNull String countryIso) { + public boolean matchesCountryCode(@NonNull String countryIso) { return mDelegate.isForCountryCode(countryIso); } @@ -183,15 +181,25 @@ public final class CountryTimeZones { } /** - * Qualifier for a country's default time zone. {@code true} indicates whether the default - * would be a good choice <em>generally</em> when there's no other information available. + * Qualifier for a country's default time zone. {@code true} indicates that the country's + * default time zone would be a good choice <em>generally</em> when there's no UTC offset + * information available. This will only be {@code true} in countries with multiple zones where + * a large majority of the population is covered by only one of them. */ public boolean isDefaultTimeZoneBoosted() { return mDelegate.isDefaultTimeZoneBoosted(); } /** - * Returns true if the country has at least one zone that is the same as UTC at the given time. + * Returns {@code true} if the country has at least one time zone that uses UTC at the given + * time. This is an efficient check when trying to validate received UTC offset information. + * For example, there are situations when a detected zero UTC offset cannot be distinguished + * from "no information available" or a corrupted signal. This method is useful because checking + * offset information for large countries is relatively expensive but it is generally only the + * countries close to the prime meridian that use UTC at <em>any</em> time of the year. + * + * @param whenMillis the time the offset information is for in milliseconds since the beginning + * of the Unix epoch */ public boolean hasUtcZone(long whenMillis) { return mDelegate.hasUtcZone(whenMillis); diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java index 8a5864e92db1..a4c3fbd33410 100644 --- a/core/java/android/timezone/TelephonyLookup.java +++ b/core/java/android/timezone/TelephonyLookup.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import com.android.internal.annotations.GuardedBy; @@ -29,7 +28,6 @@ import java.util.Objects; * * @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 487b3f2f143b..823cd251fbf0 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 @@ import java.util.Objects; * * @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 2ddd3d998d8e..4bfeff8a73ad 100644 --- a/core/java/android/timezone/TelephonyNetworkFinder.java +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import java.util.Objects; @@ -27,7 +26,6 @@ import java.util.Objects; * * @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 c76bb1d1fd28..03f5013f230c 100644 --- a/core/java/android/timezone/TimeZoneFinder.java +++ b/core/java/android/timezone/TimeZoneFinder.java @@ -18,7 +18,6 @@ package android.timezone; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import com.android.internal.annotations.GuardedBy; @@ -29,7 +28,6 @@ import java.util.Objects; * * @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 efe50a07da98..f993012aeb1c 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 @@ import java.util.Objects; * @hide */ @VisibleForTesting -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TzDataSetVersion { /** @@ -88,7 +86,6 @@ public final class TzDataSetVersion { * 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 4612a56df117..9354a695812d 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 @@ import java.util.Objects; * * @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/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java index 2bb3f1fed927..a7a90f6912c2 100644 --- a/core/java/com/android/ims/internal/uce/common/CapInfo.java +++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java @@ -78,6 +78,10 @@ public class CapInfo implements Parcelable { private boolean mChatbotSupported = false; /** Chatbot role support. */ private boolean mChatbotRoleSupported = false; + /** Standalone Chatbot communication support. */ + private boolean mSmChatbotSupported = false; + /** MMtel based call composer support. */ + private boolean mMmtelCallComposerSupported = false; /** List of supported extensions. */ private String[] mExts = new String[10]; /** Time used to compute when to query again. */ @@ -498,6 +502,34 @@ public class CapInfo implements Parcelable { this.mChatbotRoleSupported = chatbotRoleSupported; } + /** + * Checks whether standalone chatbot communication is supported. + */ + public boolean isSmChatbotSupported() { + return mSmChatbotSupported; + } + + /** + * Sets standalone chatbot communication as supported or not supported. + */ + public void setSmChatbotSupported(boolean smChatbotSupported) { + this.mSmChatbotSupported = smChatbotSupported; + } + + /** + * Checks whether Mmtel based call composer is supported. + */ + public boolean isMmtelCallComposerSupported() { + return mMmtelCallComposerSupported; + } + + /** + * Sets Mmtel based call composer as supported or not supported. + */ + public void setMmtelCallComposerSupported(boolean mmtelCallComposerSupported) { + this.mMmtelCallComposerSupported = mmtelCallComposerSupported; + } + /** Gets the list of supported extensions. */ public String[] getExts() { return mExts; @@ -553,6 +585,8 @@ public class CapInfo implements Parcelable { dest.writeInt(mSharedSketchSupported ? 1 : 0); dest.writeInt(mChatbotSupported ? 1 : 0); dest.writeInt(mChatbotRoleSupported ? 1 : 0); + dest.writeInt(mSmChatbotSupported ? 1 : 0); + dest.writeInt(mMmtelCallComposerSupported ? 1 : 0); dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0); dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0); @@ -602,6 +636,8 @@ public class CapInfo implements Parcelable { mSharedSketchSupported = (source.readInt() == 0) ? false : true; mChatbotSupported = (source.readInt() == 0) ? false : true; mChatbotRoleSupported = (source.readInt() == 0) ? false : true; + mSmChatbotSupported = (source.readInt() == 0) ? false : true; + mMmtelCallComposerSupported = (source.readInt() == 0) ? false : true; mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true; mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true; diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java index 59e5a6402fb8..a6b345fadbbe 100644 --- a/core/java/com/android/internal/util/ObjectUtils.java +++ b/core/java/com/android/internal/util/ObjectUtils.java @@ -36,4 +36,12 @@ public class ObjectUtils { return (b != null) ? -1 : 0; } } + + /** + * Returns its first argument if non-null, and the second otherwise. + */ + @Nullable + public static <T> T getOrElse(@Nullable final T object, @Nullable final T otherwise) { + return null != object ? object : otherwise; + } } diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index cbba5bbee01c..9a27e719e5d9 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -675,8 +675,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p 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 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p 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/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index a236f3143f84..079c2652cb17 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -463,6 +463,9 @@ public: if (mVintf) { ::android::internal::Stability::markVintf(b.get()); } + if (mExtension != nullptr) { + b.get()->setExtension(mExtension); + } mBinder = b; ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\n", b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount()); @@ -481,6 +484,24 @@ public: mVintf = true; } + sp<IBinder> getExtension() { + AutoMutex _l(mLock); + sp<JavaBBinder> b = mBinder.promote(); + if (b != nullptr) { + return b.get()->getExtension(); + } + return mExtension; + } + + void setExtension(const sp<IBinder>& extension) { + AutoMutex _l(mLock); + mExtension = extension; + sp<JavaBBinder> b = mBinder.promote(); + if (b != nullptr) { + b.get()->setExtension(mExtension); + } + } + private: Mutex mLock; wp<JavaBBinder> mBinder; @@ -489,6 +510,8 @@ private: // is too much binder state here, we can think about making JavaBBinder an // sp here (avoid recreating it) bool mVintf = false; + + sp<IBinder> mExtension; }; // ---------------------------------------------------------------------------- @@ -1033,6 +1056,17 @@ static jobject android_os_Binder_waitForService( return javaObjectForIBinder(env, service); } +static jobject android_os_Binder_getExtension(JNIEnv* env, jobject obj) { + JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); + return javaObjectForIBinder(env, jbh->getExtension()); +} + +static void android_os_Binder_setExtension(JNIEnv* env, jobject obj, jobject extensionObject) { + JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); + sp<IBinder> extension = ibinderForJavaObject(env, extensionObject); + jbh->setExtension(extension); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gBinderMethods[] = { @@ -1062,7 +1096,9 @@ static const JNINativeMethod gBinderMethods[] = { { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder }, { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer }, { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }, - { "waitForService", "(Ljava/lang/String;)Landroid/os/IBinder;", (void*)android_os_Binder_waitForService } + { "waitForService", "(Ljava/lang/String;)Landroid/os/IBinder;", (void*)android_os_Binder_waitForService }, + { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension }, + { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension }, }; const char* const kBinderPathName = "android/os/Binder"; @@ -1502,6 +1538,21 @@ JNIEXPORT jlong JNICALL android_os_BinderProxy_getNativeFinalizer(JNIEnv*, jclas return (jlong) BinderProxy_destroy; } +static jobject android_os_BinderProxy_getExtension(JNIEnv* env, jobject obj) { + IBinder* binder = getBPNativeData(env, obj)->mObject.get(); + if (binder == nullptr) { + jniThrowException(env, "java/lang/IllegalStateException", "Native IBinder is null"); + return nullptr; + } + sp<IBinder> extension; + status_t err = binder->getExtension(&extension); + if (err != OK) { + signalExceptionForError(env, obj, err, true /* canThrowRemoteException */); + return nullptr; + } + return javaObjectForIBinder(env, extension); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gBinderProxyMethods[] = { @@ -1513,6 +1564,7 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, + {"getExtension", "()Landroid/os/IBinder;", (void*)android_os_BinderProxy_getExtension}, }; const char* const kBinderProxyPathName = "android/os/BinderProxy"; diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index a82326f67d74..23fcf6ebc2cc 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -34,7 +34,7 @@ enum ApBand { AP_BAND_5GHZ = 2; } -// Refer to definition in ServiceState.java. +// Refer to definition in TelephonyManager.java. enum RadioTech { RADIO_TECHNOLOGY_UNKNOWN = 0; RADIO_TECHNOLOGY_GPRS = 1; @@ -49,8 +49,8 @@ enum RadioTech { RADIO_TECHNOLOGY_HSUPA = 10; RADIO_TECHNOLOGY_HSPA = 11; RADIO_TECHNOLOGY_EVDO_B = 12; - RADIO_TECHNOLOGY_EHRPD = 13; - RADIO_TECHNOLOGY_LTE = 14; + RADIO_TECHNOLOGY_LTE = 13; + RADIO_TECHNOLOGY_EHRPD = 14; RADIO_TECHNOLOGY_HSPAP = 15; RADIO_TECHNOLOGY_GSM = 16; RADIO_TECHNOLOGY_TD_SCDMA = 17; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0c0493ba65aa..d6097d82599a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2068,7 +2068,6 @@ Allows reading of detailed information about phone state for special-use applications such as dialers, carrier applications, or ims applications. --> <permission android:name="android.permission.READ_PRECISE_PHONE_STATE" - android:permissionGroup="android.permission-group.UNDEFINED" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows read access to privileged phone state. @@ -2558,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/values/config.xml b/core/res/res/values/config.xml index 04d1eef9cba1..268d6f161602 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2708,7 +2708,11 @@ <string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" >com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string> - <!-- Name of the dialog that is used to request the user's consent to VPN connection --> + <!-- Name of the dialog that is used to request the user's consent for a Platform VPN --> + <string name="config_platformVpnConfirmDialogComponent" translatable="false" + >com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string> + + <!-- Name of the dialog that is used to request the user's consent for a VpnService VPN --> <string name="config_customVpnConfirmDialogComponent" translatable="false" >com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 001d7c0118cb..95e133f952c4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2233,6 +2233,7 @@ <java-symbol type="string" name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" /> <java-symbol type="string" name="config_customVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" /> + <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" /> <java-symbol type="string" name="config_carrierAppInstallDialogComponent" /> <java-symbol type="string" name="config_defaultNetworkScorerPackageName" /> <java-symbol type="string" name="config_persistentDataPackageName" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 2d1c61cc22fc..0d7ac089ac4a 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/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 9955c512686a..a9e0a770c144 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1340,6 +1340,10 @@ <service android:name="android.os.BinderWorkSourceNestedService" android:process=":BinderWorkSourceNestedService" /> + <!-- Used by BinderProxyTest --> + <service android:name="android.os.BinderProxyService" + android:process=":BinderProxyService" /> + <!-- Application components used for search manager tests --> <activity android:name="android.app.activity.SearchableActivity" diff --git a/core/tests/coretests/src/android/os/BinderProxyService.java b/core/tests/coretests/src/android/os/BinderProxyService.java new file mode 100644 index 000000000000..bf1fbc555d8b --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderProxyService.java @@ -0,0 +1,35 @@ +/* + * 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 android.os; + +import android.app.Service; +import android.content.Intent; + +public class BinderProxyService extends Service { + private final Binder mBinder = new Binder(); + + @Override + public void onCreate() { + super.onCreate(); + mBinder.setExtension(new Binder()); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } +} diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java index aceda2d0524b..3567d17ea874 100644 --- a/core/tests/coretests/src/android/os/BinderProxyTest.java +++ b/core/tests/coretests/src/android/os/BinderProxyTest.java @@ -17,11 +17,17 @@ package android.os; import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.test.AndroidTestCase; import androidx.test.filters.MediumTest; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + public class BinderProxyTest extends AndroidTestCase { private static class CountingListener implements Binder.ProxyTransactListener { int mStartedCount; @@ -86,4 +92,41 @@ public class BinderProxyTest extends AndroidTestCase { // Check it does not throw.. mPowerManager.isInteractive(); } + + private IBinder mRemoteBinder = null; + + @MediumTest + public void testGetExtension() throws Exception { + final CountDownLatch bindLatch = new CountDownLatch(1); + ServiceConnection connection = + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mRemoteBinder = service; + bindLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) {} + }; + try { + mContext.bindService( + new Intent(mContext, BinderProxyService.class), + connection, + Context.BIND_AUTO_CREATE); + if (!bindLatch.await(500, TimeUnit.MILLISECONDS)) { + fail( + "Timed out while binding service: " + + BinderProxyService.class.getSimpleName()); + } + assertTrue(mRemoteBinder instanceof BinderProxy); + assertNotNull(mRemoteBinder); + + IBinder extension = mRemoteBinder.getExtension(); + assertNotNull(extension); + assertTrue(extension.pingBinder()); + } finally { + mContext.unbindService(connection); + } + } } diff --git a/core/tests/coretests/src/android/os/BinderTest.java b/core/tests/coretests/src/android/os/BinderTest.java index a354195c75a3..99dbe6445662 100644 --- a/core/tests/coretests/src/android/os/BinderTest.java +++ b/core/tests/coretests/src/android/os/BinderTest.java @@ -52,4 +52,18 @@ public class BinderTest extends TestCase { } catch (IllegalStateException expected) { } } + + @SmallTest + public void testGetExtension() throws Exception { + Binder binder = new Binder(); + assertNull(binder.getExtension()); + + IBinder extension = new Binder(); + binder.setExtension(extension); + assertNotNull(binder.getExtension()); + assertSame(binder.getExtension(), extension); + + binder.setExtension(null); + assertNull(binder.getExtension()); + } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 7f9a4ecb0171..03f31aecb333 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -346,6 +346,7 @@ applications that come with the platform <!-- Permissions required to test ambient display. --> <permission name="android.permission.READ_DREAM_STATE" /> <permission name="android.permission.WRITE_DREAM_STATE" /> + <permission name="android.permission.REBOOT"/> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index dcc6b95aec02..129063361b35 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -38,6 +38,10 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { 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 09b755912c43..433c6227cd5f 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -1109,6 +1109,24 @@ public final class TvContract { * <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 @@ public final class TvContract { 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 final class TvContract { 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 @@ public final class TvContract { * {@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 final class TvContract { */ 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 final class TvContract { */ 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/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml index 9bd5be7b0dd7..7595d2b1eea3 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 9ccb837cf613..9bae223a0a3e 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.PendingIntent; 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.text.TextUtils; 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 @@ public class DynamicSystemInstallationService extends Service 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 9aea0e713179..438c435ef0e4 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.os.image.DynamicSystemManager; 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog mContext = context; mDynSystem = dynSystem; mListener = listener; + mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl); } @Override @@ -152,9 +157,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog 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 000000000000..522bc547325b --- /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 000000000000..3bdf82966889 --- /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 000000000000..f5f0ae6adaba --- /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 000000000000..fdb620bfe094 --- /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 000000000000..82ce542cf5de --- /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/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java index 9db4a35c1d78..b4c95e6ee2df 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java @@ -35,8 +35,10 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.zip.GZIPInputStream; /** @@ -84,7 +86,7 @@ class LicenseHtmlGeneratorFromXml { * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2. */ - private final Map<String, String> mFileNameToContentIdMap = new HashMap(); + private final Map<String, Set<String>> mFileNameToContentIdMap = new HashMap(); /* * A map from a content id (MD5 sum of file content) to a license file content. @@ -186,10 +188,10 @@ class LicenseHtmlGeneratorFromXml { * </licenses> */ @VisibleForTesting - static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap, + static void parse(InputStreamReader in, Map<String, Set<String>> outFileNameToContentIdMap, Map<String, String> outContentIdToFileContentMap) throws XmlPullParserException, IOException { - Map<String, String> fileNameToContentIdMap = new HashMap<String, String>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<String, Set<String>>(); Map<String, String> contentIdToFileContentMap = new HashMap<String, String>(); XmlPullParser parser = Xml.newPullParser(); @@ -206,7 +208,10 @@ class LicenseHtmlGeneratorFromXml { if (!TextUtils.isEmpty(contentId)) { String fileName = readText(parser).trim(); if (!TextUtils.isEmpty(fileName)) { - fileNameToContentIdMap.put(fileName, contentId); + Set<String> contentIds = + fileNameToContentIdMap.computeIfAbsent( + fileName, k -> new HashSet<>()); + contentIds.add(contentId); } } } else if (TAG_FILE_CONTENT.equals(parser.getName())) { @@ -224,7 +229,13 @@ class LicenseHtmlGeneratorFromXml { state = parser.next(); } - outFileNameToContentIdMap.putAll(fileNameToContentIdMap); + for (Map.Entry<String, Set<String>> entry : fileNameToContentIdMap.entrySet()) { + outFileNameToContentIdMap.merge( + entry.getKey(), entry.getValue(), (s1, s2) -> { + s1.addAll(s2); + return s1; + }); + } outContentIdToFileContentMap.putAll(contentIdToFileContentMap); } @@ -240,7 +251,7 @@ class LicenseHtmlGeneratorFromXml { } @VisibleForTesting - static void generateHtml(Map<String, String> fileNameToContentIdMap, + static void generateHtml(Map<String, Set<String>> fileNameToContentIdMap, Map<String, String> contentIdToFileContentMap, PrintWriter writer, String noticeHeader) { List<String> fileNameList = new ArrayList(); @@ -259,19 +270,20 @@ class LicenseHtmlGeneratorFromXml { // Prints all the file list with a link to its license file content. for (String fileName : fileNameList) { - String contentId = fileNameToContentIdMap.get(fileName); - // Assigns an id to a newly referred license file content. - if (!contentIdToOrderMap.containsKey(contentId)) { - contentIdToOrderMap.put(contentId, count); - - // An index in contentIdAndFileNamesList is the order of each element. - contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); - count++; - } + for (String contentId : fileNameToContentIdMap.get(fileName)) { + // Assigns an id to a newly referred license file content. + if (!contentIdToOrderMap.containsKey(contentId)) { + contentIdToOrderMap.put(contentId, count); + + // An index in contentIdAndFileNamesList is the order of each element. + contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); + count++; + } - int id = contentIdToOrderMap.get(contentId); - contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); - writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + int id = contentIdToOrderMap.get(contentId); + contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); + writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + } } writer.println(HTML_MIDDLE_STRING); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java index 4b5e9097b3fe..e87461f85762 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java @@ -28,8 +28,11 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; @RunWith(RobolectricTestRunner.class) public class LicenseHtmlGeneratorFromXmlTest { @@ -68,6 +71,7 @@ public class LicenseHtmlGeneratorFromXmlTest { private static final String HTML_BODY_STRING = "<li><a href=\"#id0\">/file0</a></li>\n" + + "<li><a href=\"#id1\">/file0</a></li>\n" + "<li><a href=\"#id0\">/file1</a></li>\n" + "</ul>\n" + "</div><!-- table of contents -->\n" @@ -82,6 +86,15 @@ public class LicenseHtmlGeneratorFromXmlTest { + "license content #0\n" + "</pre><!-- license-text -->\n" + "</td></tr><!-- same-license -->\n" + + "<tr id=\"id1\"><td class=\"same-license\">\n" + + "<div class=\"label\">Notices for file(s):</div>\n" + + "<div class=\"file-list\">\n" + + "/file0 <br/>\n" + + "</div><!-- file-list -->\n" + + "<pre class=\"license-text\">\n" + + "license content #1\n" + + "</pre><!-- license-text -->\n" + + "</td></tr><!-- same-license -->\n" + "</table></body></html>\n"; private static final String EXPECTED_HTML_STRING = HTML_HEAD_STRING + HTML_BODY_STRING; @@ -91,22 +104,22 @@ public class LicenseHtmlGeneratorFromXmlTest { @Test public void testParseValidXmlStream() throws XmlPullParserException, IOException { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); LicenseHtmlGeneratorFromXml.parse( new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())), fileNameToContentIdMap, contentIdToFileContentMap); assertThat(fileNameToContentIdMap.size()).isEqualTo(2); - assertThat(fileNameToContentIdMap.get("/file0")).isEqualTo("0"); - assertThat(fileNameToContentIdMap.get("/file1")).isEqualTo("0"); + assertThat(fileNameToContentIdMap.get("/file0")).containsExactly("0"); + assertThat(fileNameToContentIdMap.get("/file1")).containsExactly("0"); assertThat(contentIdToFileContentMap.size()).isEqualTo(1); assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0"); } @Test(expected = XmlPullParserException.class) public void testParseInvalidXmlStream() throws XmlPullParserException, IOException { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); LicenseHtmlGeneratorFromXml.parse( @@ -116,12 +129,13 @@ public class LicenseHtmlGeneratorFromXmlTest { @Test public void testGenerateHtml() { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); - fileNameToContentIdMap.put("/file0", "0"); - fileNameToContentIdMap.put("/file1", "0"); + fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1"))); + fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0"))); contentIdToFileContentMap.put("0", "license content #0"); + contentIdToFileContentMap.put("1", "license content #1"); StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( @@ -131,12 +145,13 @@ public class LicenseHtmlGeneratorFromXmlTest { @Test public void testGenerateHtmlWithCustomHeading() { - Map<String, String> fileNameToContentIdMap = new HashMap<>(); + Map<String, Set<String>> fileNameToContentIdMap = new HashMap<>(); Map<String, String> contentIdToFileContentMap = new HashMap<>(); - fileNameToContentIdMap.put("/file0", "0"); - fileNameToContentIdMap.put("/file1", "0"); + fileNameToContentIdMap.put("/file0", new HashSet<String>(Arrays.asList("0", "1"))); + fileNameToContentIdMap.put("/file1", new HashSet<String>(Arrays.asList("0"))); contentIdToFileContentMap.put("0", "license content #0"); + contentIdToFileContentMap.put("1", "license content #1"); StringWriter output = new StringWriter(); LicenseHtmlGeneratorFromXml.generateHtml( diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 09eece8a4746..3c4577ad5e00 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -91,6 +91,7 @@ <uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> + <uses-permission android:name="android.permission.REBOOT" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> <uses-permission android:name="android.permission.POWER_SAVER" /> <uses-permission android:name="android.permission.INSTALL_LOCATION_PROVIDER" /> @@ -227,6 +228,9 @@ <uses-permission android:name="android.permission.READ_DREAM_STATE"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE"/> + <!-- Permission needed to test mainline permission module rollback --> + <uses-permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml index e4de6259e07d..693ca52b4ed1 100644 --- a/packages/VpnDialogs/AndroidManifest.xml +++ b/packages/VpnDialogs/AndroidManifest.xml @@ -34,6 +34,13 @@ </intent-filter> </activity> + <activity android:name=".PlatformVpnConfirmDialog" + android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" + android:noHistory="true" + android:excludeFromRecents="true" + android:exported="true"> + </activity> + <activity android:name=".ManageDialog" android:theme="@*android:style/Theme.DeviceDefault.Dialog.Alert.DayNight" android:noHistory="true" diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java index 48adb9ba3f63..e66f2cc17a7f 100644 --- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java +++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java @@ -23,6 +23,7 @@ import android.content.DialogInterface; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.net.IConnectivityManager; +import android.net.VpnManager; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; @@ -43,10 +44,20 @@ public class ConfirmDialog extends AlertActivity implements DialogInterface.OnClickListener, ImageGetter { private static final String TAG = "VpnConfirm"; + @VpnManager.VpnType private final int mVpnType; + private String mPackage; private IConnectivityManager mService; + public ConfirmDialog() { + this(VpnManager.TYPE_VPN_SERVICE); + } + + public ConfirmDialog(@VpnManager.VpnType int vpnType) { + mVpnType = vpnType; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -138,7 +149,7 @@ public class ConfirmDialog extends AlertActivity if (mService.prepareVpn(null, mPackage, UserHandle.myUserId())) { // Authorize this app to initiate VPN connections in the future without user // intervention. - mService.setVpnPackageAuthorization(mPackage, UserHandle.myUserId(), true); + mService.setVpnPackageAuthorization(mPackage, UserHandle.myUserId(), mVpnType); setResult(RESULT_OK); } } catch (Exception e) { diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java new file mode 100644 index 000000000000..e2e297caad14 --- /dev/null +++ b/packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java @@ -0,0 +1,29 @@ +/* + * 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.vpndialogs; + +import android.net.VpnManager; + +/** + * PlatformVpnConfirmDialog is a minimal subclass for requesting user consent for platform VPN + * profiles. + */ +public class PlatformVpnConfirmDialog extends ConfirmDialog { + public PlatformVpnConfirmDialog() { + super(VpnManager.TYPE_VPN_PLATFORM); + } +} diff --git a/services/Android.bp b/services/Android.bp index 943e491393dd..073fccc8d1c0 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -111,7 +111,7 @@ droidstubs { srcs: [":services-sources"], installable: false, // TODO: remove the --hide options below - args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" + + args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.SYSTEM_SERVER\\)" + " --hide-annotation android.annotation.Hide" + " --hide-package com.google.android.startop.iorap" + " --hide ReferencesHidden" + diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 51f6ab0def3e..71f751cc018c 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 android.system.OsConstants.IPPROTO_UDP; 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.content.res.Configuration; 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; @@ -112,6 +116,7 @@ import android.net.SocketKeepalive; import android.net.TetheringManager; import android.net.UidRange; import android.net.Uri; +import android.net.VpnManager; import android.net.VpnService; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; @@ -131,6 +136,7 @@ import android.os.Message; 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; @@ -171,6 +177,7 @@ import com.android.internal.util.ArrayUtils; 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; @@ -493,9 +500,9 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * 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; @@ -597,6 +604,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private Set<String> mWolSupportedInterfaces; private TelephonyManager mTelephonyManager; + private final AppOpsManager mAppOpsManager; + + private final LocationPermissionChecker mLocationPermissionChecker; private KeepaliveTracker mKeepaliveTracker; private NetworkNotificationManager mNotifier; @@ -993,6 +1003,8 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ -2093,6 +2105,12 @@ public class ConnectivityService extends IConnectivityManager.Stub 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, @@ -2739,88 +2757,21 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case EVENT_NETWORK_TESTED: { - final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); - if (nai == null) break; + final NetworkTestedResults results = (NetworkTestedResults) msg.obj; - final boolean wasPartial = nai.partialConnectivity; - nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); - final boolean partialConnectivityChanged = - (wasPartial != nai.partialConnectivity); - - 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 NetworkAgentInfo nai = getNetworkAgentInfoForNetId(results.mNetId); + if (nai == null) break; - final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; + handleNetworkTested(nai, results.mTestResult, + (results.mRedirectUrl == null) ? "" : results.mRedirectUrl); - 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: { @@ -2871,6 +2822,87 @@ public class ConnectivityService extends IConnectivityManager.Stub 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, @@ -2919,8 +2951,23 @@ public class ConnectivityService extends IConnectivityManager.Stub @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 @@ -4310,7 +4357,7 @@ public class ConnectivityService extends IConnectivityManager.Stub throwIfLockdownEnabled(); Vpn vpn = mVpns.get(userId); if (vpn != null) { - return vpn.prepare(oldPackage, newPackage); + return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); } else { return false; } @@ -4318,26 +4365,29 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Set whether the VPN package has the ability to launch VPNs without user intervention. - * This method is used by system-privileged apps. - * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId}, - * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. + * Set whether the VPN package has the ability to launch VPNs without user intervention. This + * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn} + * class. If the caller is not {@code userId}, {@link + * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required. * * @param packageName The package for which authorization state should change. * @param userId User for whom {@code packageName} is installed. * @param authorized {@code true} if this app should be able to start a VPN connection without - * explicit user approval, {@code false} if not. - * + * explicit user approval, {@code false} if not. + * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN + * permissions should be granted. When unauthorizing an app, {@link + * VpnManager.TYPE_VPN_NONE} should be used. * @hide */ @Override - public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) { + public void setVpnPackageAuthorization( + String packageName, int userId, @VpnManager.VpnType int vpnType) { enforceCrossUserPermission(userId); synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn != null) { - vpn.setPackageAuthorization(packageName, authorized); + vpn.setPackageAuthorization(packageName, vpnType); } } } @@ -4359,6 +4409,78 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Stores the given VPN profile based on the provisioning package name. + * + * <p>If there is already a VPN profile stored for the provisioning package, this call will + * overwrite the profile. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @return {@code true} if user consent has already been granted, {@code false} otherwise. + * @hide + */ + @Override + public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { + final int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mVpns) { + return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore); + } + } + + /** + * Deletes the stored VPN profile for the provisioning package + * + * <p>If there are no profiles for the given package, this method will silently succeed. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @hide + */ + @Override + public void deleteVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mVpns) { + mVpns.get(user).deleteVpnProfile(packageName, mKeyStore); + } + } + + /** + * Starts the VPN based on the stored profile for the given package + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @throws IllegalArgumentException if no profile was found for the given package name. + * @hide + */ + @Override + public void startVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + mVpns.get(user).startVpnProfile(packageName, mKeyStore); + } + } + + /** + * Stops the Platform VPN if the provided package is running one. + * + * <p>This is designed to serve the VpnManager only; settings-based VPN profiles are managed + * exclusively by the Settings app, and passed into the platform at startup time. + * + * @hide + */ + @Override + public void stopVpnProfile(@NonNull String packageName) { + final int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized (mVpns) { + mVpns.get(user).stopVpnProfile(packageName); + } + } + + /** * Start legacy VPN, controlling native daemons as needed. Creates a * secondary thread to perform connection work, returning quickly. */ @@ -4561,6 +4683,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Throws if there is any currently running, always-on Legacy VPN. + * + * <p>The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is + * running across the entire system. Tracking for app-based VPNs is done on a per-user, + * per-package basis in Vpn.java + */ @GuardedBy("mVpns") private void throwIfLockdownEnabled() { if (mLockdownEnabled) { @@ -6118,12 +6247,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void sendUpdatedScoreToFactories(NetworkRequest networkRequest, NetworkAgentInfo nai) { - int score = 0; - int serial = 0; + private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest, + @Nullable NetworkAgentInfo nai) { + final int score; + final int serial; if (nai != null) { score = nai.getCurrentScore(); serial = nai.factorySerialNumber; + } else { + score = 0; + serial = 0; } if (VDBG || DDBG){ log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); @@ -6286,20 +6419,28 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void makeDefault(@NonNull final NetworkAgentInfo newNetwork) { + private void makeDefault(@Nullable final NetworkAgentInfo newNetwork) { if (DBG) log("Switching to new default network: " + newNetwork); + mDefaultNetworkNai = newNetwork; + try { - mNMS.setDefaultNetId(newNetwork.network.netId); + if (null != newNetwork) { + mNMS.setDefaultNetId(newNetwork.network.netId); + } else { + mNMS.clearDefaultNetId(); + } } catch (Exception e) { loge("Exception setting default network :" + e); } - mDefaultNetworkNai = newNetwork; notifyLockdownVpn(newNetwork); - handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy()); - updateTcpBufferSizes(newNetwork.linkProperties.getTcpBufferSizes()); - mDnsManager.setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers()); + handleApplyDefaultProxy(null != newNetwork + ? newNetwork.linkProperties.getHttpProxy() : null); + updateTcpBufferSizes(null != newNetwork + ? newNetwork.linkProperties.getTcpBufferSizes() : null); + mDnsManager.setDefaultDnsSystemProperties(null != newNetwork + ? newNetwork.linkProperties.getDnsServers() : Collections.EMPTY_LIST); notifyIfacesChangedForNetworkStats(); // Fix up the NetworkCapabilities of any VPNs that don't specify underlying networks. updateAllVpnsCapabilities(); @@ -6361,23 +6502,62 @@ public class ConnectivityService extends IConnectivityManager.Stub } @NonNull private final Set<NetworkBgStatePair> mRematchedNetworks = new ArraySet<>(); - @NonNull private final List<RequestReassignment> mReassignments = new ArrayList<>(); + @NonNull private final Map<NetworkRequestInfo, RequestReassignment> mReassignments = + new ArrayMap<>(); @NonNull Iterable<NetworkBgStatePair> getRematchedNetworks() { return mRematchedNetworks; } @NonNull Iterable<RequestReassignment> getRequestReassignments() { - return mReassignments; + return mReassignments.values(); } void addRequestReassignment(@NonNull final RequestReassignment reassignment) { - mReassignments.add(reassignment); + final RequestReassignment oldChange = mReassignments.get(reassignment.mRequest); + if (null == oldChange) { + mReassignments.put(reassignment.mRequest, reassignment); + return; + } + if (oldChange.mNewNetwork != reassignment.mOldNetwork) { + throw new IllegalArgumentException("Reassignment <" + reassignment.mRequest + "> [" + + reassignment.mOldNetwork + " -> " + reassignment.mNewNetwork + + "] conflicts with [" + + oldChange.mOldNetwork + " -> " + oldChange.mNewNetwork + "]"); + } + // There was already a note to reassign this request from a network A to a network B, + // and a reassignment is added from network B to some other network C. The following + // synthesizes the merged reassignment that goes A -> C. An interesting (but not + // special) case to think about is when B is null, which can happen when the rematch + // loop notices the current satisfier doesn't satisfy the request any more, but + // hasn't yet encountered another network that could. + mReassignments.put(reassignment.mRequest, new RequestReassignment(reassignment.mRequest, + oldChange.mOldNetwork, reassignment.mNewNetwork)); } void addRematchedNetwork(@NonNull final NetworkBgStatePair network) { mRematchedNetworks.add(network); } + + // Will return null if this reassignment does not change the network assigned to + // the passed request. + @Nullable + private RequestReassignment getReassignment(@NonNull final NetworkRequestInfo nri) { + for (final RequestReassignment event : getRequestReassignments()) { + if (nri == event.mRequest) return event; + } + return null; + } + } + + // TODO : remove this when it's useless + @NonNull private NetworkReassignment computeInitialReassignment() { + final NetworkReassignment change = new NetworkReassignment(); + for (NetworkRequestInfo nri : mNetworkRequests.values()) { + change.addRequestReassignment(new NetworkReassignment.RequestReassignment(nri, + nri.mSatisfier, nri.mSatisfier)); + } + return change; } private ArrayMap<NetworkRequestInfo, NetworkAgentInfo> computeRequestReassignmentForNetwork( @@ -6408,7 +6588,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (currentNetwork == null || currentNetwork.getCurrentScore() < score) { reassignedRequests.put(nri, newNetwork); } - } else if (newNetwork.isSatisfyingRequest(nri.request.requestId)) { + } else if (newNetwork == currentNetwork) { reassignedRequests.put(nri, null); } } @@ -6421,19 +6601,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // satisfied by newNetwork, and reassigns to newNetwork // any such requests for which newNetwork is the best. // - // - Lingers any validated Networks that as a result are no longer - // needed. A network is needed if it is the best network for - // one or more NetworkRequests, or if it is a VPN. - // // - Writes into the passed reassignment object all changes that should be done for // rematching this network with all requests, to be applied later. // - // NOTE: This function only adds NetworkRequests that "newNetwork" could satisfy, - // it does not remove NetworkRequests that other Networks could better satisfy. - // If you need to handle decreases in score, use {@link rematchAllNetworksAndRequests}. - // This function should be used when possible instead of {@code rematchAllNetworksAndRequests} - // as it performs better by a factor of the number of Networks. - // // TODO : stop writing to the passed reassignment. This is temporarily more useful, but // it's unidiomatic Java and it's hard to read. // @@ -6444,8 +6614,6 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final NetworkAgentInfo newNetwork, final long now) { ensureRunningOnConnectivityServiceThread(); if (!newNetwork.everConnected) return; - boolean isNewDefault = false; - NetworkAgentInfo oldDefaultNetwork = null; changes.addRematchedNetwork(new NetworkReassignment.NetworkBgStatePair(newNetwork, newNetwork.isBackgroundNetwork())); @@ -6462,6 +6630,8 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequestInfo nri = entry.getKey(); final NetworkAgentInfo previousSatisfier = nri.mSatisfier; final NetworkAgentInfo newSatisfier = entry.getValue(); + changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( + nri, previousSatisfier, newSatisfier)); if (newSatisfier != null) { if (VDBG) log("rematch for " + newSatisfier.name()); if (previousSatisfier != null) { @@ -6474,25 +6644,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG) log(" accepting network in place of null"); } newSatisfier.unlingerRequest(nri.request); - nri.mSatisfier = newSatisfier; if (!newSatisfier.addRequest(nri.request)) { Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); } - changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( - nri, previousSatisfier, newSatisfier)); - // Tell NetworkProviders about the new score, so they can stop - // trying to connect if they know they cannot match it. - // TODO - this could get expensive if we have a lot of requests for this - // network. Think about if there is a way to reduce this. Push - // netid->request mapping to each provider? - sendUpdatedScoreToFactories(nri.request, newSatisfier); - if (isDefaultRequest(nri)) { - isNewDefault = true; - oldDefaultNetwork = previousSatisfier; - if (previousSatisfier != null) { - mLingerMonitor.noteLingerDefaultNetwork(previousSatisfier, newSatisfier); - } - } } else { // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri", // mark it as no longer satisfying "nri". Because networks are processed by @@ -6506,35 +6660,8 @@ public class ConnectivityService extends IConnectivityManager.Stub " request " + nri.request.requestId); } newNetwork.removeRequest(nri.request.requestId); - if (previousSatisfier == newNetwork) { - nri.mSatisfier = null; - if (isDefaultRequest(nri)) mDefaultNetworkNai = null; - sendUpdatedScoreToFactories(nri.request, null); - } else { - Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " + - newNetwork.name() + - " without updating mSatisfier or providers!"); - } - // TODO: Technically, sending CALLBACK_LOST here is - // incorrect if there is a replacement network currently - // connected that can satisfy nri, which is a request - // (not a listen). However, the only capability that can both - // a) be requested and b) change is NET_CAPABILITY_TRUSTED, - // so this code is only incorrect for a network that loses - // the TRUSTED capability, which is a rare case. - callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST, 0); } - } - - if (isNewDefault) { - updateDataActivityTracking(newNetwork, oldDefaultNetwork); - // Notify system services that this network is up. - makeDefault(newNetwork); - // Log 0 -> X and Y -> X default network transitions, where X is the new default. - mDeps.getMetricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent( - now, newNetwork, oldDefaultNetwork); - // Have a new default network, release the transition wakelock in - scheduleReleaseNetworkTransitionWakelock(); + nri.mSatisfier = newSatisfier; } } @@ -6557,19 +6684,48 @@ public class ConnectivityService extends IConnectivityManager.Stub // scoring network and then a higher scoring network, which could produce multiple // callbacks. Arrays.sort(nais); - final NetworkReassignment changes = new NetworkReassignment(); + final NetworkReassignment changes = computeInitialReassignment(); for (final NetworkAgentInfo nai : nais) { rematchNetworkAndRequests(changes, nai, now); } - final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork(); + final NetworkRequestInfo defaultRequestInfo = mNetworkRequests.get(mDefaultRequest); + final NetworkReassignment.RequestReassignment reassignment = + changes.getReassignment(defaultRequestInfo); + final NetworkAgentInfo newDefaultNetwork = + null != reassignment ? reassignment.mNewNetwork : oldDefaultNetwork; + + if (oldDefaultNetwork != newDefaultNetwork) { + if (oldDefaultNetwork != null) { + mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); + } + updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); + // Notify system services of the new default. + makeDefault(newDefaultNetwork); + // Log 0 -> X and Y -> X default network transitions, where X is the new default. + mDeps.getMetricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent( + now, newDefaultNetwork, oldDefaultNetwork); + // Have a new default network, release the transition wakelock in + scheduleReleaseNetworkTransitionWakelock(); + } // Notify requested networks are available after the default net is switched, but // before LegacyTypeTracker sends legacy broadcasts for (final NetworkReassignment.RequestReassignment event : changes.getRequestReassignments()) { + if (event.mOldNetwork == event.mNewNetwork) continue; + + // Tell NetworkProviders about the new score, so they can stop + // trying to connect if they know they cannot match it. + // TODO - this could get expensive if there are a lot of outstanding requests for this + // network. Think of a way to reduce this. Push netid->request mapping to each factory? + sendUpdatedScoreToFactories(event.mRequest.request, event.mNewNetwork); + if (null != event.mNewNetwork) { notifyNetworkAvailable(event.mNewNetwork, event.mRequest); + } else { + callCallbackForRequest(event.mRequest, event.mOldNetwork, + ConnectivityManager.CALLBACK_LOST, 0); } } @@ -7147,7 +7303,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); if (alwaysOnPackage != null) { setAlwaysOnVpnPackage(userId, null, false, null); - setVpnPackageAuthorization(alwaysOnPackage, userId, false); + setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); } // Turn Always-on VPN off @@ -7170,7 +7326,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { // Prevent this app (packagename = vpnConfig.user) from initiating // VPN connections in the future without user intervention. - setVpnPackageAuthorization(vpnConfig.user, userId, false); + setVpnPackageAuthorization( + vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); prepareVpn(null, VpnConfig.LEGACY_VPN, userId); } @@ -7271,7 +7428,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @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); @@ -7383,6 +7544,17 @@ public class ConnectivityService extends IConnectivityManager.Stub */ 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); } @@ -7400,6 +7572,19 @@ public class ConnectivityService extends IConnectivityManager.Stub (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; + } } } } @@ -7409,12 +7594,16 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ -7424,6 +7613,39 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * 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(); @@ -7471,13 +7693,80 @@ public class ConnectivityService extends IConnectivityManager.Stub 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. @@ -7495,7 +7784,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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/LingerMonitor.java b/services/core/java/com/android/server/connectivity/LingerMonitor.java index 929dfc4d1511..707151059869 100644 --- a/services/core/java/com/android/server/connectivity/LingerMonitor.java +++ b/services/core/java/com/android/server/connectivity/LingerMonitor.java @@ -16,6 +16,10 @@ package com.android.server.connectivity; +import static android.net.ConnectivityManager.NETID_UNSET; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -27,18 +31,16 @@ import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.util.SparseArray; -import android.util.SparseIntArray; import android.util.SparseBooleanArray; -import java.util.Arrays; -import java.util.HashMap; +import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.MessageUtils; -import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; -import static android.net.ConnectivityManager.NETID_UNSET; +import java.util.Arrays; +import java.util.HashMap; /** * Class that monitors default network linger events and possibly notifies the user of network @@ -206,8 +208,19 @@ public class LingerMonitor { mEverNotified.put(fromNai.network.netId, true); } + /** + * Put up or dismiss a notification or toast for of a change in the default network if needed. + * + * Putting up a notification when switching from no network to some network is not supported + * and as such this method can't be called with a null |fromNai|. It can be called with a + * null |toNai| if there isn't a default network any more. + * + * @param fromNai switching from this NAI + * @param toNai switching to this NAI + */ // The default network changed from fromNai to toNai due to a change in score. - public void noteLingerDefaultNetwork(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { + public void noteLingerDefaultNetwork(@NonNull final NetworkAgentInfo fromNai, + @Nullable final NetworkAgentInfo toNai) { if (VDBG) { Log.d(TAG, "noteLingerDefaultNetwork from=" + fromNai.name() + " everValidated=" + fromNai.everValidated + @@ -221,6 +234,10 @@ public class LingerMonitor { // Internet access). maybeStopNotifying(fromNai); + // If the network was simply lost (either because it disconnected or because it stopped + // being the default with no replacement), then don't show a notification. + if (null == toNai) return; + // If this network never validated, don't notify. Otherwise, we could do things like: // // 1. Unvalidated wifi connects. diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 2933fab465e5..86dc9c4adaa8 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -24,6 +24,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,6 +62,7 @@ import android.net.NetworkInfo.DetailedState; import android.net.NetworkProvider; import android.net.RouteInfo; import android.net.UidRange; +import android.net.VpnManager; import android.net.VpnService; import android.os.Binder; import android.os.Build.VERSION_CODES; @@ -157,6 +160,16 @@ public class Vpn { // is actually O(n²)+O(n²). private static final int MAX_ROUTES_TO_EVALUATE = 150; + /** + * Largest profile size allowable for Platform VPNs. + * + * <p>The largest platform VPN profiles use IKEv2 RSA Certificate Authentication and have two + * X509Certificates, and one RSAPrivateKey. This should lead to a max size of 2x 12kB for the + * certificates, plus a reasonable upper bound on the private key of 32kB. The rest of the + * profile is expected to be negligible in size. + */ + @VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB + // TODO: create separate trackers for each unique VPN to support // automated reconnection @@ -507,8 +520,11 @@ public class Vpn { } if (packageName != null) { - // Pre-authorize new always-on VPN package. - if (!setPackageAuthorization(packageName, true)) { + // TODO: Give the minimum permission possible; if there is a Platform VPN profile, only + // grant ACTIVATE_PLATFORM_VPN. + // Pre-authorize new always-on VPN package. Grant the full ACTIVATE_VPN appop, allowing + // both VpnService and Platform VPNs. + if (!setPackageAuthorization(packageName, VpnManager.TYPE_VPN_SERVICE)) { return false; } mAlwaysOn = true; @@ -656,6 +672,11 @@ public class Vpn { * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and * it can be revoked by itself. * + * The permission checks to verify that the VPN has already been granted + * user consent are dependent on the type of the VPN being prepared. See + * {@link AppOpsManager#OP_ACTIVATE_VPN} and {@link + * AppOpsManager#OP_ACTIVATE_PLATFORM_VPN} for more information. + * * Note: when we added VPN pre-consent in * https://android.googlesource.com/platform/frameworks/base/+/0554260 * the names oldPackage and newPackage became misleading, because when @@ -674,10 +695,12 @@ public class Vpn { * * @param oldPackage The package name of the old VPN application * @param newPackage The package name of the new VPN application - * + * @param vpnType The type of VPN being prepared. One of {@link VpnManager.VpnType} Preparing a + * platform VPN profile requires only the lesser ACTIVATE_PLATFORM_VPN appop. * @return true if the operation succeeded. */ - public synchronized boolean prepare(String oldPackage, String newPackage) { + public synchronized boolean prepare( + String oldPackage, String newPackage, @VpnManager.VpnType int vpnType) { if (oldPackage != null) { // Stop an existing always-on VPN from being dethroned by other apps. if (mAlwaysOn && !isCurrentPreparedPackage(oldPackage)) { @@ -688,13 +711,14 @@ public class Vpn { if (!isCurrentPreparedPackage(oldPackage)) { // The package doesn't match. We return false (to obtain user consent) unless the // user has already consented to that VPN package. - if (!oldPackage.equals(VpnConfig.LEGACY_VPN) && isVpnUserPreConsented(oldPackage)) { + if (!oldPackage.equals(VpnConfig.LEGACY_VPN) + && isVpnPreConsented(mContext, oldPackage, vpnType)) { prepareInternal(oldPackage); return true; } return false; } else if (!oldPackage.equals(VpnConfig.LEGACY_VPN) - && !isVpnUserPreConsented(oldPackage)) { + && !isVpnPreConsented(mContext, oldPackage, vpnType)) { // Currently prepared VPN is revoked, so unprepare it and return false. prepareInternal(VpnConfig.LEGACY_VPN); return false; @@ -777,25 +801,49 @@ public class Vpn { } } - /** - * Set whether a package has the ability to launch VPNs without user intervention. - */ - public boolean setPackageAuthorization(String packageName, boolean authorized) { + /** Set whether a package has the ability to launch VPNs without user intervention. */ + public boolean setPackageAuthorization(String packageName, @VpnManager.VpnType int vpnType) { // Check if the caller is authorized. enforceControlPermissionOrInternalCaller(); - int uid = getAppUid(packageName, mUserHandle); + final int uid = getAppUid(packageName, mUserHandle); if (uid == -1 || VpnConfig.LEGACY_VPN.equals(packageName)) { // Authorization for nonexistent packages (or fake ones) can't be updated. return false; } - long token = Binder.clearCallingIdentity(); + final long token = Binder.clearCallingIdentity(); try { - AppOpsManager appOps = + final int[] toChange; + + // Clear all AppOps if the app is being unauthorized. + switch (vpnType) { + case VpnManager.TYPE_VPN_NONE: + toChange = new int[] { + AppOpsManager.OP_ACTIVATE_VPN, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN + }; + break; + case VpnManager.TYPE_VPN_PLATFORM: + toChange = new int[] {AppOpsManager.OP_ACTIVATE_PLATFORM_VPN}; + break; + case VpnManager.TYPE_VPN_SERVICE: + toChange = new int[] {AppOpsManager.OP_ACTIVATE_VPN}; + break; + default: + Log.wtf(TAG, "Unrecognized VPN type while granting authorization"); + return false; + } + + final AppOpsManager appOpMgr = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - appOps.setMode(AppOpsManager.OP_ACTIVATE_VPN, uid, packageName, - authorized ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); + for (final int appOp : toChange) { + appOpMgr.setMode( + appOp, + uid, + packageName, + vpnType == VpnManager.TYPE_VPN_NONE + ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED); + } return true; } catch (Exception e) { Log.wtf(TAG, "Failed to set app ops for package " + packageName + ", uid " + uid, e); @@ -805,13 +853,33 @@ public class Vpn { return false; } - private boolean isVpnUserPreConsented(String packageName) { - AppOpsManager appOps = - (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + private static boolean isVpnPreConsented(Context context, String packageName, int vpnType) { + switch (vpnType) { + case VpnManager.TYPE_VPN_SERVICE: + return isVpnServicePreConsented(context, packageName); + case VpnManager.TYPE_VPN_PLATFORM: + return isVpnProfilePreConsented(context, packageName); + default: + return false; + } + } + + private static boolean doesPackageHaveAppop(Context context, String packageName, int appop) { + final AppOpsManager appOps = + (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + + // Verify that the caller matches the given package and has the required permission. + return appOps.noteOpNoThrow(appop, Binder.getCallingUid(), packageName) + == AppOpsManager.MODE_ALLOWED; + } + + private static boolean isVpnServicePreConsented(Context context, String packageName) { + return doesPackageHaveAppop(context, packageName, AppOpsManager.OP_ACTIVATE_VPN); + } - // Verify that the caller matches the given package and has permission to activate VPNs. - return appOps.noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Binder.getCallingUid(), - packageName) == AppOpsManager.MODE_ALLOWED; + private static boolean isVpnProfilePreConsented(Context context, String packageName) { + return doesPackageHaveAppop(context, packageName, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN) + || isVpnServicePreConsented(context, packageName); } private int getAppUid(String app, int userHandle) { @@ -1001,6 +1069,9 @@ public class Vpn { * Establish a VPN network and return the file descriptor of the VPN interface. This methods * returns {@code null} if the application is revoked or not prepared. * + * <p>This method supports ONLY VpnService-based VPNs. For Platform VPNs, see {@link + * provisionVpnProfile} and {@link startVpnProfile} + * * @param config The parameters to configure the network. * @return The file descriptor of the VPN interface. */ @@ -1011,7 +1082,7 @@ public class Vpn { return null; } // Check to ensure consent hasn't been revoked since we were prepared. - if (!isVpnUserPreConsented(mPackage)) { + if (!isVpnServicePreConsented(mContext, mPackage)) { return null; } // Check if the service is properly declared. @@ -1676,6 +1747,10 @@ public class Vpn { public int settingsSecureGetIntForUser(String key, int def, int userId) { return Settings.Secure.getIntForUser(mContext.getContentResolver(), key, def, userId); } + + public boolean isCallerSystem() { + return Binder.getCallingUid() == Process.SYSTEM_UID; + } } private native int jniCreate(int mtu); @@ -2224,4 +2299,148 @@ public class Vpn { } } } + + private void verifyCallingUidAndPackage(String packageName) { + if (getAppUid(packageName, mUserHandle) != Binder.getCallingUid()) { + throw new SecurityException("Mismatched package and UID"); + } + } + + @VisibleForTesting + String getProfileNameForPackage(String packageName) { + return Credentials.PLATFORM_VPN + mUserHandle + "_" + packageName; + } + + /** + * Stores an app-provisioned VPN profile and returns whether the app is already prepared. + * + * @param packageName the package name of the app provisioning this profile + * @param profile the profile to be stored and provisioned + * @param keyStore the System keystore instance to save VPN profiles + * @returns whether or not the app has already been granted user consent + */ + public synchronized boolean provisionVpnProfile( + @NonNull String packageName, @NonNull VpnProfile profile, @NonNull KeyStore keyStore) { + checkNotNull(packageName, "No package name provided"); + checkNotNull(profile, "No profile provided"); + checkNotNull(keyStore, "KeyStore missing"); + + verifyCallingUidAndPackage(packageName); + + final byte[] encodedProfile = profile.encode(); + if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) { + throw new IllegalArgumentException("Profile too big"); + } + + // Permissions checked during startVpnProfile() + Binder.withCleanCallingIdentity( + () -> { + keyStore.put( + getProfileNameForPackage(packageName), + encodedProfile, + Process.SYSTEM_UID, + 0 /* flags */); + }); + + // TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop. + // This mirrors the prepareAndAuthorize that is used by VpnService. + + // Return whether the app is already pre-consented + return isVpnProfilePreConsented(mContext, packageName); + } + + /** + * Deletes an app-provisioned VPN profile. + * + * @param packageName the package name of the app provisioning this profile + * @param keyStore the System keystore instance to save VPN profiles + */ + public synchronized void deleteVpnProfile( + @NonNull String packageName, @NonNull KeyStore keyStore) { + checkNotNull(packageName, "No package name provided"); + checkNotNull(keyStore, "KeyStore missing"); + + verifyCallingUidAndPackage(packageName); + + Binder.withCleanCallingIdentity( + () -> { + keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID); + }); + } + + /** + * Retrieves the VpnProfile. + * + * <p>Must be used only as SYSTEM_UID, otherwise the key/UID pair will not match anything in the + * keystore. + */ + @VisibleForTesting + @Nullable + VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) { + if (!mSystemServices.isCallerSystem()) { + Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID "); + return null; + } + + final byte[] encoded = keyStore.get(getProfileNameForPackage(packageName)); + if (encoded == null) return null; + + return VpnProfile.decode("" /* Key unused */, encoded); + } + + /** + * Starts an already provisioned VPN Profile, keyed by package name. + * + * <p>This method is meant to be called by apps (via VpnManager and ConnectivityService). + * Privileged (system) callers should use startVpnProfilePrivileged instead. Otherwise the UIDs + * will not match during appop checks. + * + * @param packageName the package name of the app provisioning this profile + * @param keyStore the System keystore instance to retrieve VPN profiles + */ + public synchronized void startVpnProfile( + @NonNull String packageName, @NonNull KeyStore keyStore) { + checkNotNull(packageName, "No package name provided"); + checkNotNull(keyStore, "KeyStore missing"); + + // Prepare VPN for startup + if (!prepare(packageName, null /* newPackage */, VpnManager.TYPE_VPN_PLATFORM)) { + throw new SecurityException("User consent not granted for package " + packageName); + } + + Binder.withCleanCallingIdentity( + () -> { + final VpnProfile profile = getVpnProfilePrivileged(packageName, keyStore); + if (profile == null) { + throw new IllegalArgumentException("No profile found for " + packageName); + } + + startVpnProfilePrivileged(profile); + }); + } + + private void startVpnProfilePrivileged(@NonNull VpnProfile profile) { + // TODO: Start PlatformVpnRunner + } + + /** + * Stops an already running VPN Profile for the given package. + * + * <p>This method is meant to be called by apps (via VpnManager and ConnectivityService). + * Privileged (system) callers should (re-)prepare the LEGACY_VPN instead. + * + * @param packageName the package name of the app provisioning this profile + */ + public synchronized void stopVpnProfile(@NonNull String packageName) { + checkNotNull(packageName, "No package name provided"); + + // To stop the VPN profile, the caller must be the current prepared package. Otherwise, + // the app is not prepared, and we can just return. + if (!isCurrentPreparedPackage(packageName)) { + // TODO: Also check to make sure that the running VPN is a VPN profile. + return; + } + + prepareInternal(VpnConfig.LEGACY_VPN); + } } diff --git a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java index a8c68c07231d..c908acdd1d6c 100644 --- a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java +++ b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java @@ -24,16 +24,16 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; -import android.security.keymaster.KeyAttestationPackageInfo; -import android.security.keymaster.KeyAttestationApplicationId; import android.security.keymaster.IKeyAttestationApplicationIdProvider; +import android.security.keymaster.KeyAttestationApplicationId; +import android.security.keymaster.KeyAttestationPackageInfo; /** * @hide * The KeyAttestationApplicationIdProviderService provides information describing the possible * applications identified by a UID. Due to UID sharing, this KeyAttestationApplicationId can - * comprise information about multiple packages. The Information is used by keystore to describe - * the initiating application of a key attestation procedure. + * comprise information about multiple packages. The Information is used by keystore and credstore + * to describe the initiating application of a key attestation procedure. */ public class KeyAttestationApplicationIdProviderService extends IKeyAttestationApplicationIdProvider.Stub { @@ -46,8 +46,10 @@ public class KeyAttestationApplicationIdProviderService public KeyAttestationApplicationId getKeyAttestationApplicationId(int uid) throws RemoteException { - if (Binder.getCallingUid() != android.os.Process.KEYSTORE_UID) { - throw new SecurityException("This service can only be used by Keystore"); + int callingUid = Binder.getCallingUid(); + if (callingUid != android.os.Process.KEYSTORE_UID + && callingUid != android.os.Process.CREDSTORE_UID) { + throw new SecurityException("This service can only be used by Keystore or Credstore"); } KeyAttestationPackageInfo[] keyAttestationPackageInfos = null; final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index b7d63609cff9..0bb0f94d1243 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.FileDescriptor; 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 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true, new ContentObserver(handler) { public void onChange(boolean selfChange) { - timeDetectorService.handleAutoTimeDetectionToggle(); + timeDetectorService.handleAutoTimeDetectionChanged(); } }); @@ -114,8 +117,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { 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 468b806d6dce..a7c3b4dad552 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 android.os.TimestampedValue; 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 a1e643f15a8e..19435ee16660 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.Retention; 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 adf6d7e51f4f..2520316b5d54 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.os.SystemProperties; 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 9a1fe6501221..381ee101e125 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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub 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 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub @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 b0e006908231..1d439e93a1f7 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>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. * - * <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. + * @hide */ -public class 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; +public interface TimeZoneDetectorStrategy { - @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 @@ public class TimeZoneDetectorStrategy { * 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 000000000000..f85f9fe998a5 --- /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 ae5369204428..218f43c9495d 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.NetworkTimeSuggestion; 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 @@ public class TimeDetectorServiceTest { eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion); } @@ -140,7 +139,7 @@ public class TimeDetectorServiceTest { eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion); } @@ -170,7 +169,7 @@ public class TimeDetectorServiceTest { verify(mMockContext).enforceCallingOrSelfPermission( eq(android.Manifest.permission.SET_TIME), anyString()); - mTestHandler.waitForEmptyQueue(); + mTestHandler.waitForMessagesToBeProcessed(); mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion); } @@ -187,21 +186,23 @@ public class TimeDetectorServiceTest { @Test public void testAutoTimeDetectionToggle() throws Exception { - mTimeDetectorService.handleAutoTimeDetectionToggle(); + mTimeDetectorService.handleAutoTimeDetectionChanged(); mTestHandler.assertTotalMessagesEnqueued(1); - mTestHandler.waitForEmptyQueue(); - mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled(); + mTestHandler.waitForMessagesToBeProcessed(); + mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled(); + + mStubbedTimeDetectorStrategy.resetCallTracking(); - mTimeDetectorService.handleAutoTimeDetectionToggle(); + 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 @@ public class TimeDetectorServiceTest { private PhoneTimeSuggestion mLastPhoneSuggestion; private ManualTimeSuggestion mLastManualSuggestion; private NetworkTimeSuggestion mLastNetworkSuggestion; - private boolean mLastAutoTimeDetectionToggleCalled; + private boolean mHandleAutoTimeDetectionChangedCalled; private boolean mDumpCalled; @Override @@ -231,31 +232,26 @@ public class TimeDetectorServiceTest { @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 @@ public class TimeDetectorServiceTest { mLastPhoneSuggestion = null; mLastManualSuggestion = null; mLastNetworkSuggestion = null; - mLastAutoTimeDetectionToggleCalled = false; + mHandleAutoTimeDetectionChangedCalled = false; mDumpCalled = false; } @@ -279,45 +275,12 @@ public class TimeDetectorServiceTest { 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 000000000000..21c9685b05d2 --- /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 000000000000..3e7d40a0335a --- /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 index 2429cfc1bcd0..1e387110ed43 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_MULTI 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; 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.Collections; 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 @@ public class TimeZoneDetectorStrategyTest { 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 @@ public class TimeZoneDetectorStrategyTest { } /** - * 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 @@ public class TimeZoneDetectorStrategyTest { 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 @@ public class TimeZoneDetectorStrategyTest { Script autoTimeZoneDetectionEnabled(boolean enabled) { mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled); - mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange(); + mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChanged(); return this; } diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index bc6a9e848e2a..ef11f469d9a0 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -34,6 +34,8 @@ import android.telephony.euicc.EuiccInfo; import android.telephony.euicc.EuiccManager.OtaStatus; import android.util.Log; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.LinkedBlockingQueue; @@ -583,6 +585,13 @@ public abstract class EuiccService extends Service { public abstract int onRetainSubscriptionsForFactoryReset(int slotId); /** + * Dump to a provided printWriter. + */ + public void dump(@NonNull PrintWriter printWriter) { + printWriter.println("The connected LPA does not implement EuiccService#dump()"); + } + + /** * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}. */ private class IEuiccServiceWrapper extends IEuiccService.Stub { @@ -834,5 +843,22 @@ public abstract class EuiccService extends Service { } }); } + + @Override + public void dump(IEuiccServiceDumpResultCallback callback) throws RemoteException { + mExecutor.execute(new Runnable() { + @Override + public void run() { + try { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + EuiccService.this.dump(pw); + callback.onComplete(sw.toString()); + } catch (RemoteException e) { + // Can't communicate with the phone process; ignore. + } + } + }); + } } } diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl index 2acc47aae919..bb7b569f17f9 100644 --- a/telephony/java/android/service/euicc/IEuiccService.aidl +++ b/telephony/java/android/service/euicc/IEuiccService.aidl @@ -29,6 +29,7 @@ import android.service.euicc.IOtaStatusChangedCallback; import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; +import android.service.euicc.IEuiccServiceDumpResultCallback; import android.telephony.euicc.DownloadableSubscription; import android.os.Bundle; @@ -56,4 +57,5 @@ oneway interface IEuiccService { int slotIndex, int options, in IEraseSubscriptionsCallback callback); void retainSubscriptionsForFactoryReset( int slotId, in IRetainSubscriptionsForFactoryResetCallback callback); + void dump(in IEuiccServiceDumpResultCallback callback); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java index f841df2320e0..1fe80f466b2a 100644 --- a/telephony/java/android/telephony/CellBroadcastService.java +++ b/telephony/java/android/telephony/CellBroadcastService.java @@ -20,6 +20,7 @@ import android.annotation.CallSuper; 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 @@ public abstract class CellBroadcastService extends Service { @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 @@ public abstract class CellBroadcastService extends Service { 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 11263d99cb8f..4f20ed67fda4 100644 --- a/telephony/java/android/telephony/ICellBroadcastService.aidl +++ b/telephony/java/android/telephony/ICellBroadcastService.aidl @@ -36,4 +36,7 @@ interface ICellBroadcastService { /** @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 045d1ebb5640..c0dfec9a1172 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -177,6 +177,9 @@ public final class SmsCbMessage implements Parcelable { @Nullable private final String mLanguage; + /** The 8-bit data coding scheme defined in 3GPP TS 23.038 section 4. */ + private final int mDataCodingScheme; + /** Message body, as a String. */ @Nullable private final String mBody; @@ -220,7 +223,7 @@ public final class SmsCbMessage implements Parcelable { @Nullable SmsCbCmasInfo cmasWarningInfo, int slotIndex, int subId) { this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language, - body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */, + 0, body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */, null /* geometries */, System.currentTimeMillis(), slotIndex, subId); } @@ -230,8 +233,8 @@ public final class SmsCbMessage implements Parcelable { */ public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, @NonNull SmsCbLocation location, int serviceCategory, - @Nullable String language, @Nullable String body, int priority, - @Nullable SmsCbEtwsInfo etwsWarningInfo, + @Nullable String language, int dataCodingScheme, @Nullable String body, + int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo, @Nullable SmsCbCmasInfo cmasWarningInfo, int maximumWaitTimeSec, @Nullable List<Geometry> geometries, long receivedTimeMillis, int slotIndex, int subId) { @@ -241,6 +244,7 @@ public final class SmsCbMessage implements Parcelable { mLocation = location; mServiceCategory = serviceCategory; mLanguage = language; + mDataCodingScheme = dataCodingScheme; mBody = body; mPriority = priority; mEtwsWarningInfo = etwsWarningInfo; @@ -263,6 +267,7 @@ public final class SmsCbMessage implements Parcelable { mLocation = new SmsCbLocation(in); mServiceCategory = in.readInt(); mLanguage = in.readString(); + mDataCodingScheme = in.readInt(); mBody = in.readString(); mPriority = in.readInt(); int type = in.readInt(); @@ -305,6 +310,7 @@ public final class SmsCbMessage implements Parcelable { mLocation.writeToParcel(dest, flags); dest.writeInt(mServiceCategory); dest.writeString(mLanguage); + dest.writeInt(mDataCodingScheme); dest.writeString(mBody); dest.writeInt(mPriority); if (mEtwsWarningInfo != null) { @@ -398,6 +404,15 @@ public final class SmsCbMessage implements Parcelable { } /** + * Get data coding scheme of the message + * + * @return The 8-bit data coding scheme defined in 3GPP TS 23.038 section 4. + */ + public int getDataCodingScheme() { + return mDataCodingScheme; + } + + /** * Get the body of this message, or null if no body available * * @return Body, or null @@ -557,7 +572,7 @@ public final class SmsCbMessage implements Parcelable { 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()); @@ -621,7 +636,7 @@ public final class SmsCbMessage implements Parcelable { 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); @@ -718,7 +733,7 @@ public final class SmsCbMessage implements Parcelable { cursor.getColumnIndexOrThrow(CellBroadcasts.MAXIMUM_WAIT_TIME)); return new SmsCbMessage(format, geoScope, serialNum, location, category, - language, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries, + language, 0, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries, receivedTimeMillis, slotIndex, subId); } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index df726712bffe..ae034b50553b 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,8 +16,8 @@ package android.telephony; -import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; -import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; import android.Manifest; import android.annotation.CallbackExecutor; @@ -2524,9 +2524,9 @@ public class SubscriptionManager { public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, @DurationMillisLong long timeoutMillis) { try { - final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0; - getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue, - timeoutMillis, mContext.getOpPackageName()); + final int overrideValue = overrideUnmetered ? SUBSCRIPTION_OVERRIDE_UNMETERED : 0; + getNetworkPolicy().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_UNMETERED, + overrideValue, timeoutMillis, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2559,9 +2559,9 @@ public class SubscriptionManager { public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, @DurationMillisLong long timeoutMillis) { try { - final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0; - getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue, - timeoutMillis, mContext.getOpPackageName()); + final int overrideValue = overrideCongested ? SUBSCRIPTION_OVERRIDE_CONGESTED : 0; + getNetworkPolicy().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_CONGESTED, + overrideValue, timeoutMillis, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3275,31 +3275,6 @@ public class SubscriptionManager { return subId; } - /** - * Set whether a subscription always allows MMS connection. If true, MMS network - * request will be accepted by telephony even if user turns "mobile data" off - * on this subscription. - * - * @param subId which subscription it's setting to. - * @param alwaysAllow whether Mms data is always allowed. - * @return whether operation is successful. - * - * @hide - */ - public boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow) { - try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); - if (iSub != null) { - return iSub.setAlwaysAllowMmsData(subId, alwaysAllow); - } - } catch (RemoteException ex) { - if (!isSystemProcess()) { - ex.rethrowAsRuntimeException(); - } - } - return false; - } - private interface CallISubMethodHelper { int callMethod(ISub iSub) throws RemoteException; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 811120bebda8..ffd40d0bec3c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -12128,7 +12128,7 @@ public class TelephonyManager { * 1) User data is turned on, or * 2) APN is un-metered for this subscription, or * 3) APN type is whitelisted. E.g. MMS is whitelisted if - * {@link SubscriptionManager#setAlwaysAllowMmsData} is turned on. + * {@link #setAlwaysAllowMmsData(boolean)} is turned on. * * @param apnType Value indicating the apn type. Apn types are defined in {@link ApnSetting}. * @return whether data is enabled for a apn type. @@ -12435,4 +12435,30 @@ public class TelephonyManager { } return false; } + + /** + * Set whether the specific sim card always allows MMS connection. If true, MMS network + * request will be accepted by telephony even if user turns "mobile data" off + * on this specific sim card. + * + * @param alwaysAllow whether Mms data is always allowed. + * @return whether operation is successful. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean setAlwaysAllowMmsData(boolean alwaysAllow) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.setAlwaysAllowMmsData(getSubId(), alwaysAllow); + } + } catch (RemoteException ex) { + if (!isSystemProcess()) { + ex.rethrowAsRuntimeException(); + } + } + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index cc02a409c16d..571efcee0e15 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -295,8 +295,6 @@ interface ISub { boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId); - boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow); - int getActiveDataSubscriptionId(); boolean canDisablePhysicalSubscription(); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index d7fbafbcf5c7..3a6269b5951b 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2241,6 +2241,11 @@ interface ITelephony { boolean isDataAllowedInVoiceCall(int subId); /** + * Set whether a subscription always allows MMS connection. + */ + boolean setAlwaysAllowMmsData(int subId, boolean allow); + + /** * Command line command to enable or disable handling of CEP data for test purposes. */ oneway void setCepEnabled(boolean isCepEnabled); diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java deleted file mode 100644 index bdd1fab34a3c..000000000000 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.telephony.gsm; - -import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE; -import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI; -import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY; -import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE; -import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.res.Resources; -import android.telephony.CbGeoUtils; -import android.telephony.CbGeoUtils.Circle; -import android.telephony.CbGeoUtils.Geometry; -import android.telephony.CbGeoUtils.LatLng; -import android.telephony.CbGeoUtils.Polygon; -import android.telephony.SmsCbLocation; -import android.telephony.SmsCbMessage; -import android.telephony.SubscriptionManager; -import android.util.Pair; -import android.util.Slog; - -import com.android.internal.R; -import com.android.internal.telephony.GsmAlphabet; -import com.android.internal.telephony.SmsConstants; -import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity; -import com.android.internal.telephony.gsm.SmsCbHeader.DataCodingScheme; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is - * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases. - */ -public class GsmSmsCbMessage { - private static final String TAG = GsmSmsCbMessage.class.getSimpleName(); - - private static final char CARRIAGE_RETURN = 0x0d; - - private static final int PDU_BODY_PAGE_LENGTH = 82; - - /** Utility class with only static methods. */ - private GsmSmsCbMessage() { } - - /** - * Get built-in ETWS primary messages by category. ETWS primary message does not contain text, - * so we have to show the pre-built messages to the user. - * - * @param context Device context - * @param category ETWS message category defined in SmsCbConstants - * @return ETWS text message in string. Return an empty string if no match. - */ - private static String getEtwsPrimaryMessage(Context context, int category) { - final Resources r = context.getResources(); - switch (category) { - case ETWS_WARNING_TYPE_EARTHQUAKE: - return r.getString(R.string.etws_primary_default_message_earthquake); - case ETWS_WARNING_TYPE_TSUNAMI: - return r.getString(R.string.etws_primary_default_message_tsunami); - case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI: - return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami); - case ETWS_WARNING_TYPE_TEST_MESSAGE: - return r.getString(R.string.etws_primary_default_message_test); - case ETWS_WARNING_TYPE_OTHER_EMERGENCY: - return r.getString(R.string.etws_primary_default_message_others); - default: - return ""; - } - } - - /** - * Create a new SmsCbMessage object from a header object plus one or more received PDUs. - * - * @param pdus PDU bytes - * @slotIndex slotIndex for which received sms cb message - */ - public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header, - SmsCbLocation location, byte[][] pdus, int slotIndex) - throws IllegalArgumentException { - SubscriptionManager sm = (SubscriptionManager) context.getSystemService( - Context.TELEPHONY_SUBSCRIPTION_SERVICE); - int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - int[] subIds = sm.getSubscriptionIds(slotIndex); - if (subIds != null && subIds.length > 0) { - subId = subIds[0]; - } - - long receivedTimeMillis = System.currentTimeMillis(); - if (header.isEtwsPrimaryNotification()) { - // ETSI TS 23.041 ETWS Primary Notification message - // ETWS primary message only contains 4 fields including serial number, - // message identifier, warning type, and warning security information. - // There is no field for the content/text so we get the text from the resources. - return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(), - header.getSerialNumber(), location, header.getServiceCategory(), null, - getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()), - SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(), - header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis, slotIndex, - subId); - } else if (header.isUmtsFormat()) { - // UMTS format has only 1 PDU - byte[] pdu = pdus[0]; - Pair<String, String> cbData = parseUmtsBody(header, pdu); - String language = cbData.first; - String body = cbData.second; - int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY - : SmsCbMessage.MESSAGE_PRIORITY_NORMAL; - int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; - int wacDataOffset = SmsCbHeader.PDU_HEADER_LENGTH - + 1 // number of pages - + (PDU_BODY_PAGE_LENGTH + 1) * nrPages; // cb data - - // Has Warning Area Coordinates information - List<Geometry> geometries = null; - int maximumWaitingTimeSec = 255; - if (pdu.length > wacDataOffset) { - try { - Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu, - wacDataOffset); - maximumWaitingTimeSec = wac.first; - geometries = wac.second; - } catch (Exception ex) { - // Catch the exception here, the message will be considered as having no WAC - // information which means the message will be broadcasted directly. - Slog.e(TAG, "Can't parse warning area coordinates, ex = " + ex.toString()); - } - } - - return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, - header.getGeographicalScope(), header.getSerialNumber(), location, - header.getServiceCategory(), language, body, priority, - header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries, - receivedTimeMillis, slotIndex, subId); - } else { - String language = null; - StringBuilder sb = new StringBuilder(); - for (byte[] pdu : pdus) { - Pair<String, String> p = parseGsmBody(header, pdu); - language = p.first; - sb.append(p.second); - } - int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY - : SmsCbMessage.MESSAGE_PRIORITY_NORMAL; - - return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, - header.getGeographicalScope(), header.getSerialNumber(), location, - header.getServiceCategory(), language, sb.toString(), priority, - header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */, - receivedTimeMillis, slotIndex, subId); - } - } - - /** - * Parse WEA Handset Action Message(WHAM) a.k.a geo-fencing trigger message. - * - * WEA Handset Action Message(WHAM) is a cell broadcast service message broadcast by the network - * to direct devices to perform a geo-fencing check on selected alerts. - * - * WEA Handset Action Message(WHAM) requirements from ATIS-0700041 section 4 - * 1. The Warning Message contents of a WHAM shall be in Cell Broadcast(CB) data format as - * defined in TS 23.041. - * 2. The Warning Message Contents of WHAM shall be limited to one CB page(max 20 referenced - * WEA messages). - * 3. The broadcast area for a WHAM shall be the union of the broadcast areas of the referenced - * WEA message. - * @param pdu cell broadcast pdu, including the header - * @return {@link GeoFencingTriggerMessage} instance - */ - public static GeoFencingTriggerMessage createGeoFencingTriggerMessage(byte[] pdu) { - try { - // Header length + 1(number of page). ATIS-0700041 define the number of page of - // geo-fencing trigger message is 1. - int whamOffset = SmsCbHeader.PDU_HEADER_LENGTH + 1; - - BitStreamReader bitReader = new BitStreamReader(pdu, whamOffset); - int type = bitReader.read(4); - int length = bitReader.read(7); - // Skip the remained 5 bits - bitReader.skip(); - - int messageIdentifierCount = (length - 2) * 8 / 32; - List<CellBroadcastIdentity> cbIdentifiers = new ArrayList<>(); - for (int i = 0; i < messageIdentifierCount; i++) { - // Both messageIdentifier and serialNumber are 16 bits integers. - // ATIS-0700041 Section 5.1.6 - int messageIdentifier = bitReader.read(16); - int serialNumber = bitReader.read(16); - cbIdentifiers.add(new CellBroadcastIdentity(messageIdentifier, serialNumber)); - } - return new GeoFencingTriggerMessage(type, cbIdentifiers); - } catch (Exception ex) { - Slog.e(TAG, "create geo-fencing trigger failed, ex = " + ex.toString()); - return null; - } - } - - /** - * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV. - * - * @param pdu Warning Area Coordinates TLV. - * @param wacOffset the offset of Warning Area Coordinates TLV. - * @return a pair with the first element is maximum wait time and the second is the broadcast - * area. The default value of the maximum wait time is 255 which means use the device default - * value. - */ - private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates( - byte[] pdu, int wacOffset) { - // little-endian - int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset]; - int offset = wacOffset + 2; - - if (offset + wacDataLength > pdu.length) { - throw new IllegalArgumentException("Invalid wac data, expected the length of pdu at" - + "least " + offset + wacDataLength + ", actual is " + pdu.length); - } - - BitStreamReader bitReader = new BitStreamReader(pdu, offset); - - int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET; - - List<Geometry> geo = new ArrayList<>(); - int remainedBytes = wacDataLength; - while (remainedBytes > 0) { - int type = bitReader.read(4); - int length = bitReader.read(10); - remainedBytes -= length; - // Skip the 2 remained bits - bitReader.skip(); - - switch (type) { - case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME: - maximumWaitTimeSec = bitReader.read(8); - break; - case CbGeoUtils.GEOMETRY_TYPE_POLYGON: - List<LatLng> latLngs = new ArrayList<>(); - // Each coordinate is represented by 44 bits integer. - // ATIS-0700041 5.2.4 Coordinate coding - int n = (length - 2) * 8 / 44; - for (int i = 0; i < n; i++) { - latLngs.add(getLatLng(bitReader)); - } - // Skip the padding bits - bitReader.skip(); - geo.add(new Polygon(latLngs)); - break; - case CbGeoUtils.GEOMETRY_TYPE_CIRCLE: - LatLng center = getLatLng(bitReader); - // radius = (wacRadius / 2^6). The unit of wacRadius is km, we use meter as the - // distance unit during geo-fencing. - // ATIS-0700041 5.2.5 radius coding - double radius = (bitReader.read(20) * 1.0 / (1 << 6)) * 1000.0; - geo.add(new Circle(center, radius)); - break; - default: - throw new IllegalArgumentException("Unsupported geoType = " + type); - } - } - return new Pair(maximumWaitTimeSec, geo); - } - - /** - * The coordinate is (latitude, longitude), represented by a 44 bits integer. - * The coding is defined in ATIS-0700041 5.2.4 - * @param bitReader - * @return coordinate (latitude, longitude) - */ - private static LatLng getLatLng(BitStreamReader bitReader) { - // wacLatitude = floor(((latitude + 90) / 180) * 2^22) - // wacLongitude = floor(((longitude + 180) / 360) * 2^22) - int wacLat = bitReader.read(22); - int wacLng = bitReader.read(22); - - // latitude = wacLatitude * 180 / 2^22 - 90 - // longitude = wacLongitude * 360 / 2^22 -180 - return new LatLng((wacLat * 180.0 / (1 << 22)) - 90, (wacLng * 360.0 / (1 << 22) - 180)); - } - - /** - * Parse and unpack the UMTS body text according to the encoding in the data coding scheme. - * - * @param header the message header to use - * @param pdu the PDU to decode - * @return a pair of string containing the language and body of the message in order - */ - private static Pair<String, String> parseUmtsBody(SmsCbHeader header, byte[] pdu) { - // Payload may contain multiple pages - int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; - String language = header.getDataCodingSchemeStructedData().language; - - if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) - * nrPages) { - throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " - + nrPages + " pages"); - } - - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < nrPages; i++) { - // Each page is 82 bytes followed by a length octet indicating - // the number of useful octets within those 82 - int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; - int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; - - if (length > PDU_BODY_PAGE_LENGTH) { - throw new IllegalArgumentException("Page length " + length - + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); - } - - Pair<String, String> p = unpackBody(pdu, offset, length, - header.getDataCodingSchemeStructedData()); - language = p.first; - sb.append(p.second); - } - return new Pair(language, sb.toString()); - - } - - /** - * Parse and unpack the GSM body text according to the encoding in the data coding scheme. - * @param header the message header to use - * @param pdu the PDU to decode - * @return a pair of string containing the language and body of the message in order - */ - private static Pair<String, String> parseGsmBody(SmsCbHeader header, byte[] pdu) { - // Payload is one single page - int offset = SmsCbHeader.PDU_HEADER_LENGTH; - int length = pdu.length - offset; - return unpackBody(pdu, offset, length, header.getDataCodingSchemeStructedData()); - } - - /** - * Unpack body text from the pdu using the given encoding, position and length within the pdu. - * - * @param pdu The pdu - * @param offset Position of the first byte to unpack - * @param length Number of bytes to unpack - * @param dcs data coding scheme - * @return a Pair of Strings containing the language and body of the message - */ - private static Pair<String, String> unpackBody(byte[] pdu, int offset, int length, - DataCodingScheme dcs) { - String body = null; - - String language = dcs.language; - switch (dcs.encoding) { - case SmsConstants.ENCODING_7BIT: - body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7); - - if (dcs.hasLanguageIndicator && body != null && body.length() > 2) { - // Language is two GSM characters followed by a CR. - // The actual body text is offset by 3 characters. - language = body.substring(0, 2); - body = body.substring(3); - } - break; - - case SmsConstants.ENCODING_16BIT: - if (dcs.hasLanguageIndicator && pdu.length >= offset + 2) { - // Language is two GSM characters. - // The actual body text is offset by 2 bytes. - language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2); - offset += 2; - length -= 2; - } - - try { - body = new String(pdu, offset, (length & 0xfffe), "utf-16"); - } catch (UnsupportedEncodingException e) { - // Apparently it wasn't valid UTF-16. - throw new IllegalArgumentException("Error decoding UTF-16 message", e); - } - break; - - default: - break; - } - - if (body != null) { - // Remove trailing carriage return - for (int i = body.length() - 1; i >= 0; i--) { - if (body.charAt(i) != CARRIAGE_RETURN) { - body = body.substring(0, i + 1); - break; - } - } - } else { - body = ""; - } - - return new Pair<String, String>(language, body); - } - - /** A class use to facilitate the processing of bits stream data. */ - private static final class BitStreamReader { - /** The bits stream represent by a bytes array. */ - private final byte[] mData; - - /** The offset of the current byte. */ - private int mCurrentOffset; - - /** - * The remained bits of the current byte which have not been read. The most significant - * will be read first, so the remained bits are always the least significant bits. - */ - private int mRemainedBit; - - /** - * Constructor - * @param data bit stream data represent by byte array. - * @param offset the offset of the first byte. - */ - BitStreamReader(byte[] data, int offset) { - mData = data; - mCurrentOffset = offset; - mRemainedBit = 8; - } - - /** - * Read the first {@code count} bits. - * @param count the number of bits need to read - * @return {@code bits} represent by an 32-bits integer, therefore {@code count} must be no - * greater than 32. - */ - public int read(int count) throws IndexOutOfBoundsException { - int val = 0; - while (count > 0) { - if (count >= mRemainedBit) { - val <<= mRemainedBit; - val |= mData[mCurrentOffset] & ((1 << mRemainedBit) - 1); - count -= mRemainedBit; - mRemainedBit = 8; - ++mCurrentOffset; - } else { - val <<= count; - val |= (mData[mCurrentOffset] & ((1 << mRemainedBit) - 1)) - >> (mRemainedBit - count); - mRemainedBit -= count; - count = 0; - } - } - return val; - } - - /** - * Skip the current bytes if the remained bits is less than 8. This is useful when - * processing the padding or reserved bits. - */ - public void skip() { - if (mRemainedBit < 8) { - mRemainedBit = 8; - ++mCurrentOffset; - } - } - } - - /** - * Part of a GSM SMS cell broadcast message which may trigger geo-fencing logic. - * @hide - */ - public static final class GeoFencingTriggerMessage { - /** - * Indicate the list of active alerts share their warning area coordinates which means the - * broadcast area is the union of the broadcast areas of the active alerts in this list. - */ - public static final int TYPE_ACTIVE_ALERT_SHARE_WAC = 2; - - public final int type; - public final List<CellBroadcastIdentity> cbIdentifiers; - - GeoFencingTriggerMessage(int type, @NonNull List<CellBroadcastIdentity> cbIdentifiers) { - this.type = type; - this.cbIdentifiers = cbIdentifiers; - } - - /** - * Whether the trigger message indicates that the broadcast areas are shared between all - * active alerts. - * @return true if broadcast areas are to be shared - */ - boolean shouldShareBroadcastArea() { - return type == TYPE_ACTIVE_ALERT_SHARE_WAC; - } - - static final class CellBroadcastIdentity { - public final int messageIdentifier; - public final int serialNumber; - CellBroadcastIdentity(int messageIdentifier, int serialNumber) { - this.messageIdentifier = messageIdentifier; - this.serialNumber = serialNumber; - } - } - - @Override - public String toString() { - String identifiers = cbIdentifiers.stream() - .map(cbIdentifier ->String.format("(msgId = %d, serial = %d)", - cbIdentifier.messageIdentifier, cbIdentifier.serialNumber)) - .collect(Collectors.joining(",")); - return "triggerType=" + type + " identifiers=" + identifiers; - } - } -} diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java index 2d5df4f47e00..0628691c3345 100644 --- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -38,6 +38,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; 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 @@ public class ConnectivityDiagnosticsManagerTest { 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 @@ public class ConnectivityDiagnosticsManagerTest { 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 @@ public class ConnectivityDiagnosticsManagerTest { // 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/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java index 655c4d118592..95a794235a2e 100644 --- a/tests/net/java/android/net/VpnManagerTest.java +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -16,13 +16,24 @@ package android.net; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.ComponentName; +import android.content.Intent; import android.test.mock.MockContext; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.net.VpnProfile; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,7 +42,12 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class VpnManagerTest { - private static final String VPN_PROFILE_KEY = "KEY"; + private static final String PKG_NAME = "fooPackage"; + + private static final String SESSION_NAME_STRING = "testSession"; + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); private IConnectivityManager mMockCs; private VpnManager mVpnManager; @@ -39,7 +55,7 @@ public class VpnManagerTest { new MockContext() { @Override public String getOpPackageName() { - return "fooPackage"; + return PKG_NAME; } }; @@ -50,34 +66,55 @@ public class VpnManagerTest { } @Test - public void testProvisionVpnProfile() throws Exception { - try { - mVpnManager.provisionVpnProfile(mock(PlatformVpnProfile.class)); - } catch (UnsupportedOperationException expected) { - } + public void testProvisionVpnProfilePreconsented() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true); + + // Expect there to be no intent returned, as consent has already been granted. + assertNull(mVpnManager.provisionVpnProfile(profile)); + verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + } + + @Test + public void testProvisionVpnProfileNeedsConsent() throws Exception { + final PlatformVpnProfile profile = getPlatformVpnProfile(); + when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(false); + + // Expect intent to be returned, as consent has not already been granted. + final Intent intent = mVpnManager.provisionVpnProfile(profile); + assertNotNull(intent); + + final ComponentName expectedComponentName = + ComponentName.unflattenFromString( + "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); + assertEquals(expectedComponentName, intent.getComponent()); + verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testDeleteProvisionedVpnProfile() throws Exception { - try { - mVpnManager.deleteProvisionedVpnProfile(); - } catch (UnsupportedOperationException expected) { - } + mVpnManager.deleteProvisionedVpnProfile(); + verify(mMockCs).deleteVpnProfile(eq(PKG_NAME)); } @Test public void testStartProvisionedVpnProfile() throws Exception { - try { - mVpnManager.startProvisionedVpnProfile(); - } catch (UnsupportedOperationException expected) { - } + mVpnManager.startProvisionedVpnProfile(); + verify(mMockCs).startVpnProfile(eq(PKG_NAME)); } @Test public void testStopProvisionedVpnProfile() throws Exception { - try { - mVpnManager.stopProvisionedVpnProfile(); - } catch (UnsupportedOperationException expected) { - } + mVpnManager.stopProvisionedVpnProfile(); + verify(mMockCs).stopVpnProfile(eq(PKG_NAME)); + } + + private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { + return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING) + .setBypassable(true) + .setMaxMtu(1300) + .setMetered(true) + .setAuthPsk(PSK_BYTES) + .build(); } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 50f1bbeed0c5..8162843d9823 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.GET_PERMISSIONS; 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 static org.mockito.Mockito.when; 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.PackageInfo; 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.shared.PrivateDnsConfig; 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.Looper; 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.DefaultNetworkMetrics; 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { @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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { } mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); - mNmCallbacks.notifyNetworkTested( - mNmValidationResult, mNmValidationRedirectUrl); + mNmCallbacks.notifyNetworkTestedWithExtras( + mNmValidationResult, mNmValidationRedirectUrl, TIMESTAMP, mValidationExtras); if (mNmValidationRedirectUrl != null) { mNmCallbacks.showProvisioningNotification( @@ -970,6 +983,8 @@ public class ConnectivityServiceTest { // not inherit from NetworkAgent. private TestNetworkAgentWrapper mMockNetworkAgent; + private VpnInfo mVpnInfo; + public MockVpn(int userId) { super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService, userId); @@ -1041,6 +1056,17 @@ public class ConnectivityServiceTest { 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) { @@ -5741,6 +5767,38 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(defaultCallback); } + @Test + public final void testLoseTrusted() throws Exception { + final NetworkRequest trustedRequest = new NetworkRequest.Builder() + .addCapability(NET_CAPABILITY_TRUSTED) + .build(); + final TestNetworkCallback trustedCallback = new TestNetworkCallback(); + mCm.requestNetwork(trustedRequest, trustedCallback); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + trustedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mNetworkManagementService).setDefaultNetId(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mNetworkManagementService); + + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + verify(mNetworkManagementService).setDefaultNetId(eq(mWiFiNetworkAgent.getNetwork().netId)); + reset(mNetworkManagementService); + + mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); + trustedCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + verify(mNetworkManagementService).setDefaultNetId(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mNetworkManagementService); + + mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); + trustedCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + verify(mNetworkManagementService).clearDefaultNetId(); + + mCm.unregisterNetworkCallback(trustedCallback); + } + @Ignore // 40%+ flakiness : figure out why and re-enable. @Test public final void testBatteryStatsNetworkType() throws Exception { @@ -6368,7 +6426,7 @@ public class ConnectivityServiceTest { 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) { } @@ -6382,7 +6440,7 @@ public class ConnectivityServiceTest { when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); verify(mIBinder, timeout(TIMEOUT_MS)) .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); @@ -6406,7 +6464,7 @@ public class ConnectivityServiceTest { when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); verify(mIBinder, timeout(TIMEOUT_MS)) .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); @@ -6417,7 +6475,7 @@ public class ConnectivityServiceTest { // 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); @@ -6426,4 +6484,145 @@ public class ConnectivityServiceTest { 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)); + } } diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index ce50bef53d75..155c61f3f8c7 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -28,11 +28,11 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static android.net.RouteInfo.RTN_UNREACHABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -43,6 +43,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,21 +59,21 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.net.ConnectivityManager; -import android.net.IpPrefix; -import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; -import android.net.RouteInfo; import android.net.UidRange; +import android.net.VpnManager; import android.net.VpnService; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.INetworkManagementService; import android.os.Looper; -import android.os.SystemClock; +import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.security.Credentials; +import android.security.KeyStore; import android.util.ArrayMap; import android.util.ArraySet; @@ -81,6 +82,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; import org.junit.Before; import org.junit.Test; @@ -90,9 +92,6 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -124,6 +123,8 @@ public class VpnTest { managedProfileA.profileGroupId = primaryUser.id; } + static final String TEST_VPN_PKG = "com.dummy.vpn"; + /** * Names and UIDs for some fake packages. Important points: * - UID is ordered increasing. @@ -148,6 +149,8 @@ public class VpnTest { @Mock private NotificationManager mNotificationManager; @Mock private Vpn.SystemServices mSystemServices; @Mock private ConnectivityManager mConnectivityManager; + @Mock private KeyStore mKeyStore; + private final VpnProfile mVpnProfile = new VpnProfile("key"); @Before public void setUp() throws Exception { @@ -166,6 +169,7 @@ public class VpnTest { when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) .thenReturn(Resources.getSystem().getString( R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); + when(mSystemServices.isCallerSystem()).thenReturn(true); // Used by {@link Notification.Builder} ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -175,6 +179,10 @@ public class VpnTest { .thenReturn(applicationInfo); doNothing().when(mNetService).registerObserver(any()); + + // Deny all appops by default. + when(mAppOps.noteOpNoThrow(anyInt(), anyInt(), anyString())) + .thenReturn(AppOpsManager.MODE_IGNORED); } @Test @@ -464,12 +472,12 @@ public class VpnTest { order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); // When a new VPN package is set the rules should change to cover that package. - vpn.prepare(null, PKGS[0]); + vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE); order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(entireUser)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(exceptPkg0)); // When that VPN package is unset, everything should be undone again in reverse. - vpn.prepare(null, VpnConfig.LEGACY_VPN); + vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE); order.verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(exceptPkg0)); order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser)); } @@ -632,6 +640,230 @@ public class VpnTest { } /** + * The profile name should NOT change between releases for backwards compatibility + * + * <p>If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST + * be updated to ensure backward compatibility. + */ + @Test + public void testGetProfileNameForPackage() throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + final String expected = Credentials.PLATFORM_VPN + primaryUser.id + "_" + TEST_VPN_PKG; + assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG)); + } + + private Vpn createVpnAndSetupUidChecks(int... grantedOps) throws Exception { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt())) + .thenReturn(Process.myUid()); + + for (final int op : grantedOps) { + when(mAppOps.noteOpNoThrow(op, Process.myUid(), TEST_VPN_PKG)) + .thenReturn(AppOpsManager.MODE_ALLOWED); + } + + return vpn; + } + + private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, int... checkedOps) { + assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore)); + + // The profile should always be stored, whether or not consent has been previously granted. + verify(mKeyStore) + .put( + eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), + eq(mVpnProfile.encode()), + eq(Process.SYSTEM_UID), + eq(0)); + + for (final int checkedOp : checkedOps) { + verify(mAppOps).noteOpNoThrow(checkedOp, Process.myUid(), TEST_VPN_PKG); + } + } + + @Test + public void testProvisionVpnProfilePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + checkProvisionVpnProfile( + vpn, true /* expectedResult */, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + } + + @Test + public void testProvisionVpnProfileNotPreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + // Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller + // had neither. + checkProvisionVpnProfile(vpn, false /* expectedResult */, + AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, AppOpsManager.OP_ACTIVATE_VPN); + } + + @Test + public void testProvisionVpnProfileVpnServicePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_VPN); + + checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OP_ACTIVATE_VPN); + } + + @Test + public void testProvisionVpnProfileTooLarge() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + final VpnProfile bigProfile = new VpnProfile(""); + bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]); + + try { + vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile, mKeyStore); + fail("Expected IAE due to profile size"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testDeleteVpnProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore); + + verify(mKeyStore) + .delete(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)), eq(Process.SYSTEM_UID)); + } + + @Test + public void testGetVpnProfilePrivileged() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(new VpnProfile("").encode()); + + vpn.getVpnProfilePrivileged(TEST_VPN_PKG, mKeyStore); + + verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + } + + @Test + public void testStartVpnProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + + verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG)); + } + + @Test + public void testStartVpnProfileVpnServicePreconsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_VPN); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + + // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown. + verify(mAppOps).noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Process.myUid(), TEST_VPN_PKG); + } + + @Test + public void testStartVpnProfileNotConsented() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected failure due to no user consent"); + } catch (SecurityException expected) { + } + + // Verify both appops were checked. + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG)); + verify(mAppOps).noteOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN, Process.myUid(), TEST_VPN_PKG); + + // Keystore should never have been accessed. + verify(mKeyStore, never()).get(any()); + } + + @Test + public void testStartVpnProfileMissingProfile() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN); + + when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null); + + try { + vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore); + fail("Expected failure due to missing profile"); + } catch (IllegalArgumentException expected) { + } + + verify(mKeyStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG)); + verify(mAppOps) + .noteOpNoThrow( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG)); + } + + @Test + public void testSetPackageAuthorizationVpnService() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + } + + @Test + public void testSetPackageAuthorizationPlatformVpn() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_PLATFORM)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_ALLOWED)); + } + + @Test + public void testSetPackageAuthorizationRevokeAuthorization() throws Exception { + final Vpn vpn = createVpnAndSetupUidChecks(); + + assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_IGNORED)); + verify(mAppOps) + .setMode( + eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), + eq(Process.myUid()), + eq(TEST_VPN_PKG), + eq(AppOpsManager.MODE_IGNORED)); + } + + /** * Mock some methods of vpn object. */ private Vpn createVpn(@UserIdInt int userId) { |