summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ApiDocs.bp1
-rw-r--r--StubLibraries.bp108
-rw-r--r--apex/sdkextensions/framework/Android.bp58
-rw-r--r--api/current.txt8
-rw-r--r--api/module-app-current.txt9
-rw-r--r--api/module-app-removed.txt1
-rw-r--r--api/module-lib-current.txt147
-rwxr-xr-xapi/system-current.txt26
-rw-r--r--api/test-current.txt5
-rw-r--r--cmds/statsd/src/atoms.proto41
-rw-r--r--core/java/android/annotation/SystemApi.java36
-rw-r--r--core/java/android/app/NotificationChannel.java4
-rw-r--r--core/java/android/app/timedetector/PhoneTimeSuggestion.java4
-rw-r--r--core/java/android/app/timedetector/TimeDetector.java2
-rw-r--r--core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java4
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java3
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java33
-rw-r--r--core/java/android/net/ConnectivityDiagnosticsManager.java3
-rw-r--r--core/java/android/net/IConnectivityManager.aidl12
-rw-r--r--core/java/android/net/NetworkCapabilities.java4
-rw-r--r--core/java/android/net/NetworkPolicyManager.java165
-rw-r--r--core/java/android/net/VpnManager.java86
-rw-r--r--core/java/android/net/VpnService.java2
-rw-r--r--core/java/android/os/Binder.java12
-rw-r--r--core/java/android/os/BinderProxy.java4
-rw-r--r--core/java/android/os/IBinder.java12
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/TimestampedValue.java3
-rw-r--r--core/java/android/provider/Telephony.java29
-rw-r--r--core/java/android/service/euicc/IEuiccServiceDumpResultCallback.aidl22
-rw-r--r--core/java/android/telephony/CellBroadcastIntents.java10
-rw-r--r--core/java/android/timezone/CountryTimeZones.java36
-rw-r--r--core/java/android/timezone/TelephonyLookup.java2
-rw-r--r--core/java/android/timezone/TelephonyNetwork.java2
-rw-r--r--core/java/android/timezone/TelephonyNetworkFinder.java2
-rw-r--r--core/java/android/timezone/TimeZoneFinder.java2
-rw-r--r--core/java/android/timezone/TzDataSetVersion.java3
-rw-r--r--core/java/android/timezone/ZoneInfoDb.java2
-rw-r--r--core/java/com/android/ims/internal/uce/common/CapInfo.java36
-rw-r--r--core/java/com/android/internal/util/ObjectUtils.java8
-rw-r--r--core/jni/AndroidRuntime.cpp149
-rw-r--r--core/jni/android_util_Binder.cpp54
-rw-r--r--core/proto/android/server/connectivity/data_stall_event.proto6
-rw-r--r--core/res/AndroidManifest.xml3
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/xml/sms_short_codes.xml5
-rw-r--r--core/tests/coretests/AndroidManifest.xml4
-rw-r--r--core/tests/coretests/src/android/os/BinderProxyService.java35
-rw-r--r--core/tests/coretests/src/android/os/BinderProxyTest.java43
-rw-r--r--core/tests/coretests/src/android/os/BinderTest.java14
-rw-r--r--data/etc/privapp-permissions-platform.xml1
-rw-r--r--identity/java/android/security/identity/CredstoreIdentityCredentialStore.java4
-rw-r--r--media/java/android/media/tv/TvContract.java74
-rw-r--r--packages/DynamicSystemInstallationService/res/values/strings.xml3
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java19
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java30
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java148
-rw-r--r--packages/DynamicSystemInstallationService/tests/Android.bp15
-rw-r--r--packages/DynamicSystemInstallationService/tests/AndroidManifest.xml29
-rw-r--r--packages/DynamicSystemInstallationService/tests/res/values/strings.xml19
-rw-r--r--packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java132
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java48
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java35
-rw-r--r--packages/Shell/AndroidManifest.xml4
-rw-r--r--packages/VpnDialogs/AndroidManifest.xml7
-rw-r--r--packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java13
-rw-r--r--packages/VpnDialogs/src/com/android/vpndialogs/PlatformVpnConfirmDialog.java29
-rw-r--r--services/Android.bp2
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java629
-rw-r--r--services/core/java/com/android/server/connectivity/LingerMonitor.java29
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java263
-rw-r--r--services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java14
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorService.java8
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java4
-rw-r--r--services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java2
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java4
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java24
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java489
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java514
-rw-r--r--services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java77
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java76
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java233
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java (renamed from services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java)29
-rw-r--r--telephony/java/android/service/euicc/EuiccService.java26
-rw-r--r--telephony/java/android/service/euicc/IEuiccService.aidl2
-rw-r--r--telephony/java/android/telephony/CellBroadcastService.java24
-rw-r--r--telephony/java/android/telephony/ICellBroadcastService.aidl3
-rw-r--r--telephony/java/android/telephony/SmsCbMessage.java27
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java41
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java28
-rwxr-xr-xtelephony/java/com/android/internal/telephony/ISub.aidl2
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl5
-rw-r--r--telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java521
-rw-r--r--tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java15
-rw-r--r--tests/net/java/android/net/VpnManagerTest.java75
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java211
-rw-r--r--tests/net/java/com/android/server/connectivity/VpnTest.java252
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) {