diff options
175 files changed, 6179 insertions, 2695 deletions
diff --git a/Android.bp b/Android.bp index 82915b5e753a..2ab33aced0e7 100644 --- a/Android.bp +++ b/Android.bp @@ -363,6 +363,7 @@ filegroup { ":framework-statsd-sources", ":framework-tethering-srcs", ":framework-wifi-updatable-sources", + ":ike-srcs", ":updatable-media-srcs", ], visibility: ["//visibility:private"], @@ -371,6 +372,7 @@ filegroup { java_library { name: "framework-updatable-stubs-module_libs_api", static_libs: [ + "android.net.ipsec.ike.stubs.module_lib", "framework-media.stubs.module_lib", "framework-mediaprovider.stubs.module_lib", "framework-permission.stubs.module_lib", @@ -387,6 +389,7 @@ java_library { name: "framework-all", installable: false, static_libs: [ + "android.net.ipsec.ike.impl", "framework-minus-apex", "framework-mediaprovider.impl", "framework-permission.impl", diff --git a/StubLibraries.bp b/StubLibraries.bp index 367b427d8207..6cece60c484c 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -303,6 +303,7 @@ java_library_static { name: "android_stubs_current", srcs: [ ":api-stubs-docs-non-updatable" ], static_libs: [ + "android.net.ipsec.ike.stubs", "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-media.stubs", @@ -322,6 +323,7 @@ java_library_static { name: "android_system_stubs_current", srcs: [ ":system-api-stubs-docs-non-updatable" ], static_libs: [ + "android.net.ipsec.ike.stubs.system", "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-media.stubs.system", @@ -357,6 +359,7 @@ java_library_static { static_libs: [ // Modules do not have test APIs, but we want to include their SystemApis, like we include // the SystemApi of framework-non-updatable-sources. + "android.net.ipsec.ike.stubs.system", "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-media.stubs.system", diff --git a/api/Android.bp b/api/Android.bp index fdfef4cb8a74..74e021108577 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -28,6 +28,7 @@ genrule { genrule { name: "frameworks-base-api-current.txt", srcs: [ + ":android.net.ipsec.ike{.public.api.txt}", ":art.module.public.api{.public.api.txt}", ":conscrypt.module.public.api{.public.api.txt}", ":framework-media{.public.api.txt}", @@ -61,6 +62,7 @@ genrule { genrule { name: "frameworks-base-api-current.srcjar", srcs: [ + ":android.net.ipsec.ike{.public.stubs.source}", ":api-stubs-docs-non-updatable", ":art.module.public.api{.public.stubs.source}", ":conscrypt.module.public.api{.public.stubs.source}", @@ -82,6 +84,7 @@ genrule { genrule { name: "frameworks-base-api-removed.txt", srcs: [ + ":android.net.ipsec.ike{.public.removed-api.txt}", ":art.module.public.api{.public.removed-api.txt}", ":conscrypt.module.public.api{.public.removed-api.txt}", ":framework-media{.public.removed-api.txt}", @@ -114,6 +117,7 @@ genrule { genrule { name: "frameworks-base-api-system-current.txt", srcs: [ + ":android.net.ipsec.ike{.system.api.txt}", ":framework-media{.system.api.txt}", ":framework-mediaprovider{.system.api.txt}", ":framework-permission{.system.api.txt}", @@ -144,6 +148,7 @@ genrule { genrule { name: "frameworks-base-api-system-removed.txt", srcs: [ + ":android.net.ipsec.ike{.system.removed-api.txt}", ":framework-media{.system.removed-api.txt}", ":framework-mediaprovider{.system.removed-api.txt}", ":framework-permission{.system.removed-api.txt}", @@ -174,6 +179,7 @@ genrule { genrule { name: "frameworks-base-api-module-lib-current.txt", srcs: [ + ":android.net.ipsec.ike{.module-lib.api.txt}", ":framework-media{.module-lib.api.txt}", ":framework-mediaprovider{.module-lib.api.txt}", ":framework-permission{.module-lib.api.txt}", @@ -203,6 +209,7 @@ genrule { genrule { name: "frameworks-base-api-module-lib-removed.txt", srcs: [ + ":android.net.ipsec.ike{.module-lib.removed-api.txt}", ":framework-media{.module-lib.removed-api.txt}", ":framework-mediaprovider{.module-lib.removed-api.txt}", ":framework-permission{.module-lib.removed-api.txt}", diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index e21a6b288fb3..50f400122fe1 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -132,9 +132,6 @@ cc_test { "tests/XmlParserTests.cpp", "tests/ZipFileTests.cpp", ], - required: [ - "idmap2", - ], static_libs: ["libgmock"], target: { android: { @@ -163,9 +160,19 @@ cc_test { shared_libs: [ "libz", ], + data: [ + ":libz", + ":idmap2", + ], }, }, - data: ["tests/data/**/*.apk"], + data: [ + "tests/data/**/*.apk", + ], + compile_multilib: "first", + test_options: { + unit_test: true, + }, } cc_binary { diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml deleted file mode 100644 index 5147f4e6cb4c..000000000000 --- a/cmds/idmap2/AndroidTest.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 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. ---> -<configuration description="Config for idmap2_tests"> - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" /> - </target_preparer> - <option name="test-suite-tag" value="idmap2_tests" /> - <test class="com.android.tradefed.testtype.GTest" > - <option name="native-test-device-path" value="/data/local/tmp" /> - <option name="module-name" value="idmap2_tests" /> - </test> -</configuration> diff --git a/core/api/current.txt b/core/api/current.txt index 976ec5608d5a..f6164af55889 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -35970,6 +35970,19 @@ package android.se.omapi { package android.security { + public final class AppUriAuthenticationPolicy implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map<java.lang.String,java.util.Map<android.net.Uri,java.lang.String>> getAppAndUriMappings(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.security.AppUriAuthenticationPolicy> CREATOR; + } + + public static final class AppUriAuthenticationPolicy.Builder { + ctor public AppUriAuthenticationPolicy.Builder(); + method @NonNull public android.security.AppUriAuthenticationPolicy.Builder addAppAndUriMapping(@NonNull String, @NonNull android.net.Uri, @NonNull String); + method @NonNull public android.security.AppUriAuthenticationPolicy build(); + } + public final class AttestedKeyPair { ctor public AttestedKeyPair(@Nullable java.security.KeyPair, @NonNull java.util.List<java.security.cert.Certificate>); method @NonNull public java.util.List<java.security.cert.Certificate> getAttestationRecord(); @@ -36017,6 +36030,7 @@ package android.security { method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String); method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable android.net.Uri, @Nullable String); method @NonNull public static android.content.Intent createInstallIntent(); + method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy); method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String); @@ -41974,7 +41988,11 @@ package android.telephony.ims { } public class ImsRcsManager { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @NonNull public android.telephony.ims.RcsUceAdapter getUceAdapter(); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback); field public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN = "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN"; } @@ -42162,6 +42180,15 @@ package android.telephony.ims { field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2 } + public final class ImsRegistrationAttributes implements android.os.Parcelable { + method public int describeContents(); + method public int getAttributeFlags(); + method @NonNull public java.util.Set<java.lang.String> getFeatureTags(); + method public int getTransportType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR; + } + public class RcsUceAdapter { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException; } @@ -42178,8 +42205,10 @@ package android.telephony.ims { public static class RegistrationManager.RegistrationCallback { ctor public RegistrationManager.RegistrationCallback(); - method public void onRegistered(int); - method public void onRegistering(int); + method @Deprecated public void onRegistered(int); + method public void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes); + method @Deprecated public void onRegistering(int); + method public void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes); method public void onTechnologyChangeFailed(int, @NonNull android.telephony.ims.ImsReasonInfo); method public void onUnregistered(@NonNull android.telephony.ims.ImsReasonInfo); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index ab9799ff6272..1fb5de3170db 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -15,6 +15,7 @@ package android.net { } public class ConnectivityManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); } @@ -71,6 +72,14 @@ package android.net { field @NonNull public final java.util.List<java.lang.String> underlyingIfaces; } + public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { + ctor public VpnTransportInfo(int); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR; + field public final int type; + } + } package android.os { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5fb898a282a8..84d04fceaaba 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -74,6 +74,7 @@ package android { field public static final String CONTROL_DISPLAY_SATURATION = "android.permission.CONTROL_DISPLAY_SATURATION"; field public static final String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; field public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"; + field public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE = "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"; field public static final String CONTROL_VPN = "android.permission.CONTROL_VPN"; field public static final String CREATE_USERS = "android.permission.CREATE_USERS"; field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER"; @@ -6006,11 +6007,15 @@ package android.net { method public long getExpiryTimeMillis(); method public long getRefreshTimeMillis(); method @Nullable public android.net.Uri getUserPortalUrl(); + method public int getUserPortalUrlSource(); method @Nullable public String getVenueFriendlyName(); method @Nullable public android.net.Uri getVenueInfoUrl(); + method public int getVenueInfoUrlSource(); method public boolean isCaptive(); method public boolean isSessionExtendable(); method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; } @@ -6024,8 +6029,10 @@ package android.net { method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); } public class ConnectivityManager { @@ -6039,6 +6046,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); + method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -6060,6 +6068,10 @@ package android.net { field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd } + public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { + method public void onComplete(); + } + @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); method @Deprecated public void onTetheringFailed(); @@ -6144,6 +6156,7 @@ package android.net { method public void close(); method @NonNull public String getInterfaceName(); method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void setUnderlyingNetwork(@NonNull android.net.Network) throws java.io.IOException; } public static class IpSecTransform.Builder { @@ -6287,6 +6300,7 @@ package android.net { method @NonNull public int[] getAdministratorUids(); method @Nullable public String getSsid(); method @NonNull public int[] getTransportTypes(); + method public boolean isPrivateDnsBroken(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 @@ -6301,6 +6315,7 @@ package android.net { method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int); method @NonNull public android.net.NetworkCapabilities build(); + method @NonNull public android.net.NetworkCapabilities.Builder clearAll(); method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int); method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]); @@ -6416,6 +6431,26 @@ package android.net { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); } + public final class OemNetworkPreferences implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getNetworkPreferences(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.OemNetworkPreferences> CREATOR; + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3 + field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4 + field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0 + } + + public static final class OemNetworkPreferences.Builder { + ctor public OemNetworkPreferences.Builder(); + ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences); + method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int); + method @NonNull public android.net.OemNetworkPreferences build(); + method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String); + } + public abstract class QosCallback { ctor public QosCallback(); method public void onError(@NonNull android.net.QosCallbackException); @@ -7504,6 +7539,7 @@ package android.os { public class SystemConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Set<java.lang.String> getDisabledUntilUsedPreinstalledCarrierApps(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_CARRIER_APP_INFO) public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + method @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public int[] getSystemPermissionUids(@NonNull String); } public class SystemProperties { @@ -8429,10 +8465,12 @@ package android.security.keystore { } public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec { + method @Nullable public int[] getAttestationIds(); method public int getNamespace(); } public static final class KeyGenParameterSpec.Builder { + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setAttestationIds(@NonNull int[]); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int); method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); } @@ -10730,35 +10768,6 @@ package android.telephony.cdma { package android.telephony.data { - public final class ApnThrottleStatus implements android.os.Parcelable { - method public int describeContents(); - method public int getApnType(); - method public int getRetryType(); - method public int getSlotIndex(); - method public long getThrottleExpiryTimeMillis(); - method public int getThrottleType(); - method public int getTransportType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.ApnThrottleStatus> CREATOR; - field public static final int RETRY_TYPE_HANDOVER = 3; // 0x3 - field public static final int RETRY_TYPE_NEW_CONNECTION = 2; // 0x2 - field public static final int RETRY_TYPE_NONE = 1; // 0x1 - field public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; // 0x2 - field public static final int THROTTLE_TYPE_NONE = 1; // 0x1 - } - - public static final class ApnThrottleStatus.Builder { - ctor public ApnThrottleStatus.Builder(); - method @NonNull public android.telephony.data.ApnThrottleStatus build(); - method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setApnType(int); - method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setNoThrottle(); - method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setRetryType(int); - method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setSlotIndex(int); - method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setThrottleExpiryTimeMillis(long); - method @NonNull public android.telephony.data.ApnThrottleStatus.Builder setTransportType(int); - field public static final long NO_THROTTLE_EXPIRY_TIME = -1L; // 0xffffffffffffffffL - } - public final class DataCallResponse implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<android.net.LinkAddress> getAddresses(); @@ -10928,7 +10937,7 @@ package android.telephony.data { ctor public QualifiedNetworksService.NetworkAvailabilityProvider(int); method public abstract void close(); method public final int getSlotIndex(); - method public void reportApnThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ApnThrottleStatus>); + method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>); method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>); } @@ -10958,6 +10967,34 @@ package android.telephony.data { method @NonNull public android.telephony.data.SliceInfo.Builder setSliceServiceType(int); } + public final class ThrottleStatus implements android.os.Parcelable { + method public int describeContents(); + method public int getApnType(); + method public int getRetryType(); + method public int getSlotIndex(); + method public long getThrottleExpiryTimeMillis(); + method public int getThrottleType(); + method public int getTransportType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.ThrottleStatus> CREATOR; + field public static final int RETRY_TYPE_HANDOVER = 3; // 0x3 + field public static final int RETRY_TYPE_NEW_CONNECTION = 2; // 0x2 + field public static final int RETRY_TYPE_NONE = 1; // 0x1 + field public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; // 0x2 + field public static final int THROTTLE_TYPE_NONE = 1; // 0x1 + } + + public static final class ThrottleStatus.Builder { + ctor public ThrottleStatus.Builder(); + method @NonNull public android.telephony.data.ThrottleStatus build(); + method @NonNull public android.telephony.data.ThrottleStatus.Builder setApnType(int); + method @NonNull public android.telephony.data.ThrottleStatus.Builder setNoThrottle(); + method @NonNull public android.telephony.data.ThrottleStatus.Builder setRetryType(int); + method @NonNull public android.telephony.data.ThrottleStatus.Builder setSlotIndex(int); + method @NonNull public android.telephony.data.ThrottleStatus.Builder setThrottleExpiryTimeMillis(long); + method @NonNull public android.telephony.data.ThrottleStatus.Builder setTransportType(int); + } + } package android.telephony.euicc { @@ -11545,12 +11582,35 @@ package android.telephony.ims { @Deprecated public static class ImsMmTelManager.RegistrationCallback extends android.telephony.ims.RegistrationManager.RegistrationCallback { ctor @Deprecated public ImsMmTelManager.RegistrationCallback(); + method @Deprecated public void onRegistered(int); + method @Deprecated public void onRegistering(int); + } + + public class ImsRcsManager { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addOnAvailabilityChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsRcsManager.OnAvailabilityChangedListener) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAvailable(int, int) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(int, int) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeOnAvailabilityChangedListener(@NonNull android.telephony.ims.ImsRcsManager.OnAvailabilityChangedListener); + } + + public static interface ImsRcsManager.OnAvailabilityChangedListener { + method public void onAvailabilityChanged(int); } public final class ImsReasonInfo implements android.os.Parcelable { field public static final String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service"; } + public final class ImsRegistrationAttributes implements android.os.Parcelable { + method public int getRegistrationTechnology(); + } + + public static final class ImsRegistrationAttributes.Builder { + ctor public ImsRegistrationAttributes.Builder(int); + method @NonNull public android.telephony.ims.ImsRegistrationAttributes build(); + method @NonNull public android.telephony.ims.ImsRegistrationAttributes.Builder setFeatureTags(@NonNull java.util.Set<java.lang.String>); + } + public class ImsService extends android.app.Service { ctor public ImsService(); method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); @@ -12187,11 +12247,24 @@ package android.telephony.ims.feature { ctor public RcsFeature(@NonNull java.util.concurrent.Executor); method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); method @NonNull public android.telephony.ims.stub.RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener); + method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities); method public void onFeatureReady(); method public void onFeatureRemoved(); + method public boolean queryCapabilityConfiguration(int, int); + method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus(); method public void removeCapabilityExchangeImpl(@NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase); } + public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities { + ctor public RcsFeature.RcsImsCapabilities(int); + method public void addCapabilities(int); + method public boolean isCapable(int); + method public void removeCapabilities(int); + field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0 + field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1 + field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 + } + } package android.telephony.ims.stub { @@ -12321,7 +12394,9 @@ package android.telephony.ims.stub { ctor public ImsRegistrationImplBase(); method public final void onDeregistered(android.telephony.ims.ImsReasonInfo); method public final void onRegistered(int); + method public final void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes); method public final void onRegistering(int); + method public final void onRegistering(@NonNull android.telephony.ims.ImsRegistrationAttributes); method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]); method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo); method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 668b5883cbfb..68721418b407 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -12,6 +12,7 @@ package android { field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; + field public static final String KEEP_UNINSTALLED_PACKAGES = "android.permission.KEEP_UNINSTALLED_PACKAGES"; field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; @@ -989,6 +990,12 @@ package android.net { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } + public class NetworkPolicyManager { + method public boolean getRestrictBackground(); + method @NonNull public static String resolveNetworkId(@NonNull android.net.wifi.WifiConfiguration); + method public void setRestrictBackground(boolean); + } + public class NetworkStack { method public static void setServiceForTest(@Nullable android.os.IBinder); } diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index f5ab40abdf37..b3636b9b765d 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -483,6 +483,8 @@ GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored(): GetterSetterNames: android.location.LocationRequest#isLowPowerMode(): +GetterSetterNames: android.net.NetworkPolicyManager#getRestrictBackground(): + Symmetric method for `setRestrictBackground` must be named `isRestrictBackground`; was `getRestrictBackground` GetterSetterNames: android.os.IncidentReportArgs#isAll(): GetterSetterNames: android.service.notification.NotificationStats#setDirectReplied(): diff --git a/core/java/android/accounts/OWNERS b/core/java/android/accounts/OWNERS index ea5fd36702f9..8dcc04a27af6 100644 --- a/core/java/android/accounts/OWNERS +++ b/core/java/android/accounts/OWNERS @@ -3,7 +3,6 @@ dementyev@google.com sandrakwan@google.com hackbod@google.com svetoslavganov@google.com -moltmann@google.com fkupolov@google.com yamasani@google.com omakoto@google.com diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 31ab2248236d..797253af394b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1136,9 +1136,16 @@ public class AppOpsManager { // TODO: Add as AppProtoEnums public static final int OP_RECORD_AUDIO_HOTWORD = 102; + /** + * Manage credentials in the system KeyChain. + * + * @hide + */ + public static final int OP_MANAGE_CREDENTIALS = AppProtoEnums.APP_OP_MANAGE_CREDENTIALS; + /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 104; + public static final int _NUM_OP = 105; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1485,6 +1492,13 @@ public class AppOpsManager { */ public static final String OPSTR_RECORD_AUDIO_HOTWORD = "android:record_audio_hotword"; + /** + * Manage credentials in the system KeyChain. + * + * @hide + */ + public static final String OPSTR_MANAGE_CREDENTIALS = "android:manage_credentials"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1679,6 +1693,7 @@ public class AppOpsManager { OP_PHONE_CALL_CAMERA, // OP_PHONE_CALL_CAMERA OP_RECORD_AUDIO_HOTWORD, // RECORD_AUDIO_HOTWORD OP_MANAGE_ONGOING_CALLS, // MANAGE_ONGOING_CALLS + OP_MANAGE_CREDENTIALS, // MANAGE_CREDENTIALS }; /** @@ -1789,6 +1804,7 @@ public class AppOpsManager { OPSTR_PHONE_CALL_CAMERA, OPSTR_RECORD_AUDIO_HOTWORD, OPSTR_MANAGE_ONGOING_CALLS, + OPSTR_MANAGE_CREDENTIALS, }; /** @@ -1900,6 +1916,7 @@ public class AppOpsManager { "PHONE_CALL_CAMERA", "RECORD_AUDIO_HOTWORD", "MANAGE_ONGOING_CALLS", + "MANAGE_CREDENTIALS", }; /** @@ -2012,6 +2029,7 @@ public class AppOpsManager { null, // no permission for OP_PHONE_CALL_CAMERA null, // no permission for OP_RECORD_AUDIO_HOTWORD Manifest.permission.MANAGE_ONGOING_CALLS, + null, // no permission for OP_MANAGE_CREDENTIALS }; /** @@ -2124,6 +2142,7 @@ public class AppOpsManager { null, // PHONE_CALL_MICROPHONE null, // RECORD_AUDIO_HOTWORD null, // MANAGE_ONGOING_CALLS + null, // MANAGE_CREDENTIALS }; /** @@ -2235,6 +2254,7 @@ public class AppOpsManager { null, // PHONE_CALL_CAMERA null, // RECORD_AUDIO_HOTWORD null, // MANAGE_ONGOING_CALLS + null, // MANAGE_CREDENTIALS }; /** @@ -2345,6 +2365,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // PHONE_CALL_CAMERA AppOpsManager.MODE_ALLOWED, // OP_RECORD_AUDIO_HOTWORD AppOpsManager.MODE_DEFAULT, // MANAGE_ONGOING_CALLS + AppOpsManager.MODE_DEFAULT, // MANAGE_CREDENTIALS }; /** @@ -2459,6 +2480,7 @@ public class AppOpsManager { false, // PHONE_CALL_CAMERA false, // RECORD_AUDIO_HOTWORD true, // MANAGE_ONGOING_CALLS + false, // MANAGE_CREDENTIALS }; /** diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index d151526612f0..d7eded201740 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -116,11 +116,13 @@ import android.net.EthernetManager; import android.net.IEthernetManager; import android.net.IIpSecService; import android.net.INetworkPolicyManager; +import android.net.IVpnManager; import android.net.IpSecManager; import android.net.NetworkPolicyManager; import android.net.NetworkScoreManager; import android.net.NetworkWatchlistManager; import android.net.TetheringManager; +import android.net.VpnManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; @@ -358,6 +360,15 @@ public final class SystemServiceRegistry { ctx, () -> ServiceManager.getService(Context.TETHERING_SERVICE)); }}); + registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class, + new CachedServiceFetcher<VpnManager>() { + @Override + public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE); + IVpnManager service = IVpnManager.Stub.asInterface(b); + return new VpnManager(ctx, service); + }}); + registerService(Context.VCN_MANAGEMENT_SERVICE, VcnManager.class, new CachedServiceFetcher<VcnManager>() { @Override diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index 28b73406b877..ab38832458d6 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -20,8 +20,16 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.compat.Compatibility; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.compat.CompatibilityOverrideConfig; +import com.android.internal.compat.IPlatformCompat; + +import java.util.Map; + /** * CompatChanges APIs - to be used by platform code only (including mainline * modules). @@ -89,4 +97,25 @@ public final class CompatChanges { return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid)); } + /** + * Set an app compat override for a given package. This will check whether the caller is allowed + * to perform this operation on the given apk and build. Only the installer package is allowed + * to set overrides on a non-debuggable final build and a non-test apk. + * + * @param packageName The package name of the app in question. + * @param overrides A map from changeId to the override applied for this change id. + * @hide + */ + @RequiresPermission(android.Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG) + public static void setPackageOverride(String packageName, + Map<Long, PackageOverride> overrides) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig(overrides); + try { + platformCompat.setOverridesFromInstaller(config, packageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/compat/OWNERS b/core/java/android/app/compat/OWNERS new file mode 100644 index 000000000000..f8c3520e9fa8 --- /dev/null +++ b/core/java/android/app/compat/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/compat/OWNERS diff --git a/core/java/android/app/compat/PackageOverride.java b/core/java/android/app/compat/PackageOverride.java new file mode 100644 index 000000000000..9f97cd41128a --- /dev/null +++ b/core/java/android/app/compat/PackageOverride.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2021 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.app.compat; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * An app compat override applied to a given package and change id pairing. + * + * A package override contains a list of version ranges with the desired boolean value of + * the override for the app in this version range. Ranges can be open ended in either direction. + * An instance of PackageOverride gets created via {@link Builder} and is immutable once created. + * + * @hide + */ +public class PackageOverride implements Parcelable { + + @IntDef({ + VALUE_UNDEFINED, + VALUE_ENABLED, + VALUE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + /** @hide */ + public @interface EvaluatedOverride { + } + + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * this PackageOverride does not define the value of the override for the given version. + * @hide + */ + public static final int VALUE_UNDEFINED = 0; + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * the override evaluates to {@code true} for the given version. + * @hide + */ + public static final int VALUE_ENABLED = 1; + /** + * Return value of {@link #evaluate(long)} and {@link #evaluateForAllVersions()} indicating that + * the override evaluates to {@code fakse} for the given version. + * @hide + */ + public static final int VALUE_DISABLED = 2; + + private final long mMinVersionCode; + private final long mMaxVersionCode; + private final boolean mEnabled; + + private PackageOverride(long minVersionCode, + long maxVersionCode, + boolean enabled) { + this.mMinVersionCode = minVersionCode; + this.mMaxVersionCode = maxVersionCode; + this.mEnabled = enabled; + } + + private PackageOverride(Parcel in) { + this(in.readLong(), in.readLong(), in.readBoolean()); + } + + /** + * Evaluate the override for the given {@code versionCode}. If no override is defined for + * the specified version code, {@link #VALUE_UNDEFINED} is returned. + * @hide + */ + public @EvaluatedOverride int evaluate(long versionCode) { + if (versionCode >= mMinVersionCode && versionCode <= mMaxVersionCode) { + return mEnabled ? VALUE_ENABLED : VALUE_DISABLED; + } + return VALUE_UNDEFINED; + } + + /** + * Evaluate the override independent of version code, i.e. only return an evaluated value if + * this range covers all versions, otherwise {@link #VALUE_UNDEFINED} is returned. + * @hide + */ + public int evaluateForAllVersions() { + if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) { + return mEnabled ? VALUE_ENABLED : VALUE_DISABLED; + } + return VALUE_UNDEFINED; + } + + /** Returns the minimum version code the override applies to. */ + public long getMinVersionCode() { + return mMinVersionCode; + } + + /** Returns the minimum version code the override applies from. */ + public long getMaxVersionCode() { + return mMaxVersionCode; + } + + /** Returns the enabled value for the override. */ + public boolean getEnabled() { + return mEnabled; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mMinVersionCode); + dest.writeLong(mMaxVersionCode); + dest.writeBoolean(mEnabled); + } + + /** @hide */ + @Override + public String toString() { + if (mMinVersionCode == Long.MIN_VALUE && mMaxVersionCode == Long.MAX_VALUE) { + return Boolean.toString(mEnabled); + } + return String.format("[%d,%d,%b]", mMinVersionCode, mMaxVersionCode, mEnabled); + } + + /** @hide */ + public static final Creator<PackageOverride> CREATOR = + new Creator<PackageOverride>() { + + @Override + public PackageOverride createFromParcel(Parcel in) { + return new PackageOverride(in); + } + + @Override + public PackageOverride[] newArray(int size) { + return new PackageOverride[size]; + } + }; + + /** + * Builder to construct a PackageOverride. + */ + public static class Builder { + private long mMinVersionCode = Long.MIN_VALUE; + private long mMaxVersionCode = Long.MAX_VALUE; + private boolean mEnabled; + + /** + * Sets the minimum version code the override should apply from. + * + * default value: {@code Long.MIN_VALUE}. + */ + public Builder setMinVersionCode(long minVersionCode) { + mMinVersionCode = minVersionCode; + return this; + } + + /** + * Sets the maximum version code the override should apply to. + * + * default value: {@code Long.MAX_VALUE}. + */ + public Builder setMaxVersionCode(long maxVersionCode) { + mMaxVersionCode = maxVersionCode; + return this; + } + + /** + * Sets whether the override should be enabled for the given version range. + * + * default value: {@code false}. + */ + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + /** + * Build the {@link PackageOverride}. + * + * @throws IllegalArgumentException if {@code minVersionCode} is larger than + * {@code maxVersionCode}. + */ + public PackageOverride build() { + if (mMinVersionCode > mMaxVersionCode) { + throw new IllegalArgumentException("minVersionCode must not be larger than " + + "maxVersionCode"); + } + return new PackageOverride(mMinVersionCode, mMaxVersionCode, mEnabled); + } + }; +} diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index a6089c31b140..32aa0377cd5f 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -64,7 +64,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.StorageManager; -import android.system.Int32Ref; +import android.system.Int64Ref; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -4040,7 +4040,7 @@ public abstract class ContentResolver implements ContentInterface { // Convert to Point, since that's what the API is defined as final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_SIZE, Point.convert(size)); - final Int32Ref orientation = new Int32Ref(0); + final Int64Ref orientation = new Int64Ref(0); Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts, diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS index cde7b2ac1898..d302b0ae1ea8 100644 --- a/core/java/android/content/pm/permission/OWNERS +++ b/core/java/android/content/pm/permission/OWNERS @@ -3,7 +3,6 @@ toddke@android.com toddke@google.com patb@google.com -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com zhanghai@google.com diff --git a/core/java/android/hardware/location/OWNERS b/core/java/android/hardware/location/OWNERS index 383321bc3d69..bd40409f71c6 100644 --- a/core/java/android/hardware/location/OWNERS +++ b/core/java/android/hardware/location/OWNERS @@ -4,3 +4,6 @@ mstogaitis@google.com wyattriley@google.com etn@google.com weiwa@google.com + +# ContextHub team +per-file *ContextHub*,*NanoApp* = file:platform/system/chre:/OWNERS diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS index 8f2b39da4f63..8f5c2a025672 100644 --- a/core/java/android/hardware/usb/OWNERS +++ b/core/java/android/hardware/usb/OWNERS @@ -1,4 +1,3 @@ # Bug component: 175220 -moltmann@google.com badhri@google.com diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index d6774d47b49e..933256a3b475 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -58,6 +58,9 @@ interface IIpSecService in LinkAddress localAddr, in String callingPackage); + void setNetworkForTunnelInterface( + int tunnelResourceId, in Network underlyingNetwork, in String callingPackage); + void deleteTunnelInterface(int resourceId, in String callingPackage); IpSecTransformResponse createTransform( diff --git a/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl new file mode 100644 index 000000000000..7979afc54f90 --- /dev/null +++ b/core/java/android/net/IOnSetOemNetworkPreferenceListener.aidl @@ -0,0 +1,23 @@ +/** + * + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +/** @hide */ +oneway interface IOnSetOemNetworkPreferenceListener { + void onComplete(); +} diff --git a/core/java/android/net/IVpnManager.aidl b/core/java/android/net/IVpnManager.aidl new file mode 100644 index 000000000000..271efe41a9ef --- /dev/null +++ b/core/java/android/net/IVpnManager.aidl @@ -0,0 +1,62 @@ +/** + * 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.net; + +import android.net.Network; + +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; + +/** + * Interface that manages VPNs. + */ +/** {@hide} */ +interface IVpnManager { + /** VpnService APIs */ + boolean prepareVpn(String oldPackage, String newPackage, int userId); + void setVpnPackageAuthorization(String packageName, int userId, int vpnType); + ParcelFileDescriptor establishVpn(in VpnConfig config); + boolean addVpnAddress(String address, int prefixLength); + boolean removeVpnAddress(String address, int prefixLength); + boolean setUnderlyingNetworksForVpn(in Network[] networks); + + /** VpnManager APIs */ + boolean provisionVpnProfile(in VpnProfile profile, String packageName); + void deleteVpnProfile(String packageName); + void startVpnProfile(String packageName); + void stopVpnProfile(String packageName); + + /** Always-on VPN APIs */ + boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); + boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, + in List<String> lockdownAllowlist); + String getAlwaysOnVpnPackage(int userId); + boolean isVpnLockdownEnabled(int userId); + List<String> getVpnLockdownAllowlist(int userId); + boolean isCallerCurrentAlwaysOnVpnApp(); + boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + /** Legacy VPN APIs */ + void startLegacyVpn(in VpnProfile profile); + LegacyVpnInfo getLegacyVpnInfo(int userId); + boolean updateLockdownVpn(); + + /** General system APIs */ + VpnConfig getVpnConfig(int userId); + void factoryReset(); +} diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 70bca3019818..98acd98cc465 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -782,6 +782,42 @@ public final class IpSecManager { } } + /** + * Update the underlying network for this IpSecTunnelInterface. + * + * <p>This new underlying network will be used for all transforms applied AFTER this call is + * complete. Before new {@link IpSecTransform}(s) with matching addresses are applied to + * this tunnel interface, traffic will still use the old SA, and be routed on the old + * underlying network. + * + * <p>To migrate IPsec tunnel mode traffic, a caller should: + * + * <ol> + * <li>Update the IpSecTunnelInterface’s underlying network. + * <li>Apply {@link IpSecTransform}(s) with matching addresses to this + * IpSecTunnelInterface. + * </ol> + * + * @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel. + * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise + * this method will throw an {@link IllegalArgumentException}. + */ + // TODO: b/169171001 Update the documentation when transform migration is supported. + // The purpose of making updating network and applying transforms separate is to leave open + // the possibility to support lossless migration procedures. To do that, Android platform + // will need to support multiple inbound tunnel mode transforms, just like it can support + // multiple transport mode transforms. + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public void setUnderlyingNetwork(@NonNull Network underlyingNetwork) throws IOException { + try { + mService.setNetworkForTunnelInterface( + mResourceId, underlyingNetwork, mOpPackageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private IpSecTunnelInterface(@NonNull Context ctx, @NonNull IIpSecService service, @NonNull InetAddress localAddress, @NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork) diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java index e01e5aecc7e6..f7c1c4bb6d96 100644 --- a/core/java/android/net/LocalSocketImpl.java +++ b/core/java/android/net/LocalSocketImpl.java @@ -19,7 +19,6 @@ package android.net; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.system.ErrnoException; -import android.system.Int32Ref; import android.system.Os; import android.system.OsConstants; import android.system.StructLinger; @@ -65,14 +64,11 @@ class LocalSocketImpl public int available() throws IOException { FileDescriptor myFd = fd; if (myFd == null) throw new IOException("socket closed"); - - Int32Ref avail = new Int32Ref(0); try { - Os.ioctlInt(myFd, OsConstants.FIONREAD, avail); + return Os.ioctlInt(myFd, OsConstants.FIONREAD); } catch (ErrnoException e) { throw e.rethrowAsIOException(); } - return avail.value; } /** {@inheritDoc} */ @@ -134,7 +130,7 @@ class LocalSocketImpl public void write (byte[] b) throws IOException { write(b, 0, b.length); } - + /** {@inheritDoc} */ @Override public void write (byte[] b, int off, int len) throws IOException { @@ -255,7 +251,7 @@ class LocalSocketImpl /** note timeout presently ignored */ protected void connect(LocalSocketAddress address, int timeout) throws IOException - { + { if (fd == null) { throw new IOException("socket not created"); } @@ -339,7 +335,7 @@ class LocalSocketImpl * @throws IOException if socket has been closed or cannot be created. */ protected OutputStream getOutputStream() throws IOException - { + { if (fd == null) { throw new IOException("socket not created"); } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 11146bd45fe4..b1bca6efa02b 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.ActivityManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -55,6 +56,7 @@ import java.util.concurrent.ConcurrentHashMap; * * @hide */ +@TestApi @SystemService(Context.NETWORK_POLICY_SERVICE) public class NetworkPolicyManager { @@ -125,6 +127,7 @@ public class NetworkPolicyManager { public static final int RULE_REJECT_ALL = 1 << 6; /** * Reject traffic on all networks for restricted networking mode. + * @hide */ public static final int RULE_REJECT_RESTRICTED_MODE = 1 << 10; @@ -351,6 +354,7 @@ public class NetworkPolicyManager { } /** @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setRestrictBackground(boolean restrictBackground) { try { @@ -361,6 +365,7 @@ public class NetworkPolicyManager { } /** @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean getRestrictBackground() { try { @@ -506,6 +511,8 @@ public class NetworkPolicyManager { /** * Get multipath preference for the given network. + * + * @hide */ public int getMultipathPreference(Network network) { try { @@ -624,7 +631,9 @@ public class NetworkPolicyManager { } /** @hide */ - public static String resolveNetworkId(WifiConfiguration config) { + @TestApi + @NonNull + public static String resolveNetworkId(@NonNull WifiConfiguration config) { return WifiInfo.sanitizeSsid(config.isPasspoint() ? config.providerFriendlyName : config.SSID); } diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java index 5e56164cc82c..b4034556f66e 100644 --- a/core/java/android/net/OemNetworkPreferences.java +++ b/core/java/android/net/OemNetworkPreferences.java @@ -18,6 +18,7 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcelable; @@ -29,11 +30,12 @@ import java.util.Map; import java.util.Objects; /** @hide */ +@SystemApi public final class OemNetworkPreferences implements Parcelable { /** - * Use default behavior requesting networks. Equivalent to not setting any preference at all. + * Default in case this value is not set. Using it will result in an error. */ - public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0; + public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; /** * If an unmetered network is available, use it. @@ -45,17 +47,17 @@ public final class OemNetworkPreferences implements Parcelable { /** * If an unmetered network is available, use it. * Otherwise, if a network with the OEM_PAID capability is available, use it. - * Otherwise, the app doesn't get a network. + * Otherwise, the app doesn't get a default network. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; /** - * Prefer only NET_CAPABILITY_OEM_PAID networks. + * Use only NET_CAPABILITY_OEM_PAID networks. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; /** - * Prefer only NET_CAPABILITY_OEM_PRIVATE networks. + * Use only NET_CAPABILITY_OEM_PRIVATE networks. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; @@ -95,8 +97,6 @@ public final class OemNetworkPreferences implements Parcelable { /** * Builder used to create {@link OemNetworkPreferences} objects. Specify the preferred Network * to package name mappings. - * - * @hide */ public static final class Builder { private final Bundle mNetworkMappings; @@ -135,7 +135,7 @@ public final class OemNetworkPreferences implements Parcelable { * @return The builder to facilitate chaining. */ @NonNull - public Builder removeNetworkPreference(@NonNull final String packageName) { + public Builder clearNetworkPreference(@NonNull final String packageName) { Objects.requireNonNull(packageName); mNetworkMappings.remove(packageName); return this; @@ -160,7 +160,7 @@ public final class OemNetworkPreferences implements Parcelable { /** @hide */ @IntDef(prefix = "OEM_NETWORK_PREFERENCE_", value = { - OEM_NETWORK_PREFERENCE_DEFAULT, + OEM_NETWORK_PREFERENCE_UNINITIALIZED, OEM_NETWORK_PREFERENCE_OEM_PAID, OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK, OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY, @@ -174,12 +174,14 @@ public final class OemNetworkPreferences implements Parcelable { * * @param value int value of OemNetworkPreference * @return string version of OemNetworkPreference + * + * @hide */ @NonNull public static String oemNetworkPreferenceToString(@OemNetworkPreference int value) { switch (value) { - case OEM_NETWORK_PREFERENCE_DEFAULT: - return "OEM_NETWORK_PREFERENCE_DEFAULT"; + case OEM_NETWORK_PREFERENCE_UNINITIALIZED: + return "OEM_NETWORK_PREFERENCE_UNINITIALIZED"; case OEM_NETWORK_PREFERENCE_OEM_PAID: return "OEM_NETWORK_PREFERENCE_OEM_PAID"; case OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK: diff --git a/packages/Connectivity/framework/src/android/net/VpnManager.java b/core/java/android/net/VpnManager.java index 1812509ba6d2..f472ed4381d1 100644 --- a/packages/Connectivity/framework/src/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.Activity; import android.content.ComponentName; @@ -37,6 +38,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; +import java.util.List; /** * This class provides an interface for apps to manage platform VPN profiles @@ -55,18 +57,40 @@ import java.security.GeneralSecurityException; public class VpnManager { /** Type representing a lack of VPN @hide */ public static final int TYPE_VPN_NONE = -1; - /** VPN service type code @hide */ + + /** + * A VPN created by an app using the {@link VpnService} API. + * @hide + */ public static final int TYPE_VPN_SERVICE = 1; - /** Platform VPN type code @hide */ + + /** + * A VPN created using a {@link VpnManager} API such as {@link #startProvisionedVpnProfile}. + * @hide + */ public static final int TYPE_VPN_PLATFORM = 2; + /** + * An IPsec VPN created by the built-in LegacyVpnRunner. + * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead. + * @hide + */ + @Deprecated + public static final int TYPE_VPN_LEGACY = 3; + + /** + * Channel for VPN notifications. + * @hide + */ + public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; + /** @hide */ - @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM}) + @IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY}) @Retention(RetentionPolicy.SOURCE) public @interface VpnType {} @NonNull private final Context mContext; - @NonNull private final IConnectivityManager mService; + @NonNull private final IVpnManager mService; private static Intent getIntentForConfirmation() { final Intent intent = new Intent(); @@ -85,9 +109,9 @@ public class VpnManager { * * @hide */ - public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + public VpnManager(@NonNull Context ctx, @NonNull IVpnManager service) { mContext = checkNotNull(ctx, "missing Context"); - mService = checkNotNull(service, "missing IConnectivityManager"); + mService = checkNotNull(service, "missing IVpnManager"); } /** @@ -179,6 +203,19 @@ public class VpnManager { } /** + * Resets all VPN settings back to factory defaults. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void factoryReset() { + try { + mService.factoryReset(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Prepare for a VPN application. * 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. @@ -224,6 +261,108 @@ public class VpnManager { } /** + * Checks if a VPN app supports always-on mode. + * + * In order to support the always-on feature, an app has to + * <ul> + * <li>target {@link VERSION_CODES#N API 24} or above, and + * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} + * meta-data field. + * </ul> + * + * @param userId The identifier of the user for whom the VPN app is installed. + * @param vpnPackage The canonical package name of the VPN app. + * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * @hide + */ + public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { + try { + return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Configures an always-on VPN connection through a specific application. + * This connection is automatically granted and persisted after a reboot. + * + * <p>The designated package should declare a {@link VpnService} in its + * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, + * otherwise the call will fail. + * + * @param userId The identifier of the user to set an always-on VPN for. + * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} + * to remove an existing always-on VPN configuration. + * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or + * {@code false} otherwise. + * @param lockdownAllowlist The list of packages that are allowed to access network directly + * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so + * this method must be called when a package that should be allowed is installed or + * uninstalled. + * @return {@code true} if the package is set as always-on VPN controller; + * {@code false} otherwise. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, + boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) { + try { + return mService.setAlwaysOnVpnPackage( + userId, vpnPackage, lockdownEnabled, lockdownAllowlist); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the package name of the currently set always-on VPN application. + * If there is no always-on VPN set, or the VPN is provided by the system instead + * of by an app, {@code null} will be returned. + * + * @return Package name of VPN controller responsible for always-on VPN, + * or {@code null} if none is set. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public String getAlwaysOnVpnPackageForUser(int userId) { + try { + return mService.getAlwaysOnVpnPackage(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return whether always-on VPN is in lockdown mode. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public boolean isVpnLockdownEnabled(int userId) { + try { + return mService.isVpnLockdownEnabled(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @return the list of packages that are allowed to access network when always-on VPN is in + * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. + * + * @hide + **/ + @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + public List<String> getVpnLockdownAllowlist(int userId) { + try { + return mService.getVpnLockdownAllowlist(userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the legacy VPN information for the specified user ID. * @hide */ diff --git a/packages/Connectivity/framework/src/android/net/VpnService.java b/core/java/android/net/VpnService.java index 8e90a119fe21..e43b0b6fa635 100644 --- a/packages/Connectivity/framework/src/android/net/VpnService.java +++ b/core/java/android/net/VpnService.java @@ -170,12 +170,11 @@ public class VpnService extends Service { "android.net.VpnService.SUPPORTS_ALWAYS_ON"; /** - * Use IConnectivityManager since those methods are hidden and not - * available in ConnectivityManager. + * Use IVpnManager since those methods are hidden and not available in VpnManager. */ - private static IConnectivityManager getService() { - return IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + private static IVpnManager getService() { + return IVpnManager.Stub.asInterface( + ServiceManager.getService(Context.VPN_MANAGEMENT_SERVICE)); } /** @@ -226,15 +225,15 @@ public class VpnService extends Service { @SystemApi @RequiresPermission(android.Manifest.permission.CONTROL_VPN) public static void prepareAndAuthorize(Context context) { - IConnectivityManager cm = getService(); + IVpnManager vm = getService(); String packageName = context.getPackageName(); try { // Only prepare if we're not already prepared. int userId = context.getUserId(); - if (!cm.prepareVpn(packageName, null, userId)) { - cm.prepareVpn(null, packageName, userId); + if (!vm.prepareVpn(packageName, null, userId)) { + vm.prepareVpn(null, packageName, userId); } - cm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); + vm.setVpnPackageAuthorization(packageName, userId, VpnManager.TYPE_VPN_SERVICE); } catch (RemoteException e) { // ignore } diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index fa090f59a8b9..1a38338c26aa 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -28,8 +28,10 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; import java.io.IOException; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -67,8 +69,7 @@ import java.util.concurrent.Executor; public class VcnManager { @NonNull private static final String TAG = VcnManager.class.getSimpleName(); - @VisibleForTesting - public static final Map< + private static final Map< VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> REGISTERED_POLICY_LISTENERS = new ConcurrentHashMap<>(); @@ -88,6 +89,18 @@ public class VcnManager { mService = requireNonNull(service, "missing service"); } + /** + * Get all currently registered VcnUnderlyingNetworkPolicyListeners for testing purposes. + * + * @hide + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + public static Map<VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> + getAllPolicyListeners() { + return Collections.unmodifiableMap(REGISTERED_POLICY_LISTENERS); + } + // TODO: Make setVcnConfig(), clearVcnConfig() Public API /** * Sets the VCN configuration for a given subscription group. diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 27dc6e0ada43..ea282afb8b8e 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -1902,7 +1902,8 @@ public final class Debug * Retrieves the PSS memory used by the process as given by the smaps. Optionally supply a long * array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and * Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and - * another array to also retrieve the separate memtrack size. + * another array to also retrieve the separate memtrack sizes (up to 4 values in order): the + * total memtrack reported size, memtrack graphics, memtrack gl and memtrack other. * * @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone). * @hide @@ -2551,6 +2552,14 @@ public final class Debug public static native long getZramFreeKb(); /** + * Return total memory size in kilobytes for exported DMA-BUFs or -1 if + * the DMA-BUF sysfs stats at /sys/kernel/dmabuf/buffers could not be read. + * + * @hide + */ + public static native long getDmabufTotalExportedKb(); + + /** * Return memory size in kilobytes allocated for ION heaps or -1 if * /sys/kernel/ion/total_heaps_kb could not be read. * @@ -2559,6 +2568,14 @@ public final class Debug public static native long getIonHeapsSizeKb(); /** + * Return memory size in kilobytes allocated for DMA-BUF heap pools or -1 if + * /sys/kernel/dma_heap/total_pools_kb could not be read. + * + * @hide + */ + public static native long getDmabufHeapPoolsSizeKb(); + + /** * Return memory size in kilobytes allocated for ION pools or -1 if * /sys/kernel/ion/total_pools_kb could not be read. * @@ -2567,13 +2584,20 @@ public final class Debug public static native long getIonPoolsSizeKb(); /** - * Return ION memory mapped by processes in kB. + * Return GPU DMA buffer usage in kB or -1 on error. + * + * @hide + */ + public static native long getGpuDmaBufUsageKb(); + + /** + * Return DMA-BUF memory mapped by processes in kB. * Notes: * * Warning: Might impact performance as it reads /proc/<pid>/maps files for each process. * * @hide */ - public static native long getIonMappedSizeKb(); + public static native long getDmabufMappedSizeKb(); /** * Return memory size in kilobytes used by GPU. diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl index 52f0ce1f054f..4d160da22ff8 100644 --- a/core/java/android/os/ISystemConfig.aidl +++ b/core/java/android/os/ISystemConfig.aidl @@ -35,4 +35,9 @@ interface ISystemConfig { * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries */ Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); + + /** + * @see SystemConfigManager#getSystemPermissionUids + */ + int[] getSystemPermissionUids(String permissionName); } diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java index 3f0632be90d1..9bfa8adc8571 100644 --- a/core/java/android/os/SystemConfigManager.java +++ b/core/java/android/os/SystemConfigManager.java @@ -111,4 +111,22 @@ public class SystemConfigManager { return Collections.emptyMap(); } } + + /** + * Get uids which have been granted given permission in system configuration. + * + * The uids and assigning permissions are defined on data/etc/platform.xml + * + * @param permissionName The target permission. + * @return The uids have been granted given permission in system configuration. + */ + @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS) + @NonNull + public int[] getSystemPermissionUids(@NonNull String permissionName) { + try { + return mInterface.getSystemPermissionUids(permissionName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index d09f351bdfd1..b32346848a69 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,6 +1,5 @@ # Bug component: 137825 -moltmann@google.com evanseverson@google.com ntmyren@google.com zhanghai@google.com diff --git a/core/java/android/permissionpresenterservice/OWNERS b/core/java/android/permissionpresenterservice/OWNERS index d09f351bdfd1..b32346848a69 100644 --- a/core/java/android/permissionpresenterservice/OWNERS +++ b/core/java/android/permissionpresenterservice/OWNERS @@ -1,6 +1,5 @@ # Bug component: 137825 -moltmann@google.com evanseverson@google.com ntmyren@google.com zhanghai@google.com diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/print/OWNERS +++ b/core/java/android/print/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/print/pdf/OWNERS b/core/java/android/print/pdf/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/print/pdf/OWNERS +++ b/core/java/android/print/pdf/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/printservice/OWNERS +++ b/core/java/android/printservice/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/android/printservice/recommendation/OWNERS b/core/java/android/printservice/recommendation/OWNERS index 72f09832becf..28a242037f6a 100644 --- a/core/java/android/printservice/recommendation/OWNERS +++ b/core/java/android/printservice/recommendation/OWNERS @@ -1,5 +1,4 @@ # Bug component: 47273 -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl new file mode 100644 index 000000000000..5d02a29edcd5 --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.compat; + +parcelable CompatibilityOverrideConfig; diff --git a/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java new file mode 100644 index 000000000000..1c222a73eabc --- /dev/null +++ b/core/java/com/android/internal/compat/CompatibilityOverrideConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.compat; + + +import android.app.compat.PackageOverride; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parcelable containing compat config overrides for a given application. + * @hide + */ +public final class CompatibilityOverrideConfig implements Parcelable { + public final Map<Long, PackageOverride> overrides; + + public CompatibilityOverrideConfig(Map<Long, PackageOverride> overrides) { + this.overrides = overrides; + } + + private CompatibilityOverrideConfig(Parcel in) { + int keyCount = in.readInt(); + overrides = new HashMap<>(); + for (int i = 0; i < keyCount; i++) { + long key = in.readLong(); + PackageOverride override = in.readParcelable(PackageOverride.class.getClassLoader()); + overrides.put(key, override); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(overrides.size()); + for (Long key : overrides.keySet()) { + dest.writeLong(key); + dest.writeParcelable(overrides.get(key), 0); + } + } + + public static final Creator<CompatibilityOverrideConfig> CREATOR = + new Creator<CompatibilityOverrideConfig>() { + + @Override + public CompatibilityOverrideConfig createFromParcel(Parcel in) { + return new CompatibilityOverrideConfig(in); + } + + @Override + public CompatibilityOverrideConfig[] newArray(int size) { + return new CompatibilityOverrideConfig[size]; + } + }; +} diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl index a5eb5f607c12..60213e4411c3 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -21,6 +21,7 @@ import com.android.internal.compat.IOverrideValidator; import java.util.Map; parcelable CompatibilityChangeConfig; +parcelable CompatibilityOverrideConfig; parcelable CompatibilityChangeInfo; /** * Platform private API for talking with the PlatformCompat service. @@ -152,6 +153,17 @@ interface IPlatformCompat { /** * Adds overrides to compatibility changes. * + * <p>Kills the app to allow the changes to take effect. + * + * @param overrides parcelable containing the compat change overrides to be applied + * @param packageName the package name of the app whose changes will be overridden + * @throws SecurityException if overriding changes is not permitted + */ + void setOverridesFromInstaller(in CompatibilityOverrideConfig overrides, in String packageName); + + /** + * Adds overrides to compatibility changes. + * * <p>Does not kill the app, to be only used in tests. * * @param overrides parcelable containing the compat change overrides to be applied diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 79a0dfd61e9f..088682c9f2d8 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -206,6 +206,7 @@ cc_library_shared { ], shared_libs: [ + "android.hardware.memtrack-unstable-ndk_platform", "libandroidicu", "libbpf_android", "libnetdbpf", diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 28fc8ede1653..223b4dcc0549 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -33,6 +33,7 @@ #include <string> #include <vector> +#include <aidl/android/hardware/memtrack/DeviceInfo.h> #include <android-base/logging.h> #include <bionic/malloc.h> #include <debuggerd/client.h> @@ -43,7 +44,9 @@ #include <nativehelper/JNIPlatformHelp.h> #include <nativehelper/ScopedUtfChars.h> #include "jni.h" +#include <dmabufinfo/dmabuf_sysfs_stats.h> #include <dmabufinfo/dmabufinfo.h> +#include <dmabufinfo/dmabuf_sysfs_stats.h> #include <meminfo/procmeminfo.h> #include <meminfo/sysmeminfo.h> #include <memtrack/memtrack.h> @@ -519,14 +522,15 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, } if (outUssSwapPssRss != NULL) { - if (env->GetArrayLength(outUssSwapPssRss) >= 1) { + int outLen = env->GetArrayLength(outUssSwapPssRss); + if (outLen >= 1) { jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0); if (outUssSwapPssRssArray != NULL) { outUssSwapPssRssArray[0] = uss; - if (env->GetArrayLength(outUssSwapPssRss) >= 2) { + if (outLen >= 2) { outUssSwapPssRssArray[1] = swapPss; } - if (env->GetArrayLength(outUssSwapPssRss) >= 3) { + if (outLen >= 3) { outUssSwapPssRssArray[2] = rss; } } @@ -535,10 +539,20 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, } if (outMemtrack != NULL) { - if (env->GetArrayLength(outMemtrack) >= 1) { + int outLen = env->GetArrayLength(outMemtrack); + if (outLen >= 1) { jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0); if (outMemtrackArray != NULL) { outMemtrackArray[0] = memtrack; + if (outLen >= 2) { + outMemtrackArray[1] = graphics_mem.graphics; + } + if (outLen >= 3) { + outMemtrackArray[2] = graphics_mem.gl; + } + if (outLen >= 4) { + outMemtrackArray[3] = graphics_mem.other; + } } env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0); } @@ -802,6 +816,16 @@ static jlong android_os_Debug_getIonHeapsSizeKb(JNIEnv* env, jobject clazz) { return heapsSizeKb; } +static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject clazz) { + jlong dmabufTotalSizeKb = -1; + uint64_t size; + + if (dmabufinfo::GetDmabufTotalExportedKb(&size)) { + dmabufTotalSizeKb = size; + } + return dmabufTotalSizeKb; +} + static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) { jlong poolsSizeKb = -1; uint64_t size; @@ -813,8 +837,44 @@ static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) { return poolsSizeKb; } -static jlong android_os_Debug_getIonMappedSizeKb(JNIEnv* env, jobject clazz) { - jlong ionPss = 0; +static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject clazz) { + jlong poolsSizeKb = -1; + uint64_t size; + + if (meminfo::ReadDmabufHeapPoolsSizeKb(&size)) { + poolsSizeKb = size; + } + + return poolsSizeKb; +} + +static jlong android_os_Debug_getGpuDmaBufUsageKb(JNIEnv* env, jobject clazz) { + std::vector<aidl::android::hardware::memtrack::DeviceInfo> gpu_device_info; + if (!memtrack_gpu_device_info(&gpu_device_info)) { + return -1; + } + + dmabufinfo::DmabufSysfsStats stats; + if (!GetDmabufSysfsStats(&stats)) { + return -1; + } + + jlong sizeKb = 0; + const auto& importer_stats = stats.importer_info(); + for (const auto& dev_info : gpu_device_info) { + const auto& importer_info = importer_stats.find(dev_info.name); + if (importer_info == importer_stats.end()) { + continue; + } + + sizeKb += importer_info->second.size; + } + + return sizeKb; +} + +static jlong android_os_Debug_getDmabufMappedSizeKb(JNIEnv* env, jobject clazz) { + jlong dmabufPss = 0; std::vector<dmabufinfo::DmaBuffer> dmabufs; std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir("/proc"), closedir); @@ -838,10 +898,10 @@ static jlong android_os_Debug_getIonMappedSizeKb(JNIEnv* env, jobject clazz) { } for (const dmabufinfo::DmaBuffer& buf : dmabufs) { - ionPss += buf.size() / 1024; + dmabufPss += buf.size() / 1024; } - return ionPss; + return dmabufPss; } static jlong android_os_Debug_getGpuTotalUsageKb(JNIEnv* env, jobject clazz) { @@ -919,10 +979,16 @@ static const JNINativeMethod gMethods[] = { (void*)android_os_Debug_getFreeZramKb }, { "getIonHeapsSizeKb", "()J", (void*)android_os_Debug_getIonHeapsSizeKb }, + { "getDmabufTotalExportedKb", "()J", + (void*)android_os_Debug_getDmabufTotalExportedKb }, + { "getGpuDmaBufUsageKb", "()J", + (void*)android_os_Debug_getGpuDmaBufUsageKb }, { "getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb }, - { "getIonMappedSizeKb", "()J", - (void*)android_os_Debug_getIonMappedSizeKb }, + { "getDmabufMappedSizeKb", "()J", + (void*)android_os_Debug_getDmabufMappedSizeKb }, + { "getDmabufHeapPoolsSizeKb", "()J", + (void*)android_os_Debug_getDmabufHeapPoolsSizeKb }, { "getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb }, { "isVmapStack", "()Z", diff --git a/core/proto/android/net/networkrequest.proto b/core/proto/android/net/networkrequest.proto index 6794c8cd8acb..0041f199b448 100644 --- a/core/proto/android/net/networkrequest.proto +++ b/core/proto/android/net/networkrequest.proto @@ -63,6 +63,9 @@ message NetworkRequestProto { // higher-scoring network will not go into the background immediately, // but will linger and go into the background after the linger timeout. TYPE_BACKGROUND_REQUEST = 5; + // Like TRACK_DEFAULT, but tracks the system default network, instead of + // the default network of the calling application. + TYPE_TRACK_SYSTEM_DEFAULT = 6; } // The type of the request. This is only used by the system and is always // NONE elsewhere. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index af6b97318103..24539b724f70 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1790,6 +1790,12 @@ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows an application to manage an automotive device's application network + preference as it relates to OEM_PAID and OEM_PRIVATE capable networks. + <p>Not for use by third-party or privileged applications. --> + <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" + android:protectionLevel="signature" /> + <!-- ======================================= --> <!-- Permissions for short range, peripheral networks --> <!-- ======================================= --> @@ -3797,6 +3803,11 @@ <permission android:name="android.permission.MOVE_PACKAGE" android:protectionLevel="signature|privileged" /> + <!-- @TestApi Allows an application to keep uninstalled packages as apks. + @hide --> + <permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" + android:protectionLevel="signature" /> + <!-- Allows an application to change whether an application component (other than its own) is enabled or not. <p>Not for use by third-party applications. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 20cb27085661..ee45249c2030 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1547,8 +1547,8 @@ take precedence over lower ones. See com.android.server.timedetector.TimeDetectorStrategy for available sources. --> <string-array name="config_autoTimeSourcesPriority"> - <item>telephony</item> <item>network</item> + <item>telephony</item> </string-array> <!-- Enables the TimeZoneRuleManager service. This is the global switch for the updateable time diff --git a/core/tests/coretests/src/android/provider/OWNERS b/core/tests/coretests/src/android/provider/OWNERS new file mode 100644 index 000000000000..581da714f326 --- /dev/null +++ b/core/tests/coretests/src/android/provider/OWNERS @@ -0,0 +1 @@ +include /core/java/android/provider/OWNERS diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java index 7917a06cb9b7..2c1bbf0d0b83 100644 --- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java @@ -232,7 +232,7 @@ public final class LooperStatsTest { assertThat(entry3.handlerClassName).isEqualTo( "com.android.internal.os.LooperStatsTest$TestHandlerSecond"); assertThat(entry3.messageName).startsWith( - "com.android.internal.os.-$$Lambda$LooperStatsTest$"); + "com.android.internal.os.LooperStatsTest-$$ExternalSyntheticLambda"); assertThat(entry3.messageCount).isEqualTo(1); assertThat(entry3.recordedMessageCount).isEqualTo(1); assertThat(entry3.exceptionCount).isEqualTo(0); diff --git a/core/tests/coretests/src/com/android/internal/widget/OWNERS b/core/tests/coretests/src/com/android/internal/widget/OWNERS new file mode 100644 index 000000000000..b40fe240d80c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/widget/OWNERS @@ -0,0 +1,3 @@ +# LockSettings related +per-file *LockPattern* = file:/services/core/java/com/android/server/locksettings/OWNERS +per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings/OWNERS diff --git a/data/etc/OWNERS b/data/etc/OWNERS index 65d3a012b129..549e074d297c 100644 --- a/data/etc/OWNERS +++ b/data/etc/OWNERS @@ -6,7 +6,6 @@ jeffv@google.com jsharkey@android.com jsharkey@google.com lorenzo@google.com -moltmann@google.com svetoslavganov@android.com svetoslavganov@google.com toddke@android.com diff --git a/graphics/java/android/graphics/pdf/OWNERS b/graphics/java/android/graphics/pdf/OWNERS index f04e2008a437..057dc0d9583c 100644 --- a/graphics/java/android/graphics/pdf/OWNERS +++ b/graphics/java/android/graphics/pdf/OWNERS @@ -5,4 +5,3 @@ djsollen@google.com sumir@google.com svetoslavganov@android.com svetoslavganov@google.com -moltmann@google.com diff --git a/tools/hiddenapi/Android.bp b/keystore/java/android/security/AppUriAuthenticationPolicy.aidl index e0eb06cbea7f..5c52c86f0426 100644 --- a/tools/hiddenapi/Android.bp +++ b/keystore/java/android/security/AppUriAuthenticationPolicy.aidl @@ -14,17 +14,6 @@ * limitations under the License. */ -python_binary_host { - name: "merge_csv", - main: "merge_csv.py", - srcs: ["merge_csv.py"], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - embedded_launcher: true - }, - }, -} +package android.security; + +parcelable AppUriAuthenticationPolicy; diff --git a/keystore/java/android/security/AppUriAuthenticationPolicy.java b/keystore/java/android/security/AppUriAuthenticationPolicy.java new file mode 100644 index 000000000000..0244ce97c0d4 --- /dev/null +++ b/keystore/java/android/security/AppUriAuthenticationPolicy.java @@ -0,0 +1,241 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * The app-URI authentication policy is set by the credential management app. This policy determines + * which alias for a private key and certificate pair should be used for authentication. + * <p> + * The authentication policy should be added as a parameter when calling + * {@link KeyChain#createManageCredentialsIntent}. + * <p> + * Example: + * <pre>{@code + * AppUriAuthenticationPolicy authenticationPolicy = new AppUriAuthenticationPolicy.Builder() + * .addAppAndUriMapping("com.test.pkg", testUri, "testAlias") + * .addAppAndUriMapping("com.test2.pkg", testUri1, "testAlias2") + * .addAppAndUriMapping("com.test2.pkg", testUri2, "testAlias2") + * .build(); + * Intent requestIntent = KeyChain.createManageCredentialsIntent(authenticationPolicy); + * }</pre> + * <p> + */ +public final class AppUriAuthenticationPolicy implements Parcelable { + + private static final String KEY_AUTHENTICATION_POLICY_APP_TO_URIS = + "authentication_policy_app_to_uris"; + private static final String KEY_AUTHENTICATION_POLICY_APP = "policy_app"; + + /** + * The mappings from an app and list of URIs to a list of aliases, which will be used for + * authentication. + * <p> + * appPackageName -> uri -> alias + */ + @NonNull + private final Map<String, UrisToAliases> mAppToUris; + + private AppUriAuthenticationPolicy(@NonNull Map<String, UrisToAliases> appToUris) { + Objects.requireNonNull(appToUris); + this.mAppToUris = appToUris; + } + + /** + * Builder class for {@link AppUriAuthenticationPolicy} objects. + */ + public static final class Builder { + private Map<String, UrisToAliases> mPackageNameToUris; + + /** + * Initialize a new Builder to construct an {@link AppUriAuthenticationPolicy}. + */ + public Builder() { + mPackageNameToUris = new HashMap<>(); + } + + /** + * Adds mappings from an app and URI to an alias, which will be used for authentication. + * <p> + * If this method is called with a package name and URI that was previously added, the + * previous alias will be overwritten. + * + * @param appPackageName The app's package name to authenticate the user to. + * @param uri The URI to authenticate the user to. + * @param alias The alias which will be used for authentication. + * + * @return the same Builder instance. + */ + @NonNull + public Builder addAppAndUriMapping(@NonNull String appPackageName, @NonNull Uri uri, + @NonNull String alias) { + Objects.requireNonNull(appPackageName); + Objects.requireNonNull(uri); + Objects.requireNonNull(alias); + UrisToAliases urisToAliases = + mPackageNameToUris.getOrDefault(appPackageName, new UrisToAliases()); + urisToAliases.addUriToAlias(uri, alias); + mPackageNameToUris.put(appPackageName, urisToAliases); + return this; + } + + /** + * Adds mappings from an app and list of URIs to a list of aliases, which will be used for + * authentication. + * <p> + * appPackageName -> uri -> alias + * + * @hide + */ + @NonNull + public Builder addAppAndUriMapping(@NonNull String appPackageName, + @NonNull UrisToAliases urisToAliases) { + Objects.requireNonNull(appPackageName); + Objects.requireNonNull(urisToAliases); + mPackageNameToUris.put(appPackageName, urisToAliases); + return this; + } + + /** + * Combines all of the attributes that have been set on the {@link Builder} + * + * @return a new {@link AppUriAuthenticationPolicy} object. + */ + @NonNull + public AppUriAuthenticationPolicy build() { + return new AppUriAuthenticationPolicy(mPackageNameToUris); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeMap(mAppToUris); + } + + @NonNull + public static final Parcelable.Creator<AppUriAuthenticationPolicy> CREATOR = + new Parcelable.Creator<AppUriAuthenticationPolicy>() { + @Override + public AppUriAuthenticationPolicy createFromParcel(Parcel in) { + Map<String, UrisToAliases> appToUris = new HashMap<>(); + in.readMap(appToUris, UrisToAliases.class.getClassLoader()); + return new AppUriAuthenticationPolicy(appToUris); + } + + @Override + public AppUriAuthenticationPolicy[] newArray(int size) { + return new AppUriAuthenticationPolicy[size]; + } + }; + + @Override + public String toString() { + return "AppUriAuthenticationPolicy{" + + "mPackageNameToUris=" + mAppToUris + + '}'; + } + + /** + * Return the authentication policy mapping, which determines which alias for a private key + * and certificate pair should be used for authentication. + * <p> + * appPackageName -> uri -> alias + */ + @NonNull + public Map<String, Map<Uri, String>> getAppAndUriMappings() { + Map<String, Map<Uri, String>> appAndUris = new HashMap<>(); + for (Map.Entry<String, UrisToAliases> entry : mAppToUris.entrySet()) { + appAndUris.put(entry.getKey(), entry.getValue().getUrisToAliases()); + } + return appAndUris; + } + + /** + * Restore a previously saved {@link AppUriAuthenticationPolicy} from XML. + * + * @hide + */ + @Nullable + public static AppUriAuthenticationPolicy readFromXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + AppUriAuthenticationPolicy.Builder builder = new AppUriAuthenticationPolicy.Builder(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (!parser.getName().equals(KEY_AUTHENTICATION_POLICY_APP_TO_URIS)) { + continue; + } + String app = parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_APP); + UrisToAliases urisToAliases = UrisToAliases.readFromXml(parser); + builder.addAppAndUriMapping(app, urisToAliases); + } + return builder.build(); + } + + /** + * Save the {@link AppUriAuthenticationPolicy} to XML. + * + * @hide + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + for (Map.Entry<String, UrisToAliases> appsToUris : mAppToUris.entrySet()) { + out.startTag(null, KEY_AUTHENTICATION_POLICY_APP_TO_URIS); + out.attribute(null, KEY_AUTHENTICATION_POLICY_APP, appsToUris.getKey()); + appsToUris.getValue().writeToXml(out); + out.endTag(null, KEY_AUTHENTICATION_POLICY_APP_TO_URIS); + } + } + + /** + * Get the set of aliases found in the policy. + * + * @hide + */ + public Set<String> getAliases() { + Set<String> aliases = new HashSet<>(); + for (UrisToAliases appsToUris : mAppToUris.values()) { + aliases.addAll(appsToUris.getUrisToAliases().values()); + } + return aliases; + } + +} diff --git a/keystore/java/android/security/CredentialManagementApp.java b/keystore/java/android/security/CredentialManagementApp.java new file mode 100644 index 000000000000..cbb23015dbe8 --- /dev/null +++ b/keystore/java/android/security/CredentialManagementApp.java @@ -0,0 +1,123 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Objects; + +/** + * The credential management app has the ability to manage the user's KeyChain credentials on + * unmanaged devices. {@link KeyChain#createManageCredentialsIntent} should be used by an app to + * request to become the credential management app. The user must approve this request before the + * app can manage the user's credentials. + * <p> + * Note: there can only be one credential management on the device. If another app requests to + * become the credential management app and the user approves, then the existing credential + * management app will no longer be able to manage credentials. + * <p> + * The requesting credential management app should include its authentication policy in the + * requesting intent. The authentication policy declares which certificates should be used for a + * given list of apps and URIs. + * + * @hide + * @see AppUriAuthenticationPolicy + */ +public class CredentialManagementApp { + + private static final String TAG = "CredentialManagementApp"; + private static final String KEY_PACKAGE_NAME = "package_name"; + + /** + * The credential management app's package name + */ + @NonNull + private final String mPackageName; + + /** + * The mappings from an app and list of URIs to a list of aliases, which will be used for + * authentication. + * <p> + * appPackageName -> uri -> alias + */ + @NonNull + private AppUriAuthenticationPolicy mAuthenticationPolicy; + + public CredentialManagementApp(@NonNull String packageName, + @NonNull AppUriAuthenticationPolicy authenticationPolicy) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(authenticationPolicy); + mPackageName = packageName; + mAuthenticationPolicy = authenticationPolicy; + } + + /** + * Returns the package name of the credential management app. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the authentication policy of the credential management app. + */ + @NonNull + public AppUriAuthenticationPolicy getAuthenticationPolicy() { + return mAuthenticationPolicy; + } + + /** + * Sets the authentication policy of the credential management app. + */ + public void setAuthenticationPolicy(@Nullable AppUriAuthenticationPolicy authenticationPolicy) { + Objects.requireNonNull(authenticationPolicy); + mAuthenticationPolicy = authenticationPolicy; + } + + /** + * Restore a previously saved {@link CredentialManagementApp} from XML. + */ + @Nullable + public static CredentialManagementApp readFromXml(@NonNull XmlPullParser parser) { + try { + String packageName = parser.getAttributeValue(null, KEY_PACKAGE_NAME); + AppUriAuthenticationPolicy policy = AppUriAuthenticationPolicy.readFromXml(parser); + return new CredentialManagementApp(packageName, policy); + } catch (XmlPullParserException | IOException e) { + Log.w(TAG, "Reading from xml failed", e); + } + return null; + } + + /** + * Save the {@link CredentialManagementApp} to XML. + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + out.attribute(null, KEY_PACKAGE_NAME, mPackageName); + if (mAuthenticationPolicy != null) { + mAuthenticationPolicy.writeToXml(out); + } + } +} diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java index 9e1fb54bedbe..ae9f866459d6 100644 --- a/keystore/java/android/security/Credentials.java +++ b/keystore/java/android/security/Credentials.java @@ -49,6 +49,8 @@ public class Credentials { public static final String INSTALL_AS_USER_ACTION = "android.credentials.INSTALL_AS_USER"; + public static final String ACTION_MANAGE_CREDENTIALS = "android.security.MANAGE_CREDENTIALS"; + /** * Key prefix for CA certificates. * diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 1ae6a631dbcb..f708298a2cbd 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -18,6 +18,8 @@ package android.security; import android.content.pm.StringParceledListSlice; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; +import android.security.AppUriAuthenticationPolicy; +import android.net.Uri; /** * Caller is required to ensure that {@link KeyStore#unlock @@ -47,6 +49,7 @@ interface IKeyChainService { in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid); boolean removeKeyPair(String alias); boolean containsKeyPair(String alias); + int[] getGrants(String alias); // APIs used by Settings boolean deleteCaCertificate(String alias); @@ -56,6 +59,12 @@ interface IKeyChainService { boolean containsCaAlias(String alias); byte[] getEncodedCaCertificate(String alias, boolean includeDeletedSystem); List<String> getCaCertificateChainAliases(String rootAlias, boolean includeDeletedSystem); + void setCredentialManagementApp(String packageName, in AppUriAuthenticationPolicy policy); + boolean hasCredentialManagementApp(); + String getCredentialManagementAppPackageName(); + AppUriAuthenticationPolicy getCredentialManagementAppPolicy(); + String getPredefinedAliasForPackageAndUri(String packageName, in Uri uri); + void removeCredentialManagementApp(); // APIs used by KeyChainActivity void setGrant(int uid, String alias, boolean value); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 6df62c0a0cba..63690d3c1567 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -15,6 +15,8 @@ */ package android.security; +import static android.security.Credentials.ACTION_MANAGE_CREDENTIALS; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -122,6 +124,11 @@ public final class KeyChain { private static final String CERT_INSTALLER_PACKAGE = "com.android.certinstaller"; /** + * Package name for Settings. + */ + private static final String SETTINGS_PACKAGE = "com.android.settings"; + + /** * Extra for use with {@link #ACTION_CHOOSER} * @hide Also used by KeyChainActivity implementation */ @@ -202,6 +209,20 @@ public final class KeyChain { public static final String EXTRA_PKCS12 = "PKCS12"; /** + * Extra used by {@link #createManageCredentialsIntent(AppUriAuthenticationPolicy)} to specify + * the authentication policy of the credential management app. + * + * <p>The authentication policy declares which alias for a private key and certificate pair + * should be used for authentication, given a list of apps and URIs. + * + * <p>The extra value should be a {@link AppUriAuthenticationPolicy}. + * + * @hide + */ + public static final String EXTRA_AUTHENTICATION_POLICY = + "android.security.extra.AUTHENTICATION_POLICY"; + + /** * Broadcast Action: Indicates the trusted storage has changed. Sent when * one of this happens: * @@ -386,6 +407,23 @@ public final class KeyChain { } /** + * Returns an {@code Intent} that should be used by an app to request to manage the user's + * credentials. This is limited to unmanaged devices. The authentication policy must be + * provided to be able to make this request successfully. + * + * @param policy The authentication policy determines which alias for a private key and + * certificate pair should be used for authentication. + */ + @NonNull + public static Intent createManageCredentialsIntent(@NonNull AppUriAuthenticationPolicy policy) { + Intent intent = new Intent(ACTION_MANAGE_CREDENTIALS); + intent.setComponent(ComponentName.createRelative(SETTINGS_PACKAGE, + ".security.RequestManageCredentials")); + intent.putExtra(EXTRA_AUTHENTICATION_POLICY, policy); + return intent; + } + + /** * Launches an {@code Activity} for the user to select the alias * for a private key and certificate pair for authentication. The * selected alias or null will be returned via the diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java index 372add9b7ecb..d188b6525579 100644 --- a/keystore/java/android/security/KeyStoreSecurityLevel.java +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -190,7 +190,7 @@ public class KeyStoreSecurityLevel { keyDescriptor.blob = wrappedKey; keyDescriptor.domain = wrappedKeyDescriptor.domain; - return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor, + return handleExceptions(() -> mSecurityLevel.importWrappedKey(keyDescriptor, wrappingKeyDescriptor, maskingKey, args.toArray(new KeyParameter[args.size()]), authenticatorSpecs)); } diff --git a/keystore/java/android/security/UrisToAliases.java b/keystore/java/android/security/UrisToAliases.java new file mode 100644 index 000000000000..65d433abe166 --- /dev/null +++ b/keystore/java/android/security/UrisToAliases.java @@ -0,0 +1,138 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The mapping from URI to alias, which determines the alias to use when the user visits a URI. + * This mapping is part of the {@link AppUriAuthenticationPolicy}, which specifies which app this + * mapping should be used for. + * + * @hide + * @see AppUriAuthenticationPolicy + */ +public final class UrisToAliases implements Parcelable { + + private static final String KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS = + "authentication_policy_uri_to_alias"; + private static final String KEY_AUTHENTICATION_POLICY_URI = "policy_uri"; + private static final String KEY_AUTHENTICATION_POLICY_ALIAS = "policy_alias"; + + /** + * The mappings from URIs to aliases, which will be used for authentication. + */ + @NonNull + private final Map<Uri, String> mUrisToAliases; + + public UrisToAliases() { + this.mUrisToAliases = new HashMap<>(); + } + + private UrisToAliases(@NonNull Map<Uri, String> urisToAliases) { + this.mUrisToAliases = urisToAliases; + } + + @NonNull + public static final Creator<UrisToAliases> CREATOR = new Creator<UrisToAliases>() { + @Override + public UrisToAliases createFromParcel(Parcel in) { + Map<Uri, String> urisToAliases = new HashMap<>(); + in.readMap(urisToAliases, String.class.getClassLoader()); + return new UrisToAliases(urisToAliases); + } + + @Override + public UrisToAliases[] newArray(int size) { + return new UrisToAliases[size]; + } + }; + + /** + * Returns the mapping from URIs to aliases. + */ + @NonNull + public Map<Uri, String> getUrisToAliases() { + return Collections.unmodifiableMap(mUrisToAliases); + } + + /** + * Adds mapping from an URI to an alias. + */ + public void addUriToAlias(@NonNull Uri uri, @NonNull String alias) { + mUrisToAliases.put(uri, alias); + } + + /** + * Restore a previously saved {@link UrisToAliases} from XML. + */ + @Nullable + public static UrisToAliases readFromXml(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + Map<Uri, String> urisToAliases = new HashMap<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (!parser.getName().equals(KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS)) { + continue; + } + Uri uri = Uri.parse(parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_URI)); + String alias = parser.getAttributeValue(null, KEY_AUTHENTICATION_POLICY_ALIAS); + urisToAliases.put(uri, alias); + } + return new UrisToAliases(urisToAliases); + } + + /** + * Save the {@link UrisToAliases} to XML. + */ + public void writeToXml(@NonNull XmlSerializer out) throws IOException { + for (Map.Entry<Uri, String> urisToAliases : mUrisToAliases.entrySet()) { + out.startTag(null, KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS); + out.attribute(null, KEY_AUTHENTICATION_POLICY_URI, urisToAliases.getKey().toString()); + out.attribute(null, KEY_AUTHENTICATION_POLICY_ALIAS, urisToAliases.getValue()); + out.endTag(null, KEY_AUTHENTICATION_POLICY_URI_TO_ALIAS); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeMap(mUrisToAliases); + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index 334b1110d651..988838b46334 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -17,6 +17,7 @@ package android.security.keystore; import android.annotation.Nullable; +import android.content.Context; import android.os.Build; import android.security.Credentials; import android.security.KeyPairGeneratorSpec; @@ -25,6 +26,8 @@ import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterCertificateChain; import android.security.keymaster.KeymasterDefs; +import android.telephony.TelephonyManager; +import android.util.ArraySet; import com.android.internal.org.bouncycastle.asn1.ASN1EncodableVector; import com.android.internal.org.bouncycastle.asn1.ASN1InputStream; @@ -477,11 +480,11 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato success = true; return keyPair; - } catch (ProviderException e) { + } catch (ProviderException | IllegalArgumentException | DeviceIdAttestationException e) { if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { throw new SecureKeyImportUnavailableException(e); } else { - throw e; + throw new ProviderException(e); } } finally { if (!success) { @@ -491,7 +494,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair) - throws ProviderException { + throws ProviderException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { KeymasterArguments args = new KeymasterArguments(); @@ -510,6 +513,60 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato Build.MODEL.getBytes(StandardCharsets.UTF_8)); } + int[] idTypes = mSpec.getAttestationIds(); + if (idTypes != null) { + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI) + || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) { + telephonyService = + (TelephonyManager) KeyStore.getApplicationContext().getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException( + "Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case AttestationUtils.ID_TYPE_SERIAL: + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8) + ); + break; + case AttestationUtils.ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8) + ); + break; + } + case AttestationUtils.ID_TYPE_MEID: { + final String meid = telephonyService.getMeid(0); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8) + ); + break; + } + case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: { + args.addBoolean(KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } + } + } + return getAttestationChain(privateKeyAlias, keyPair, args); } @@ -547,7 +604,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } - private KeymasterArguments constructKeyGenerationArguments() { + private KeymasterArguments constructKeyGenerationArguments() + throws IllegalArgumentException, DeviceIdAttestationException { KeymasterArguments args = new KeymasterArguments(); args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits); args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm); @@ -565,9 +623,9 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getKeyValidityForConsumptionEnd()); addAlgorithmSpecificParameters(args); - if (mSpec.isUniqueIdIncluded()) + if (mSpec.isUniqueIdIncluded()) { args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID); - + } return args; } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index 16bf5469296f..087151711138 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -434,14 +434,16 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static java.security.KeyStore getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException { - String providerName = PROVIDER_NAME; + final java.security.KeyStore.LoadStoreParameter loadParameter; if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) { - providerName = "AndroidKeyStoreLegacy"; + loadParameter = new android.security.keystore2.AndroidKeyStoreLoadStoreParameter( + KeyProperties.legacyUidToNamespace(uid)); + } else { + loadParameter = new AndroidKeyStoreLoadStoreParameter(uid); } - java.security.KeyStore result = - java.security.KeyStore.getInstance(providerName); + java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME); try { - result.load(new AndroidKeyStoreLoadStoreParameter(uid)); + result.load(loadParameter); } catch (NoSuchAlgorithmException | CertificateException | IOException e) { throw new KeyStoreException( "Failed to load AndroidKeyStore KeyStore for UID " + uid, e); diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index c8c1de4a5e83..f22b6041800f 100644 --- a/keystore/java/android/security/keystore/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -34,6 +34,14 @@ public abstract class ArrayUtils { return ((array != null) && (array.length > 0)) ? array.clone() : array; } + /** + * Clones an array if it is not null and has a length greater than 0. Otherwise, returns the + * array. + */ + public static int[] cloneIfNotEmpty(int[] array) { + return ((array != null) && (array.length > 0)) ? array.clone() : array; + } + public static byte[] cloneIfNotEmpty(byte[] array) { return ((array != null) && (array.length > 0)) ? array.clone() : array; } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index c2a7b2ee6323..e92eaca2b6e9 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -267,6 +267,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUserPresenceRequired; private final byte[] mAttestationChallenge; private final boolean mDevicePropertiesAttestationIncluded; + private final int[] mAttestationIds; private final boolean mUniqueIdIncluded; private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; @@ -308,6 +309,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean userPresenceRequired, byte[] attestationChallenge, boolean devicePropertiesAttestationIncluded, + int[] attestationIds, boolean uniqueIdIncluded, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, @@ -361,6 +363,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationType = userAuthenticationType; mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge); mDevicePropertiesAttestationIncluded = devicePropertiesAttestationIncluded; + mAttestationIds = attestationIds; mUniqueIdIncluded = uniqueIdIncluded; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; @@ -720,6 +723,25 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @hide + * Allows the caller to specify device IDs to be attested to in the certificate for the + * generated key pair. These values are the enums specified in + * {@link android.security.keystore.AttestationUtils} + * + * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL + * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI + * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID + * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION + * + * @return integer array representing the requested device IDs to attest. + */ + @SystemApi + @Nullable + public int[] getAttestationIds() { + return Utils.cloneIfNotNull(mAttestationIds); + } + + /** * @hide This is a system-only API * * Returns {@code true} if the attestation certificate will contain a unique ID field. @@ -834,6 +856,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUserPresenceRequired = false; private byte[] mAttestationChallenge = null; private boolean mDevicePropertiesAttestationIncluded = false; + private int[] mAttestationIds = null; private boolean mUniqueIdIncluded = false; private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; @@ -902,6 +925,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mAttestationChallenge = sourceSpec.getAttestationChallenge(); mDevicePropertiesAttestationIncluded = sourceSpec.isDevicePropertiesAttestationIncluded(); + mAttestationIds = sourceSpec.getAttestationIds(); mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded(); mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody(); mInvalidatedByBiometricEnrollment = sourceSpec.isInvalidatedByBiometricEnrollment(); @@ -1473,6 +1497,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * @hide + * Sets which IDs to attest in the attestation certificate for the key. The acceptable + * values in this integer array are the enums specified in + * {@link android.security.keystore.AttestationUtils} + * + * @param attestationIds the array of ID types to attest to in the certificate. + * + * @see android.security.keystore.AttestationUtils#ID_TYPE_SERIAL + * @see android.security.keystore.AttestationUtils#ID_TYPE_IMEI + * @see android.security.keystore.AttestationUtils#ID_TYPE_MEID + * @see android.security.keystore.AttestationUtils#USE_INDIVIDUAL_ATTESTATION + */ + @SystemApi + @NonNull + public Builder setAttestationIds(@NonNull int[] attestationIds) { + mAttestationIds = attestationIds; + return this; + } + + /** * @hide Only system apps can use this method. * * Sets whether to include a temporary unique ID field in the attestation certificate. @@ -1638,6 +1682,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserPresenceRequired, mAttestationChallenge, mDevicePropertiesAttestationIncluded, + mAttestationIds, mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 8163472abdfb..1f2f853b67a8 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -101,6 +101,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isUserPresenceRequired()); out.writeByteArray(mSpec.getAttestationChallenge()); out.writeBoolean(mSpec.isDevicePropertiesAttestationIncluded()); + out.writeIntArray(mSpec.getAttestationIds()); out.writeBoolean(mSpec.isUniqueIdIncluded()); out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody()); out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment()); @@ -160,6 +161,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean userPresenceRequired = in.readBoolean(); final byte[] attestationChallenge = in.createByteArray(); final boolean devicePropertiesAttestationIncluded = in.readBoolean(); + final int[] attestationIds = in.createIntArray(); final boolean uniqueIdIncluded = in.readBoolean(); final boolean userAuthenticationValidWhileOnBody = in.readBoolean(); final boolean invalidatedByBiometricEnrollment = in.readBoolean(); @@ -195,6 +197,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { userPresenceRequired, attestationChallenge, devicePropertiesAttestationIncluded, + attestationIds, uniqueIdIncluded, userAuthenticationValidWhileOnBody, invalidatedByBiometricEnrollment, diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java index 5722c7b53ef4..e58b1ccb5370 100644 --- a/keystore/java/android/security/keystore/Utils.java +++ b/keystore/java/android/security/keystore/Utils.java @@ -33,4 +33,8 @@ abstract class Utils { static byte[] cloneIfNotNull(byte[] value) { return (value != null) ? value.clone() : null; } + + static int[] cloneIfNotNull(int[] value) { + return (value != null) ? value.clone() : null; + } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java index 8475ad9fd57b..0f777495a3fe 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java @@ -164,6 +164,9 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC List<KeyParameter> parameters = new ArrayList<>(); parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_SIGN + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC )); parameters.add(KeyStore2ParameterUtils.makeEnum( diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java index 32650aeda1b1..5619585d9c3c 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java @@ -21,7 +21,6 @@ import android.security.KeyStoreSecurityLevel; import android.system.keystore2.Authorization; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; -import android.util.Log; import java.security.Key; @@ -127,15 +126,6 @@ public class AndroidKeyStoreKey implements Key { return false; } - // If the key ids are equal and the class matches all the other fields cannot differ - // unless we have a bug. - if (!mAlgorithm.equals(other.mAlgorithm) - || !mAuthorizations.equals(other.mAuthorizations) - || !mDescriptor.equals(other.mDescriptor)) { - Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata" - + "differs."); - return false; - } return true; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 70e30d2de5a1..4d27c3454a84 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -18,16 +18,20 @@ package android.security.keystore2; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.SecurityLevel; import android.os.Build; import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore; import android.security.KeyStore2; import android.security.KeyStoreException; import android.security.KeyStoreSecurityLevel; import android.security.keymaster.KeymasterArguments; import android.security.keymaster.KeymasterDefs; import android.security.keystore.ArrayUtils; +import android.security.keystore.AttestationUtils; +import android.security.keystore.DeviceIdAttestationException; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeymasterUtils; @@ -38,6 +42,8 @@ import android.system.keystore2.IKeystoreSecurityLevel; import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyMetadata; import android.system.keystore2.ResponseCode; +import android.telephony.TelephonyManager; +import android.util.ArraySet; import android.util.Log; import libcore.util.EmptyArray; @@ -478,7 +484,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } throw p; } - } catch (UnrecoverableKeyException e) { + } catch (UnrecoverableKeyException | IllegalArgumentException + | DeviceIdAttestationException e) { throw new ProviderException( "Failed to construct key object from newly generated key pair.", e); } finally { @@ -496,7 +503,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } private void addAttestationParameters(@NonNull List<KeyParameter> params) - throws ProviderException { + throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); if (challenge != null) { @@ -526,15 +533,69 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato Build.MODEL.getBytes(StandardCharsets.UTF_8) )); } - } else { - if (mSpec.isDevicePropertiesAttestationIncluded()) { - throw new ProviderException("An attestation challenge must be provided when " - + "requesting device properties attestation."); + + int[] idTypes = mSpec.getAttestationIds(); + if (idTypes == null) { + return; + } + final Set<Integer> idTypesSet = new ArraySet<>(idTypes.length); + for (int idType : idTypes) { + idTypesSet.add(idType); + } + TelephonyManager telephonyService = null; + if (idTypesSet.contains(AttestationUtils.ID_TYPE_IMEI) + || idTypesSet.contains(AttestationUtils.ID_TYPE_MEID)) { + telephonyService = + (TelephonyManager) KeyStore.getApplicationContext().getSystemService( + Context.TELEPHONY_SERVICE); + if (telephonyService == null) { + throw new DeviceIdAttestationException("Unable to access telephony service"); + } + } + for (final Integer idType : idTypesSet) { + switch (idType) { + case AttestationUtils.ID_TYPE_SERIAL: + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_SERIAL, + Build.getSerial().getBytes(StandardCharsets.UTF_8) + )); + break; + case AttestationUtils.ID_TYPE_IMEI: { + final String imei = telephonyService.getImei(0); + if (imei == null) { + throw new DeviceIdAttestationException("Unable to retrieve IMEI"); + } + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI, + imei.getBytes(StandardCharsets.UTF_8) + )); + break; + } + case AttestationUtils.ID_TYPE_MEID: { + final String meid = telephonyService.getMeid(0); + if (meid == null) { + throw new DeviceIdAttestationException("Unable to retrieve MEID"); + } + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MEID, + meid.getBytes(StandardCharsets.UTF_8) + )); + break; + } + case AttestationUtils.USE_INDIVIDUAL_ATTESTATION: { + params.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION)); + break; + } + default: + throw new IllegalArgumentException("Unknown device ID type " + idType); + } } } } - private Collection<KeyParameter> constructKeyGenerationArguments() { + private Collection<KeyParameter> constructKeyGenerationArguments() + throws DeviceIdAttestationException, IllegalArgumentException { List<KeyParameter> params = new ArrayList<>(); params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits)); params.add(KeyStore2ParameterUtils.makeEnum( diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 8c8acc418a0e..39607aeb3852 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -866,7 +866,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { try { response = mKeyStore.getKeyEntry(wrappingkey); } catch (android.security.KeyStoreException e) { - throw new KeyStoreException("Failed to load wrapping key.", e); + throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " + + e.getErrorCode(), e); } KeyDescriptor wrappedKey = makeKeyDescriptor(alias); diff --git a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java index 9b56b23cc879..f4b46e9f11ed 100644 --- a/packages/Connectivity/framework/src/android/net/CaptivePortalData.java +++ b/packages/Connectivity/framework/src/android/net/CaptivePortalData.java @@ -16,12 +16,15 @@ package android.net; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -40,10 +43,29 @@ public final class CaptivePortalData implements Parcelable { private final long mExpiryTimeMillis; private final boolean mCaptive; private final String mVenueFriendlyName; + private final int mVenueInfoUrlSource; + private final int mTermsAndConditionsSource; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"CAPTIVE_PORTAL_DATA_SOURCE_"}, value = { + CAPTIVE_PORTAL_DATA_SOURCE_OTHER, + CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT}) + public @interface CaptivePortalDataSource {} + + /** + * Source of information: Other (default) + */ + public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; + + /** + * Source of information: Wi-Fi Passpoint + */ + public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl, boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive, - String venueFriendlyName) { + String venueFriendlyName, int venueInfoUrlSource, int termsAndConditionsSource) { mRefreshTimeMillis = refreshTimeMillis; mUserPortalUrl = userPortalUrl; mVenueInfoUrl = venueInfoUrl; @@ -52,11 +74,14 @@ public final class CaptivePortalData implements Parcelable { mExpiryTimeMillis = expiryTimeMillis; mCaptive = captive; mVenueFriendlyName = venueFriendlyName; + mVenueInfoUrlSource = venueInfoUrlSource; + mTermsAndConditionsSource = termsAndConditionsSource; } private CaptivePortalData(Parcel p) { this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(), - p.readLong(), p.readLong(), p.readBoolean(), p.readString()); + p.readLong(), p.readLong(), p.readBoolean(), p.readString(), p.readInt(), + p.readInt()); } @Override @@ -74,6 +99,8 @@ public final class CaptivePortalData implements Parcelable { dest.writeLong(mExpiryTimeMillis); dest.writeBoolean(mCaptive); dest.writeString(mVenueFriendlyName); + dest.writeInt(mVenueInfoUrlSource); + dest.writeInt(mTermsAndConditionsSource); } /** @@ -88,6 +115,9 @@ public final class CaptivePortalData implements Parcelable { private long mExpiryTime = -1; private boolean mCaptive; private String mVenueFriendlyName; + private @CaptivePortalDataSource int mVenueInfoUrlSource = CAPTIVE_PORTAL_DATA_SOURCE_OTHER; + private @CaptivePortalDataSource int mUserPortalUrlSource = + CAPTIVE_PORTAL_DATA_SOURCE_OTHER; /** * Create an empty builder. @@ -100,8 +130,8 @@ public final class CaptivePortalData implements Parcelable { public Builder(@Nullable CaptivePortalData data) { if (data == null) return; setRefreshTime(data.mRefreshTimeMillis) - .setUserPortalUrl(data.mUserPortalUrl) - .setVenueInfoUrl(data.mVenueInfoUrl) + .setUserPortalUrl(data.mUserPortalUrl, data.mTermsAndConditionsSource) + .setVenueInfoUrl(data.mVenueInfoUrl, data.mVenueInfoUrlSource) .setSessionExtendable(data.mIsSessionExtendable) .setBytesRemaining(data.mByteLimit) .setExpiryTime(data.mExpiryTimeMillis) @@ -123,7 +153,18 @@ public final class CaptivePortalData implements Parcelable { */ @NonNull public Builder setUserPortalUrl(@Nullable Uri userPortalUrl) { + return setUserPortalUrl(userPortalUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER); + } + + /** + * Set the URL to be used for users to login to the portal, if captive, and the source of + * the data, see {@link CaptivePortalDataSource} + */ + @NonNull + public Builder setUserPortalUrl(@Nullable Uri userPortalUrl, + @CaptivePortalDataSource int source) { mUserPortalUrl = userPortalUrl; + mUserPortalUrlSource = source; return this; } @@ -132,7 +173,18 @@ public final class CaptivePortalData implements Parcelable { */ @NonNull public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl) { + return setVenueInfoUrl(venueInfoUrl, CAPTIVE_PORTAL_DATA_SOURCE_OTHER); + } + + /** + * Set the URL that can be used by users to view information about the network venue, and + * the source of the data, see {@link CaptivePortalDataSource} + */ + @NonNull + public Builder setVenueInfoUrl(@Nullable Uri venueInfoUrl, + @CaptivePortalDataSource int source) { mVenueInfoUrl = venueInfoUrl; + mVenueInfoUrlSource = source; return this; } @@ -188,7 +240,8 @@ public final class CaptivePortalData implements Parcelable { public CaptivePortalData build() { return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl, mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive, - mVenueFriendlyName); + mVenueFriendlyName, mVenueInfoUrlSource, + mUserPortalUrlSource); } } @@ -249,6 +302,22 @@ public final class CaptivePortalData implements Parcelable { } /** + * Get the information source of the Venue URL + * @return The source that the Venue URL was obtained from + */ + public @CaptivePortalDataSource int getVenueInfoUrlSource() { + return mVenueInfoUrlSource; + } + + /** + * Get the information source of the user portal URL + * @return The source that the user portal URL was obtained from + */ + public @CaptivePortalDataSource int getUserPortalUrlSource() { + return mTermsAndConditionsSource; + } + + /** * Get the venue friendly name */ @Nullable @@ -272,7 +341,8 @@ public final class CaptivePortalData implements Parcelable { @Override public int hashCode() { return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl, - mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName); + mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName, + mVenueInfoUrlSource, mTermsAndConditionsSource); } @Override @@ -286,7 +356,9 @@ public final class CaptivePortalData implements Parcelable { && mByteLimit == other.mByteLimit && mExpiryTimeMillis == other.mExpiryTimeMillis && mCaptive == other.mCaptive - && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName); + && Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName) + && mVenueInfoUrlSource == other.mVenueInfoUrlSource + && mTermsAndConditionsSource == other.mTermsAndConditionsSource; } @Override @@ -300,6 +372,8 @@ public final class CaptivePortalData implements Parcelable { + ", expiryTime: " + mExpiryTimeMillis + ", captive: " + mCaptive + ", venueFriendlyName: " + mVenueFriendlyName + + ", venueInfoUrlSource: " + mVenueInfoUrlSource + + ", termsAndConditionsSource: " + mTermsAndConditionsSource + "}"; } } diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java index 9afa5d1311c5..92a792b78410 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -49,17 +49,6 @@ public final class ConnectivityFrameworkInitializer { } ); - // TODO: move outside of the connectivity JAR - SystemServiceRegistry.registerContextAwareService( - Context.VPN_MANAGEMENT_SERVICE, - VpnManager.class, - (context) -> { - final ConnectivityManager cm = context.getSystemService( - ConnectivityManager.class); - return cm.createVpnManager(); - } - ); - SystemServiceRegistry.registerContextAwareService( Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, ConnectivityDiagnosticsManager.class, diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index d04a5bee5105..4213f8af95fc 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -21,6 +21,7 @@ import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; import android.annotation.CallbackExecutor; @@ -823,6 +824,7 @@ public class ConnectivityManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; + /** * A kludge to facilitate static access where a Context pointer isn't available, like in the * case of the static set/getProcessDefaultNetwork methods and from the Network class. @@ -1068,106 +1070,55 @@ public class ConnectivityManager { } /** - * Checks if a VPN app supports always-on mode. - * - * In order to support the always-on feature, an app has to - * <ul> - * <li>target {@link VERSION_CODES#N API 24} or above, and - * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON} - * meta-data field. - * </ul> - * - * @param userId The identifier of the user for whom the VPN app is installed. - * @param vpnPackage The canonical package name of the VPN app. - * @return {@code true} if and only if the VPN app exists and supports always-on mode. + * Calls VpnManager#isAlwaysOnVpnPackageSupportedForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ + @Deprecated public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) { - try { - return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().isAlwaysOnVpnPackageSupportedForUser(userId, vpnPackage); } /** - * Configures an always-on VPN connection through a specific application. - * This connection is automatically granted and persisted after a reboot. - * - * <p>The designated package should declare a {@link VpnService} in its - * manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE}, - * otherwise the call will fail. - * - * @param userId The identifier of the user to set an always-on VPN for. - * @param vpnPackage The package name for an installed VPN app on the device, or {@code null} - * to remove an existing always-on VPN configuration. - * @param lockdownEnabled {@code true} to disallow networking when the VPN is not connected or - * {@code false} otherwise. - * @param lockdownAllowlist The list of packages that are allowed to access network directly - * when VPN is in lockdown mode but is not running. Non-existent packages are ignored so - * this method must be called when a package that should be allowed is installed or - * uninstalled. - * @return {@code true} if the package is set as always-on VPN controller; - * {@code false} otherwise. + * Calls VpnManager#setAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public boolean setAlwaysOnVpnPackageForUser(int userId, @Nullable String vpnPackage, boolean lockdownEnabled, @Nullable List<String> lockdownAllowlist) { - try { - return mService.setAlwaysOnVpnPackage( - userId, vpnPackage, lockdownEnabled, lockdownAllowlist); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().setAlwaysOnVpnPackageForUser(userId, vpnPackage, lockdownEnabled, + lockdownAllowlist); } - /** - * Returns the package name of the currently set always-on VPN application. - * If there is no always-on VPN set, or the VPN is provided by the system instead - * of by an app, {@code null} will be returned. - * - * @return Package name of VPN controller responsible for always-on VPN, - * or {@code null} if none is set. + /** + * Calls VpnManager#getAlwaysOnVpnPackageForUser. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide */ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + @Deprecated public String getAlwaysOnVpnPackageForUser(int userId) { - try { - return mService.getAlwaysOnVpnPackage(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().getAlwaysOnVpnPackageForUser(userId); } /** - * @return whether always-on VPN is in lockdown mode. - * + * Calls VpnManager#isVpnLockdownEnabled. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) + */ + @Deprecated public boolean isVpnLockdownEnabled(int userId) { - try { - return mService.isVpnLockdownEnabled(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + return getVpnManager().isVpnLockdownEnabled(userId); } /** - * @return the list of packages that are allowed to access network when always-on VPN is in - * lockdown mode but not connected. Returns {@code null} when VPN lockdown is not active. - * + * Calls VpnManager#getVpnLockdownAllowlist. + * @deprecated TODO: remove when callers have migrated to VpnManager. * @hide - **/ - @RequiresPermission(android.Manifest.permission.CONTROL_ALWAYS_ON_VPN) - public List<String> getVpnLockdownWhitelist(int userId) { - try { - return mService.getVpnLockdownWhitelist(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + */ + @Deprecated + public List<String> getVpnLockdownAllowlist(int userId) { + return getVpnManager().getVpnLockdownAllowlist(userId); } /** @@ -1220,6 +1171,45 @@ public class ConnectivityManager { } /** + * Informs ConnectivityService of whether the legacy lockdown VPN, as implemented by + * LockdownVpnTracker, is in use. This is deprecated for new devices starting from Android 12 + * but is still supported for backwards compatibility. + * <p> + * This type of VPN is assumed always to use the system default network, and must always declare + * exactly one underlying network, which is the network that was the default when the VPN + * connected. + * <p> + * Calling this method with {@code true} enables legacy behaviour, specifically: + * <ul> + * <li>Any VPN that applies to userId 0 behaves specially with respect to deprecated + * {@link #CONNECTIVITY_ACTION} broadcasts. Any such broadcasts will have the state in the + * {@link #EXTRA_NETWORK_INFO} replaced by state of the VPN network. Also, any time the VPN + * connects, a {@link #CONNECTIVITY_ACTION} broadcast will be sent for the network + * underlying the VPN.</li> + * <li>Deprecated APIs that return {@link NetworkInfo} objects will have their state + * similarly replaced by the VPN network state.</li> + * <li>Information on current network interfaces passed to NetworkStatsService will not + * include any VPN interfaces.</li> + * </ul> + * + * @param enabled whether legacy lockdown VPN is enabled or disabled + * + * TODO: @SystemApi(client = MODULE_LIBRARIES) + * + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void setLegacyLockdownVpnEnabled(boolean enabled) { + try { + mService.setLegacyLockdownVpnEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns details about the currently active default data network * for a given uid. This is for internal use only to avoid spying * other apps. @@ -3179,20 +3169,13 @@ public class ConnectivityManager { } /** - * If the LockdownVpn mechanism is enabled, updates the vpn - * with a reload of its profile. - * - * @return a boolean with {@code} indicating success - * - * <p>This method can only be called by the system UID - * {@hide} + * Calls VpnManager#updateLockdownVpn. + * @deprecated TODO: remove when callers have migrated to VpnManager. + * @hide */ + @Deprecated public boolean updateLockdownVpn() { - try { - return mService.updateLockdownVpn(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return getVpnManager().updateLockdownVpn(); } /** @@ -3721,7 +3704,8 @@ public class ConnectivityManager { printStackTrace(); checkCallbackNotNull(callback); Preconditions.checkArgument( - reqType == TRACK_DEFAULT || need != null, "null NetworkCapabilities"); + reqType == TRACK_DEFAULT || reqType == TRACK_SYSTEM_DEFAULT || need != null, + "null NetworkCapabilities"); final NetworkRequest request; final String callingPackageName = mContext.getOpPackageName(); try { @@ -4192,8 +4176,9 @@ public class ConnectivityManager { } /** - * Registers to receive notifications about changes in the system default network. The callbacks - * will continue to be called until either the application exits or + * Registers to receive notifications about changes in the application's default network. This + * may be a physical network or a virtual network, such as a VPN that applies to the + * application. The callbacks will continue to be called until either the application exits or * {@link #unregisterNetworkCallback(NetworkCallback)} is called. * * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the @@ -4206,7 +4191,7 @@ public class ConnectivityManager { * {@link #unregisterNetworkCallback(NetworkCallback)}. * * @param networkCallback The {@link NetworkCallback} that the system will call as the - * system default network changes. + * application's default network changes. * The callback is invoked on the default internal Handler. * @throws RuntimeException if the app already has too many callbacks registered. */ @@ -4216,8 +4201,9 @@ public class ConnectivityManager { } /** - * Registers to receive notifications about changes in the system default network. The callbacks - * will continue to be called until either the application exits or + * Registers to receive notifications about changes in the application's default network. This + * may be a physical network or a virtual network, such as a VPN that applies to the + * application. The callbacks will continue to be called until either the application exits or * {@link #unregisterNetworkCallback(NetworkCallback)} is called. * * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the @@ -4230,26 +4216,60 @@ public class ConnectivityManager { * {@link #unregisterNetworkCallback(NetworkCallback)}. * * @param networkCallback The {@link NetworkCallback} that the system will call as the - * system default network changes. + * application's default network changes. * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. * @throws RuntimeException if the app already has too many callbacks registered. */ @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, @NonNull Handler handler) { - // This works because if the NetworkCapabilities are null, - // ConnectivityService takes them from the default request. - // - // Since the capabilities are exactly the same as the default request's - // capabilities, this request is guaranteed, at all times, to be - // satisfied by the same network, if any, that satisfies the default - // request, i.e., the system default network. CallbackHandler cbHandler = new CallbackHandler(handler); sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, TRACK_DEFAULT, TYPE_NONE, cbHandler); } /** + * Registers to receive notifications about changes in the system default network. The callbacks + * will continue to be called until either the application exits or + * {@link #unregisterNetworkCallback(NetworkCallback)} is called. + * + * This method should not be used to determine networking state seen by applications, because in + * many cases, most or even all application traffic may not use the default network directly, + * and traffic from different applications may go on different networks by default. As an + * example, if a VPN is connected, traffic from all applications might be sent through the VPN + * and not onto the system default network. Applications or system components desiring to do + * determine network state as seen by applications should use other methods such as + * {@link #registerDefaultNetworkCallback(NetworkCallback, Handler)}. + * + * <p>To avoid performance issues due to apps leaking callbacks, the system will limit the + * number of outstanding requests to 100 per app (identified by their UID), shared with + * all variants of this method, of {@link #requestNetwork} as well as + * {@link ConnectivityDiagnosticsManager#registerConnectivityDiagnosticsCallback}. + * Requesting a network with this method will count toward this limit. If this limit is + * exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, + * make sure to unregister the callbacks with + * {@link #unregisterNetworkCallback(NetworkCallback)}. + * + * @param networkCallback The {@link NetworkCallback} that the system will call as the + * system default network changes. + * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @SuppressLint({"ExecutorRegistration", "PairedRegistration"}) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + public void registerSystemDefaultNetworkCallback(@NonNull NetworkCallback networkCallback, + @NonNull Handler handler) { + CallbackHandler cbHandler = new CallbackHandler(handler); + sendRequestForNetwork(null /* NetworkCapabilities need */, networkCallback, 0, + TRACK_SYSTEM_DEFAULT, TYPE_NONE, cbHandler); + } + + /** * Requests bandwidth update for a given {@link Network} and returns whether the update request * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying * network connection for updated bandwidth information. The caller will be notified via @@ -4519,6 +4539,8 @@ public class ConnectivityManager { try { mService.factoryReset(); mTetheringManager.stopAllTethering(); + // TODO: Migrate callers to VpnManager#factoryReset. + getVpnManager().factoryReset(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -4812,9 +4834,13 @@ public class ConnectivityManager { return new TestNetworkManager(ITestNetworkManager.Stub.asInterface(tnBinder)); } - /** @hide */ - public VpnManager createVpnManager() { - return new VpnManager(mContext, mService); + /** + * Temporary hack to shim calls from ConnectivityManager to VpnManager. We cannot store a + * private final mVpnManager because ConnectivityManager is initialized before VpnManager. + * @hide TODO: remove. + */ + public VpnManager getVpnManager() { + return mContext.getSystemService(VpnManager.class); } /** @hide */ @@ -4848,15 +4874,6 @@ public class ConnectivityManager { } } - private void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { - try { - mService.setOemNetworkPreference(preference); - } catch (RemoteException e) { - Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); - throw e.rethrowFromSystemServer(); - } - } - @NonNull private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>(); @@ -5058,4 +5075,60 @@ public class ConnectivityManager { sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST, TYPE_NONE, handler == null ? getDefaultHandler() : new CallbackHandler(handler)); } + + /** + * Listener for {@link #setOemNetworkPreference(OemNetworkPreferences, Executor, + * OnSetOemNetworkPreferenceListener)}. + * @hide + */ + @SystemApi + public interface OnSetOemNetworkPreferenceListener { + /** + * Called when setOemNetworkPreference() successfully completes. + */ + void onComplete(); + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param executor the executor on which listener will be invoked. + * @param listener {@link OnSetOemNetworkPreferenceListener} optional listener used to + * communicate completion of setOemNetworkPreference(). This will only be + * called once upon successful completion of setOemNetworkPreference(). + * @throws IllegalArgumentException if {@code preference} contains invalid preference values. + * @throws SecurityException if missing the appropriate permissions. + * @throws UnsupportedOperationException if called on a non-automotive device. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) + public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference, + @Nullable @CallbackExecutor final Executor executor, + @Nullable final OnSetOemNetworkPreferenceListener listener) { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (null != listener) { + Objects.requireNonNull(executor, "Executor must be non-null"); + } + final IOnSetOemNetworkPreferenceListener listenerInternal = listener == null ? null : + new IOnSetOemNetworkPreferenceListener.Stub() { + @Override + public void onComplete() { + executor.execute(listener::onComplete); + } + }; + + try { + mService.setOemNetworkPreference(preference, listenerInternal); + } catch (RemoteException e) { + Log.e(TAG, "setOemNetworkPreference() failed for preference: " + preference.toString()); + throw e.rethrowFromSystemServer(); + } + } } diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index f909d1362550..6391802f3330 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; @@ -42,9 +43,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import com.android.connectivity.aidl.INetworkAgent; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; /** * Interface that answers queries about, and allows changing, the @@ -122,35 +120,8 @@ interface IConnectivityManager ProxyInfo getProxyForNetwork(in Network nework); - boolean prepareVpn(String oldPackage, String newPackage, int userId); - - 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(maxTargetSdk = 30, trackingBug = 170729553) - void startLegacyVpn(in VpnProfile profile); - - LegacyVpnInfo getLegacyVpnInfo(int userId); - - boolean updateLockdownVpn(); - boolean isAlwaysOnVpnPackageSupported(int userId, String packageName); - boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown, - in List<String> lockdownWhitelist); - String getAlwaysOnVpnPackage(int userId); - boolean isVpnLockdownEnabled(int userId); - List<String> getVpnLockdownWhitelist(int userId); void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges); + void setLegacyLockdownVpnEnabled(boolean enabled); void setProvisioningNotificationVisible(boolean visible, int networkType, in String action); @@ -199,10 +170,6 @@ interface IConnectivityManager int getRestoreDefaultNetworkDelay(int networkType); - boolean addVpnAddress(String address, int prefixLength); - boolean removeVpnAddress(String address, int prefixLength); - boolean setUnderlyingNetworksForVpn(in Network[] networks); - void factoryReset(); void startNattKeepalive(in Network network, int intervalSeconds, @@ -222,8 +189,6 @@ interface IConnectivityManager byte[] getNetworkWatchlistConfigHash(); int getConnectionOwnerUid(in ConnectionInfo connectionInfo); - boolean isCallerCurrentAlwaysOnVpnApp(); - boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, in NetworkRequest request, String callingPackageName); @@ -245,5 +210,6 @@ interface IConnectivityManager void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); void unregisterQosCallback(in IQosCallback callback); - void setOemNetworkPreference(in OemNetworkPreferences preference); + void setOemNetworkPreference(in OemNetworkPreferences preference, + in IOnSetOemNetworkPreferenceListener listener); } diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 55b2c3c9e11f..26d14cbfaa95 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -762,12 +762,14 @@ public final class NetworkCapabilities implements Parcelable { final int originalSignalStrength = mSignalStrength; final int originalOwnerUid = getOwnerUid(); final int[] originalAdministratorUids = getAdministratorUids(); + final TransportInfo originalTransportInfo = getTransportInfo(); clearAll(); mTransportTypes = (originalTransportTypes & TEST_NETWORKS_ALLOWED_TRANSPORTS) | (1 << TRANSPORT_TEST); mNetworkCapabilities = originalCapabilities & TEST_NETWORKS_ALLOWED_CAPABILITIES; mNetworkSpecifier = originalSpecifier; mSignalStrength = originalSignalStrength; + mTransportInfo = originalTransportInfo; // Only retain the owner and administrator UIDs if they match the app registering the remote // caller that registered the network. @@ -2083,9 +2085,10 @@ public final class NetworkCapabilities implements Parcelable { /** * Check if private dns is broken. * - * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken. + * @return {@code true} if private DNS is broken on this network. * @hide */ + @SystemApi public boolean isPrivateDnsBroken() { return mPrivateDnsBroken; } @@ -2328,6 +2331,17 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Completely clears the contents of this object, removing even the capabilities that are + * set by default when the object is constructed. + * @return this builder + */ + @NonNull + public Builder clearAll() { + mCaps.clearAll(); + return this; + } + + /** * Sets the owner UID. * * The default value is {@link Process#INVALID_UID}. Pass this value to reset. diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index 6540397d6200..4e3085f4704d 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -16,22 +16,6 @@ package android.net; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -46,8 +30,6 @@ import android.os.Process; import android.text.TextUtils; import android.util.proto.ProtoOutputStream; -import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.Set; @@ -104,17 +86,14 @@ public class NetworkRequest implements Parcelable { * callbacks about the single, highest scoring current network * (if any) that matches the specified NetworkCapabilities, or * - * - TRACK_DEFAULT, a hybrid of the two designed such that the - * framework will issue callbacks for the single, highest scoring - * current network (if any) that matches the capabilities of the - * default Internet request (mDefaultRequest), but which cannot cause - * the framework to either create or retain the existence of any - * specific network. Note that from the point of view of the request - * matching code, TRACK_DEFAULT is identical to REQUEST: its special - * behaviour is not due to different semantics, but to the fact that - * the system will only ever create a TRACK_DEFAULT with capabilities - * that are identical to the default request's capabilities, thus - * causing it to share fate in every way with the default request. + * - TRACK_DEFAULT, which causes the framework to issue callbacks for + * the single, highest scoring current network (if any) that will + * be chosen for an app, but which cannot cause the framework to + * either create or retain the existence of any specific network. + * + * - TRACK_SYSTEM_DEFAULT, which causes the framework to send callbacks + * for the network (if any) that satisfies the default Internet + * request. * * - BACKGROUND_REQUEST, like REQUEST but does not cause any networks * to retain the NET_CAPABILITY_FOREGROUND capability. A network with @@ -137,6 +116,7 @@ public class NetworkRequest implements Parcelable { TRACK_DEFAULT, REQUEST, BACKGROUND_REQUEST, + TRACK_SYSTEM_DEFAULT, }; /** @@ -174,30 +154,8 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { - /** - * Capabilities that are currently compatible with VCN networks. - */ - private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList( - NET_CAPABILITY_CAPTIVE_PORTAL, - NET_CAPABILITY_DUN, - NET_CAPABILITY_FOREGROUND, - NET_CAPABILITY_INTERNET, - NET_CAPABILITY_NOT_CONGESTED, - NET_CAPABILITY_NOT_METERED, - NET_CAPABILITY_NOT_RESTRICTED, - NET_CAPABILITY_NOT_ROAMING, - NET_CAPABILITY_NOT_SUSPENDED, - NET_CAPABILITY_NOT_VPN, - NET_CAPABILITY_PARTIAL_CONNECTIVITY, - NET_CAPABILITY_TEMPORARILY_NOT_METERED, - NET_CAPABILITY_TRUSTED, - NET_CAPABILITY_VALIDATED); - private final NetworkCapabilities mNetworkCapabilities; - // A boolean that represents the user modified NOT_VCN_MANAGED capability. - private boolean mModifiedNotVcnManaged = false; - /** * Default constructor for Builder. */ @@ -219,7 +177,6 @@ public class NetworkRequest implements Parcelable { // maybeMarkCapabilitiesRestricted() doesn't add back. final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); nc.maybeMarkCapabilitiesRestricted(); - deduceNotVcnManagedCapability(nc); return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); } @@ -236,9 +193,6 @@ public class NetworkRequest implements Parcelable { */ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); - if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { - mModifiedNotVcnManaged = true; - } return this; } @@ -250,9 +204,6 @@ public class NetworkRequest implements Parcelable { */ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); - if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { - mModifiedNotVcnManaged = true; - } return this; } @@ -310,9 +261,6 @@ public class NetworkRequest implements Parcelable { @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); - // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities - // should not be add back later. - mModifiedNotVcnManaged = true; return this; } @@ -432,25 +380,6 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSignalStrength(signalStrength); return this; } - - /** - * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities - * and user intention, which includes: - * 1. For the requests that don't have anything besides - * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to - * allow the callers automatically utilize VCN networks if available. - * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED, - * do not alter them to allow user fire request that suits their need. - * - * @hide - */ - private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) { - if (mModifiedNotVcnManaged) return; - for (final int cap : nc.getCapabilities()) { - if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return; - } - nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); - } } // implement the Parcelable interface @@ -601,6 +530,8 @@ public class NetworkRequest implements Parcelable { return NetworkRequestProto.TYPE_REQUEST; case BACKGROUND_REQUEST: return NetworkRequestProto.TYPE_BACKGROUND_REQUEST; + case TRACK_SYSTEM_DEFAULT: + return NetworkRequestProto.TYPE_TRACK_SYSTEM_DEFAULT; default: return NetworkRequestProto.TYPE_UNKNOWN; } diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java new file mode 100644 index 000000000000..0242ba08742c --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.SparseArray; + +import com.android.internal.util.MessageUtils; + +import java.util.Objects; + +/** + * Container for VPN-specific transport information. + * + * @see android.net.TransportInfo + * @see NetworkCapabilities#getTransportInfo() + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +public final class VpnTransportInfo implements TransportInfo, Parcelable { + private static final SparseArray<String> sTypeToString = + MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"}); + + /** Type of this VPN. */ + @VpnManager.VpnType public final int type; + + public VpnTransportInfo(@VpnManager.VpnType int type) { + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VpnTransportInfo)) return false; + + VpnTransportInfo that = (VpnTransportInfo) o; + return this.type == that.type; + } + + @Override + public int hashCode() { + return Objects.hash(type); + } + + @Override + public String toString() { + final String typeString = sTypeToString.get(type, "VPN_TYPE_???"); + return String.format("VpnTransportInfo{%s}", typeString); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(type); + } + + public static final @NonNull Creator<VpnTransportInfo> CREATOR = + new Creator<VpnTransportInfo>() { + public VpnTransportInfo createFromParcel(Parcel in) { + return new VpnTransportInfo(in.readInt()); + } + public VpnTransportInfo[] newArray(int size) { + return new VpnTransportInfo[size]; + } + }; +} diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp index 8fc318180778..ed1716fad8c0 100644 --- a/packages/Connectivity/service/Android.bp +++ b/packages/Connectivity/service/Android.bp @@ -25,7 +25,6 @@ cc_library_shared { ], srcs: [ "jni/com_android_server_TestNetworkService.cpp", - "jni/com_android_server_connectivity_Vpn.cpp", "jni/onload.cpp", ], shared_libs: [ diff --git a/packages/Connectivity/service/jni/onload.cpp b/packages/Connectivity/service/jni/onload.cpp index 3afcb0e8f688..00128794bcd0 100644 --- a/packages/Connectivity/service/jni/onload.cpp +++ b/packages/Connectivity/service/jni/onload.cpp @@ -19,7 +19,6 @@ namespace android { -int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_TestNetworkService(JNIEnv* env); extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { @@ -29,12 +28,11 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } - if (register_android_server_connectivity_Vpn(env) < 0 - || register_android_server_TestNetworkService(env) < 0) { + if (register_android_server_TestNetworkService(env) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; } -};
\ No newline at end of file +}; diff --git a/packages/PackageInstaller/OWNERS b/packages/PackageInstaller/OWNERS index 252670a6fb13..8e1774b0baa2 100644 --- a/packages/PackageInstaller/OWNERS +++ b/packages/PackageInstaller/OWNERS @@ -1,5 +1,4 @@ svetoslavganov@google.com -moltmann@google.com toddke@google.com suprabh@google.com diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 39be9cbff40d..c3fc019c6787 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -87,6 +87,7 @@ <!-- TODO(b/152310230): remove once APIs are confirmed to be sufficient --> <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" /> <uses-permission android:name="android.permission.MOVE_PACKAGE" /> + <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" /> <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" /> <uses-permission android:name="android.permission.CLEAR_APP_CACHE" /> <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" /> @@ -357,6 +358,7 @@ <uses-permission android:name="android.permission.BIND_VOICE_INTERACTION" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> + <uses-permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" /> <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS index 6ba1fcb058b1..34901f5830c4 100644 --- a/packages/Shell/OWNERS +++ b/packages/Shell/OWNERS @@ -6,7 +6,6 @@ nandana@google.com svetoslavganov@google.com hackbod@google.com yamasani@google.com -moltmann@google.com toddke@google.com cbrubaker@google.com omakoto@google.com diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index c5a35eaf3e6c..d18902a7796b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -27,12 +27,11 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; -import android.net.IConnectivityManager; import android.net.Network; import android.net.NetworkRequest; +import android.net.VpnManager; import android.os.Handler; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.security.KeyChain; @@ -75,7 +74,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi private final Context mContext; private final ConnectivityManager mConnectivityManager; - private final IConnectivityManager mConnectivityManagerService; + private final VpnManager mVpnManager; private final DevicePolicyManager mDevicePolicyManager; private final PackageManager mPackageManager; private final UserManager mUserManager; @@ -107,8 +106,7 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi context.getSystemService(Context.DEVICE_POLICY_SERVICE); mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - mConnectivityManagerService = IConnectivityManager.Stub.asInterface( - ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); + mVpnManager = context.getSystemService(VpnManager.class); mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mBgExecutor = bgExecutor; @@ -346,25 +344,19 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi private void updateState() { // Find all users with an active VPN SparseArray<VpnConfig> vpns = new SparseArray<>(); - try { - for (UserInfo user : mUserManager.getUsers()) { - VpnConfig cfg = mConnectivityManagerService.getVpnConfig(user.id); - if (cfg == null) { + for (UserInfo user : mUserManager.getUsers()) { + VpnConfig cfg = mVpnManager.getVpnConfig(user.id); + if (cfg == null) { + continue; + } else if (cfg.legacy) { + // Legacy VPNs should do nothing if the network is disconnected. Third-party + // VPN warnings need to continue as traffic can still go to the app. + LegacyVpnInfo legacyVpn = mVpnManager.getLegacyVpnInfo(user.id); + if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) { continue; - } else if (cfg.legacy) { - // Legacy VPNs should do nothing if the network is disconnected. Third-party - // VPN warnings need to continue as traffic can still go to the app. - LegacyVpnInfo legacyVpn = mConnectivityManagerService.getLegacyVpnInfo(user.id); - if (legacyVpn == null || legacyVpn.state != LegacyVpnInfo.STATE_CONNECTED) { - continue; - } } - vpns.put(user.id, cfg); } - } catch (RemoteException rme) { - // Roll back to previous state - Log.e(TAG, "Unable to list active VPNs", rme); - return; + vpns.put(user.id, cfg); } mCurrentVpns = vpns; } diff --git a/services/core/Android.bp b/services/core/Android.bp index ba7f0805f3da..8cb52e5f6f4e 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -98,7 +98,6 @@ java_library_static { "android.hardware.power-V1-java", "android.hardware.power-V1.0-java", "android.hardware.vibrator-V1-java", - "android.net.ipsec.ike.stubs.module_lib", "app-compat-annotations", "framework-tethering.stubs.module_lib", "service-permission.stubs.system_server", @@ -207,8 +206,5 @@ filegroup { "java/com/android/server/connectivity/QosCallbackAgentConnection.java", "java/com/android/server/connectivity/QosCallbackTracker.java", "java/com/android/server/connectivity/TcpKeepaliveController.java", - "java/com/android/server/connectivity/Vpn.java", - "java/com/android/server/connectivity/VpnIkev2Utils.java", - "java/com/android/server/net/LockdownVpnTracker.java", ], } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 2f883517a378..558fbc25d7df 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -16,7 +16,6 @@ package com.android.server; -import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; @@ -45,8 +44,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; +import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -95,6 +95,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IOnSetOemNetworkPreferenceListener; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; @@ -132,12 +133,13 @@ import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; import android.net.TetheringManager; +import android.net.TransportInfo; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; -import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; @@ -168,8 +170,6 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.security.Credentials; -import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; @@ -185,9 +185,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; -import com.android.internal.net.LegacyVpnInfo; -import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; @@ -212,9 +209,7 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; -import com.android.server.net.LockdownVpnTracker; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -307,18 +302,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final PerUidCounter mNetworkRequestCounter; - private KeyStore mKeyStore; - - @VisibleForTesting - @GuardedBy("mVpns") - protected final SparseArray<Vpn> mVpns = new SparseArray<>(); - - // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by - // a direct call to LockdownVpnTracker.isEnabled(). - @GuardedBy("mVpns") - private boolean mLockdownEnabled; - @GuardedBy("mVpns") - private LockdownVpnTracker mLockdownTracker; + private volatile boolean mLockdownEnabled; /** * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal @@ -569,6 +553,12 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47; /** + * used internally when setting the default networks for OemNetworkPreferences. + * obj = OemNetworkPreferences + */ + private static final int EVENT_SET_OEM_NETWORK_PREFERENCE = 48; + + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. */ @@ -753,6 +743,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // When a lockdown VPN connects, send another CONNECTED broadcast for the underlying + // network type, to preserve previous behaviour. + private void maybeSendLegacyLockdownBroadcast(@NonNull NetworkAgentInfo vpnNai) { + if (vpnNai != mService.getLegacyLockdownNai()) return; + + if (vpnNai.declaredUnderlyingNetworks == null + || vpnNai.declaredUnderlyingNetworks.length != 1) { + Log.wtf(TAG, "Legacy lockdown VPN must have exactly one underlying network: " + + Arrays.toString(vpnNai.declaredUnderlyingNetworks)); + return; + } + final NetworkAgentInfo underlyingNai = mService.getNetworkAgentInfoForNetwork( + vpnNai.declaredUnderlyingNetworks[0]); + if (underlyingNai == null) return; + + final int type = underlyingNai.networkInfo.getType(); + final DetailedState state = DetailedState.CONNECTED; + maybeLogBroadcast(underlyingNai, state, type, true /* isDefaultNetwork */); + mService.sendLegacyNetworkBroadcast(underlyingNai, state, type); + } + /** Adds the given network to the specified legacy type list. */ public void add(int type, NetworkAgentInfo nai) { if (!isTypeSupported(type)) { @@ -770,9 +781,17 @@ public class ConnectivityService extends IConnectivityManager.Stub // Send a broadcast if this is the first network of its type or if it's the default. final boolean isDefaultNetwork = mService.isDefaultNetwork(nai); + + // If a legacy lockdown VPN is active, override the NetworkInfo state in all broadcasts + // to preserve previous behaviour. + final DetailedState state = mService.getLegacyLockdownState(DetailedState.CONNECTED); if ((list.size() == 1) || isDefaultNetwork) { - maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork); - mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type); + maybeLogBroadcast(nai, state, type, isDefaultNetwork); + mService.sendLegacyNetworkBroadcast(nai, state, type); + } + + if (type == TYPE_VPN && state == DetailedState.CONNECTED) { + maybeSendLegacyLockdownBroadcast(nai); } } @@ -967,13 +986,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Get a reference to the system keystore. - */ - public KeyStore getKeyStore() { - return KeyStore.getInstance(); - } - - /** * @see ProxyTracker */ public ProxyTracker makeProxyTracker(@NonNull Context context, @@ -1037,10 +1049,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mMetricsLog = logger; mNetworkRanker = new NetworkRanker(); - final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport( - -1, NetworkRequest.Type.REQUEST); - mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder(), - null /* attributionTag */); + final NetworkRequest defaultInternetRequest = createDefaultRequest(); + mDefaultRequest = new NetworkRequestInfo( + defaultInternetRequest, null, new Binder(), + null /* attributionTags */); mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); mDefaultNetworkRequests.add(mDefaultRequest); mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest); @@ -1082,7 +1094,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mProxyTracker = mDeps.makeProxyTracker(mContext, mHandler); mNetd = netd; - mKeyStore = mDeps.getKeyStore(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mLocationPermissionChecker = new LocationPermissionChecker(mContext); @@ -1171,43 +1182,15 @@ public class ConnectivityService extends IConnectivityManager.Stub mPermissionMonitor = new PermissionMonitor(mContext, mNetd); - // Set up the listener for user state for creating user VPNs. + // Listen for user add/removes to inform PermissionMonitor. // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_STARTED); - intentFilter.addAction(Intent.ACTION_USER_STOPPED); intentFilter.addAction(Intent.ACTION_USER_ADDED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); - intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( - mUserPresentReceiver, - new IntentFilter(Intent.ACTION_USER_PRESENT), - null /* broadcastPermission */, - null /* scheduler */); - - // Listen to package add and removal events for all users. - intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mUserAllContext.registerReceiver( - mIntentReceiver, - intentFilter, - null /* broadcastPermission */, - mHandler); - - // Listen to lockdown VPN reset. - intentFilter = new IntentFilter(); - intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); - mUserAllContext.registerReceiver( - mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + mUserAllContext.registerReceiver(mIntentReceiver, intentFilter, + null /* broadcastPermission */, mHandler); mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); @@ -1248,21 +1231,29 @@ public class ConnectivityService extends IConnectivityManager.Stub private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); netCap.setSingleUid(uid); return netCap; } + private NetworkRequest createDefaultRequest() { + return createDefaultInternetRequestForTransport( + TYPE_NONE, NetworkRequest.Type.REQUEST); + } + private NetworkRequest createDefaultInternetRequestForTransport( int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); - if (transportType > -1) { + if (transportType > TYPE_NONE) { netCap.addTransportType(transportType); } + return createNetworkRequest(type, netCap); + } + + private NetworkRequest createNetworkRequest( + NetworkRequest.Type type, NetworkCapabilities netCap) { return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId(), type); } @@ -1312,7 +1303,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (enable) { handleRegisterNetworkRequest(new NetworkRequestInfo( - null, networkRequest, new Binder(), null /* attributionTag */)); + networkRequest, null, new Binder(), + null /* attributionTags */)); } else { handleReleaseNetworkRequest(networkRequest, Process.SYSTEM_UID, /* callOnUnavailable */ false); @@ -1385,9 +1377,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - synchronized (mVpns) { - if (mLockdownEnabled) return null; - } + if (mLockdownEnabled) return null; final NetworkAgentInfo nai = getVpnForUid(uid); if (nai != null) return nai.declaredUnderlyingNetworks; return null; @@ -1472,11 +1462,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) { networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - synchronized (mVpns) { - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(networkInfo); - } - } + networkInfo.setDetailedState( + getLegacyLockdownState(networkInfo.getDetailedState()), + "" /* reason */, null /* extraInfo */); } /** @@ -1535,14 +1523,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return nai.network; } - // Public because it's used by mLockdownTracker. - public NetworkInfo getActiveNetworkInfoUnfiltered() { - enforceAccessPermission(); - final int uid = mDeps.getCallingUid(); - NetworkState state = getUnfilteredActiveNetworkState(uid); - return state.networkInfo; - } - @Override public NetworkInfo getActiveNetworkInfoForUid(int uid, boolean ignoreBlocked) { NetworkStack.checkNetworkStackPermission(mContext); @@ -2164,22 +2144,6 @@ public class ConnectivityService extends IConnectivityManager.Stub isBackgroundRestricted); } - /** - * Require that the caller is either in the same user or has appropriate permission to interact - * across users. - * - * @param userId Target user for whatever operation the current IPC is supposed to perform. - */ - private void enforceCrossUserPermission(int userId) { - if (userId == UserHandle.getCallingUserId()) { - // Not a cross-user call. - return; - } - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "ConnectivityService"); - } - private boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { @@ -2260,12 +2224,6 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, pid, uid); } - private void enforceControlAlwaysOnVpnPermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, - "ConnectivityService"); - } - private void enforceNetworkStackOrSettingsPermission() { enforceAnyPermissionOf( android.Manifest.permission.NETWORK_SETTINGS, @@ -2290,6 +2248,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private void enforceOemNetworkPreferencesPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_STACK, @@ -2338,13 +2302,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); - } - } - Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); @@ -2438,10 +2395,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait - // for user to unlock device too. - updateLockdownVpn(); - // Create network requests for always-on networks. mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); } @@ -2632,6 +2585,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } pw.println(); + pw.print("Current per-app default networks: "); + pw.increaseIndent(); + dumpPerAppNetworkPreferences(pw); + pw.decreaseIndent(); + pw.println(); + pw.println("Current Networks:"); pw.increaseIndent(); dumpNetworks(pw); @@ -2752,6 +2711,40 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void dumpPerAppNetworkPreferences(IndentingPrintWriter pw) { + pw.println("Per-App Network Preference:"); + pw.increaseIndent(); + if (0 == mOemNetworkPreferences.getNetworkPreferences().size()) { + pw.println("none"); + } else { + pw.println(mOemNetworkPreferences.toString()); + } + pw.decreaseIndent(); + + for (final NetworkRequestInfo defaultRequest : mDefaultNetworkRequests) { + if (mDefaultRequest == defaultRequest) { + continue; + } + + final boolean isActive = null != defaultRequest.getSatisfier(); + pw.println("Is per-app network active:"); + pw.increaseIndent(); + pw.println(isActive); + if (isActive) { + pw.println("Active network: " + defaultRequest.getSatisfier().network.netId); + } + pw.println("Tracked UIDs:"); + pw.increaseIndent(); + if (0 == defaultRequest.mRequests.size()) { + pw.println("none, this should never occur."); + } else { + pw.println(defaultRequest.mRequests.get(0).networkCapabilities.getUids()); + } + pw.decreaseIndent(); + pw.decreaseIndent(); + } + } + private void dumpNetworkRequests(IndentingPrintWriter pw) { for (NetworkRequestInfo nri : requestsSortedById()) { pw.println(nri.toString()); @@ -2885,7 +2878,15 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "Non-virtual networks cannot have underlying networks"); break; } + final List<Network> underlying = (List<Network>) arg.second; + + if (isLegacyLockdownNai(nai) + && (underlying == null || underlying.size() != 1)) { + Log.wtf(TAG, "Legacy lockdown VPN " + nai.toShortString() + + " must have exactly one underlying network: " + underlying); + } + final Network[] oldUnderlying = nai.declaredUnderlyingNetworks; nai.declaredUnderlyingNetworks = (underlying != null) ? underlying.toArray(new Network[0]) : null; @@ -3494,7 +3495,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // incorrect) behavior. mNetworkActivityTracker.updateDataActivityTracking( null /* newNetwork */, nai); - notifyLockdownVpn(nai); ensureNetworkTransitionWakelock(nai.toShortString()); } } @@ -3584,29 +3584,38 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) { + handleRegisterNetworkRequest(Collections.singletonList(nri)); + } + + private void handleRegisterNetworkRequest(@NonNull final List<NetworkRequestInfo> nris) { ensureRunningOnConnectivityServiceThread(); - mNetworkRequestInfoLogs.log("REGISTER " + nri); - for (final NetworkRequest req : nri.mRequests) { - mNetworkRequests.put(req, nri); - if (req.isListen()) { - for (final NetworkAgentInfo network : mNetworkAgentInfos) { - if (req.networkCapabilities.hasSignalStrength() - && network.satisfiesImmutableCapabilitiesOf(req)) { - updateSignalStrengthThresholds(network, "REGISTER", req); + for (final NetworkRequestInfo nri : nris) { + mNetworkRequestInfoLogs.log("REGISTER " + nri); + for (final NetworkRequest req : nri.mRequests) { + mNetworkRequests.put(req, nri); + if (req.isListen()) { + for (final NetworkAgentInfo network : mNetworkAgentInfos) { + if (req.networkCapabilities.hasSignalStrength() + && network.satisfiesImmutableCapabilitiesOf(req)) { + updateSignalStrengthThresholds(network, "REGISTER", req); + } } } } } + rematchAllNetworksAndRequests(); - // If the nri is satisfied, return as its score has already been sent if needed. - if (nri.isBeingSatisfied()) { - return; - } + for (final NetworkRequestInfo nri : nris) { + // If the nri is satisfied, return as its score has already been sent if needed. + if (nri.isBeingSatisfied()) { + return; + } - // As this request was not satisfied on rematch and thus never had any scores sent to the - // factories, send null now for each request of type REQUEST. - for (final NetworkRequest req : nri.mRequests) { - if (req.isRequest()) sendUpdatedScoreToFactories(req, null); + // As this request was not satisfied on rematch and thus never had any scores sent to + // the factories, send null now for each request of type REQUEST. + for (final NetworkRequest req : nri.mRequests) { + if (req.isRequest()) sendUpdatedScoreToFactories(req, null); + } } } @@ -3779,6 +3788,7 @@ public class ConnectivityService extends IConnectivityManager.Stub removeListenRequestFromNetworks(req); } } + mDefaultNetworkRequests.remove(nri); mNetworkRequestCounter.decrementCount(nri.mUid); mNetworkRequestInfoLogs.log("RELEASE " + nri); @@ -4417,6 +4427,16 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_SET_REQUIRE_VPN_FOR_UIDS: handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj); break; + case EVENT_SET_OEM_NETWORK_PREFERENCE: + final Pair<OemNetworkPreferences, IOnSetOemNetworkPreferenceListener> arg = + (Pair<OemNetworkPreferences, + IOnSetOemNetworkPreferenceListener>) msg.obj; + try { + handleSetOemNetworkPreference(arg.first, arg.second); + } catch (RemoteException e) { + loge("handleMessage.EVENT_SET_OEM_NETWORK_PREFERENCE failed", e); + } + break; } } } @@ -4719,183 +4739,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Prepare for a VPN application. - * 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 oldPackage Package name of the application which currently controls VPN, which will - * be replaced. If there is no such application, this should should either be - * {@code null} or {@link VpnConfig.LEGACY_VPN}. - * @param newPackage Package name of the application which should gain control of VPN, or - * {@code null} to disable. - * @param userId User for whom to prepare the new VPN. - * - * @hide - */ - @Override - public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, - int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - throwIfLockdownEnabled(); - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); - } else { - return false; - } - } - } - - /** - * 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. - * @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, @VpnManager.VpnType int vpnType) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - vpn.setPackageAuthorization(packageName, vpnType); - } - } - } - - /** - * Configure a TUN interface and return its file descriptor. Parameters - * are encoded and opaque to this class. This method is used by VpnBuilder - * and not available in ConnectivityManager. Permissions are checked in - * Vpn class. - * @hide - */ - @Override - public ParcelFileDescriptor establishVpn(VpnConfig config) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).establish(config); - } - } - - /** - * 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(mDeps.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(mDeps.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(mDeps.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(mDeps.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. - */ - @Override - public void startLegacyVpn(VpnProfile profile) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final LinkProperties egress = getActiveLinkProperties(); - if (egress == null) { - throw new IllegalStateException("Missing active network connection"); - } - synchronized (mVpns) { - throwIfLockdownEnabled(); - mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); - } - } - - /** - * Return the information of the ongoing legacy VPN. This method is used - * by VpnSettings and not available in ConnectivityManager. Permissions - * are checked in Vpn class. - */ - @Override - public LegacyVpnInfo getLegacyVpnInfo(int userId) { - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - return mVpns.get(userId).getLegacyVpnInfo(); - } - } - - /** * Return the information of all ongoing VPNs. * * <p>This method is used to update NetworkStatsService. @@ -4904,10 +4747,8 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private UnderlyingNetworkInfo[] getAllVpnInfo() { ensureRunningOnConnectivityServiceThread(); - synchronized (mVpns) { - if (mLockdownEnabled) { - return new UnderlyingNetworkInfo[0]; - } + if (mLockdownEnabled) { + return new UnderlyingNetworkInfo[0]; } List<UnderlyingNetworkInfo> infoList = new ArrayList<>(); for (NetworkAgentInfo nai : mNetworkAgentInfos) { @@ -4963,25 +4804,6 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.linkProperties.getInterfaceName(), interfaces); } - /** - * Returns the information of the ongoing VPN for {@code userId}. This method is used by - * VpnDialogs and not available in ConnectivityManager. - * Permissions are checked in Vpn class. - * @hide - */ - @Override - public VpnConfig getVpnConfig(int userId) { - enforceCrossUserPermission(userId); - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn != null) { - return vpn.getVpnConfig(); - } else { - return null; - } - } - } - // TODO This needs to be the default network that applies to the NAI. private Network[] underlyingNetworksOrDefault(final int ownerUid, Network[] underlyingNetworks) { @@ -5069,195 +4891,54 @@ public class ConnectivityService extends IConnectivityManager.Stub mVpnBlockedUidRanges = newVpnBlockedUidRanges; } - private boolean isLockdownVpnEnabled() { - return mKeyStore.contains(Credentials.LOCKDOWN_VPN); - } - @Override - public boolean updateLockdownVpn() { - // Allow the system UID for the system server and for Settings. - // Also, for unit tests, allow the process that ConnectivityService is running in. - if (mDeps.getCallingUid() != Process.SYSTEM_UID - && Binder.getCallingPid() != Process.myPid()) { - logw("Lockdown VPN only available to system process or AID_SYSTEM"); - return false; - } - - synchronized (mVpns) { - // Tear down existing lockdown if profile was removed - mLockdownEnabled = isLockdownVpnEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - loge("Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - loge("Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(mDeps.getCallingUid()); - Vpn vpn = mVpns.get(user); - if (vpn == null) { - logw("VPN for user " + user + " not ready yet. Skipping lockdown"); - return false; - } - setLockdownTracker( - new LockdownVpnTracker(mContext, this, mHandler, mKeyStore, vpn, profile)); - } else { - setLockdownTracker(null); - } - } - - return true; - } - - /** - * Internally set new {@link LockdownVpnTracker}, shutting down any existing - * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. - */ - @GuardedBy("mVpns") - private void setLockdownTracker(LockdownVpnTracker tracker) { - // Shutdown any existing tracker - final LockdownVpnTracker existing = mLockdownTracker; - // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the - // necessary onBlockedStatusChanged callbacks. - mLockdownTracker = null; - if (existing != null) { - existing.shutdown(); - } - - if (tracker != null) { - mLockdownTracker = tracker; - mLockdownTracker.init(); - } - } - - /** - * 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) { - throw new IllegalStateException("Unavailable in lockdown mode"); - } + public void setLegacyLockdownVpnEnabled(boolean enabled) { + enforceSettingsPermission(); + mHandler.post(() -> mLockdownEnabled = enabled); } - /** - * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform - * some setup and then call {@code establish()} to connect. - * - * @return {@code true} if the service was started, the service was already connected, or there - * was no always-on VPN to start. {@code false} otherwise. - */ - private boolean startAlwaysOnVpn(int userId) { - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - // Shouldn't happen as all code paths that point here should have checked the Vpn - // exists already. - Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); - return false; - } - - return vpn.startAlwaysOnVpn(mKeyStore); - } + private boolean isLegacyLockdownNai(NetworkAgentInfo nai) { + return mLockdownEnabled + && getVpnType(nai) == VpnManager.TYPE_VPN_LEGACY + && nai.networkCapabilities.appliesToUid(Process.FIRST_APPLICATION_UID); } - @Override - public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { - enforceSettingsPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + private NetworkAgentInfo getLegacyLockdownNai() { + if (!mLockdownEnabled) { + return null; } - } - - @Override - public boolean setAlwaysOnVpnPackage( - int userId, String packageName, boolean lockdown, List<String> lockdownWhitelist) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (isLockdownVpnEnabled()) { - return false; - } + // The legacy lockdown VPN always only applies to userId 0. + final NetworkAgentInfo nai = getVpnForUid(Process.FIRST_APPLICATION_UID); + if (nai == null || !isLegacyLockdownNai(nai)) return null; - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) { - return false; - } - if (!startAlwaysOnVpn(userId)) { - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - return false; - } + // The legacy lockdown VPN must always have exactly one underlying network. + // This code may run on any thread and declaredUnderlyingNetworks may change, so store it in + // a local variable. There is no need to make a copy because its contents cannot change. + final Network[] underlying = nai.declaredUnderlyingNetworks; + if (underlying == null || underlying.length != 1) { + return null; } - return true; - } - @Override - public String getAlwaysOnVpnPackage(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getAlwaysOnPackage(); + // The legacy lockdown VPN always uses the default network. + // If the VPN's underlying network is no longer the current default network, it means that + // the default network has just switched, and the VPN is about to disconnect. + // Report that the VPN is not connected, so when the state of NetworkInfo objects + // overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED. + final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); + if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) { + return null; } - } - @Override - public boolean isVpnLockdownEnabled(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return false; - } - return vpn.getLockdown(); - } - } + return nai; + }; - @Override - public List<String> getVpnLockdownWhitelist(int userId) { - enforceControlAlwaysOnVpnPermission(); - enforceCrossUserPermission(userId); - - synchronized (mVpns) { - Vpn vpn = mVpns.get(userId); - if (vpn == null) { - logw("User " + userId + " has no Vpn configuration"); - return null; - } - return vpn.getLockdownAllowlist(); + private DetailedState getLegacyLockdownState(DetailedState origState) { + if (origState != DetailedState.CONNECTED) { + return origState; } + return (mLockdownEnabled && getLegacyLockdownNai() == null) + ? DetailedState.CONNECTING + : DetailedState.CONNECTED; } @Override @@ -5292,111 +4973,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserStarted(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn != null) { - loge("Starting user already has a VPN"); - return; - } - userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); - mVpns.put(userId, userVpn); - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } - } - } - - private void onUserStopped(int userId) { - synchronized (mVpns) { - Vpn userVpn = mVpns.get(userId); - if (userVpn == null) { - loge("Stopped user has no VPN"); - return; - } - userVpn.onUserStopped(); - mVpns.delete(userId); - } - } - private void onUserAdded(int userId) { mPermissionMonitor.onUserAdded(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserAdded(userId); - } - } } private void onUserRemoved(int userId) { mPermissionMonitor.onUserRemoved(userId); - synchronized (mVpns) { - final int vpnsSize = mVpns.size(); - for (int i = 0; i < vpnsSize; i++) { - Vpn vpn = mVpns.valueAt(i); - vpn.onUserRemoved(userId); - } - } - } - - private void onPackageReplaced(String packageName, int uid) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); - return; - } - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { - log("Restarting always-on VPN package " + packageName + " for user " - + userId); - vpn.startAlwaysOnVpn(mKeyStore); - } - } - } - - private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { - if (TextUtils.isEmpty(packageName) || uid < 0) { - Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); - return; - } - - final int userId = UserHandle.getUserId(uid); - synchronized (mVpns) { - final Vpn vpn = mVpns.get(userId); - if (vpn == null) { - return; - } - // Legacy always-on VPN won't be affected since the package name is not set. - if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { - log("Removing always-on VPN package " + packageName + " for user " - + userId); - vpn.setAlwaysOnPackage(null, false, null, mKeyStore); - } - } - } - - private void onUserUnlocked(int userId) { - synchronized (mVpns) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); - } - } - } - - private void onVpnLockdownReset() { - synchronized (mVpns) { - if (mLockdownTracker != null) mLockdownTracker.reset(); - } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -5405,52 +4987,20 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); - final Uri packageData = intent.getData(); - final String packageName = - packageData != null ? packageData.getSchemeSpecificPart() : null; - - if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { - onVpnLockdownReset(); - } // UserId should be filled for below intents, check the existence. if (userId == UserHandle.USER_NULL) return; - if (Intent.ACTION_USER_STARTED.equals(action)) { - onUserStarted(userId); - } else if (Intent.ACTION_USER_STOPPED.equals(action)) { - onUserStopped(userId); - } else if (Intent.ACTION_USER_ADDED.equals(action)) { + if (Intent.ACTION_USER_ADDED.equals(action)) { onUserAdded(userId); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - onUserUnlocked(userId); - } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { - onPackageReplaced(packageName, uid); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - final boolean isReplacing = intent.getBooleanExtra( - Intent.EXTRA_REPLACING, false); - onPackageRemoved(packageName, uid, isReplacing); - } else { + } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; - private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Try creating lockdown tracker, since user present usually means - // unlocked keystore. - updateLockdownVpn(); - // Use the same context that registered receiver before to unregister it. Because use - // different context to unregister receiver will cause exception. - context.unregisterReceiver(this); - } - }; - private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); @@ -5549,10 +5099,12 @@ public class ConnectivityService extends IConnectivityManager.Stub final PendingIntent mPendingIntent; boolean mPendingIntentSent; + @Nullable + final Messenger mMessenger; + @Nullable private final IBinder mBinder; final int mPid; final int mUid; - final Messenger messenger; @Nullable final String mCallingAttributionTag; @@ -5568,12 +5120,17 @@ public class ConnectivityService extends IConnectivityManager.Stub return uids; } - NetworkRequestInfo(NetworkRequest r, PendingIntent pi, + NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), pi, callingAttributionTag); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, + @Nullable final PendingIntent pi, @Nullable String callingAttributionTag) { mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); mPendingIntent = pi; - messenger = null; + mMessenger = null; mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); @@ -5581,11 +5138,16 @@ public class ConnectivityService extends IConnectivityManager.Stub mCallingAttributionTag = callingAttributionTag; } - NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder, - @Nullable String callingAttributionTag) { + NetworkRequestInfo(@NonNull final NetworkRequest r, @Nullable final Messenger m, + @Nullable final IBinder binder, @Nullable String callingAttributionTag) { + this(Collections.singletonList(r), m, binder, callingAttributionTag); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r, @Nullable final Messenger m, + @Nullable final IBinder binder, @Nullable String callingAttributionTag) { super(); - messenger = m; mRequests = initializeRequests(r); + mMessenger = m; ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; mPid = getCallingPid(); @@ -5601,7 +5163,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - NetworkRequestInfo(NetworkRequest r) { + NetworkRequestInfo(@NonNull final NetworkRequest r) { + this(Collections.singletonList(r)); + } + + NetworkRequestInfo(@NonNull final List<NetworkRequest> r) { this(r, null /* pi */, null /* callingAttributionTag */); } @@ -5616,9 +5182,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return mRequests.size() > 1; } - private List<NetworkRequest> initializeRequests(NetworkRequest r) { - final ArrayList<NetworkRequest> tempRequests = new ArrayList<>(); - tempRequests.add(new NetworkRequest(r)); + private List<NetworkRequest> initializeRequests(List<NetworkRequest> r) { + // Creating a defensive copy to prevent the sender from modifying the list being + // reflected in the return value of this method. + final List<NetworkRequest> tempRequests = new ArrayList<>(r); return Collections.unmodifiableList(tempRequests); } @@ -5747,6 +5314,7 @@ public class ConnectivityService extends IConnectivityManager.Stub throw new SecurityException("Insufficient permissions to specify legacy type"); } } + final NetworkCapabilities defaultNc = mDefaultRequest.mRequests.get(0).networkCapabilities; final int callingUid = mDeps.getCallingUid(); final NetworkRequest.Type reqType; try { @@ -5757,11 +5325,15 @@ public class ConnectivityService extends IConnectivityManager.Stub switch (reqType) { case TRACK_DEFAULT: // If the request type is TRACK_DEFAULT, the passed {@code networkCapabilities} - // is unused and will be replaced by the one from the default network request. - // This allows callers to keep track of the system default network. + // is unused and will be replaced by ones appropriate for the caller. + // This allows callers to keep track of the default network for their app. networkCapabilities = createDefaultNetworkCapabilitiesForUid(callingUid); enforceAccessPermission(); break; + case TRACK_SYSTEM_DEFAULT: + enforceSettingsPermission(); + networkCapabilities = new NetworkCapabilities(defaultNc); + break; case BACKGROUND_REQUEST: enforceNetworkStackOrSettingsPermission(); // Fall-through since other checks are the same with normal requests. @@ -5780,6 +5352,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ensureRequestableCapabilities(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, Binder.getCallingPid(), callingUid, callingPackageName); + // Set the UID range for this request to the single UID of the requester, or to an empty // set of UIDs if the caller has the appropriate permission and UIDs have not been set. // This will overwrite any allowed UIDs in the requested capabilities. Though there @@ -5796,9 +5369,20 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType, nextNetworkRequestId(), reqType); NetworkRequestInfo nri = - new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag); + new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); if (DBG) log("requestNetwork for " + nri); + // For TRACK_SYSTEM_DEFAULT callbacks, the capabilities have been modified since they were + // copied from the default request above. (This is necessary to ensure, for example, that + // the callback does not leak sensitive information to unprivileged apps.) Check that the + // changes don't alter request matching. + if (reqType == NetworkRequest.Type.TRACK_SYSTEM_DEFAULT && + (!networkCapabilities.equalRequestableCapabilities(defaultNc))) { + throw new IllegalStateException( + "TRACK_SYSTEM_DEFAULT capabilities don't match default request: " + + networkCapabilities + " vs. " + defaultNc); + } + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri)); if (timeoutMs > 0) { mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_TIMEOUT_NETWORK_REQUEST, @@ -5952,7 +5536,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(), NetworkRequest.Type.LISTEN); NetworkRequestInfo nri = - new NetworkRequestInfo(messenger, networkRequest, binder, callingAttributionTag); + new NetworkRequestInfo(networkRequest, messenger, binder, callingAttributionTag); if (VDBG) log("listenForNetwork for " + nri); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); @@ -6080,13 +5664,20 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mBlockedAppUids") private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); + // Current OEM network preferences. + @NonNull + private OemNetworkPreferences mOemNetworkPreferences = + new OemNetworkPreferences.Builder().build(); + // The always-on request for an Internet-capable network that apps without a specific default // fall back to. + @VisibleForTesting @NonNull - private final NetworkRequestInfo mDefaultRequest; + final NetworkRequestInfo mDefaultRequest; // Collection of NetworkRequestInfo's used for default networks. + @VisibleForTesting @NonNull - private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); + final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) { return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri); @@ -6389,20 +5980,18 @@ public class ConnectivityService extends IConnectivityManager.Stub Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis())); } - // Prioritize the user portal URL from the network agent. - if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null - || TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) { - captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl()); + // Prioritize the user portal URL from the network agent if the source is authenticated. + if (apiData.getUserPortalUrl() != null && naData.getUserPortalUrlSource() + != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) { + captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl(), + apiData.getUserPortalUrlSource()); } - // Prioritize the venue information URL from the network agent. - if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null - || TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) { - captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl()); - - // Note that venue friendly name can only come from the network agent because it is not - // in use in RFC8908. However, if using the Capport venue URL, make sure that the - // friendly name is not set from the network agent. - captivePortalBuilder.setVenueFriendlyName(null); + // Prioritize the venue information URL from the network agent if the source is + // authenticated. + if (apiData.getVenueInfoUrl() != null && naData.getVenueInfoUrlSource() + != CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) { + captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl(), + apiData.getVenueInfoUrlSource()); } return captivePortalBuilder.build(); } @@ -7165,7 +6754,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri, @NonNull final NetworkAgentInfo networkAgent, final int notificationType, final int arg1) { - if (nri.messenger == null) { + if (nri.mMessenger == null) { // Default request has no msgr. Also prevents callbacks from being invoked for // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks // are Type.LISTEN, but should not have NetworkCallbacks invoked. @@ -7234,7 +6823,7 @@ public class ConnectivityService extends IConnectivityManager.Stub String notification = ConnectivityManager.getCallbackName(notificationType); log("sending notification " + notification + " for " + nrForCallback); } - nri.messenger.send(msg); + nri.mMessenger.send(msg); } catch (RemoteException e) { // may occur naturally in the race of binder death. loge("RemoteException caught trying to send a callback msg for " + nrForCallback); @@ -7323,7 +6912,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } mNetworkActivityTracker.updateDataActivityTracking(newDefaultNetwork, oldDefaultNetwork); - notifyLockdownVpn(newDefaultNetwork); handleApplyDefaultProxy(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getHttpProxy() : null); updateTcpBufferSizes(null != newDefaultNetwork @@ -7781,12 +7369,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; mLegacyTypeTracker.add( newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); - // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast - // to reflect the NetworkInfo of this new network. This broadcast has to be sent - // after the disconnect broadcasts above, but before the broadcasts sent by the - // legacy type tracker below. - // TODO : refactor this, it's too complex - notifyLockdownVpn(newDefaultNetwork); } } @@ -7844,18 +7426,6 @@ public class ConnectivityService extends IConnectivityManager.Stub sendInetConditionBroadcast(nai.networkInfo); } - private void notifyLockdownVpn(NetworkAgentInfo nai) { - synchronized (mVpns) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); - } - } - } - } - @NonNull private NetworkInfo mixInInfo(@NonNull final NetworkAgentInfo nai, @NonNull NetworkInfo info) { final NetworkInfo newInfo = new NetworkInfo(info); @@ -7894,7 +7464,6 @@ public class ConnectivityService extends IConnectivityManager.Stub oldInfo = networkAgent.networkInfo; networkAgent.networkInfo = newInfo; } - notifyLockdownVpn(networkAgent); if (DBG) { log(networkAgent.toShortString() + " EVENT_NETWORK_INFO_CHANGED, going from " @@ -8195,34 +7764,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public boolean addVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).addAddress(address, prefixLength); - } - } - - @Override - public boolean removeVpnAddress(String address, int prefixLength) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - synchronized (mVpns) { - throwIfLockdownEnabled(); - return mVpns.get(user).removeAddress(address, prefixLength); - } - } - - @Override - public boolean setUnderlyingNetworksForVpn(Network[] networks) { - int user = UserHandle.getUserId(mDeps.getCallingUid()); - final boolean success; - synchronized (mVpns) { - success = mVpns.get(user).setUnderlyingNetworks(networks); - } - return success; - } - - @Override public String getCaptivePortalServerUrl() { enforceNetworkStackOrSettingsPermission(); String settingUrl = mContext.getResources().getString( @@ -8301,8 +7842,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - final int userId = UserHandle.getCallingUserId(); - final long token = Binder.clearCallingIdentity(); try { final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext); @@ -8314,44 +7853,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // Turn airplane mode off setAirplaneMode(false); - if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { - // Remove always-on package - synchronized (mVpns) { - final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); - if (alwaysOnPackage != null) { - setAlwaysOnVpnPackage(userId, null, false, null); - setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); - } - - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating - // VPN connections in the future without user intervention. - setVpnPackageAuthorization( - vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); - - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); - } - } - } - } - // restore private DNS settings to default mode (opportunistic) if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_PRIVATE_DNS)) { Settings.Global.putString(mContext.getContentResolver(), @@ -8443,41 +7944,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - @GuardedBy("mVpns") - private Vpn getVpnIfOwner() { - return getVpnIfOwner(mDeps.getCallingUid()); - } - - // TODO: stop calling into Vpn.java and get this information from data in this class. - @GuardedBy("mVpns") - private Vpn getVpnIfOwner(int uid) { - final int user = UserHandle.getUserId(uid); - - final Vpn vpn = mVpns.get(user); - if (vpn == null) { - return null; - } else { - final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); - return (info == null || info.ownerUid != uid) ? null : vpn; - } - } - - /** - * Caller either needs to be an active VPN, or hold the NETWORK_STACK permission - * for testing. - */ - private Vpn enforceActiveVpnOrNetworkStackPermission() { - if (checkNetworkStackPermission()) { - return null; - } - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - if (vpn != null) { - return vpn; - } - } - throw new SecurityException("App must either be an active VPN or have the NETWORK_STACK " - + "permission"); + private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) { + if (vpn == null) return VpnManager.TYPE_VPN_NONE; + final TransportInfo ti = vpn.networkCapabilities.getTransportInfo(); + if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE; + return ((VpnTransportInfo) ti).type; } /** @@ -8487,14 +7958,6 @@ public class ConnectivityService extends IConnectivityManager.Stub * connection is not found. */ public int getConnectionOwnerUid(ConnectionInfo connectionInfo) { - final Vpn vpn = enforceActiveVpnOrNetworkStackPermission(); - - // Only VpnService based VPNs should be able to get this information. - if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) { - throw new SecurityException( - "getConnectionOwnerUid() not allowed for non-VpnService VPNs"); - } - if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) { throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol); } @@ -8502,28 +7965,19 @@ public class ConnectivityService extends IConnectivityManager.Stub final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol, connectionInfo.local, connectionInfo.remote); - /* Filter out Uids not associated with the VPN. */ - if (vpn != null && !vpn.appliesToUid(uid)) { - return INVALID_UID; - } + if (uid == INVALID_UID) return uid; // Not found. - return uid; - } + // Connection owner UIDs are visible only to the network stack and to the VpnService-based + // VPN, if any, that applies to the UID that owns the connection. + if (checkNetworkStackPermission()) return uid; - @Override - public boolean isCallerCurrentAlwaysOnVpnApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getAlwaysOn(); + final NetworkAgentInfo vpn = getVpnForUid(uid); + if (vpn == null || getVpnType(vpn) != VpnManager.TYPE_VPN_SERVICE + || vpn.networkCapabilities.getOwnerUid() != Binder.getCallingUid()) { + return INVALID_UID; } - } - @Override - public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { - synchronized (mVpns) { - Vpn vpn = getVpnIfOwner(); - return vpn != null && vpn.getLockdown(); - } + return uid; } /** @@ -9201,9 +8655,212 @@ public class ConnectivityService extends IConnectivityManager.Stub mQosCallbackTracker.unregisterCallback(callback); } + private void enforceAutomotiveDevice() { + final boolean isAutomotiveDevice = + mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + if (!isAutomotiveDevice) { + throw new UnsupportedOperationException( + "setOemNetworkPreference() is only available on automotive devices."); + } + } + + /** + * Used by automotive devices to set the network preferences used to direct traffic at an + * application level as per the given OemNetworkPreferences. An example use-case would be an + * automotive OEM wanting to provide connectivity for applications critical to the usage of a + * vehicle via a particular network. + * + * Calling this will overwrite the existing preference. + * + * @param preference {@link OemNetworkPreferences} The application network preference to be set. + * @param listener {@link ConnectivityManager.OnSetOemNetworkPreferenceListener} Listener used + * to communicate completion of setOemNetworkPreference(); + */ @Override - public void setOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { - // TODO http://b/176495594 track multiple default networks with networkPreferences - if (DBG) log("setOemNetworkPreference() called with: " + preference.toString()); + public void setOemNetworkPreference( + @NonNull final OemNetworkPreferences preference, + @Nullable final IOnSetOemNetworkPreferenceListener listener) { + + enforceAutomotiveDevice(); + enforceOemNetworkPreferencesPermission(); + + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + validateOemNetworkPreferences(preference); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, + new Pair<>(preference, listener))); + } + + private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) { + for (@OemNetworkPreferences.OemNetworkPreference final int pref + : preference.getNetworkPreferences().values()) { + if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) { + final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value."; + throw new IllegalArgumentException(msg); + } + } + } + + private void handleSetOemNetworkPreference( + @NonNull final OemNetworkPreferences preference, + @NonNull final IOnSetOemNetworkPreferenceListener listener) throws RemoteException { + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + if (DBG) { + log("set OEM network preferences :" + preference.toString()); + } + final List<NetworkRequestInfo> nris = + new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(preference); + updateDefaultNetworksForOemNetworkPreference(nris); + mOemNetworkPreferences = preference; + // TODO http://b/176496396 persist data to shared preferences. + + if (null != listener) { + listener.onComplete(); + } + } + + private void updateDefaultNetworksForOemNetworkPreference( + @NonNull final List<NetworkRequestInfo> nris) { + ensureRunningOnConnectivityServiceThread(); + clearNonDefaultNetworkAgents(); + addDefaultNetworkRequests(nris); + } + + private void clearNonDefaultNetworkAgents() { + // Copy mDefaultNetworkRequests to iterate and remove elements from it in + // handleRemoveNetworkRequest() without getting a ConcurrentModificationException. + final NetworkRequestInfo[] nris = + mDefaultNetworkRequests.toArray(new NetworkRequestInfo[0]); + for (final NetworkRequestInfo nri : nris) { + if (mDefaultRequest != nri) { + handleRemoveNetworkRequest(nri); + } + } + } + + private void addDefaultNetworkRequests(@NonNull final List<NetworkRequestInfo> nris) { + mDefaultNetworkRequests.addAll(nris); + handleRegisterNetworkRequest(nris); + } + + /** + * Class used to generate {@link NetworkRequestInfo} based off of {@link OemNetworkPreferences}. + */ + @VisibleForTesting + final class OemNetworkRequestFactory { + List<NetworkRequestInfo> createNrisFromOemNetworkPreferences( + @NonNull final OemNetworkPreferences preference) { + final List<NetworkRequestInfo> nris = new ArrayList<>(); + final SparseArray<Set<Integer>> uids = + createUidsFromOemNetworkPreferences(preference); + for (int i = 0; i < uids.size(); i++) { + final int key = uids.keyAt(i); + final Set<Integer> value = uids.valueAt(i); + final NetworkRequestInfo nri = createNriFromOemNetworkPreferences(key, value); + // No need to add an nri without any requests. + if (0 == nri.mRequests.size()) { + continue; + } + nris.add(nri); + } + + return nris; + } + + private SparseArray<Set<Integer>> createUidsFromOemNetworkPreferences( + @NonNull final OemNetworkPreferences preference) { + final SparseArray<Set<Integer>> uids = new SparseArray<>(); + final PackageManager pm = mContext.getPackageManager(); + for (final Map.Entry<String, Integer> entry : + preference.getNetworkPreferences().entrySet()) { + @OemNetworkPreferences.OemNetworkPreference final int pref = entry.getValue(); + try { + final int uid = pm.getApplicationInfo(entry.getKey(), 0).uid; + if (!uids.contains(pref)) { + uids.put(pref, new ArraySet<>()); + } + uids.get(pref).add(uid); + } catch (PackageManager.NameNotFoundException e) { + // Although this may seem like an error scenario, it is ok that uninstalled + // packages are sent on a network preference as the system will watch for + // package installations associated with this network preference and update + // accordingly. This is done so as to minimize race conditions on app install. + // TODO b/177092163 add app install watching. + continue; + } + } + return uids; + } + + private NetworkRequestInfo createNriFromOemNetworkPreferences( + @OemNetworkPreferences.OemNetworkPreference final int preference, + @NonNull final Set<Integer> uids) { + final List<NetworkRequest> requests = new ArrayList<>(); + // Requests will ultimately be evaluated by order of insertion therefore it matters. + switch (preference) { + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createOemPaidNetworkRequest()); + requests.add(createDefaultRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createOemPaidNetworkRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY: + requests.add(createOemPaidNetworkRequest()); + break; + case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY: + requests.add(createOemPrivateNetworkRequest()); + break; + default: + // This should never happen. + throw new IllegalArgumentException("createNriFromOemNetworkPreferences()" + + " called with invalid preference of " + preference); + } + + setOemNetworkRequestUids(requests, uids); + return new NetworkRequestInfo(requests); + } + + private NetworkRequest createUnmeteredNetworkRequest() { + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_NOT_METERED) + .addCapability(NET_CAPABILITY_VALIDATED); + return createNetworkRequest(NetworkRequest.Type.LISTEN, netcap); + } + + private NetworkRequest createOemPaidNetworkRequest() { + // NET_CAPABILITY_OEM_PAID is a restricted capability. + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_OEM_PAID) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); + } + + private NetworkRequest createOemPrivateNetworkRequest() { + // NET_CAPABILITY_OEM_PRIVATE is a restricted capability. + final NetworkCapabilities netcap = createDefaultPerAppNetCap() + .addCapability(NET_CAPABILITY_OEM_PRIVATE) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); + } + + private NetworkCapabilities createDefaultPerAppNetCap() { + final NetworkCapabilities netCap = new NetworkCapabilities(); + netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); + return netCap; + } + + private void setOemNetworkRequestUids(@NonNull final List<NetworkRequest> requests, + @NonNull final Set<Integer> uids) { + final Set<UidRange> ranges = new ArraySet<>(); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } + for (final NetworkRequest req : requests) { + req.networkCapabilities.setUids(ranges); + } + } } } diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java index c56cef2d58dc..a83c981235df 100644 --- a/services/core/java/com/android/server/EntropyMixer.java +++ b/services/core/java/com/android/server/EntropyMixer.java @@ -16,12 +16,6 @@ package com.android.server; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,10 +27,15 @@ import android.os.Message; import android.os.SystemProperties; import android.util.Slog; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + /** * A service designed to load and periodically save "randomness" - * for the Linux kernel RNG and to mix in data from Hardware RNG (if present) - * into the Linux RNG. + * for the Linux kernel RNG. * * <p>When a Linux system starts up, the entropy pool associated with * {@code /dev/random} may be in a fairly predictable state. Applications which @@ -45,15 +44,8 @@ import android.util.Slog; * this effect, it's helpful to carry the entropy pool information across * shutdowns and startups. * - * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW - * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever - * EntropyMixer periodically runs to save a block of output from Linux RNG on - * disk. This mixing is done in a way that does not increase the Linux RNG's - * entropy estimate is not increased. This is to avoid having to trust/verify - * the quality and authenticity of the "randomness" of the HW RNG. - * * <p>This class was modeled after the script in the - * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html"> + * <a href="https://man7.org/linux/man-pages/man4/random.4.html"> * random(4) manual page</a>. */ public class EntropyMixer extends Binder { @@ -64,7 +56,6 @@ public class EntropyMixer extends Binder { private static final long START_NANOTIME = System.nanoTime(); private final String randomDevice; - private final String hwRandomDevice; private final String entropyFile; /** @@ -80,7 +71,6 @@ public class EntropyMixer extends Binder { Slog.e(TAG, "Will not process invalid message"); return; } - addHwRandomEntropy(); writeEntropy(); scheduleEntropyWriter(); } @@ -94,25 +84,21 @@ public class EntropyMixer extends Binder { }; public EntropyMixer(Context context) { - this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random"); + this(context, getSystemDir() + "/entropy.dat", "/dev/urandom"); } /** Test only interface, not for public use */ public EntropyMixer( Context context, String entropyFile, - String randomDevice, - String hwRandomDevice) { + String randomDevice) { if (randomDevice == null) { throw new NullPointerException("randomDevice"); } - if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); } if (entropyFile == null) { throw new NullPointerException("entropyFile"); } this.randomDevice = randomDevice; - this.hwRandomDevice = hwRandomDevice; this.entropyFile = entropyFile; loadInitialEntropy(); addDeviceSpecificEntropy(); - addHwRandomEntropy(); writeEntropy(); scheduleEntropyWriter(); IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); @@ -192,23 +178,6 @@ public class EntropyMixer extends Binder { } } - /** - * Mixes in the output from HW RNG (if present) into the Linux RNG. - */ - private void addHwRandomEntropy() { - if (!new File(hwRandomDevice).exists()) { - // HW RNG not present/exposed -- ignore - return; - } - - try { - RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false); - Slog.i(TAG, "Added HW RNG output to entropy pool"); - } catch (IOException e) { - Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e); - } - } - private static String getSystemDir() { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index f648c3e146de..b48bc900aa84 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.IIpSecService; import android.net.INetd; import android.net.InetAddresses; @@ -41,6 +42,7 @@ import android.net.IpSecTransformResponse; import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.net.TrafficStats; import android.net.util.NetdService; @@ -797,9 +799,15 @@ public class IpSecService extends IIpSecService.Stub { } } - private final class TunnelInterfaceRecord extends OwnedResourceRecord { + /** + * Tracks an tunnel interface, and manages cleanup paths. + * + * <p>This class is not thread-safe, and expects that that users of this class will ensure + * synchronization and thread safety by holding the IpSecService.this instance lock + */ + @VisibleForTesting + final class TunnelInterfaceRecord extends OwnedResourceRecord { private final String mInterfaceName; - private final Network mUnderlyingNetwork; // outer addresses private final String mLocalAddress; @@ -810,6 +818,8 @@ public class IpSecService extends IIpSecService.Stub { private final int mIfId; + private Network mUnderlyingNetwork; + TunnelInterfaceRecord( int resourceId, String interfaceName, @@ -870,14 +880,22 @@ public class IpSecService extends IIpSecService.Stub { releaseNetId(mOkey); } - public String getInterfaceName() { - return mInterfaceName; + @GuardedBy("IpSecService.this") + public void setUnderlyingNetwork(Network underlyingNetwork) { + // When #applyTunnelModeTransform is called, this new underlying network will be used to + // update the output mark of the input transform. + mUnderlyingNetwork = underlyingNetwork; } + @GuardedBy("IpSecService.this") public Network getUnderlyingNetwork() { return mUnderlyingNetwork; } + public String getInterfaceName() { + return mInterfaceName; + } + /** Returns the local, outer address for the tunnelInterface */ public String getLocalAddress() { return mLocalAddress; @@ -1429,6 +1447,34 @@ public class IpSecService extends IIpSecService.Stub { } } + /** Set TunnelInterface to use a specific underlying network. */ + @Override + public synchronized void setNetworkForTunnelInterface( + int tunnelResourceId, Network underlyingNetwork, String callingPackage) { + enforceTunnelFeatureAndPermissions(callingPackage); + Objects.requireNonNull(underlyingNetwork, "No underlying network was specified"); + + final UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + + // Get tunnelInterface record; if no such interface is found, will throw + // IllegalArgumentException. userRecord.mTunnelInterfaceRecords is never null + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelResourceId); + + final ConnectivityManager connectivityManager = + mContext.getSystemService(ConnectivityManager.class); + final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork); + if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) { + throw new IllegalArgumentException( + "Underlying network cannot be the network being exposed by this tunnel"); + } + + // It is meaningless to check if the network exists or is valid because the network might + // disconnect at any time after it passes the check. + + tunnelInterfaceInfo.setUnderlyingNetwork(underlyingNetwork); + } + /** * Delete a TunnelInterface that has been been allocated by and registered with the system * server diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 4dce59f23a79..27210daac241 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -641,21 +641,34 @@ public class VcnManagementService extends IVcnManagementService.Stub { } boolean isVcnManagedNetwork = false; + boolean isRestrictedCarrierWifi = false; if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { synchronized (mLock) { ParcelUuid subGroup = mLastSnapshot.getGroupForSubId(subId); Vcn vcn = mVcns.get(subGroup); - if (vcn != null && vcn.isActive()) { - isVcnManagedNetwork = true; + if (vcn != null) { + if (vcn.isActive()) { + isVcnManagedNetwork = true; + } + + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + // Carrier WiFi always restricted if VCN exists (even in safe mode). + isRestrictedCarrierWifi = true; + } } } } + if (isVcnManagedNetwork) { networkCapabilities.removeCapability( NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); } + if (isRestrictedCarrierWifi) { + networkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + } + return new VcnUnderlyingNetworkPolicy(false /* isTearDownRequested */, networkCapabilities); } diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java new file mode 100644 index 000000000000..5d89bf1b1d82 --- /dev/null +++ b/services/core/java/com/android/server/VpnManagerService.java @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2021 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; + +import static android.Manifest.permission.NETWORK_STACK; + +import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.INetd; +import android.net.IVpnManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkStack; +import android.net.UnderlyingNetworkInfo; +import android.net.Uri; +import android.net.VpnManager; +import android.net.VpnService; +import android.net.util.NetdService; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.INetworkManagementService; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; +import android.security.Credentials; +import android.security.KeyStore; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.net.LegacyVpnInfo; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.Vpn; +import com.android.server.net.LockdownVpnTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs. + * @hide + */ +public class VpnManagerService extends IVpnManager.Stub { + private static final String TAG = VpnManagerService.class.getSimpleName(); + + @VisibleForTesting + protected final HandlerThread mHandlerThread; + private final Handler mHandler; + + private final Context mContext; + private final Context mUserAllContext; + + private final Dependencies mDeps; + + private final ConnectivityManager mCm; + private final KeyStore mKeyStore; + private final INetworkManagementService mNMS; + private final INetd mNetd; + private final UserManager mUserManager; + + @VisibleForTesting + @GuardedBy("mVpns") + protected final SparseArray<Vpn> mVpns = new SparseArray<>(); + + // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by + // a direct call to LockdownVpnTracker.isEnabled(). + @GuardedBy("mVpns") + private boolean mLockdownEnabled; + @GuardedBy("mVpns") + private LockdownVpnTracker mLockdownTracker; + + /** + * Dependencies of VpnManager, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** Returns the calling UID of an IPC. */ + public int getCallingUid() { + return Binder.getCallingUid(); + } + + /** Creates a HandlerThread to be used by this class. */ + public HandlerThread makeHandlerThread() { + return new HandlerThread("VpnManagerService"); + } + + /** Returns the KeyStore instance to be used by this class. */ + public KeyStore getKeyStore() { + return KeyStore.getInstance(); + } + + public INetd getNetd() { + return NetdService.getInstance(); + } + + public INetworkManagementService getINetworkManagementService() { + return INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + } + } + + public VpnManagerService(Context context, Dependencies deps) { + mContext = context; + mDeps = deps; + mHandlerThread = mDeps.makeHandlerThread(); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + mKeyStore = mDeps.getKeyStore(); + mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); + mCm = mContext.getSystemService(ConnectivityManager.class); + mNMS = mDeps.getINetworkManagementService(); + mNetd = mDeps.getNetd(); + mUserManager = mContext.getSystemService(UserManager.class); + registerReceivers(); + log("VpnManagerService starting up"); + } + + /** Creates a new VpnManagerService */ + public static VpnManagerService create(Context context) { + return new VpnManagerService(context, new Dependencies()); + } + + /** Informs the service that the system is ready. */ + public void systemReady() { + // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait + // for user to unlock device too. + updateLockdownVpn(); + } + + @Override + /** Dumps service state. */ + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, + @Nullable String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println("VPNs:"); + pw.increaseIndent(); + synchronized (mVpns) { + for (int i = 0; i < mVpns.size(); i++) { + pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage()); + } + pw.decreaseIndent(); + } + } + + /** + * Prepare for a VPN application. + * 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 oldPackage Package name of the application which currently controls VPN, which will + * be replaced. If there is no such application, this should should either be + * {@code null} or {@link VpnConfig.LEGACY_VPN}. + * @param newPackage Package name of the application which should gain control of VPN, or + * {@code null} to disable. + * @param userId User for whom to prepare the new VPN. + * + * @hide + */ + @Override + public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, + int userId) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + throwIfLockdownEnabled(); + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE); + } else { + return false; + } + } + } + + /** + * 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 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, @VpnManager.VpnType int vpnType) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + vpn.setPackageAuthorization(packageName, vpnType); + } + } + } + + /** + * Configure a TUN interface and return its file descriptor. Parameters + * are encoded and opaque to this class. This method is used by VpnBuilder + * and not available in VpnManager. Permissions are checked in + * Vpn class. + * @hide + */ + @Override + public ParcelFileDescriptor establishVpn(VpnConfig config) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).establish(config); + } + } + + @Override + public boolean addVpnAddress(String address, int prefixLength) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).addAddress(address, prefixLength); + } + } + + @Override + public boolean removeVpnAddress(String address, int prefixLength) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + synchronized (mVpns) { + throwIfLockdownEnabled(); + return mVpns.get(user).removeAddress(address, prefixLength); + } + } + + @Override + public boolean setUnderlyingNetworksForVpn(Network[] networks) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + final boolean success; + synchronized (mVpns) { + success = mVpns.get(user).setUnderlyingNetworks(networks); + } + return success; + } + + /** + * 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(mDeps.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(mDeps.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(mDeps.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(mDeps.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. + */ + @Override + public void startLegacyVpn(VpnProfile profile) { + int user = UserHandle.getUserId(mDeps.getCallingUid()); + final LinkProperties egress = mCm.getActiveLinkProperties(); + if (egress == null) { + throw new IllegalStateException("Missing active network connection"); + } + synchronized (mVpns) { + throwIfLockdownEnabled(); + mVpns.get(user).startLegacyVpn(profile, mKeyStore, null /* underlying */, egress); + } + } + + /** + * Return the information of the ongoing legacy VPN. This method is used + * by VpnSettings and not available in ConnectivityManager. Permissions + * are checked in Vpn class. + */ + @Override + public LegacyVpnInfo getLegacyVpnInfo(int userId) { + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + return mVpns.get(userId).getLegacyVpnInfo(); + } + } + + /** + * Returns the information of the ongoing VPN for {@code userId}. This method is used by + * VpnDialogs and not available in ConnectivityManager. + * Permissions are checked in Vpn class. + * @hide + */ + @Override + public VpnConfig getVpnConfig(int userId) { + enforceCrossUserPermission(userId); + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn != null) { + return vpn.getVpnConfig(); + } else { + return null; + } + } + } + + private boolean isLockdownVpnEnabled() { + return mKeyStore.contains(Credentials.LOCKDOWN_VPN); + } + + @Override + public boolean updateLockdownVpn() { + // Allow the system UID for the system server and for Settings. + // Also, for unit tests, allow the process that ConnectivityService is running in. + if (mDeps.getCallingUid() != Process.SYSTEM_UID + && Binder.getCallingPid() != Process.myPid()) { + logw("Lockdown VPN only available to system process or AID_SYSTEM"); + return false; + } + + synchronized (mVpns) { + // Tear down existing lockdown if profile was removed + mLockdownEnabled = isLockdownVpnEnabled(); + if (!mLockdownEnabled) { + setLockdownTracker(null); + return true; + } + + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + loge("Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + loge("Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(mDeps.getCallingUid()); + Vpn vpn = mVpns.get(user); + if (vpn == null) { + logw("VPN for user " + user + " not ready yet. Skipping lockdown"); + return false; + } + setLockdownTracker( + new LockdownVpnTracker(mContext, mHandler, mKeyStore, vpn, profile)); + } + + return true; + } + + /** + * Internally set new {@link LockdownVpnTracker}, shutting down any existing + * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. + */ + @GuardedBy("mVpns") + private void setLockdownTracker(LockdownVpnTracker tracker) { + // Shutdown any existing tracker + final LockdownVpnTracker existing = mLockdownTracker; + // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the + // necessary onBlockedStatusChanged callbacks. + mLockdownTracker = null; + if (existing != null) { + existing.shutdown(); + } + + if (tracker != null) { + mLockdownTracker = tracker; + mLockdownTracker.init(); + } + } + + /** + * 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) { + throw new IllegalStateException("Unavailable in lockdown mode"); + } + } + + /** + * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform + * some setup and then call {@code establish()} to connect. + * + * @return {@code true} if the service was started, the service was already connected, or there + * was no always-on VPN to start. {@code false} otherwise. + */ + private boolean startAlwaysOnVpn(int userId) { + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + // Shouldn't happen as all code paths that point here should have checked the Vpn + // exists already. + Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); + return false; + } + + return vpn.startAlwaysOnVpn(mKeyStore); + } + } + + @Override + public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { + enforceSettingsPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); + } + } + + @Override + public boolean setAlwaysOnVpnPackage( + int userId, String packageName, boolean lockdown, List<String> lockdownAllowlist) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + // Can't set always-on VPN if legacy VPN is already in lockdown mode. + if (isLockdownVpnEnabled()) { + return false; + } + + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist, mKeyStore)) { + return false; + } + if (!startAlwaysOnVpn(userId)) { + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + return false; + } + } + return true; + } + + @Override + public String getAlwaysOnVpnPackage(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getAlwaysOnPackage(); + } + } + + @Override + public boolean isVpnLockdownEnabled(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return false; + } + return vpn.getLockdown(); + } + } + + @Override + public List<String> getVpnLockdownAllowlist(int userId) { + enforceControlAlwaysOnVpnPermission(); + enforceCrossUserPermission(userId); + + synchronized (mVpns) { + Vpn vpn = mVpns.get(userId); + if (vpn == null) { + logw("User " + userId + " has no Vpn configuration"); + return null; + } + return vpn.getLockdownAllowlist(); + } + } + + @GuardedBy("mVpns") + private Vpn getVpnIfOwner() { + return getVpnIfOwner(mDeps.getCallingUid()); + } + + // TODO: stop calling into Vpn.java and get this information from data in this class. + @GuardedBy("mVpns") + private Vpn getVpnIfOwner(int uid) { + final int user = UserHandle.getUserId(uid); + + final Vpn vpn = mVpns.get(user); + if (vpn == null) { + return null; + } else { + final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); + return (info == null || info.ownerUid != uid) ? null : vpn; + } + } + + private void registerReceivers() { + // Set up the listener for user state for creating user VPNs. + // Should run on mHandler to avoid any races. + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_STARTED); + intentFilter.addAction(Intent.ACTION_USER_STOPPED); + intentFilter.addAction(Intent.ACTION_USER_ADDED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); + + mUserAllContext.registerReceiver( + mIntentReceiver, + intentFilter, + null /* broadcastPermission */, + mHandler); + mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( + mUserPresentReceiver, + new IntentFilter(Intent.ACTION_USER_PRESENT), + null /* broadcastPermission */, + mHandler /* scheduler */); + + // Listen to package add and removal events for all users. + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mUserAllContext.registerReceiver( + mIntentReceiver, + intentFilter, + null /* broadcastPermission */, + mHandler); + + // Listen to lockdown VPN reset. + intentFilter = new IntentFilter(); + intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); + mUserAllContext.registerReceiver( + mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); + } + + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ensureRunningOnHandlerThread(); + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final Uri packageData = intent.getData(); + final String packageName = + packageData != null ? packageData.getSchemeSpecificPart() : null; + + if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { + onVpnLockdownReset(); + } + + // UserId should be filled for below intents, check the existence. + if (userId == UserHandle.USER_NULL) return; + + if (Intent.ACTION_USER_STARTED.equals(action)) { + onUserStarted(userId); + } else if (Intent.ACTION_USER_STOPPED.equals(action)) { + onUserStopped(userId); + } else if (Intent.ACTION_USER_ADDED.equals(action)) { + onUserAdded(userId); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + onUserUnlocked(userId); + } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { + onPackageReplaced(packageName, uid); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + final boolean isReplacing = intent.getBooleanExtra( + Intent.EXTRA_REPLACING, false); + onPackageRemoved(packageName, uid, isReplacing); + } else { + Log.wtf(TAG, "received unexpected intent: " + action); + } + } + }; + + private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ensureRunningOnHandlerThread(); + // Try creating lockdown tracker, since user present usually means + // unlocked keystore. + updateLockdownVpn(); + // Use the same context that registered receiver before to unregister it. Because use + // different context to unregister receiver will cause exception. + context.unregisterReceiver(this); + } + }; + + private void onUserStarted(int userId) { + synchronized (mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn != null) { + loge("Starting user already has a VPN"); + return; + } + userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, mKeyStore); + mVpns.put(userId, userVpn); + if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { + updateLockdownVpn(); + } + } + } + + private void onUserStopped(int userId) { + synchronized (mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn == null) { + loge("Stopped user has no VPN"); + return; + } + userVpn.onUserStopped(); + mVpns.delete(userId); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getAlwaysOn(); + } + } + + @Override + public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { + synchronized (mVpns) { + Vpn vpn = getVpnIfOwner(); + return vpn != null && vpn.getLockdown(); + } + } + + + private void onUserAdded(int userId) { + synchronized (mVpns) { + final int vpnsSize = mVpns.size(); + for (int i = 0; i < vpnsSize; i++) { + Vpn vpn = mVpns.valueAt(i); + vpn.onUserAdded(userId); + } + } + } + + private void onUserRemoved(int userId) { + synchronized (mVpns) { + final int vpnsSize = mVpns.size(); + for (int i = 0; i < vpnsSize; i++) { + Vpn vpn = mVpns.valueAt(i); + vpn.onUserRemoved(userId); + } + } + } + + private void onPackageReplaced(String packageName, int uid) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); + return; + } + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { + log("Restarting always-on VPN package " + packageName + " for user " + + userId); + vpn.startAlwaysOnVpn(mKeyStore); + } + } + } + + private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); + return; + } + + final int userId = UserHandle.getUserId(uid); + synchronized (mVpns) { + final Vpn vpn = mVpns.get(userId); + if (vpn == null) { + return; + } + // Legacy always-on VPN won't be affected since the package name is not set. + if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { + log("Removing always-on VPN package " + packageName + " for user " + + userId); + vpn.setAlwaysOnPackage(null, false, null, mKeyStore); + } + } + } + + private void onUserUnlocked(int userId) { + synchronized (mVpns) { + // User present may be sent because of an unlock, which might mean an unlocked keystore. + if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { + updateLockdownVpn(); + } else { + startAlwaysOnVpn(userId); + } + } + } + + private void onVpnLockdownReset() { + synchronized (mVpns) { + if (mLockdownTracker != null) mLockdownTracker.reset(); + } + } + + + @Override + public void factoryReset() { + enforceSettingsPermission(); + + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET) + || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { + return; + } + + // Remove always-on package + final int userId = UserHandle.getCallingUserId(); + synchronized (mVpns) { + final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); + if (alwaysOnPackage != null) { + setAlwaysOnVpnPackage(userId, null, false, null); + setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); + } + + // Turn Always-on VPN off + if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { + final long ident = Binder.clearCallingIdentity(); + try { + mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mLockdownEnabled = false; + setLockdownTracker(null); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(userId); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); + } else { + // Prevent this app (packagename = vpnConfig.user) from initiating + // VPN connections in the future without user intervention. + setVpnPackageAuthorization( + vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); + + prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + } + } + } + } + + private void ensureRunningOnHandlerThread() { + if (mHandler.getLooper().getThread() != Thread.currentThread()) { + throw new IllegalStateException( + "Not running on VpnManagerService thread: " + + Thread.currentThread().getName()); + } + } + + private void enforceControlAlwaysOnVpnPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, + "VpnManagerService"); + } + + /** + * Require that the caller is either in the same user or has appropriate permission to interact + * across users. + * + * @param userId Target user for whatever operation the current IPC is supposed to perform. + */ + private void enforceCrossUserPermission(int userId) { + if (userId == UserHandle.getCallingUserId()) { + // Not a cross-user call. + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "VpnManagerService"); + } + + private void enforceSettingsPermission() { + enforceAnyPermissionOf(mContext, + android.Manifest.permission.NETWORK_SETTINGS, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + + private static void log(String s) { + Log.d(TAG, s); + } + + private static void logw(String s) { + Log.w(TAG, s); + } + + private static void loge(String s) { + Log.e(TAG, s); + } +} diff --git a/services/core/java/com/android/server/accounts/OWNERS b/services/core/java/com/android/server/accounts/OWNERS index ea5fd36702f9..8dcc04a27af6 100644 --- a/services/core/java/com/android/server/accounts/OWNERS +++ b/services/core/java/com/android/server/accounts/OWNERS @@ -3,7 +3,6 @@ dementyev@google.com sandrakwan@google.com hackbod@google.com svetoslavganov@google.com -moltmann@google.com fkupolov@google.com yamasani@google.com omakoto@google.com diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8f1e4e945bcb..126d04f794f5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13318,6 +13318,7 @@ public class ActivityManagerService extends IActivityManager.Stub long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long[] miscRss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; + long[] memtrackTmp = new long[4]; long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length]; @@ -13330,6 +13331,8 @@ public class ActivityManagerService extends IActivityManager.Stub long totalRss = 0; long cachedPss = 0; long cachedSwapPss = 0; + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; boolean hasSwapPss = false; Debug.MemoryInfo mi = null; @@ -13352,6 +13355,8 @@ public class ActivityManagerService extends IActivityManager.Stub final int reportType; final long startTime; final long endTime; + long memtrackGraphics = 0; + long memtrackGl = 0; if (opts.dumpDetails || (!brief && !opts.oomOnly)) { reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW; startTime = SystemClock.currentThreadTimeMillis(); @@ -13363,7 +13368,7 @@ public class ActivityManagerService extends IActivityManager.Stub } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); - long pss = Debug.getPss(pid, tmpLong, null); + long pss = Debug.getPss(pid, tmpLong, memtrackTmp); if (pss == 0) { continue; } @@ -13371,6 +13376,8 @@ public class ActivityManagerService extends IActivityManager.Stub endTime = SystemClock.currentThreadTimeMillis(); mi.dalvikPrivateDirty = (int) tmpLong[0]; mi.dalvikRss = (int) tmpLong[2]; + memtrackGraphics = memtrackTmp[1]; + memtrackGl = memtrackTmp[2]; } if (!opts.isCheckinRequest && opts.dumpDetails) { pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **"); @@ -13435,6 +13442,8 @@ public class ActivityManagerService extends IActivityManager.Stub totalPss += myTotalPss; totalSwapPss += myTotalSwapPss; totalRss += myTotalRss; + totalMemtrackGraphics += memtrackGraphics; + totalMemtrackGl += memtrackGl; MemItem pssItem = new MemItem(r.processName + " (pid " + pid + (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss, myTotalSwapPss, myTotalRss, pid, hasActivities); @@ -13500,6 +13509,8 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=0; i<N; i++) { ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + long memtrackGraphics = 0; + long memtrackGl = 0; if (mi == null) { mi = new Debug.MemoryInfo(); } @@ -13508,13 +13519,15 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } } else { - long pss = Debug.getPss(st.pid, tmpLong, null); + long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp); if (pss == 0) { continue; } mi.nativePss = (int) pss; mi.nativePrivateDirty = (int) tmpLong[0]; mi.nativeRss = (int) tmpLong[2]; + memtrackGraphics = memtrackTmp[1]; + memtrackGl = memtrackTmp[2]; } final long myTotalPss = mi.getTotalPss(); @@ -13524,6 +13537,8 @@ public class ActivityManagerService extends IActivityManager.Stub totalSwapPss += myTotalSwapPss; totalRss += myTotalRss; nativeProcTotalPss += myTotalPss; + totalMemtrackGraphics += memtrackGraphics; + totalMemtrackGl += memtrackGl; MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", st.name, myTotalPss, mi.getSummaryTotalSwapPss(), myTotalRss, @@ -13720,13 +13735,13 @@ public class ActivityManagerService extends IActivityManager.Stub long kernelUsed = memInfo.getKernelUsedSizeKb(); final long ionHeap = Debug.getIonHeapsSizeKb(); final long ionPool = Debug.getIonPoolsSizeKb(); + final long dmabufMapped = Debug.getDmabufMappedSizeKb(); if (ionHeap >= 0 && ionPool >= 0) { - final long ionMapped = Debug.getIonMappedSizeKb(); - final long ionUnmapped = ionHeap - ionMapped; + final long ionUnmapped = ionHeap - dmabufMapped; pw.print(" ION: "); pw.print(stringifyKBSize(ionHeap + ionPool)); pw.print(" ("); - pw.print(stringifyKBSize(ionMapped)); + pw.print(stringifyKBSize(dmabufMapped)); pw.print(" mapped + "); pw.print(stringifyKBSize(ionUnmapped)); pw.print(" unmapped + "); @@ -13735,11 +13750,52 @@ public class ActivityManagerService extends IActivityManager.Stub // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being // set on ION VMAs, therefore consider the entire ION heap as used kernel memory kernelUsed += ionHeap; + } else { + final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); + if (totalExportedDmabuf >= 0) { + final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; + pw.print("DMA-BUF: "); + pw.print(stringifyKBSize(totalExportedDmabuf)); + pw.print(" ("); + pw.print(stringifyKBSize(dmabufMapped)); + pw.print(" mapped + "); + pw.print(stringifyKBSize(dmabufUnmapped)); + pw.println(" unmapped)"); + // Account unmapped dmabufs as part of kernel memory allocations + kernelUsed += dmabufUnmapped; + // Replace memtrack HAL reported Graphics category with mapped dmabufs + totalPss -= totalMemtrackGraphics; + totalPss += dmabufMapped; + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); + if (totalDmabufHeapPool >= 0) { + pw.print("DMA-BUF Heaps pool: "); + pw.println(stringifyKBSize(totalDmabufHeapPool)); + } } final long gpuUsage = Debug.getGpuTotalUsageKb(); if (gpuUsage >= 0) { - pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage)); + final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb(); + if (gpuDmaBufUsage >= 0) { + final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage; + pw.print(" GPU: "); + pw.print(stringifyKBSize(gpuUsage)); + pw.print(" ("); + pw.print(stringifyKBSize(gpuDmaBufUsage)); + pw.print(" dmabuf + "); + pw.print(stringifyKBSize(gpuPrivateUsage)); + pw.println(" private)"); + // Replace memtrack HAL reported GL category with private GPU allocations and + // account it as part of kernel memory allocations + totalPss -= totalMemtrackGl; + kernelUsed += gpuPrivateUsage; + } else { + pw.print(" GPU: "); pw.println(stringifyKBSize(gpuUsage)); + } } + + // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of + // memInfo.getCachedSizeKb(). final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() - kernelUsed - memInfo.getZramTotalSizeKb(); @@ -14343,7 +14399,7 @@ public class ActivityManagerService extends IActivityManager.Stub infoMap.put(mi.pid, mi); } updateCpuStatsNow(); - long[] memtrackTmp = new long[1]; + long[] memtrackTmp = new long[4]; long[] swaptrackTmp = new long[2]; final List<ProcessCpuTracker.Stats> stats; // Get a list of Stats that have vsize > 0 @@ -14371,6 +14427,8 @@ public class ActivityManagerService extends IActivityManager.Stub long totalPss = 0; long totalSwapPss = 0; long totalMemtrack = 0; + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; for (int i=0, N=memInfos.size(); i<N; i++) { ProcessMemInfo mi = memInfos.get(i); if (mi.pss == 0) { @@ -14381,6 +14439,8 @@ public class ActivityManagerService extends IActivityManager.Stub totalPss += mi.pss; totalSwapPss += mi.swapPss; totalMemtrack += mi.memtrack; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; } Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { @@ -14539,25 +14599,64 @@ public class ActivityManagerService extends IActivityManager.Stub final long ionHeap = Debug.getIonHeapsSizeKb(); final long ionPool = Debug.getIonPoolsSizeKb(); if (ionHeap >= 0 && ionPool >= 0) { - final long ionMapped = Debug.getIonMappedSizeKb(); - final long ionUnmapped = ionHeap - ionMapped; memInfoBuilder.append(" ION: "); memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool)); memInfoBuilder.append("\n"); // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being // set on ION VMAs, therefore consider the entire ION heap as used kernel memory kernelUsed += ionHeap; + } else { + final long totalExportedDmabuf = Debug.getDmabufTotalExportedKb(); + if (totalExportedDmabuf >= 0) { + final long dmabufMapped = Debug.getDmabufMappedSizeKb(); + final long dmabufUnmapped = totalExportedDmabuf - dmabufMapped; + memInfoBuilder.append("DMA-BUF: "); + memInfoBuilder.append(stringifyKBSize(totalExportedDmabuf)); + memInfoBuilder.append("\n"); + // Account unmapped dmabufs as part of kernel memory allocations + kernelUsed += dmabufUnmapped; + // Replace memtrack HAL reported Graphics category with mapped dmabufs + totalPss -= totalMemtrackGraphics; + totalPss += dmabufMapped; + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); + if (totalDmabufHeapPool >= 0) { + memInfoBuilder.append("DMA-BUF Heaps pool: "); + memInfoBuilder.append(stringifyKBSize(totalDmabufHeapPool)); + memInfoBuilder.append("\n"); + } } + final long gpuUsage = Debug.getGpuTotalUsageKb(); if (gpuUsage >= 0) { - memInfoBuilder.append(" GPU: "); - memInfoBuilder.append(stringifyKBSize(gpuUsage)); - memInfoBuilder.append("\n"); + final long gpuDmaBufUsage = Debug.getGpuDmaBufUsageKb(); + if (gpuDmaBufUsage >= 0) { + final long gpuPrivateUsage = gpuUsage - gpuDmaBufUsage; + memInfoBuilder.append(" GPU: "); + memInfoBuilder.append(stringifyKBSize(gpuUsage)); + memInfoBuilder.append(" ("); + memInfoBuilder.append(stringifyKBSize(gpuDmaBufUsage)); + memInfoBuilder.append(" dmabuf + "); + memInfoBuilder.append(stringifyKBSize(gpuPrivateUsage)); + memInfoBuilder.append(" private)\n"); + // Replace memtrack HAL reported GL category with private GPU allocations and + // account it as part of kernel memory allocations + totalPss -= totalMemtrackGl; + kernelUsed += gpuPrivateUsage; + } else { + memInfoBuilder.append(" GPU: "); + memInfoBuilder.append(stringifyKBSize(gpuUsage)); + memInfoBuilder.append("\n"); + } + } memInfoBuilder.append(" Used RAM: "); memInfoBuilder.append(stringifyKBSize( totalPss - cachedPss + kernelUsed)); memInfoBuilder.append("\n"); + + // Note: ION/DMA-BUF heap pools are reclaimable and hence, they are included as part of + // memInfo.getCachedSizeKb(). memInfoBuilder.append(" Lost RAM: "); memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index e3757dfc6a59..df83df9a73fb 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -16,15 +16,24 @@ package com.android.server.compat; +import static android.app.compat.PackageOverride.VALUE_DISABLED; +import static android.app.compat.PackageOverride.VALUE_ENABLED; +import static android.app.compat.PackageOverride.VALUE_UNDEFINED; + import android.annotation.Nullable; +import android.app.compat.PackageOverride; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; +import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; import com.android.server.compat.overrides.ChangeOverrides; import com.android.server.compat.overrides.OverrideValue; +import com.android.server.compat.overrides.RawOverrideValue; import java.util.HashMap; import java.util.List; @@ -36,7 +45,7 @@ import java.util.Map; * <p>A compatibility change has a default setting, determined by the {@code enableAfterTargetSdk} * and {@code disabled} constructor parameters. If a change is {@code disabled}, this overrides any * target SDK criteria set. These settings can be overridden for a specific package using - * {@link #addPackageOverride(String, boolean)}. + * {@link #addPackageOverrideInternal(String, boolean)}. * * <p>Note, this class is not thread safe so callers must ensure thread safety. */ @@ -63,8 +72,8 @@ public final class CompatChange extends CompatibilityChangeInfo { ChangeListener mListener = null; - private Map<String, Boolean> mPackageOverrides; - private Map<String, Boolean> mDeferredOverrides; + private Map<String, Boolean> mEvaluatedOverrides; + private Map<String, PackageOverride> mRawOverrides; public CompatChange(long changeId) { this(changeId, null, -1, -1, false, false, null, false); @@ -113,18 +122,26 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param pname Package name to enable the change for. * @param enabled Whether or not to enable the change. */ - void addPackageOverride(String pname, boolean enabled) { + private void addPackageOverrideInternal(String pname, boolean enabled) { if (getLoggingOnly()) { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + if (mEvaluatedOverrides == null) { + mEvaluatedOverrides = new HashMap<>(); } - mPackageOverrides.put(pname, enabled); + mEvaluatedOverrides.put(pname, enabled); notifyListener(pname); } + private void removePackageOverrideInternal(String pname) { + if (mEvaluatedOverrides != null) { + if (mEvaluatedOverrides.remove(pname) != null) { + notifyListener(pname); + } + } + } + /** * Tentatively set the state of this change for a given package name. * The override will only take effect after that package is installed, if applicable. @@ -132,17 +149,19 @@ public final class CompatChange extends CompatibilityChangeInfo { * <p>Note, this method is not thread safe so callers must ensure thread safety. * * @param packageName Package name to tentatively enable the change for. - * @param enabled Whether or not to enable the change. + * @param override The package override to be set */ - void addPackageDeferredOverride(String packageName, boolean enabled) { + void addPackageOverride(String packageName, PackageOverride override, + OverrideAllowedState allowedState, Context context) { if (getLoggingOnly()) { throw new IllegalArgumentException( "Can't add overrides for a logging only change " + toString()); } - if (mDeferredOverrides == null) { - mDeferredOverrides = new HashMap<>(); + if (mRawOverrides == null) { + mRawOverrides = new HashMap<>(); } - mDeferredOverrides.put(packageName, enabled); + mRawOverrides.put(packageName, override); + recheckOverride(packageName, allowedState, context); } /** @@ -157,24 +176,44 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the recheck yielded a result that requires invalidating caches * (a deferred override was consolidated or a regular override was removed). */ - boolean recheckOverride(String packageName, boolean allowed) { - // A deferred override now is allowed by the policy, so promote it to a regular override. - if (hasDeferredOverride(packageName) && allowed) { - boolean overrideValue = mDeferredOverrides.remove(packageName); - addPackageOverride(packageName, overrideValue); - return true; + boolean recheckOverride(String packageName, OverrideAllowedState allowedState, + Context context) { + boolean allowed = (allowedState.state == OverrideAllowedState.ALLOWED); + + Long version = null; + try { + ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo( + packageName, 0); + version = applicationInfo.longVersionCode; + } catch (PackageManager.NameNotFoundException e) { + // Do nothing } - // A previously set override is no longer allowed by the policy, so make it deferred. - if (hasOverride(packageName) && !allowed) { - boolean overrideValue = mPackageOverrides.remove(packageName); - addPackageDeferredOverride(packageName, overrideValue); - // Notify because the override was removed. - notifyListener(packageName); - return true; + + // If the app is not installed or no longer has raw overrides, evaluate to false + if (version == null || !hasRawOverride(packageName) || !allowed) { + removePackageOverrideInternal(packageName); + return false; } - return false; + + // Evaluate the override based on its version + int overrideValue = mRawOverrides.get(packageName).evaluate(version); + switch (overrideValue) { + case VALUE_UNDEFINED: + removePackageOverrideInternal(packageName); + break; + case VALUE_ENABLED: + addPackageOverrideInternal(packageName, true); + break; + case VALUE_DISABLED: + addPackageOverrideInternal(packageName, false); + break; + } + return true; } + boolean hasPackageOverride(String pname) { + return mRawOverrides != null && mRawOverrides.containsKey(pname); + } /** * Remove any package override for the given package name, restoring the default behaviour. * @@ -182,15 +221,13 @@ public final class CompatChange extends CompatibilityChangeInfo { * * @param pname Package name to reset to defaults for. */ - void removePackageOverride(String pname) { - if (mPackageOverrides != null) { - if (mPackageOverrides.remove(pname) != null) { - notifyListener(pname); - } - } - if (mDeferredOverrides != null) { - mDeferredOverrides.remove(pname); + boolean removePackageOverride(String pname, OverrideAllowedState allowedState, + Context context) { + if (mRawOverrides != null && (mRawOverrides.remove(pname) != null)) { + recheckOverride(pname, allowedState, context); + return true; } + return false; } /** @@ -204,8 +241,8 @@ public final class CompatChange extends CompatibilityChangeInfo { if (app == null) { return defaultValue(); } - if (mPackageOverrides != null && mPackageOverrides.containsKey(app.packageName)) { - return mPackageOverrides.get(app.packageName); + if (mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(app.packageName)) { + return mEvaluatedOverrides.get(app.packageName); } if (getDisabled()) { return false; @@ -223,8 +260,16 @@ public final class CompatChange extends CompatibilityChangeInfo { * @return {@code true} if the change should be enabled for the package. */ boolean willBeEnabled(String packageName) { - if (hasDeferredOverride(packageName)) { - return mDeferredOverrides.get(packageName); + if (hasRawOverride(packageName)) { + int eval = mRawOverrides.get(packageName).evaluateForAllVersions(); + switch (eval) { + case VALUE_ENABLED: + return true; + case VALUE_DISABLED: + return false; + case VALUE_UNDEFINED: + return defaultValue(); + } } return defaultValue(); } @@ -243,8 +288,8 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param packageName name of the package * @return true if there is such override */ - boolean hasOverride(String packageName) { - return mPackageOverrides != null && mPackageOverrides.containsKey(packageName); + private boolean hasOverride(String packageName) { + return mEvaluatedOverrides != null && mEvaluatedOverrides.containsKey(packageName); } /** @@ -252,65 +297,77 @@ public final class CompatChange extends CompatibilityChangeInfo { * @param packageName name of the package * @return true if there is such a deferred override */ - boolean hasDeferredOverride(String packageName) { - return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName); - } - - /** - * Checks whether a change has any package overrides. - * @return true if the change has at least one deferred override - */ - boolean hasAnyPackageOverride() { - return mDeferredOverrides != null && !mDeferredOverrides.isEmpty(); - } - - /** - * Checks whether a change has any deferred overrides. - * @return true if the change has at least one deferred override - */ - boolean hasAnyDeferredOverride() { - return mPackageOverrides != null && !mPackageOverrides.isEmpty(); + private boolean hasRawOverride(String packageName) { + return mRawOverrides != null && mRawOverrides.containsKey(packageName); } void loadOverrides(ChangeOverrides changeOverrides) { - if (mDeferredOverrides == null) { - mDeferredOverrides = new HashMap<>(); + if (mRawOverrides == null) { + mRawOverrides = new HashMap<>(); } - mDeferredOverrides.clear(); - for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { - mDeferredOverrides.put(override.getPackageName(), override.getEnabled()); + mRawOverrides.clear(); + + if (mEvaluatedOverrides == null) { + mEvaluatedOverrides = new HashMap<>(); } + mEvaluatedOverrides.clear(); - if (mPackageOverrides == null) { - mPackageOverrides = new HashMap<>(); + // Load deferred overrides for backwards compatibility + if (changeOverrides.getDeferred() != null) { + for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { + mRawOverrides.put(override.getPackageName(), + new PackageOverride.Builder().setEnabled( + override.getEnabled()).build()); + } + } + + // Load validated overrides. For backwards compatibility, we also add them to raw overrides. + if (changeOverrides.getValidated() != null) { + for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { + mEvaluatedOverrides.put(override.getPackageName(), override.getEnabled()); + mRawOverrides.put(override.getPackageName(), + new PackageOverride.Builder().setEnabled( + override.getEnabled()).build()); + } } - mPackageOverrides.clear(); - for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { - mPackageOverrides.put(override.getPackageName(), override.getEnabled()); + + // Load raw overrides + if (changeOverrides.getRaw() != null) { + for (RawOverrideValue override : changeOverrides.getRaw().getRawOverrideValue()) { + PackageOverride packageOverride = new PackageOverride.Builder() + .setMinVersionCode(override.getMinVersionCode()) + .setMaxVersionCode(override.getMaxVersionCode()) + .setEnabled(override.getEnabled()) + .build(); + mRawOverrides.put(override.getPackageName(), packageOverride); + } } } ChangeOverrides saveOverrides() { - if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) { + if (mRawOverrides == null || mRawOverrides.isEmpty()) { return null; } ChangeOverrides changeOverrides = new ChangeOverrides(); changeOverrides.setChangeId(getId()); - ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred(); - List<OverrideValue> deferredList = deferredOverrides.getOverrideValue(); - if (mDeferredOverrides != null) { - for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) { - OverrideValue override = new OverrideValue(); + ChangeOverrides.Raw rawOverrides = new ChangeOverrides.Raw(); + List<RawOverrideValue> rawList = rawOverrides.getRawOverrideValue(); + if (mRawOverrides != null) { + for (Map.Entry<String, PackageOverride> entry : mRawOverrides.entrySet()) { + RawOverrideValue override = new RawOverrideValue(); override.setPackageName(entry.getKey()); - override.setEnabled(entry.getValue()); - deferredList.add(override); + override.setMinVersionCode(entry.getValue().getMinVersionCode()); + override.setMaxVersionCode(entry.getValue().getMaxVersionCode()); + override.setEnabled(entry.getValue().getEnabled()); + rawList.add(override); } } - changeOverrides.setDeferred(deferredOverrides); + changeOverrides.setRaw(rawOverrides); + ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated(); List<OverrideValue> validatedList = validatedOverrides.getOverrideValue(); - if (mPackageOverrides != null) { - for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) { + if (mEvaluatedOverrides != null) { + for (Map.Entry<String, Boolean> entry : mEvaluatedOverrides.entrySet()) { OverrideValue override = new OverrideValue(); override.setPackageName(entry.getKey()); override.setEnabled(entry.getValue()); @@ -337,11 +394,11 @@ public final class CompatChange extends CompatibilityChangeInfo { if (getLoggingOnly()) { sb.append("; loggingOnly"); } - if (mPackageOverrides != null && mPackageOverrides.size() > 0) { - sb.append("; packageOverrides=").append(mPackageOverrides); + if (mEvaluatedOverrides != null && mEvaluatedOverrides.size() > 0) { + sb.append("; packageOverrides=").append(mEvaluatedOverrides); } - if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) { - sb.append("; deferredOverrides=").append(mDeferredOverrides); + if (mRawOverrides != null && mRawOverrides.size() > 0) { + sb.append("; rawOverrides=").append(mRawOverrides); } if (getOverridable()) { sb.append("; overridable"); diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 6b77b9d4ce39..422991e082a9 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -17,6 +17,7 @@ package com.android.server.compat; import android.app.compat.ChangeIdStateCache; +import android.app.compat.PackageOverride; import android.compat.Compatibility.ChangeConfig; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -31,6 +32,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; @@ -70,11 +72,13 @@ final class CompatConfig { private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); private final OverrideValidatorImpl mOverrideValidator; + private Context mContext; private File mOverridesFile; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this); + mContext = context; } static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) { @@ -210,17 +214,33 @@ final class CompatConfig { * @throws IllegalStateException if overriding is not allowed */ boolean addOverride(long changeId, String packageName, boolean enabled) { - boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled); + boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(enabled).build()); saveOverrides(); invalidateCache(); return alreadyKnown; } /** - * Unsafe version of {@link #addOverride(long, String, boolean)}. - * It does not invalidate the cache nor save the overrides. + * Overrides the enabled state for a given change and app. + * + * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. + * + * @param overrides list of overrides to default changes config. + * @param packageName app for which the overrides will be applied. */ - private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) { + void addOverrides(CompatibilityOverrideConfig overrides, String packageName) { + synchronized (mChanges) { + for (Long changeId : overrides.overrides.keySet()) { + addOverrideUnsafe(changeId, packageName, overrides.overrides.get(changeId)); + } + saveOverrides(); + invalidateCache(); + } + } + + private boolean addOverrideUnsafe(long changeId, String packageName, + PackageOverride overrides) { boolean alreadyKnown = true; OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); @@ -232,17 +252,8 @@ final class CompatConfig { c = new CompatChange(changeId); addChange(c); } - switch (allowedState.state) { - case OverrideAllowedState.ALLOWED: - c.addPackageOverride(packageName, enabled); - break; - case OverrideAllowedState.DEFERRED_VERIFICATION: - c.addPackageDeferredOverride(packageName, enabled); - break; - default: - throw new IllegalStateException("Should only be able to override changes that " - + "are allowed or can be deferred."); - } + c.addPackageOverride(packageName, overrides, allowedState, mContext); + invalidateCache(); } return alreadyKnown; } @@ -311,47 +322,20 @@ final class CompatConfig { * It does not invalidate the cache nor save the overrides. */ private boolean removeOverrideUnsafe(long changeId, String packageName) { - boolean overrideExists = false; synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c != null) { - // Always allow removing a deferred override. - if (c.hasDeferredOverride(packageName)) { - c.removePackageOverride(packageName); - overrideExists = true; - } else if (c.hasOverride(packageName)) { - // Regular overrides need to pass the policy. - overrideExists = true; - OverrideAllowedState allowedState = - mOverrideValidator.getOverrideAllowedState(changeId, packageName); + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + if (c.hasPackageOverride(packageName)) { allowedState.enforce(changeId, packageName); - c.removePackageOverride(packageName); + c.removePackageOverride(packageName, allowedState, mContext); + invalidateCache(); + return true; } } } - return overrideExists; - } - - /** - * Overrides the enabled state for a given change and app. - * - * <p>Note: package overrides are not persistent and will be lost on system or runtime restart. - * - * @param overrides list of overrides to default changes config - * @param packageName app for which the overrides will be applied - */ - void addOverrides(CompatibilityChangeConfig overrides, String packageName) { - synchronized (mChanges) { - for (Long changeId : overrides.enabledChanges()) { - addOverrideUnsafe(changeId, packageName, true); - } - for (Long changeId : overrides.disabledChanges()) { - addOverrideUnsafe(changeId, packageName, false); - - } - saveOverrides(); - invalidateCache(); - } + return false; } /** @@ -402,7 +386,8 @@ final class CompatConfig { int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(true).build()); } saveOverrides(); invalidateCache(); @@ -418,7 +403,8 @@ final class CompatConfig { int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverrideUnsafe(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, + new PackageOverride.Builder().setEnabled(false).build()); } saveOverrides(); invalidateCache(); @@ -615,8 +601,7 @@ final class CompatConfig { CompatChange c = mChanges.valueAt(idx); OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(c.getId(), packageName); - boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED); - shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride); + shouldInvalidateCache |= c.recheckOverride(packageName, allowedState, mContext); } if (shouldInvalidateCache) { invalidateCache(); diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 6b2a1c950e38..edfc8b8f31b0 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -25,6 +25,7 @@ import static android.os.Process.SYSTEM_UID; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; +import android.app.compat.PackageOverride; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,6 +44,7 @@ import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.ChangeReporter; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.IPlatformCompat; import com.android.internal.util.DumpUtils; @@ -51,6 +53,8 @@ import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * System server internal API for gating and reporting compatibility changes. @@ -161,6 +165,22 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { checkCompatChangeOverridePermission(); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); + for (long change : overrides.enabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); + } + for (long change : overrides.disabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) + .build()); + } + mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); + killPackage(packageName); + } + + @Override + public void setOverridesFromInstaller(CompatibilityOverrideConfig overrides, + String packageName) { + checkCompatChangeOverridePermission(); mCompatConfig.addOverrides(overrides, packageName); killPackage(packageName); } @@ -168,7 +188,15 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { checkCompatChangeOverridePermission(); - mCompatConfig.addOverrides(overrides, packageName); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); + for (long change : overrides.enabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); + } + for (long change : overrides.disabledChanges()) { + overridesMap.put(change, new PackageOverride.Builder().setEnabled(false) + .build()); + } + mCompatConfig.addOverrides(new CompatibilityOverrideConfig(overridesMap), packageName); } @Override diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 397af7ba2991..61b11a5851a9 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -16,7 +16,6 @@ package com.android.server.connectivity; -import static android.net.NetworkCapabilities.MAX_TRANSPORT; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -25,15 +24,14 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; -import android.net.ConnectivityManager; import android.net.ConnectivityMetricsEvent; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; +import android.net.metrics.ConnectStats; import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.DhcpClientEvent; import android.net.metrics.DhcpErrorEvent; import android.net.metrics.DnsEvent; -import android.net.metrics.ConnectStats; import android.net.metrics.IpManagerEvent; import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; @@ -41,12 +39,13 @@ import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.net.metrics.WakeupStats; import android.os.Parcelable; -import android.util.SparseArray; import android.util.SparseIntArray; + import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair; + import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -361,29 +360,22 @@ final public class IpConnectivityEventBuilder { return IpConnectivityLogClass.UNKNOWN; case 1: int t = Long.numberOfTrailingZeros(transports); - return transportToLinkLayer(t); + return TRANSPORT_LINKLAYER_MAP.get(t, IpConnectivityLogClass.UNKNOWN); default: return IpConnectivityLogClass.MULTIPLE; } } - private static int transportToLinkLayer(int transport) { - if (0 <= transport && transport < TRANSPORT_LINKLAYER_MAP.length) { - return TRANSPORT_LINKLAYER_MAP[transport]; - } - return IpConnectivityLogClass.UNKNOWN; - } - - private static final int[] TRANSPORT_LINKLAYER_MAP = new int[MAX_TRANSPORT + 1]; + private static final SparseIntArray TRANSPORT_LINKLAYER_MAP = new SparseIntArray(); static { - TRANSPORT_LINKLAYER_MAP[TRANSPORT_CELLULAR] = IpConnectivityLogClass.CELLULAR; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI] = IpConnectivityLogClass.WIFI; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN; - TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN] = IpConnectivityLogClass.LOWPAN; - }; + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_CELLULAR, IpConnectivityLogClass.CELLULAR); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI, IpConnectivityLogClass.WIFI); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_BLUETOOTH, IpConnectivityLogClass.BLUETOOTH); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_ETHERNET, IpConnectivityLogClass.ETHERNET); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_VPN, IpConnectivityLogClass.UNKNOWN); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_WIFI_AWARE, IpConnectivityLogClass.WIFI_NAN); + TRANSPORT_LINKLAYER_MAP.append(TRANSPORT_LOWPAN, IpConnectivityLogClass.LOWPAN); + } private static int ifnameToLinkLayer(String ifname) { // Do not try to catch all interface names with regexes, instead only catch patterns that diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index bff1a5c99dd2..c05e25367d03 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -717,8 +717,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mNumBackgroundNetworkRequests += delta; break; - case TRACK_DEFAULT: case LISTEN: + case TRACK_DEFAULT: + case TRACK_SYSTEM_DEFAULT: break; case NONE: diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 3d71b0a269c9..508739f2e1e0 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -79,7 +79,6 @@ public class NetworkNotificationManager { // server. public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS"; public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS"; - public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; // The context is for the current user (system server) private final Context mContext; @@ -161,13 +160,20 @@ public class NetworkNotificationManager { if (nai != null) { transportType = approximateTransportType(nai); final String extraInfo = nai.networkInfo.getExtraInfo(); - name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSsid() : extraInfo; + if (nai.linkProperties != null && nai.linkProperties.getCaptivePortalData() != null + && !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData() + .getVenueFriendlyName())) { + name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName(); + } else { + name = TextUtils.isEmpty(extraInfo) + ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo; + } // Only notify for Internet-capable networks. if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return; } else { // Legacy notifications. transportType = TRANSPORT_CELLULAR; - name = null; + name = ""; } // Clear any previous notification with lower priority, otherwise return. http://b/63676954. @@ -193,35 +199,30 @@ public class NetworkNotificationManager { final CharSequence details; int icon = getIcon(transportType); if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.wifi_no_internet, name); details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) { if (transportType == TRANSPORT_CELLULAR) { title = r.getString(R.string.mobile_no_internet); } else if (transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.wifi_no_internet, name); } else { title = r.getString(R.string.other_networks_no_internet); } details = r.getString(R.string.private_dns_broken_detailed); } else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.network_partial_connectivity, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.network_partial_connectivity, name); details = r.getString(R.string.network_partial_connectivity_detailed); } else if (notifyType == NotificationType.LOST_INTERNET && transportType == TRANSPORT_WIFI) { - title = r.getString(R.string.wifi_no_internet, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + title = r.getString(R.string.wifi_no_internet, name); details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.SIGN_IN) { switch (transportType) { case TRANSPORT_WIFI: title = r.getString(R.string.wifi_available_sign_in, 0); - details = r.getString(R.string.network_available_sign_in_detailed, - WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid())); + details = r.getString(R.string.network_available_sign_in_detailed, name); break; case TRANSPORT_CELLULAR: title = r.getString(R.string.network_available_sign_in, 0); diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java index 87b4c162a2cc..7ef315c469ae 100644 --- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -27,7 +27,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.telephony.data.EpsBearerQosSessionAttributes; -import android.util.Slog; +import android.util.Log; import com.android.internal.util.CollectionUtils; import com.android.server.ConnectivityService; @@ -260,18 +260,18 @@ public class QosCallbackTracker { } private static void log(final String msg) { - Slog.d(TAG, msg); + Log.d(TAG, msg); } private static void logw(final String msg) { - Slog.w(TAG, msg); + Log.w(TAG, msg); } private static void loge(final String msg) { - Slog.e(TAG, msg); + Log.e(TAG, msg); } private static void logwtf(final String msg) { - Slog.wtf(TAG, msg); + Log.wtf(TAG, msg); } } diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java index b5f20d70db7f..c480594b8c60 100644 --- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java +++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java @@ -41,7 +41,6 @@ import android.os.Handler; import android.os.MessageQueue; import android.os.Messenger; import android.system.ErrnoException; -import android.system.Int32Ref; import android.system.Os; import android.util.Log; import android.util.SparseArray; @@ -306,9 +305,8 @@ public class TcpKeepaliveController { private static boolean isReceiveQueueEmpty(FileDescriptor fd) throws ErrnoException { - Int32Ref result = new Int32Ref(-1); - Os.ioctlInt(fd, SIOCINQ, result); - if (result.value != 0) { + final int result = Os.ioctlInt(fd, SIOCINQ); + if (result != 0) { Log.e(TAG, "Read queue has data"); return false; } @@ -317,9 +315,8 @@ public class TcpKeepaliveController { private static boolean isSendQueueEmpty(FileDescriptor fd) throws ErrnoException { - Int32Ref result = new Int32Ref(-1); - Os.ioctlInt(fd, SIOCOUTQ, result); - if (result.value != 0) { + final int result = Os.ioctlInt(fd, SIOCOUTQ); + if (result != 0) { Log.e(TAG, "Write queue has data"); return false; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index fc2c7e01efde..a769e88f77d7 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -21,10 +21,10 @@ import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.Manifest; import android.annotation.NonNull; @@ -74,6 +74,7 @@ import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.VpnManager; import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; import android.net.ipsec.ike.ChildSessionParams; @@ -171,6 +172,12 @@ public class Vpn { */ @VisibleForTesting static final int MAX_VPN_PROFILE_SIZE_BYTES = 1 << 17; // 128kB + /** + * Network score that VPNs will announce to ConnectivityService. + * TODO: remove when the network scoring refactor lands. + */ + private static final int VPN_DEFAULT_SCORE = 101; + // TODO: create separate trackers for each unique VPN to support // automated reconnection @@ -435,6 +442,7 @@ public class Vpn { mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); loadAlwaysOnPackage(keyStore); } @@ -494,6 +502,11 @@ public class Vpn { updateAlwaysOnNotification(detailedState); } + private void resetNetworkCapabilities() { + mNetworkCapabilities.setUids(null); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE)); + } + /** * Chooses whether to force all connections to go though VPN. * @@ -518,6 +531,11 @@ public class Vpn { } } + /** Returns the package name that is currently prepared. */ + public String getPackage() { + return mPackage; + } + /** * Check whether to prevent all traffic outside of a VPN even when the VPN is not connected. * @@ -928,7 +946,7 @@ public class Vpn { agentDisconnect(); jniReset(mInterface); mInterface = null; - mNetworkCapabilities.setUids(null); + resetNetworkCapabilities(); } // Revoke the connection or stop the VpnRunner. @@ -999,6 +1017,8 @@ public class Vpn { case VpnManager.TYPE_VPN_SERVICE: toChange = new String[] {AppOpsManager.OPSTR_ACTIVATE_VPN}; break; + case VpnManager.TYPE_VPN_LEGACY: + return false; default: Log.wtf(TAG, "Unrecognized VPN type while granting authorization"); return false; @@ -1029,6 +1049,8 @@ public class Vpn { return isVpnServicePreConsented(context, packageName); case VpnManager.TYPE_VPN_PLATFORM: return isVpnProfilePreConsented(context, packageName); + case VpnManager.TYPE_VPN_LEGACY: + return VpnConfig.LEGACY_VPN.equals(packageName); default: return false; } @@ -1211,6 +1233,8 @@ public class Vpn { mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserId, mConfig.allowedApplications, mConfig.disallowedApplications)); + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType())); + // Only apps targeting Q and above can explicitly declare themselves as metered. // These VPNs are assumed metered unless they state otherwise. if (mIsPackageTargetingAtLeastQ && mConfig.isMetered) { @@ -1220,8 +1244,7 @@ public class Vpn { } mNetworkAgent = new NetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */, - mNetworkCapabilities, lp, - ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) { + mNetworkCapabilities, lp, VPN_DEFAULT_SCORE, networkAgentConfig, mNetworkProvider) { @Override public void unwanted() { // We are user controlled, not driven by NetworkRequest. @@ -1735,7 +1758,7 @@ public class Vpn { private void cleanupVpnStateLocked() { mStatusIntent = null; - mNetworkCapabilities.setUids(null); + resetNetworkCapabilities(); mConfig = null; mInterface = null; @@ -1846,22 +1869,18 @@ public class Vpn { } /** - * Gets the currently running App-based VPN type + * Gets the currently running VPN type * - * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an - * app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always + * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running a + * VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always * Settings-based, the Platform VPNs can be initiated by both apps and Settings. */ - public synchronized int getActiveAppVpnType() { - if (VpnConfig.LEGACY_VPN.equals(mPackage)) { - return VpnManager.TYPE_VPN_NONE; - } - - if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) { - return VpnManager.TYPE_VPN_PLATFORM; - } else { - return VpnManager.TYPE_VPN_SERVICE; - } + public synchronized int getActiveVpnType() { + if (!mNetworkInfo.isConnectedOrConnecting()) return VpnManager.TYPE_VPN_NONE; + if (mVpnRunner == null) return VpnManager.TYPE_VPN_SERVICE; + return mVpnRunner instanceof IkeV2VpnRunner + ? VpnManager.TYPE_VPN_PLATFORM + : VpnManager.TYPE_VPN_LEGACY; } private void updateAlwaysOnNotification(DetailedState networkState) { diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 5d1c4e6715f1..16d4f94277cb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -555,7 +555,7 @@ public class HdmiControlService extends SystemService { private void bootCompleted() { // on boot, if device is interactive, set HDMI CEC state as powered on as well if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) { - onWakeUp(); + mPowerStatus = HdmiControlManager.POWER_STATUS_ON; } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index c005af4e9696..a589fedaec72 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -280,6 +280,7 @@ public class LockSettingsService extends ILockSettings.Stub { super.onBootPhase(phase); if (phase == PHASE_ACTIVITY_MANAGER_READY) { mLockSettingsService.migrateOldDataAfterSystemReady(); + mLockSettingsService.loadEscrowData(); } } @@ -832,11 +833,15 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.initWeaverService(); getAuthSecretHal(); mDeviceProvisionedObserver.onSystemReady(); - mRebootEscrowManager.loadRebootEscrowDataIfAvailable(); + // TODO: maybe skip this for split system user mode. mStorage.prefetchUser(UserHandle.USER_SYSTEM); } + private void loadEscrowData() { + mRebootEscrowManager.loadRebootEscrowDataIfAvailable(mHandler); + } + private void getAuthSecretHal() { try { mAuthSecretService = IAuthSecret.getService(/* retry */ true); @@ -2381,10 +2386,17 @@ public class LockSettingsService extends ILockSettings.Stub { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { enforceShell(); + final int origPid = Binder.getCallingPid(); + final int origUid = Binder.getCallingUid(); + + // The original identity is an opaque integer. final long origId = Binder.clearCallingIdentity(); + Slog.e(TAG, "Caller pid " + origPid + " Caller uid " + origUid); try { - (new LockSettingsShellCommand(new LockPatternUtils(mContext))).exec( - this, in, out, err, args, callback, resultReceiver); + final LockSettingsShellCommand command = + new LockSettingsShellCommand(new LockPatternUtils(mContext), mContext, origPid, + origUid); + command.exec(this, in, out, err, args, callback, resultReceiver); } finally { Binder.restoreCallingIdentity(origId); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java index 7b767b86f0d4..8d38bfede942 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java @@ -23,8 +23,11 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTE import android.app.ActivityManager; import android.app.admin.PasswordMetrics; +import android.content.Context; import android.os.ShellCommand; +import android.os.SystemProperties; import android.text.TextUtils; +import android.util.Slog; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils.RequestThrottledException; @@ -45,15 +48,25 @@ class LockSettingsShellCommand extends ShellCommand { private static final String COMMAND_VERIFY = "verify"; private static final String COMMAND_GET_DISABLED = "get-disabled"; private static final String COMMAND_REMOVE_CACHE = "remove-cache"; + private static final String COMMAND_SET_ROR_PROVIDER_PACKAGE = + "set-resume-on-reboot-provider-package"; private static final String COMMAND_HELP = "help"; private int mCurrentUserId; private final LockPatternUtils mLockPatternUtils; + private final Context mContext; + private final int mCallingPid; + private final int mCallingUid; + private String mOld = ""; private String mNew = ""; - LockSettingsShellCommand(LockPatternUtils lockPatternUtils) { + LockSettingsShellCommand(LockPatternUtils lockPatternUtils, Context context, int callingPid, + int callingUid) { mLockPatternUtils = lockPatternUtils; + mCallingPid = callingPid; + mCallingUid = callingUid; + mContext = context; } @Override @@ -70,6 +83,7 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_HELP: case COMMAND_GET_DISABLED: case COMMAND_SET_DISABLED: + case COMMAND_SET_ROR_PROVIDER_PACKAGE: break; default: getErrPrintWriter().println( @@ -82,6 +96,9 @@ class LockSettingsShellCommand extends ShellCommand { case COMMAND_REMOVE_CACHE: runRemoveCache(); return 0; + case COMMAND_SET_ROR_PROVIDER_PACKAGE: + runSetResumeOnRebootProviderPackage(); + return 0; case COMMAND_HELP: onHelp(); return 0; @@ -173,6 +190,10 @@ class LockSettingsShellCommand extends ShellCommand { pw.println(" remove-cache [--user USER_ID]"); pw.println(" Removes cached unified challenge for the managed profile."); pw.println(""); + pw.println(" set-resume-on-reboot-provider-package <package_name>"); + pw.println(" Sets the package name for server based resume on reboot service " + + "provider."); + pw.println(""); } } @@ -258,6 +279,17 @@ class LockSettingsShellCommand extends ShellCommand { return true; } + private boolean runSetResumeOnRebootProviderPackage() { + final String packageName = mNew; + String name = ResumeOnRebootServiceProvider.PROP_ROR_PROVIDER_PACKAGE; + Slog.i(TAG, "Setting " + name + " to " + packageName); + + mContext.enforcePermission(android.Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE, + mCallingPid, mCallingUid, TAG); + SystemProperties.set(name, packageName); + return true; + } + private boolean runClear() { LockscreenCredential none = LockscreenCredential.createNone(); if (!isNewCredentialSufficient(none)) { diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java index 38eeb88e63b0..af0774c6c3fa 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java @@ -35,6 +35,12 @@ class RebootEscrowData { */ private static final int CURRENT_VERSION = 2; + /** + * This is the legacy version of the escrow data format for R builds. The escrow data is only + * encrypted by the escrow key, without additional wrap of another key from keystore. + */ + private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1; + private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob, RebootEscrowKey key) { mSpVersion = spVersion; @@ -64,6 +70,19 @@ class RebootEscrowData { return mKey; } + private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks, + DataInputStream dis) throws IOException { + if (kk == null) { + throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the" + + " escrow data"); + } + + // Decrypt the blob with the key from keystore first, then decrypt again with the reboot + // escrow key. + byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis); + return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob); + } + static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk) throws IOException { Objects.requireNonNull(ks); @@ -71,17 +90,20 @@ class RebootEscrowData { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob)); int version = dis.readInt(); - if (version != CURRENT_VERSION) { - throw new IOException("Unsupported version " + version); - } byte spVersion = dis.readByte(); - - // Decrypt the blob with the key from keystore first, then decrypt again with the reboot - // escrow key. - byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis); - final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob); - - return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + switch (version) { + case CURRENT_VERSION: { + byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis); + return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + } + case LEGACY_SINGLE_ENCRYPTED_VERSION: { + // Decrypt the blob with the escrow key directly. + byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis); + return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + } + default: + throw new IOException("Unsupported version " + version); + } } static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 06962d414009..30ea5556b41c 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; +import android.os.Handler; import android.os.SystemClock; import android.os.UserManager; import android.provider.DeviceConfig; @@ -39,6 +40,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Objects; import javax.crypto.SecretKey; @@ -76,6 +78,13 @@ class RebootEscrowManager { private static final int BOOT_COUNT_TOLERANCE = 5; /** + * The default retry specs for loading reboot escrow data. We will attempt to retry loading + * escrow data on temporarily errors, e.g. unavailable network. + */ + private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3; + private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30; + + /** * Logs events for later debugging in bugreports. */ private final RebootEscrowEventLog mEventLog; @@ -137,6 +146,7 @@ class RebootEscrowManager { RebootEscrowProviderInterface rebootEscrowProvider; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, "server_based_ror_enabled", false)) { + Slog.i(TAG, "Using server based resume on reboot"); rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { rebootEscrowProvider = new RebootEscrowProviderHalImpl(); @@ -148,6 +158,14 @@ class RebootEscrowManager { return null; } + void post(Handler handler, Runnable runnable) { + handler.post(runnable); + } + + void postDelayed(Handler handler, Runnable runnable, long delayMillis) { + handler.postDelayed(runnable, delayMillis); + } + public Context getContext() { return mContext; } @@ -199,7 +217,18 @@ class RebootEscrowManager { mKeyStoreManager = injector.getKeyStoreManager(); } - void loadRebootEscrowDataIfAvailable() { + private void onGetRebootEscrowKeyFailed(List<UserInfo> users) { + Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + + // Clear the old key in keystore. + mKeyStoreManager.clearKeyStoreEncryptionKey(); + onEscrowRestoreComplete(false); + } + + void loadRebootEscrowDataIfAvailable(Handler retryHandler) { List<UserInfo> users = mUserManager.getUsers(); List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { @@ -212,17 +241,53 @@ class RebootEscrowManager { return; } + mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry( + retryHandler, 0, users, rebootEscrowUsers)); + } + + void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber, + List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { + Objects.requireNonNull(retryHandler); + + final int retryLimit = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_count", DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT); + final int retryIntervalInSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_OTA, + "load_escrow_data_retry_interval_seconds", + DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS); + + if (attemptNumber < retryLimit) { + Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber); + mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry( + retryHandler, attemptNumber, users, rebootEscrowUsers), + retryIntervalInSeconds * 1000); + return; + } + + Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts"); + onGetRebootEscrowKeyFailed(users); + } + + void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber, + List<UserInfo> users, List<UserInfo> rebootEscrowUsers) { // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is // generated before reboot. Note that we will clear the escrow key even if the keystore key // is null. SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey(); - RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk); - if (kk == null || escrowKey == null) { - Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); - for (UserInfo user : users) { - mStorage.removeRebootEscrow(user.id); - } - onEscrowRestoreComplete(false); + if (kk == null) { + Slog.i(TAG, "Failed to load the key for resume on reboot from key store."); + } + + RebootEscrowKey escrowKey; + try { + escrowKey = getAndClearRebootEscrowKey(kk); + } catch (IOException e) { + scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users, + rebootEscrowUsers); + return; + } + + if (escrowKey == null) { + onGetRebootEscrowKeyFailed(users); return; } @@ -249,7 +314,7 @@ class RebootEscrowManager { } } - private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) { + private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException { RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider(); if (rebootEscrowProvider == null) { Slog.w(TAG, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java index 6c1040b596c8..4b00772088f2 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderHalImpl.java @@ -33,7 +33,7 @@ import javax.crypto.SecretKey; * An implementation of the {@link RebootEscrowProviderInterface} by calling the RebootEscrow HAL. */ class RebootEscrowProviderHalImpl implements RebootEscrowProviderInterface { - private static final String TAG = "RebootEscrowProvider"; + private static final String TAG = "RebootEscrowProviderHal"; private final Injector mInjector; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java index 857ad5fc312a..af6faad3c76e 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderInterface.java @@ -16,6 +16,8 @@ package com.android.server.locksettings; +import java.io.IOException; + import javax.crypto.SecretKey; /** @@ -33,9 +35,10 @@ public interface RebootEscrowProviderInterface { /** * Returns the stored RebootEscrowKey, and clears the storage. If the stored key is encrypted, - * use the input key to decrypt the RebootEscrowKey. Returns null on failure. + * use the input key to decrypt the RebootEscrowKey. Returns null on failure. Throws an + * IOException if the failure is non-fatal, and a retry may succeed. */ - RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey); + RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException; /** * Clears the stored RebootEscrowKey. diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index ba1a680ba7fb..b3b45460899d 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -35,7 +35,7 @@ import javax.crypto.SecretKey; * encrypt & decrypt the blob. */ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { - private static final String TAG = "RebootEscrowProvider"; + private static final String TAG = "RebootEscrowProviderServerBased"; // Timeout for service binding private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; @@ -50,6 +50,8 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa private final Injector mInjector; + private byte[] mServerBlob; + static class Injector { private ResumeOnRebootServiceConnection mServiceConnection = null; @@ -124,17 +126,25 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } @Override - public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) { - byte[] serverBlob = mStorage.readRebootEscrowServerBlob(); + public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) throws IOException { + if (mServerBlob == null) { + mServerBlob = mStorage.readRebootEscrowServerBlob(); + } // Delete the server blob in storage. mStorage.removeRebootEscrowServerBlob(); - if (serverBlob == null) { + if (mServerBlob == null) { Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); return null; } + if (decryptionKey == null) { + Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is" + + " null."); + return null; + } + Slog.i(TAG, "Loaded reboot escrow server blob from storage"); try { - byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey); + byte[] escrowKeyBytes = unwrapServerBlob(mServerBlob, decryptionKey); if (escrowKeyBytes == null) { Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); return null; @@ -145,7 +155,7 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa } return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); - } catch (TimeoutException | RemoteException | IOException e) { + } catch (TimeoutException | RemoteException e) { Slog.w(TAG, "Failed to decrypt the server blob ", e); return null; } diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java index a1e18bd5a6bd..9c471b85eb76 100644 --- a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java +++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java @@ -31,6 +31,7 @@ import android.os.IBinder; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.resumeonreboot.IResumeOnRebootService; @@ -55,6 +56,10 @@ public class ResumeOnRebootServiceProvider { Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; private static final String TAG = "ResumeOnRebootServiceProvider"; + // The system property name that overrides the default service provider package name. + static final String PROP_ROR_PROVIDER_PACKAGE = + "persist.sys.resume_on_reboot_provider_package"; + private final Context mContext; private final PackageManager mPackageManager; @@ -72,12 +77,19 @@ public class ResumeOnRebootServiceProvider { private ServiceInfo resolveService() { Intent intent = new Intent(); intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); - if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { - intent.setPackage(PROVIDER_PACKAGE); + int queryFlag = PackageManager.GET_SERVICES; + String testAppName = SystemProperties.get(PROP_ROR_PROVIDER_PACKAGE, ""); + if (!testAppName.isEmpty()) { + Slog.i(TAG, "Using test app: " + testAppName); + intent.setPackage(testAppName); + } else { + queryFlag |= PackageManager.MATCH_SYSTEM_ONLY; + if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { + intent.setPackage(PROVIDER_PACKAGE); + } } - List<ResolveInfo> resolvedIntents = - mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); + List<ResolveInfo> resolvedIntents = mPackageManager.queryIntentServices(intent, queryFlag); for (ResolveInfo resolvedInfo : resolvedIntents) { if (resolvedInfo.serviceInfo != null && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { @@ -120,6 +132,7 @@ public class ResumeOnRebootServiceProvider { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); } + mBinder = null; } /** Bind to the service */ diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 6d1c68039ee5..3cc32bef0e67 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -16,10 +16,10 @@ package com.android.server.net; -import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static android.provider.Settings.ACTION_VPN_SETTINGS; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,43 +28,37 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; +import android.net.NetworkRequest; import android.os.Handler; import android.security.KeyStore; import android.text.TextUtils; import android.util.Log; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; -import com.android.server.ConnectivityService; -import com.android.server.EventLogTags; import com.android.server.connectivity.Vpn; import java.util.List; import java.util.Objects; /** - * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be - * connected and kicks off VPN connection, managing any required {@code netd} - * firewall rules. + * State tracker for legacy lockdown VPN. Watches for physical networks to be + * connected and kicks off VPN connection. */ public class LockdownVpnTracker { private static final String TAG = "LockdownVpnTracker"; - /** Number of VPN attempts before waiting for user intervention. */ - private static final int MAX_ERROR_COUNT = 4; - public static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET"; @NonNull private final Context mContext; - @NonNull private final ConnectivityService mConnService; + @NonNull private final ConnectivityManager mCm; @NonNull private final NotificationManager mNotificationManager; @NonNull private final Handler mHandler; @NonNull private final Vpn mVpn; @@ -76,19 +70,73 @@ public class LockdownVpnTracker { @NonNull private final PendingIntent mConfigIntent; @NonNull private final PendingIntent mResetIntent; + @NonNull private final NetworkCallback mDefaultNetworkCallback = new NetworkCallback(); + @NonNull private final VpnNetworkCallback mVpnNetworkCallback = new VpnNetworkCallback(); + + private class NetworkCallback extends ConnectivityManager.NetworkCallback { + private Network mNetwork = null; + private LinkProperties mLinkProperties = null; + + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + boolean networkChanged = false; + if (!network.equals(mNetwork)) { + // The default network just changed. + mNetwork = network; + networkChanged = true; + } + mLinkProperties = lp; + // Backwards compatibility: previously, LockdownVpnTracker only responded to connects + // and disconnects, not LinkProperties changes on existing networks. + if (networkChanged) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + } + + @Override + public void onLost(Network network) { + // The default network has gone down. + mNetwork = null; + mLinkProperties = null; + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + + public Network getNetwork() { + return mNetwork; + } + + public LinkProperties getLinkProperties() { + return mLinkProperties; + } + } + + private class VpnNetworkCallback extends NetworkCallback { + @Override + public void onAvailable(Network network) { + synchronized (mStateLock) { + handleStateChangedLocked(); + } + } + @Override + public void onLost(Network network) { + onAvailable(network); + } + } + @Nullable private String mAcceptedEgressIface; - private int mErrorCount; - public LockdownVpnTracker(@NonNull Context context, - @NonNull ConnectivityService connService, @NonNull Handler handler, @NonNull KeyStore keyStore, @NonNull Vpn vpn, @NonNull VpnProfile profile) { mContext = Objects.requireNonNull(context); - mConnService = Objects.requireNonNull(connService); + mCm = mContext.getSystemService(ConnectivityManager.class); mHandler = Objects.requireNonNull(handler); mVpn = Objects.requireNonNull(vpn); mProfile = Objects.requireNonNull(profile); @@ -110,24 +158,20 @@ public class LockdownVpnTracker { * connection when ready, or setting firewall rules once VPN is connected. */ private void handleStateChangedLocked() { - - final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered(); - final LinkProperties egressProp = mConnService.getActiveLinkProperties(); + final Network network = mDefaultNetworkCallback.getNetwork(); + final LinkProperties egressProp = mDefaultNetworkCallback.getLinkProperties(); final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig(); // Restart VPN when egress network disconnected or changed - final boolean egressDisconnected = egressInfo == null - || State.DISCONNECTED.equals(egressInfo.getState()); + final boolean egressDisconnected = (network == null); final boolean egressChanged = egressProp == null || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName()); - final int egressType = (egressInfo == null) ? TYPE_NONE : egressInfo.getType(); final String egressIface = (egressProp == null) ? null : egressProp.getInterfaceName(); - Log.d(TAG, "handleStateChanged: egress=" + egressType - + " " + mAcceptedEgressIface + "->" + egressIface); + Log.d(TAG, "handleStateChanged: egress=" + mAcceptedEgressIface + "->" + egressIface); if (egressDisconnected || egressChanged) { mAcceptedEgressIface = null; @@ -138,46 +182,49 @@ public class LockdownVpnTracker { return; } - if (vpnInfo.getDetailedState() == DetailedState.FAILED) { - EventLogTags.writeLockdownVpnError(egressType); - } - - if (mErrorCount > MAX_ERROR_COUNT) { - showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); - - } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) { - if (mProfile.isValidLockdownProfile()) { - Log.d(TAG, "Active network connected; starting VPN"); - EventLogTags.writeLockdownVpnConnecting(egressType); - showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); - - mAcceptedEgressIface = egressProp.getInterfaceName(); - try { - // Use the privileged method because Lockdown VPN is initiated by the system, so - // no additional permission checks are necessary. - mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, null, egressProp); - } catch (IllegalStateException e) { - mAcceptedEgressIface = null; - Log.e(TAG, "Failed to start VPN", e); - showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); - } - } else { + // At this point, |network| is known to be non-null. + if (!vpnInfo.isConnectedOrConnecting()) { + if (!mProfile.isValidLockdownProfile()) { Log.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + return; } + Log.d(TAG, "Active network connected; starting VPN"); + showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); + + mAcceptedEgressIface = egressIface; + try { + // Use the privileged method because Lockdown VPN is initiated by the system, so + // no additional permission checks are necessary. + // + // Pass in the underlying network here because the legacy VPN is, in fact, tightly + // coupled to a given underlying network and cannot provide mobility. This makes + // things marginally more correct in two ways: + // + // 1. When the legacy lockdown VPN connects, LegacyTypeTracker broadcasts an extra + // CONNECTED broadcast for the underlying network type. The underlying type comes + // from here. LTT *could* assume that the underlying network is the default + // network, but that might introduce a race condition if, say, the VPN starts + // connecting on cell, but when the connection succeeds and the agent is + // registered, the default network is now wifi. + // 2. If no underlying network is passed in, then CS will assume the underlying + // network is the system default. So, if the VPN is up and underlying network + // (e.g., wifi) disconnects, CS will inform apps that the VPN's capabilities have + // changed to match the new default network (e.g., cell). + mVpn.startLegacyVpnPrivileged(mProfile, mKeyStore, network, egressProp); + } catch (IllegalStateException e) { + mAcceptedEgressIface = null; + Log.e(TAG, "Failed to start VPN", e); + showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + } } else if (vpnInfo.isConnected() && vpnConfig != null) { final String iface = vpnConfig.interfaze; final List<LinkAddress> sourceAddrs = vpnConfig.addresses; Log.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddrs.toString()); - EventLogTags.writeLockdownVpnConnected(egressType); showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); - - final NetworkInfo clone = new NetworkInfo(egressInfo); - augmentNetworkInfo(clone); - mConnService.sendConnectedBroadcast(clone); } } @@ -192,7 +239,15 @@ public class LockdownVpnTracker { mVpn.setEnableTeardown(false); mVpn.setLockdown(true); + mCm.setLegacyLockdownVpnEnabled(true); handleStateChangedLocked(); + + mCm.registerSystemDefaultNetworkCallback(mDefaultNetworkCallback, mHandler); + final NetworkRequest vpnRequest = new NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(TRANSPORT_VPN) + .build(); + mCm.registerNetworkCallback(vpnRequest, mVpnNetworkCallback, mHandler); } public void shutdown() { @@ -205,20 +260,21 @@ public class LockdownVpnTracker { Log.d(TAG, "shutdownLocked()"); mAcceptedEgressIface = null; - mErrorCount = 0; mVpn.stopVpnRunnerPrivileged(); mVpn.setLockdown(false); + mCm.setLegacyLockdownVpnEnabled(false); hideNotification(); mVpn.setEnableTeardown(true); + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + mCm.unregisterNetworkCallback(mVpnNetworkCallback); } /** * Reset VPN lockdown tracker. Called by ConnectivityService when receiving * {@link #ACTION_LOCKDOWN_RESET} pending intent. */ - @GuardedBy("mConnService.mVpns") public void reset() { Log.d(TAG, "reset()"); synchronized (mStateLock) { @@ -229,28 +285,6 @@ public class LockdownVpnTracker { } } - public void onNetworkInfoChanged() { - synchronized (mStateLock) { - handleStateChangedLocked(); - } - } - - public void onVpnStateChanged(NetworkInfo info) { - if (info.getDetailedState() == DetailedState.FAILED) { - mErrorCount++; - } - synchronized (mStateLock) { - handleStateChangedLocked(); - } - } - - public void augmentNetworkInfo(NetworkInfo info) { - if (info.isConnected()) { - final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); - info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); - } - } - private void showNotification(int titleRes, int iconRes) { final Notification.Builder builder = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN) diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 48ec9b4b502d..acec93cac34d 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -241,7 +241,7 @@ public class BackgroundDexOptService extends JobService { // trade-off worth doing to save boot time work. int result = pm.performDexOptWithStatus(new DexoptOptions( pkg, - PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_POST_BOOT, DexoptOptions.DEXOPT_BOOT_COMPLETE)); if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 79607351b18a..4a2fb5da5e70 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -687,7 +687,8 @@ public class PackageDexOptimizer { boolean generateCompactDex = true; switch (compilationReason) { case PackageManagerService.REASON_FIRST_BOOT: - case PackageManagerService.REASON_BOOT: + case PackageManagerService.REASON_BOOT_AFTER_OTA: + case PackageManagerService.REASON_POST_BOOT: case PackageManagerService.REASON_INSTALL: generateCompactDex = false; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a7b9622ab3c0..febbfbce9e6c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -676,17 +676,18 @@ public class PackageManagerService extends IPackageManager.Stub // Compilation reasons. public static final int REASON_UNKNOWN = -1; public static final int REASON_FIRST_BOOT = 0; - public static final int REASON_BOOT = 1; - public static final int REASON_INSTALL = 2; - public static final int REASON_INSTALL_FAST = 3; - public static final int REASON_INSTALL_BULK = 4; - public static final int REASON_INSTALL_BULK_SECONDARY = 5; - public static final int REASON_INSTALL_BULK_DOWNGRADED = 6; - public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 7; - public static final int REASON_BACKGROUND_DEXOPT = 8; - public static final int REASON_AB_OTA = 9; - public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 10; - public static final int REASON_SHARED = 11; + public static final int REASON_BOOT_AFTER_OTA = 1; + public static final int REASON_POST_BOOT = 2; + public static final int REASON_INSTALL = 3; + public static final int REASON_INSTALL_FAST = 4; + public static final int REASON_INSTALL_BULK = 5; + public static final int REASON_INSTALL_BULK_SECONDARY = 6; + public static final int REASON_INSTALL_BULK_DOWNGRADED = 7; + public static final int REASON_INSTALL_BULK_SECONDARY_DOWNGRADED = 8; + public static final int REASON_BACKGROUND_DEXOPT = 9; + public static final int REASON_AB_OTA = 10; + public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11; + public static final int REASON_SHARED = 12; public static final int REASON_LAST = REASON_SHARED; @@ -9664,10 +9665,7 @@ public class PackageManagerService extends IPackageManager.Stub // first boot, as they do not have profile data. boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade; - // We need to re-extract after a pruned cache, as AoT-ed files will be out of date. - boolean causePrunedCache = VMRuntime.didPruneDalvikCache(); - - if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) { + if (!causeUpgrade && !causeFirstBoot) { return; } @@ -9684,7 +9682,7 @@ public class PackageManagerService extends IPackageManager.Stub final long startTime = System.nanoTime(); final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */, - causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT, + causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA, false /* bootComplete */); final int elapsedTimeSeconds = diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index 9cd55a6bb07e..636db111be88 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -29,7 +29,8 @@ public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. public static final String REASON_STRINGS[] = { "first-boot", - "boot", + "boot-after-ota", + "post-boot", "install", "install-fast", "install-bulk", diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 6e145b5ecbe4..7de5c9467930 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -587,7 +587,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_REASON_ERROR = 0; private static final int TRON_COMPILATION_REASON_UNKNOWN = 1; private static final int TRON_COMPILATION_REASON_FIRST_BOOT = 2; - private static final int TRON_COMPILATION_REASON_BOOT = 3; + private static final int TRON_COMPILATION_REASON_BOOT_DEPRECATED_SINCE_S = 3; private static final int TRON_COMPILATION_REASON_INSTALL = 4; private static final int TRON_COMPILATION_REASON_BG_DEXOPT = 5; private static final int TRON_COMPILATION_REASON_AB_OTA = 6; @@ -605,6 +605,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { private static final int TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED_WITH_DM = 18; private static final int TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED_WITH_DM = 19; + private static final int TRON_COMPILATION_REASON_BOOT_AFTER_OTA = 20; + private static final int TRON_COMPILATION_REASON_POST_BOOT = 21; // The annotation to add as a suffix to the compilation reason when dexopt was // performed with dex metadata. @@ -618,7 +620,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub { case "unknown" : return TRON_COMPILATION_REASON_UNKNOWN; case "error" : return TRON_COMPILATION_REASON_ERROR; case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT; - case "boot" : return TRON_COMPILATION_REASON_BOOT; + case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA; + case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT; case "install" : return TRON_COMPILATION_REASON_INSTALL; case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT; case "ab-ota" : return TRON_COMPILATION_REASON_AB_OTA; diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java new file mode 100644 index 000000000000..6cdd4df824a8 --- /dev/null +++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 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.pm.parsing.library; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +/** + * Updates a package to remove dependency on android.net.ipsec.ike library. + * + * @hide + */ +@VisibleForTesting +public class AndroidNetIpSecIkeUpdater extends PackageSharedLibraryUpdater { + + private static final String LIBRARY_NAME = "android.net.ipsec.ike"; + + @Override + public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) { + removeLibrary(parsedPackage, LIBRARY_NAME); + } +} diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index 1405a7d613f1..8a8a302734b1 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -45,6 +45,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { static { final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + // Remove android.net.ipsec.ike library, it is added to boot classpath since Android S. + packageUpdaters.add(new AndroidNetIpSecIkeUpdater()); + // Remove com.google.android.maps library. packageUpdaters.add(new ComGoogleAndroidMapsUpdater()); diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS index 0e88862e01b1..e05ef482ec08 100644 --- a/services/core/java/com/android/server/pm/permission/OWNERS +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -1,4 +1,3 @@ -moltmann@google.com zhanghai@google.com per-file DefaultPermissionGrantPolicy.java = hackbod@android.com per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com @@ -7,5 +6,4 @@ per-file DefaultPermissionGrantPolicy.java = toddke@google.com per-file DefaultPermissionGrantPolicy.java = yamasani@google.com per-file DefaultPermissionGrantPolicy.java = patb@google.com per-file DefaultPermissionGrantPolicy.java = eugenesusla@google.com -per-file DefaultPermissionGrantPolicy.java = moltmann@google.com per-file DefaultPermissionGrantPolicy.java = zhanghai@google.com diff --git a/services/core/java/com/android/server/role/OWNERS b/services/core/java/com/android/server/role/OWNERS index b94d98827d71..31e3549d9111 100644 --- a/services/core/java/com/android/server/role/OWNERS +++ b/services/core/java/com/android/server/role/OWNERS @@ -1,5 +1,4 @@ svetoslavganov@google.com -moltmann@google.com zhanghai@google.com evanseverson@google.com eugenesusla@google.com diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 2503e812f9e1..37d13fb86dc0 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -125,10 +125,11 @@ import java.util.concurrent.TimeUnit; public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); + private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; - - private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; private static final String DISCONNECT_REASON_INTERNAL_ERROR = "Uncaught exception: "; @@ -412,11 +413,11 @@ public class VcnGatewayConnection extends StateMachine { @NonNull private final VcnGatewayConnectionConfig mConnectionConfig; @NonNull private final VcnGatewayStatusCallback mGatewayStatusCallback; @NonNull private final Dependencies mDeps; - @NonNull private final VcnUnderlyingNetworkTrackerCallback mUnderlyingNetworkTrackerCallback; @NonNull private final IpSecManager mIpSecManager; - @NonNull private final IpSecTunnelInterface mTunnelIface; + + @Nullable private IpSecTunnelInterface mTunnelIface = null; /** Running state of this VcnGatewayConnection. */ private boolean mIsRunning = true; @@ -526,20 +527,6 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTrackerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); - IpSecTunnelInterface iface; - try { - iface = - mIpSecManager.createIpSecTunnelInterface( - DUMMY_ADDR, DUMMY_ADDR, new Network(-1)); - } catch (IOException | ResourceUnavailableException e) { - teardownAsynchronously(); - mTunnelIface = null; - - return; - } - - mTunnelIface = iface; - addState(mDisconnectedState); addState(mDisconnectingState); addState(mConnectingState); @@ -1117,6 +1104,18 @@ public class VcnGatewayConnection extends StateMachine { class ConnectedState extends ConnectedStateBase { @Override protected void enterState() throws Exception { + if (mTunnelIface == null) { + try { + // Requires a real Network object in order to be created; doing this any earlier + // means not having a real Network object, or picking an incorrect Network. + mTunnelIface = + mIpSecManager.createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, mUnderlying.network); + } catch (IOException | ResourceUnavailableException e) { + teardownAsynchronously(); + } + } + // Successful connection, clear failed attempt counter mFailedAttempts = 0; } @@ -1434,6 +1433,11 @@ public class VcnGatewayConnection extends StateMachine { } @VisibleForTesting(visibility = Visibility.PRIVATE) + void setTunnelInterface(IpSecTunnelInterface tunnelIface) { + mTunnelIface = tunnelIface; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { return mUnderlyingNetworkTrackerCallback; } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 87c766d961a2..0e2549e164d4 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -28,6 +28,7 @@ cc_library_static { "com_android_server_am_BatteryStatsService.cpp", "com_android_server_ConsumerIrService.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", + "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", "com_android_server_input_InputManagerService.cpp", diff --git a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp b/services/core/jni/com_android_server_connectivity_Vpn.cpp index ea5e7183c905..ea5e7183c905 100644 --- a/packages/Connectivity/service/jni/com_android_server_connectivity_Vpn.cpp +++ b/services/core/jni/com_android_server_connectivity_Vpn.cpp diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index ccf685c1abd7..1e6f05322223 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -40,6 +40,7 @@ int register_android_server_UsbHostManager(JNIEnv* env); int register_android_server_vr_VrManagerService(JNIEnv* env); int register_android_server_VibratorService(JNIEnv* env); int register_android_server_location_GnssLocationProvider(JNIEnv* env); +int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*); int register_android_server_tv_TvUinputBridge(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); @@ -91,6 +92,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_VibratorService(env); register_android_server_SystemServer(env); register_android_server_location_GnssLocationProvider(env); + register_android_server_connectivity_Vpn(env); register_android_server_devicepolicy_CryptoTestHelper(env); register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd index e27e1b8ca89d..1406dbb12e02 100644 --- a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd +++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd @@ -27,6 +27,13 @@ <xs:attribute type="xs:boolean" name="enabled" use="required" /> </xs:complexType> + <xs:complexType name="raw-override-value"> + <xs:attribute type="xs:string" name="packageName" use="required" /> + <xs:attribute type="xs:long" name="minVersionCode" /> + <xs:attribute type="xs:long" name="maxVersionCode" /> + <xs:attribute type="xs:boolean" name="enabled" use="required" /> + </xs:complexType> + <xs:complexType name="change-overrides"> <xs:attribute type="xs:long" name="changeId" use="required"/> <xs:element name="validated"> @@ -43,6 +50,13 @@ </xs:sequence> </xs:complexType> </xs:element> + <xs:element name="raw"> + <xs:complexType> + <xs:sequence> + <xs:element name="raw-override-value" type="raw-override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> </xs:complexType> <xs:element name="overrides"> diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt index 08b82072747b..a5ccffcfbb2b 100644 --- a/services/core/xsd/platform-compat/overrides/schema/current.txt +++ b/services/core/xsd/platform-compat/overrides/schema/current.txt @@ -5,9 +5,11 @@ package com.android.server.compat.overrides { ctor public ChangeOverrides(); method public long getChangeId(); method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred(); + method public com.android.server.compat.overrides.ChangeOverrides.Raw getRaw(); method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated(); method public void setChangeId(long); method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred); + method public void setRaw(com.android.server.compat.overrides.ChangeOverrides.Raw); method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated); } @@ -16,6 +18,11 @@ package com.android.server.compat.overrides { method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); } + public static class ChangeOverrides.Raw { + ctor public ChangeOverrides.Raw(); + method public java.util.List<com.android.server.compat.overrides.RawOverrideValue> getRawOverrideValue(); + } + public static class ChangeOverrides.Validated { ctor public ChangeOverrides.Validated(); method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); @@ -34,6 +41,18 @@ package com.android.server.compat.overrides { method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides(); } + public class RawOverrideValue { + ctor public RawOverrideValue(); + method public boolean getEnabled(); + method public long getMaxVersionCode(); + method public long getMinVersionCode(); + method public String getPackageName(); + method public void setEnabled(boolean); + method public void setMaxVersionCode(long); + method public void setMinVersionCode(long); + method public void setPackageName(String); + } + public class XmlParser { ctor public XmlParser(); method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 43537d067bce..7565fe103b1e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7158,7 +7158,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = mInjector.userHandleGetCallingUserId(); return mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getConnectivityManager().getVpnLockdownWhitelist(userId)); + () -> mInjector.getConnectivityManager().getVpnLockdownAllowlist(userId)); } private void forceWipeDeviceNoLock(boolean wipeExtRequested, String reason, boolean wipeEuicc) { diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java index 1801f3bca30e..a2768c637d79 100644 --- a/services/java/com/android/server/SystemConfigService.java +++ b/services/java/com/android/server/SystemConfigService.java @@ -21,6 +21,10 @@ import static java.util.stream.Collectors.toMap; import android.Manifest; import android.content.Context; import android.os.ISystemConfig; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; @@ -64,6 +68,22 @@ public class SystemConfigService extends SystemService { return SystemConfig.getInstance() .getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); } + + @Override + public int[] getSystemPermissionUids(String permissionName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, + "getSystemPermissionUids requires GET_RUNTIME_PERMISSIONS"); + final List<Integer> uids = new ArrayList<>(); + final SparseArray<ArraySet<String>> systemPermissions = + SystemConfig.getInstance().getSystemPermissions(); + for (int i = 0; i < systemPermissions.size(); i++) { + final ArraySet<String> permissions = systemPermissions.valueAt(i); + if (permissions != null && permissions.contains(permissionName)) { + uids.add(systemPermissions.keyAt(i)); + } + } + return ArrayUtils.convertToIntArray(uids); + } }; public SystemConfigService(Context context) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 62c3f43d2676..c665ca32bff0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1104,6 +1104,7 @@ public final class SystemServer { IStorageManager storageManager = null; NetworkManagementService networkManagement = null; IpSecService ipSecService = null; + VpnManagerService vpnManager = null; VcnManagementService vcnManagement = null; NetworkStatsService networkStats = null; NetworkPolicyManagerService networkPolicy = null; @@ -1637,6 +1638,15 @@ public final class SystemServer { networkPolicy.bindConnectivityManager(connectivity); t.traceEnd(); + t.traceBegin("StartVpnManagerService"); + try { + vpnManager = VpnManagerService.create(context); + ServiceManager.addService(Context.VPN_MANAGEMENT_SERVICE, vpnManager); + } catch (Throwable e) { + reportWtf("starting VPN Manager Service", e); + } + t.traceEnd(); + t.traceBegin("StartVcnManagementService"); try { vcnManagement = VcnManagementService.create(context); @@ -2338,6 +2348,7 @@ public final class SystemServer { final MediaRouterService mediaRouterF = mediaRouter; final MmsServiceBroker mmsServiceF = mmsService; final IpSecService ipSecServiceF = ipSecService; + final VpnManagerService vpnManagerF = vpnManager; final VcnManagementService vcnManagementF = vcnManagement; final WindowManagerService windowManagerF = wm; final ConnectivityManager connectivityF = (ConnectivityManager) @@ -2445,6 +2456,15 @@ public final class SystemServer { reportWtf("making Connectivity Service ready", e); } t.traceEnd(); + t.traceBegin("MakeVpnManagerServiceReady"); + try { + if (vpnManagerF != null) { + vpnManagerF.systemReady(); + } + } catch (Throwable e) { + reportWtf("making VpnManagerService ready", e); + } + t.traceEnd(); t.traceBegin("MakeVcnManagementServiceReady"); try { if (vcnManagementF != null) { diff --git a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java index 50e7a0395a2a..58d6dae1637a 100644 --- a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java +++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java @@ -34,7 +34,7 @@ public class EntropyMixerTest extends AndroidTestCase { assertEquals(0, FileUtils.readTextFile(file, 0, null).length()); // The constructor has the side effect of writing to file - new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath(), "/dev/null"); + new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath()); assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0); } diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java index d0767ccb6f87..c165c661a625 100644 --- a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java +++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java @@ -22,6 +22,7 @@ class ApplicationInfoBuilder { private boolean mIsDebuggable; private int mTargetSdk; private String mPackageName; + private long mVersionCode; private ApplicationInfoBuilder() { mTargetSdk = -1; @@ -46,6 +47,11 @@ class ApplicationInfoBuilder { return this; } + ApplicationInfoBuilder withVersionCode(Long versionCode) { + mVersionCode = versionCode; + return this; + } + ApplicationInfo build() { final ApplicationInfo applicationInfo = new ApplicationInfo(); if (mIsDebuggable) { @@ -53,6 +59,7 @@ class ApplicationInfoBuilder { } applicationInfo.packageName = mPackageName; applicationInfo.targetSdkVersion = mTargetSdk; + applicationInfo.longVersionCode = mVersionCode; return applicationInfo; } } diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index a53ff9bc7fdc..8b0e948579fb 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -18,6 +18,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -25,6 +26,7 @@ import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.app.compat.ChangeIdStateCache; +import android.app.compat.PackageOverride; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -33,6 +35,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.CompatibilityOverrideConfig; import org.junit.Before; import org.junit.Test; @@ -46,6 +49,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Collections; import java.util.UUID; @RunWith(AndroidJUnit4.class) @@ -83,6 +87,8 @@ public class CompatConfigTest { when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); ChangeIdStateCache.disable(); + when(mPackageManager.getApplicationInfo(anyString(), anyInt())) + .thenThrow(new NameNotFoundException()); } @Test @@ -163,6 +169,10 @@ public class CompatConfigTest { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addDisabledChangeWithId(1234L) .build(); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", true); @@ -177,6 +187,10 @@ public class CompatConfigTest { CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) .addEnabledChangeWithId(1234L) .build(); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", false); @@ -191,6 +205,10 @@ public class CompatConfigTest { CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); compatConfig.forceNonDebuggableFinalForTest(false); + ApplicationInfo info = ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(info); compatConfig.addOverride(1234L, "com.some.package", false); @@ -265,6 +283,71 @@ public class CompatConfigTest { } @Test + public void testOverrideWithAppVersion() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.installed.foo") + .withVersionCode(100L) + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.installed.foo"), anyInt())) + .thenReturn(applicationInfo); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override that doesn't include the installed app version + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, + new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.installed.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Add override that does include the installed app version + config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, + new PackageOverride.Builder() + .setMinVersionCode(100L) + .setMaxVersionCode(100L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.installed.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + } + + @Test + public void testApplyDeferredOverridesAfterInstallingAppVersion() throws Exception { + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.notinstalled.foo") + .withVersionCode(100L) + .debuggable().build(); + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenThrow(new NameNotFoundException()); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L).build(); + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + // Add override before the app is available. + CompatibilityOverrideConfig config = new CompatibilityOverrideConfig( + Collections.singletonMap(1234L, new PackageOverride.Builder() + .setMaxVersionCode(99L) + .setEnabled(true) + .build())); + compatConfig.addOverrides(config, "com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + // Pretend the app is now installed. + when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt())) + .thenReturn(applicationInfo); + + compatConfig.recheckOverrides("com.notinstalled.foo"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + } + + @Test public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("com.installedapp.foo") @@ -384,6 +467,8 @@ public class CompatConfigTest { ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() .withPackageName("com.some.package") .build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue(); assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); @@ -404,6 +489,8 @@ public class CompatConfigTest { .withPackageName("foo.bar") .withTargetSdk(2) .build(); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isFalse(); assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse(); @@ -425,7 +512,8 @@ public class CompatConfigTest { .withPackageName("foo.bar") .withTargetSdk(2) .build(); - + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); assertThat(compatConfig.enableTargetSdkChangesForPackage("foo.bar", 3)).isEqualTo(1); assertThat(compatConfig.isChangeEnabled(3, applicationInfo)).isTrue(); assertThat(compatConfig.isChangeEnabled(4, applicationInfo)).isFalse(); @@ -533,22 +621,114 @@ public class CompatConfigTest { + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + " </override-value>\n" + " </validated>\n" - + " <deferred>\n" - + " </deferred>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + " </change-overrides>\n" + " <change-overrides changeId=\"2\">\n" + " <validated>\n" + " </validated>\n" - + " <deferred>\n" - + " <override-value packageName=\"bar.baz\" enabled=\"false\">\n" - + " </override-value>\n" - + " </deferred>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"); + } + + @Test + public void testSaveOverridesWithRanges() throws Exception { + File overridesFile = new File(createTempDir(), "overrides.xml"); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addEnableSinceSdkChangeWithId(2, 2L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + compatConfig.initOverrides(overridesFile); + + compatConfig.addOverrides(new CompatibilityOverrideConfig(Collections.singletonMap(1L, + new PackageOverride.Builder() + .setMinVersionCode(99L) + .setMaxVersionCode(101L) + .setEnabled(true) + .build())), "foo.bar"); + + assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"99\" maxVersionCode=\"101\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + " </change-overrides>\n" + "</overrides>\n"); } @Test - public void testLoadOverrides() throws Exception { + public void testLoadOverridesRaw() throws Exception { + File tempDir = createTempDir(); + File overridesFile = new File(tempDir, "overrides.xml"); + // Change 1 is enabled for foo.bar (validated) + // Change 2 is disabled for bar.baz (deferred) + String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + + " </override-value>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"foo.bar\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"true\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + " <change-overrides changeId=\"2\">\n" + + " <validated>\n" + + " </validated>\n" + + " <raw>\n" + + " <raw-override-value packageName=\"bar.baz\" " + + "minVersionCode=\"-9223372036854775808\" " + + "maxVersionCode=\"9223372036854775807\" enabled=\"false\">\n" + + " </raw-override-value>\n" + + " </raw>\n" + + " </change-overrides>\n" + + "</overrides>\n"; + writeToFile(tempDir, "overrides.xml", xmlData); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addEnableSinceSdkChangeWithId(2, 2L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + compatConfig.initOverrides(overridesFile); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("foo.bar") + .withVersionCode(100L) + .debuggable() + .build(); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) + .thenThrow(new NameNotFoundException()); + + assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); + assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse(); + + compatConfig.recheckOverrides("foo.bar"); + assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); + } + + @Test + public void testLoadOverridesDeferred() throws Exception { File tempDir = createTempDir(); File overridesFile = new File(tempDir, "overrides.xml"); // Change 1 is enabled for foo.bar (validated) diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index a1b2dc8bd82d..799b06734b54 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -196,6 +196,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -208,6 +211,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -219,6 +225,9 @@ public class PlatformCompatTest { public void testListenerCalledOnSetOverridesTwoListeners() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -244,6 +253,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -252,9 +264,12 @@ public class PlatformCompatTest { } @Test - public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception { + public void testListenerCalledOnSetOverridesForTestTwoListeners() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -280,6 +295,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); @@ -299,6 +317,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); @@ -318,6 +339,9 @@ public class PlatformCompatTest { mPlatformCompat.registerListener(1, mListener1); mPlatformCompat.registerListener(2, mListener2); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.setOverrides( CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); @@ -336,6 +360,9 @@ public class PlatformCompatTest { public void testListenerCalledOnClearOverrideDoesntExist() throws Exception { mPlatformCompat.registerListener(1, mListener1); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create().withPackageName(PACKAGE_NAME).build()); + mPlatformCompat.clearOverride(1, PACKAGE_NAME); // Listener not called when a non existing override is removed. verify(mListener1, never()).onCompatChange(PACKAGE_NAME); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java index 220569449ffb..336bbaeae2d9 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java @@ -42,6 +42,7 @@ import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.os.ResultReceiver; import android.os.ShellCallback; import android.platform.test.annotations.Presubmit; @@ -86,7 +87,8 @@ public class LockSettingsShellCommandTest { MockitoAnnotations.initMocks(this); final Context context = InstrumentationRegistry.getTargetContext(); mUserId = ActivityManager.getCurrentUser(); - mCommand = new LockSettingsShellCommand(mLockPatternUtils); + mCommand = new LockSettingsShellCommand(mLockPatternUtils, context, 0, + Process.SHELL_UID); when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java index 32445fd1a47d..2eedc3251daa 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java @@ -19,19 +19,17 @@ package com.android.server.locksettings; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; - import androidx.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.security.GeneralSecurityException; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; -import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; /** * atest FrameworksServicesTests:RebootEscrowDataTest @@ -41,22 +39,18 @@ public class RebootEscrowDataTest { private RebootEscrowKey mKey; private SecretKey mKeyStoreEncryptionKey; - private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException { - KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); - generator.init(new KeyGenParameterSpec.Builder( - "reboot_escrow_data_test_key", - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setKeySize(256) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .build()); - return generator.generateKey(); - } + // Hex encoding of a randomly generated AES key for test. + private static final byte[] TEST_AES_KEY = new byte[] { + 0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61, + 0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31, + 0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09, + 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23, + }; @Before public void generateKey() throws Exception { mKey = RebootEscrowKey.generate(); - mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey(); + mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES"); } private static byte[] getTestSp() { @@ -114,4 +108,23 @@ public class RebootEscrowDataTest { assertThat(decrypted, is(testSp)); } + @Test + public void fromEncryptedData_legacyVersion_success() throws Exception { + byte[] testSp = getTestSp(); + byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp); + + // Write a legacy blob encrypted only by k_s. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + dos.writeInt(1); + dos.writeByte(3); + dos.write(ksEncryptedBlob); + byte[] legacyBlob = bos.toByteArray(); + + RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null); + + assertThat(actual.getSpVersion(), is((byte) 3)); + assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes())); + assertThat(actual.getSyntheticPassword(), is(testSp)); + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index a4ba4c86a8fd..a896f1b0d60f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -43,6 +43,7 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; +import android.os.Handler; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserManager; @@ -155,6 +156,11 @@ public class RebootEscrowManagerTests { } @Override + void post(Handler handler, Runnable runnable) { + runnable.run(); + } + + @Override public UserManager getUserManager() { return mUserManager; } @@ -369,7 +375,7 @@ public class RebootEscrowManagerTests { @Test public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception { - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); } @Test @@ -401,7 +407,7 @@ public class RebootEscrowManagerTests { doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -435,7 +441,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mServiceConnection).unwrap(any(), anyLong()); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -466,7 +472,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); verify(mInjected, never()).reportMetric(anyBoolean()); } @@ -493,7 +499,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected, never()).reportMetric(anyBoolean()); } @@ -527,7 +533,7 @@ public class RebootEscrowManagerTests { when(mInjected.getBootCount()).thenReturn(10); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue()); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mInjected).reportMetric(eq(true)); } @@ -557,7 +563,7 @@ public class RebootEscrowManagerTests { ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> new byte[32]); - mService.loadRebootEscrowDataIfAvailable(); + mService.loadRebootEscrowDataIfAvailable(null); verify(mRebootEscrow).retrieveKey(); assertFalse(metricsSuccessCaptor.getValue()); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java index bc1e025dd99f..28b737b412d2 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.ContextWrapper; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -42,7 +43,6 @@ import org.junit.runner.RunWith; import org.mockito.stubbing.Answer; import java.io.File; -import java.io.IOException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -130,7 +130,7 @@ public class RebootEscrowProviderServerBasedImplTests { @Test public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception { when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); - doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong()); + doThrow(RemoteException.class).when(mServiceConnection).unwrap(any(), anyLong()); assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index 22020ad45666..bc84e350a329 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -96,7 +96,7 @@ public class DexoptOptionsTests { int[] reasons = new int[] { PackageManagerService.REASON_FIRST_BOOT, - PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_POST_BOOT, PackageManagerService.REASON_INSTALL, PackageManagerService.REASON_BACKGROUND_DEXOPT, PackageManagerService.REASON_AB_OTA, diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java new file mode 100644 index 000000000000..70d85b6e0411 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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.pm.parsing.library; + +import android.os.Build; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.parsing.pkg.PackageImpl; +import com.android.server.pm.parsing.pkg.ParsedPackage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link AndroidNetIpSecIkeUpdater} + */ +@Presubmit +@SmallTest +@RunWith(JUnit4.class) +public class AndroidNetIpSecIkeUpdaterTest extends PackageSharedLibraryUpdaterTest { + + @Test + public void otherUsesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.O) + .addUsesLibrary("other") + .addUsesOptionalLibrary("optional") + .addUsesLibrary("android.net.ipsec.ike") + .hideAsParsed()); + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.O) + .addUsesLibrary("other") + .addUsesOptionalLibrary("optional") + .hideAsParsed()) + .hideAsFinal(); + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesLibrary("android.net.ipsec.ike") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .hideAsParsed()) + .hideAsFinal(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesOptionalLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesOptionalLibrary("android.net.ipsec.ike") + .hideAsParsed()); + + AndroidPackage after = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .hideAsParsed()) + .hideAsFinal(); + + checkBackwardsCompatibility(before, after); + } + + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { + checkBackwardsCompatibility(before, after, AndroidNetIpSecIkeUpdater::new); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index 09c8142105cc..9768f176ea85 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -165,6 +165,23 @@ public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdate checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses a + * {@link AndroidNetIpSecIkeUpdater}. + */ + @Test + public void android_net_ipsec_ike_in_usesLibraries() { + ParsedPackage before = ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT) + .addUsesLibrary("android.net.ipsec.ike") + .hideAsParsed()); + + ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME) + .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT); + + checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal()); + } + private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) { checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance); } diff --git a/services/tests/servicestests/test-apps/ConnTestApp/OWNERS b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS new file mode 100644 index 000000000000..aa87958f1d53 --- /dev/null +++ b/services/tests/servicestests/test-apps/ConnTestApp/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/net/OWNERS diff --git a/services/usb/OWNERS b/services/usb/OWNERS index 8ee72b577f3c..60172a36128e 100644 --- a/services/usb/OWNERS +++ b/services/usb/OWNERS @@ -1,6 +1,5 @@ badhri@google.com elaurent@google.com -moltmann@google.com albertccwang@google.com jameswei@google.com howardyen@google.com
\ No newline at end of file diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index a4ecb722bd2c..580513c12453 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1992,7 +1992,7 @@ public abstract class ConnectionService extends Service { boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean( TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false); boolean addSelfManaged = request.getExtras() != null && request.getExtras().getBoolean( - PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false); + PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true); Log.i(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b, " + " addSelfManaged: %b", callManagerAccount, callId, request, isIncoming, diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl index 2904082616e7..ba2b62d14bec 100644 --- a/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl +++ b/telephony/java/android/telephony/data/IQualifiedNetworksService.aidl @@ -17,7 +17,7 @@ package android.telephony.data; import android.telephony.data.IQualifiedNetworksServiceCallback; -import android.telephony.data.ApnThrottleStatus; +import android.telephony.data.ThrottleStatus; /** * {@hide} @@ -26,5 +26,5 @@ interface IQualifiedNetworksService { oneway void createNetworkAvailabilityProvider(int slotId, IQualifiedNetworksServiceCallback callback); oneway void removeNetworkAvailabilityProvider(int slotId); - oneway void reportApnThrottleStatusChanged(int slotId, in List<ApnThrottleStatus> statuses); + oneway void reportThrottleStatusChanged(int slotId, in List<ThrottleStatus> statuses); } diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index 4af63b4cf981..4e85d8926f11 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -168,8 +168,8 @@ public abstract class QualifiedNetworksService extends Service { * * @param statuses the statuses that have changed */ - public void reportApnThrottleStatusChanged(@NonNull List<ApnThrottleStatus> statuses) { - Log.d(TAG, "reportApnThrottleStatusChanged: statuses size=" + statuses.size()); + public void reportThrottleStatusChanged(@NonNull List<ThrottleStatus> statuses) { + Log.d(TAG, "reportThrottleStatusChanged: statuses size=" + statuses.size()); } /** @@ -212,8 +212,8 @@ public abstract class QualifiedNetworksService extends Service { break; case QNS_APN_THROTTLE_STATUS_CHANGED: if (provider != null) { - List<ApnThrottleStatus> statuses = (List<ApnThrottleStatus>) message.obj; - provider.reportApnThrottleStatusChanged(statuses); + List<ThrottleStatus> statuses = (List<ThrottleStatus>) message.obj; + provider.reportThrottleStatusChanged(statuses); } break; @@ -307,8 +307,8 @@ public abstract class QualifiedNetworksService extends Service { } @Override - public void reportApnThrottleStatusChanged(int slotIndex, - List<ApnThrottleStatus> statuses) { + public void reportThrottleStatusChanged(int slotIndex, + List<ThrottleStatus> statuses) { mHandler.obtainMessage(QNS_APN_THROTTLE_STATUS_CHANGED, slotIndex, 0, statuses) .sendToTarget(); } diff --git a/telephony/java/android/telephony/data/ApnThrottleStatus.aidl b/telephony/java/android/telephony/data/ThrottleStatus.aidl index 46bc4abde159..f75f35709816 100644 --- a/telephony/java/android/telephony/data/ApnThrottleStatus.aidl +++ b/telephony/java/android/telephony/data/ThrottleStatus.aidl @@ -17,4 +17,4 @@ /** @hide */ package android.telephony.data; -parcelable ApnThrottleStatus; +parcelable ThrottleStatus; diff --git a/telephony/java/android/telephony/data/ApnThrottleStatus.java b/telephony/java/android/telephony/data/ThrottleStatus.java index 51461d17690a..0335c6868340 100644 --- a/telephony/java/android/telephony/data/ApnThrottleStatus.java +++ b/telephony/java/android/telephony/data/ThrottleStatus.java @@ -35,7 +35,7 @@ import java.util.Objects; * @hide */ @SystemApi -public final class ApnThrottleStatus implements Parcelable { +public final class ThrottleStatus implements Parcelable { /** * The APN type is not throttled. */ @@ -43,14 +43,14 @@ public final class ApnThrottleStatus implements Parcelable { /** * The APN type is throttled until {@link android.os.SystemClock#elapsedRealtime()} - * has reached {@link ApnThrottleStatus#getThrottleExpiryTimeMillis} + * has reached {@link ThrottleStatus#getThrottleExpiryTimeMillis} */ public static final int THROTTLE_TYPE_ELAPSED_TIME = 2; /** {@hide} */ @IntDef(flag = true, prefix = {"THROTTLE_TYPE_"}, value = { - ApnThrottleStatus.THROTTLE_TYPE_NONE, - ApnThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME, + ThrottleStatus.THROTTLE_TYPE_NONE, + ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME, }) public @interface ThrottleType { } @@ -72,9 +72,9 @@ public final class ApnThrottleStatus implements Parcelable { /** {@hide} */ @IntDef(flag = true, prefix = {"RETRY_TYPE_"}, value = { - ApnThrottleStatus.RETRY_TYPE_NONE, - ApnThrottleStatus.RETRY_TYPE_NEW_CONNECTION, - ApnThrottleStatus.RETRY_TYPE_HANDOVER, + ThrottleStatus.RETRY_TYPE_NONE, + ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, + ThrottleStatus.RETRY_TYPE_HANDOVER, }) public @interface RetryType { } @@ -141,7 +141,7 @@ public final class ApnThrottleStatus implements Parcelable { * {@link SystemClock#elapsedRealtime}. * * This value only applies when the throttle type is set to - * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}. + * {@link ThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}. * * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely. * @@ -152,7 +152,7 @@ public final class ApnThrottleStatus implements Parcelable { return mThrottleExpiryTimeMillis; } - private ApnThrottleStatus(int slotIndex, + private ThrottleStatus(int slotIndex, @AccessNetworkConstants.TransportType int transportType, @Annotation.ApnType int apnTypes, @ThrottleType int throttleType, @@ -166,7 +166,7 @@ public final class ApnThrottleStatus implements Parcelable { mRetryType = retryType; } - private ApnThrottleStatus(@NonNull Parcel source) { + private ThrottleStatus(@NonNull Parcel source) { mSlotIndex = source.readInt(); mTransportType = source.readInt(); mApnType = source.readInt(); @@ -185,16 +185,16 @@ public final class ApnThrottleStatus implements Parcelable { dest.writeInt(mThrottleType); } - public static final @NonNull Parcelable.Creator<ApnThrottleStatus> CREATOR = - new Parcelable.Creator<ApnThrottleStatus>() { + public static final @NonNull Parcelable.Creator<ThrottleStatus> CREATOR = + new Parcelable.Creator<ThrottleStatus>() { @Override - public ApnThrottleStatus createFromParcel(@NonNull Parcel source) { - return new ApnThrottleStatus(source); + public ThrottleStatus createFromParcel(@NonNull Parcel source) { + return new ThrottleStatus(source); } @Override - public ApnThrottleStatus[] newArray(int size) { - return new ApnThrottleStatus[size]; + public ThrottleStatus[] newArray(int size) { + return new ThrottleStatus[size]; } }; @@ -213,8 +213,8 @@ public final class ApnThrottleStatus implements Parcelable { public boolean equals(Object obj) { if (obj == null) { return false; - } else if (obj instanceof ApnThrottleStatus) { - ApnThrottleStatus other = (ApnThrottleStatus) obj; + } else if (obj instanceof ThrottleStatus) { + ThrottleStatus other = (ThrottleStatus) obj; return this.mSlotIndex == other.mSlotIndex && this.mApnType == other.mApnType && this.mRetryType == other.mRetryType @@ -228,7 +228,7 @@ public final class ApnThrottleStatus implements Parcelable { @Override public String toString() { - return "ApnThrottleStatus{" + return "ThrottleStatus{" + "mSlotIndex=" + mSlotIndex + ", mTransportType=" + mTransportType + ", mApnType=" + ApnSetting.getApnTypeString(mApnType) @@ -239,18 +239,18 @@ public final class ApnThrottleStatus implements Parcelable { } /** - * Provides a convenient way to set the fields of an {@link ApnThrottleStatus} when creating a + * Provides a convenient way to set the fields of an {@link ThrottleStatus} when creating a * new instance. * - * <p>The example below shows how you might create a new {@code ApnThrottleStatus}: + * <p>The example below shows how you might create a new {@code ThrottleStatus}: * * <pre><code> * - * DataCallResponseApnThrottleStatus = new ApnThrottleStatus.Builder() + * ThrottleStatus = new ThrottleStatus.Builder() * .setSlotIndex(1) * .setApnType({@link ApnSetting#TYPE_EMERGENCY}) * .setNoThrottle() - * .setRetryType({@link ApnThrottleStatus#RETRY_TYPE_NEW_CONNECTION}) + * .setRetryType({@link ThrottleStatus#RETRY_TYPE_NEW_CONNECTION}) * .build(); * </code></pre> */ @@ -261,6 +261,10 @@ public final class ApnThrottleStatus implements Parcelable { private long mThrottleExpiryTimeMillis; private @RetryType int mRetryType; private @ThrottleType int mThrottleType; + + /** + * @hide + */ public static final long NO_THROTTLE_EXPIRY_TIME = DataCallResponse.RETRY_DURATION_UNDEFINED; @@ -312,7 +316,7 @@ public final class ApnThrottleStatus implements Parcelable { * {@link SystemClock#elapsedRealtime}. * * When setting this value, the throttle type is set to - * {@link ApnThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}. + * {@link ThrottleStatus#THROTTLE_TYPE_ELAPSED_TIME}. * * A value of {@link Long#MAX_VALUE} implies that the APN type is throttled indefinitely. * @@ -338,7 +342,7 @@ public final class ApnThrottleStatus implements Parcelable { * Sets the status of the APN type as not being throttled. * * When setting this value, the throttle type is set to - * {@link ApnThrottleStatus#THROTTLE_TYPE_NONE} and the expiry time is set to + * {@link ThrottleStatus#THROTTLE_TYPE_NONE} and the expiry time is set to * {@link Builder#NO_THROTTLE_EXPIRY_TIME}. * * @return The same instance of the builder. @@ -365,13 +369,13 @@ public final class ApnThrottleStatus implements Parcelable { } /** - * Build the {@link ApnThrottleStatus} + * Build the {@link ThrottleStatus} * - * @return the {@link ApnThrottleStatus} object + * @return the {@link ThrottleStatus} object */ @NonNull - public ApnThrottleStatus build() { - return new ApnThrottleStatus( + public ThrottleStatus build() { + return new ThrottleStatus( mSlotIndex, mTransportType, mApnType, diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index bd623e055743..5f4e1e6f3148 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -39,6 +40,8 @@ import android.util.Log; import com.android.internal.telephony.IIntegerConsumer; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -77,31 +80,49 @@ public class ImsRcsManager { "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN"; /** - * Receives RCS Feature availability status updates from the ImsService. - * - * @see #isAvailable(int) - * @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback) - * @see #unregisterRcsAvailabilityCallback(AvailabilityCallback) + * An application can use {@link #addOnAvailabilityChangedListener} to register a + * {@link OnAvailabilityChangedListener}, which will notify the user when the RCS feature + * availability status updates from the ImsService. * @hide */ - public static class AvailabilityCallback { + @SystemApi + public interface OnAvailabilityChangedListener { + /** + * The availability of the feature's capabilities has changed to either available or + * unavailable. + * <p> + * If unavailable, the feature does not support the capability at the current time. This may + * be due to network or subscription provisioning changes, such as the IMS registration + * being lost, network type changing, or OMA-DM provisioning updates. + * + * @param capabilities The new availability of the capabilities. + */ + void onAvailabilityChanged(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities); + } - private static class CapabilityBinder extends IImsCapabilityCallback.Stub { + /** + * Receive the availability status changed from the ImsService and pass the status change to + * the associated {@link OnAvailabilityChangedListener} + */ + private static class AvailabilityCallbackAdapter { - private final AvailabilityCallback mLocalCallback; - private Executor mExecutor; + private static class CapabilityBinder extends IImsCapabilityCallback.Stub { + private final OnAvailabilityChangedListener mOnAvailabilityChangedListener; + private final Executor mExecutor; - CapabilityBinder(AvailabilityCallback c) { - mLocalCallback = c; + CapabilityBinder(OnAvailabilityChangedListener listener, Executor executor) { + mExecutor = executor; + mOnAvailabilityChangedListener = listener; } @Override public void onCapabilitiesStatusChanged(int config) { - if (mLocalCallback == null) return; + if (mOnAvailabilityChangedListener == null) return; long callingIdentity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> mLocalCallback.onAvailabilityChanged(config)); + mExecutor.execute(() -> + mOnAvailabilityChangedListener.onAvailabilityChanged(config)); } finally { restoreCallingIdentity(callingIdentity); } @@ -110,48 +131,34 @@ public class ImsRcsManager { @Override public void onQueryCapabilityConfiguration(int capability, int radioTech, boolean isEnabled) { - // This is not used for public interfaces. + // This is not used. } @Override public void onChangeCapabilityConfigurationError(int capability, int radioTech, @ImsFeature.ImsCapabilityError int reason) { - // This is not used for public interfaces - } - - private void setExecutor(Executor executor) { - mExecutor = executor; + // This is not used. } } - private final CapabilityBinder mBinder = new CapabilityBinder(this); + private final CapabilityBinder mBinder; - /** - * The availability of the feature's capabilities has changed to either available or - * unavailable. - * <p> - * If unavailable, the feature does not support the capability at the current time. This may - * be due to network or subscription provisioning changes, such as the IMS registration - * being lost, network type changing, or OMA-DM provisioning updates. - * - * @param capabilities The new availability of the capabilities. - */ - public void onAvailabilityChanged(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { + AvailabilityCallbackAdapter(@NonNull Executor executor, + @NonNull OnAvailabilityChangedListener listener) { + mBinder = new CapabilityBinder(listener, executor); } /**@hide*/ public final IImsCapabilityCallback getBinder() { return mBinder; } - - private void setExecutor(Executor executor) { - mBinder.setExecutor(executor); - } } private final int mSubId; private final Context mContext; private final BinderCacheManager<IImsRcsController> mBinderCache; + private final Map<OnAvailabilityChangedListener, AvailabilityCallbackAdapter> + mAvailabilityChangedCallbacks; /** * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this class. @@ -162,6 +169,7 @@ public class ImsRcsManager { mSubId = subId; mContext = context; mBinderCache = binderCache; + mAvailabilityChangedCallbacks = new HashMap<>(); } /** @@ -174,10 +182,23 @@ public class ImsRcsManager { } /** - * @hide + * Registers a {@link RegistrationManager.RegistrationCallback} with the system. When the + * callback is registered, it will initiate the callback c to be called with the current + * registration state. + * + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges + * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). + * + * @param executor The executor the callback events should be run on. + * @param c The {@link RegistrationManager.RegistrationCallback} to be added. + * @see #unregisterImsRegistrationCallback(RegistrationManager.RegistrationCallback) + * @throws ImsException if the subscription associated with this callback is valid, but + * the {@link ImsService} associated with the subscription is not available. This can happen if + * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed + * reason. */ - // @Override add back to RegistrationManager interface once public. - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerImsRegistrationCallback( @NonNull @CallbackExecutor Executor executor, @NonNull RegistrationManager.RegistrationCallback c) @@ -191,7 +212,7 @@ public class ImsRcsManager { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "Register registration callback: IImsRcsController is null"); + Log.w(TAG, "Register registration callback: IImsRcsController is null"); throw new ImsException("Cannot find remote IMS service", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } @@ -207,10 +228,21 @@ public class ImsRcsManager { } /** - * @hide + * Removes an existing {@link RegistrationManager.RegistrationCallback}. + * + * When the subscription associated with this callback is removed (SIM removed, ESIM swap, + * etc...), this callback will automatically be removed. If this method is called for an + * inactive subscription, it will result in a no-op. + * + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges + * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). + * + * @param c The {@link RegistrationManager.RegistrationCallback} to be removed. + * @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener + * @see #registerImsRegistrationCallback(Executor, RegistrationCallback) */ - // @Override add back to RegistrationManager interface once public. - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public void unregisterImsRegistrationCallback( @NonNull RegistrationManager.RegistrationCallback c) { if (c == null) { @@ -219,7 +251,7 @@ public class ImsRcsManager { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "Unregister registration callback: IImsRcsController is null"); + Log.w(TAG, "Unregister registration callback: IImsRcsController is null"); throw new IllegalStateException("Cannot find remote IMS service"); } @@ -231,10 +263,21 @@ public class ImsRcsManager { } /** - * @hide + * Gets the registration state of the IMS service. + * + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges + * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). + * + * @param executor The {@link Executor} that will be used to call the IMS registration state + * callback. + * @param stateCallback A callback called on the supplied {@link Executor} that will contain the + * registration state of the IMS service, which will be one of the + * following: {@link RegistrationManager#REGISTRATION_STATE_NOT_REGISTERED}, + * {@link RegistrationManager#REGISTRATION_STATE_REGISTERING}, or + * {@link RegistrationManager#REGISTRATION_STATE_REGISTERED}. */ - // @Override add back to RegistrationManager interface once public. - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public void getRegistrationState(@NonNull @CallbackExecutor Executor executor, @NonNull @RegistrationManager.ImsRegistrationState Consumer<Integer> stateCallback) { if (stateCallback == null) { @@ -246,7 +289,7 @@ public class ImsRcsManager { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "Get registration state error: IImsRcsController is null"); + Log.w(TAG, "Get registration state error: IImsRcsController is null"); throw new IllegalStateException("Cannot find remote IMS service"); } @@ -263,9 +306,20 @@ public class ImsRcsManager { } /** - * @hide + * Gets the Transport Type associated with the current IMS registration. + * + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} or that the calling app has carrier privileges + * (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}). + * + * @param executor The {@link Executor} that will be used to call the transportTypeCallback. + * @param transportTypeCallback The transport type associated with the current IMS registration, + * which will be one of following: + * {@see AccessNetworkConstants#TRANSPORT_TYPE_WWAN}, + * {@see AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or + * {@see AccessNetworkConstants#TRANSPORT_TYPE_INVALID}. */ - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor, @NonNull @AccessNetworkConstants.TransportType Consumer<Integer> transportTypeCallback) { @@ -278,7 +332,7 @@ public class ImsRcsManager { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "Get registration transport type error: IImsRcsController is null"); + Log.w(TAG, "Get registration transport type error: IImsRcsController is null"); throw new IllegalStateException("Cannot find remote IMS service"); } @@ -296,31 +350,33 @@ public class ImsRcsManager { } /** - * Registers an {@link AvailabilityCallback} with the system, which will provide RCS + * Add an {@link OnAvailabilityChangedListener} with the system, which will provide RCS * availability updates for the subscription specified. * * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to * subscription changed events and call - * {@link #unregisterRcsAvailabilityCallback(AvailabilityCallback)} to clean up after a - * subscription is removed. + * {@link #removeOnAvailabilityChangedListener(OnAvailabilityChangedListener)} to clean up + * after a subscription is removed. * <p> - * When the callback is registered, it will initiate the callback c to be called with the - * current capabilities. + * When the listener is registered, it will initiate the callback listener to be called with + * the current capabilities. * * @param executor The executor the callback events should be run on. - * @param c The RCS {@link AvailabilityCallback} to be registered. - * @see #unregisterRcsAvailabilityCallback(AvailabilityCallback) + * @param listener The RCS {@link OnAvailabilityChangedListener} to be registered. + * @see #removeOnAvailabilityChangedListener(OnAvailabilityChangedListener) * @throws ImsException if the subscription associated with this instance of * {@link ImsRcsManager} is valid, but the ImsService associated with the subscription is not * available. This can happen if the ImsService has crashed, for example, or if the subscription * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void registerRcsAvailabilityCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull AvailabilityCallback c) throws ImsException { - if (c == null) { - throw new IllegalArgumentException("Must include a non-null AvailabilityCallback."); + public void addOnAvailabilityChangedListener(@NonNull @CallbackExecutor Executor executor, + @NonNull OnAvailabilityChangedListener listener) throws ImsException { + if (listener == null) { + throw new IllegalArgumentException("Must include a non-null" + + "OnAvailabilityChangedListener."); } if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); @@ -328,56 +384,61 @@ public class ImsRcsManager { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "Register availability callback: IImsRcsController is null"); + Log.w(TAG, "Add availability changed listener: IImsRcsController is null"); throw new ImsException("Cannot find remote IMS service", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } - c.setExecutor(executor); + AvailabilityCallbackAdapter adapter = + addAvailabilityChangedListenerToCollection(executor, listener); try { - imsRcsController.registerRcsAvailabilityCallback(mSubId, c.getBinder()); - + imsRcsController.registerRcsAvailabilityCallback(mSubId, adapter.getBinder()); } catch (ServiceSpecificException e) { throw new ImsException(e.toString(), e.errorCode); } catch (RemoteException e) { - Log.e(TAG, "Error calling IImsRcsController#registerRcsAvailabilityCallback", e); + Log.w(TAG, "Error calling IImsRcsController#registerRcsAvailabilityCallback", e); throw new ImsException("Remote IMS Service is not available", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } - /** - * Removes an existing RCS {@link AvailabilityCallback}. + /** + * Removes an existing RCS {@link OnAvailabilityChangedListener}. * <p> * When the subscription associated with this callback is removed (SIM removed, ESIM swap, * etc...), this callback will automatically be unregistered. If this method is called for an * inactive subscription, it will result in a no-op. - * @param c The RCS {@link AvailabilityCallback} to be removed. - * @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback) + * @param listener The RCS {@link OnAvailabilityChangedListener} to be removed. + * @see #addOnAvailabilityChangedListener(Executor, OnAvailabilityChangedListener) * @throws ImsException if the IMS service is not available when calling this method. * See {@link ImsException#getCode()} for more information on the error codes. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public void unregisterRcsAvailabilityCallback(@NonNull AvailabilityCallback c) - throws ImsException { - if (c == null) { - throw new IllegalArgumentException("Must include a non-null AvailabilityCallback."); + public void removeOnAvailabilityChangedListener( + @NonNull OnAvailabilityChangedListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Must include a non-null" + + "OnAvailabilityChangedListener."); } IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "Unregister availability callback: IImsRcsController is null"); - throw new ImsException("Cannot find remote IMS service", - ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + Log.w(TAG, "Remove availability changed listener: IImsRcsController is null"); + return; + } + + AvailabilityCallbackAdapter callback = + removeAvailabilityChangedListenerFromCollection(listener); + if (callback == null) { + return; } try { - imsRcsController.unregisterRcsAvailabilityCallback(mSubId, c.getBinder()); + imsRcsController.unregisterRcsAvailabilityCallback(mSubId, callback.getBinder()); } catch (RemoteException e) { - Log.e(TAG, "Error calling IImsRcsController#unregisterRcsAvailabilityCallback", e); - throw new ImsException("Remote IMS Service is not available", - ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + Log.w(TAG, "Error calling IImsRcsController#unregisterRcsAvailabilityCallback", e); } } @@ -388,25 +449,24 @@ public class ImsRcsManager { * RCS capabilities provided over-the-top by applications. * * @param capability The RCS capability to query. - * @param radioTech The radio tech that this capability failed for, defined as - * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or - * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}. + * @param radioTech The radio technology type that we are querying. * @return true if the RCS capability is capable for this subscription, false otherwise. This * does not necessarily mean that we are registered for IMS and the capability is available, but * rather the subscription is capable of this service over IMS. - * @see #isAvailable(int) + * @see #isAvailable(int, int) * @see android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL * @see android.telephony.CarrierConfigManager.Ims#KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL * @throws ImsException if the IMS service is not available when calling this method. * See {@link ImsException#getCode()} for more information on the error codes. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isCapable(@RcsUceAdapter.RcsImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "isCapable: IImsRcsController is null"); + Log.w(TAG, "isCapable: IImsRcsController is null"); throw new ImsException("Cannot find remote IMS service", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } @@ -414,7 +474,7 @@ public class ImsRcsManager { try { return imsRcsController.isCapable(mSubId, capability, radioTech); } catch (RemoteException e) { - Log.e(TAG, "Error calling IImsRcsController#isCapable", e); + Log.w(TAG, "Error calling IImsRcsController#isCapable", e); throw new ImsException("Remote IMS Service is not available", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } @@ -427,6 +487,7 @@ public class ImsRcsManager { * RCS capabilities provided by over-the-top by applications. * * @param capability the RCS capability to query. + * @param radioTech The radio technology type that we are querying. * @return true if the RCS capability is currently available for the associated subscription, * false otherwise. If the capability is available, IMS is registered and the service is * currently available over IMS. @@ -435,25 +496,57 @@ public class ImsRcsManager { * See {@link ImsException#getCode()} for more information on the error codes. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - public boolean isAvailable(@RcsUceAdapter.RcsImsCapabilityFlag int capability) + public boolean isAvailable(@RcsUceAdapter.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) throws ImsException { IImsRcsController imsRcsController = getIImsRcsController(); if (imsRcsController == null) { - Log.e(TAG, "isAvailable: IImsRcsController is null"); + Log.w(TAG, "isAvailable: IImsRcsController is null"); throw new ImsException("Cannot find remote IMS service", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } try { - return imsRcsController.isAvailable(mSubId, capability); + return imsRcsController.isAvailable(mSubId, capability, radioTech); } catch (RemoteException e) { - Log.e(TAG, "Error calling IImsRcsController#isAvailable", e); + Log.w(TAG, "Error calling IImsRcsController#isAvailable", e); throw new ImsException("Remote IMS Service is not available", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } + /** + * Add the {@link OnAvailabilityChangedListener} to collection for tracking. + * @param executor The executor that will be used when the publish state is changed and the + * {@link OnAvailabilityChangedListener} is called. + * @param listener The {@link OnAvailabilityChangedListener} to call the publish state changed. + * @return The {@link AvailabilityCallbackAdapter} to wrapper the + * {@link OnAvailabilityChangedListener} + */ + private AvailabilityCallbackAdapter addAvailabilityChangedListenerToCollection( + @NonNull Executor executor, @NonNull OnAvailabilityChangedListener listener) { + AvailabilityCallbackAdapter adapter = new AvailabilityCallbackAdapter(executor, listener); + synchronized (mAvailabilityChangedCallbacks) { + mAvailabilityChangedCallbacks.put(listener, adapter); + } + return adapter; + } + + /** + * Remove the existing {@link OnAvailabilityChangedListener} from the collection. + * @param listener The {@link OnAvailabilityChangedListener} to remove from the collection. + * @return The wrapper class {@link AvailabilityCallbackAdapter} associated with the + * {@link OnAvailabilityChangedListener}. + */ + private AvailabilityCallbackAdapter removeAvailabilityChangedListenerFromCollection( + @NonNull OnAvailabilityChangedListener listener) { + synchronized (mAvailabilityChangedCallbacks) { + return mAvailabilityChangedCallbacks.remove(listener); + } + } + private IImsRcsController getIImsRcsController() { IBinder binder = TelephonyFrameworkInitializer .getTelephonyServiceManager() diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl new file mode 100644 index 000000000000..0830ff2ff050 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +parcelable ImsRegistrationAttributes; diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java new file mode 100644 index 000000000000..a36f54953e63 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.AccessNetworkConstants; +import android.telephony.ims.stub.ImsRegistrationImplBase; +import android.util.ArraySet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +/** + * Contains the attributes associated with the current IMS registration. + */ +public final class ImsRegistrationAttributes implements Parcelable { + + /** + * Builder for creating {@link ImsRegistrationAttributes} instances. + * @hide + */ + @SystemApi + public static final class Builder { + private final int mRegistrationTech; + private Set<String> mFeatureTags = Collections.emptySet(); + + /** + * Build a new instance of {@link ImsRegistrationAttributes}. + * + * @param registrationTech The Radio Access Technology that IMS is registered on. + */ + public Builder(@ImsRegistrationImplBase.ImsRegistrationTech int registrationTech) { + mRegistrationTech = registrationTech; + } + + /** + * Optional IMS feature tags included in this IMS registration. + * @param tags A set of Strings containing the MMTEL and RCS feature tags associated with + * the IMS registration. This information is used for services such as the UCE + * service to ascertain the complete IMS registration state to ensure the SIP + * PUBLISH is accurate. The format of the set of feature tags must be one feature + * tag key and value per entry. Each feature tag will contain the feature tag name + * and string value (if applicable), even if they have the same feature tag name. + * For example, + * {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg, + * urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} must + * be split into three feature tag entries: + * {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg", + * +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", + * +g.gsma.callcomposer}}. + */ + public @NonNull Builder setFeatureTags(@NonNull Set<String> tags) { + if (tags == null) { + throw new IllegalArgumentException("feature tag set must not be null"); + } + mFeatureTags = new ArraySet<>(tags); + return this; + } + + /** + * @return A new instance created from this builder. + */ + public @NonNull ImsRegistrationAttributes build() { + return new ImsRegistrationAttributes(mRegistrationTech, + RegistrationManager.getAccessType(mRegistrationTech), + 0 /* No attributes in AOSP */, mFeatureTags); + } + + } + + private final int mRegistrationTech; + private final int mTransportType; + private final int mImsAttributeFlags; + private final ArrayList<String> mFeatureTags; + + /** + * Create a new {@link ImsRegistrationAttributes} instance. + * + * @param registrationTech The technology that IMS has been registered on. + * @param transportType The transport type that IMS has been registered on. + * @param imsAttributeFlags The attributes associated with the IMS registration. + * @param featureTags The feature tags included in the IMS registration. + * @see Builder + * @hide + */ + public ImsRegistrationAttributes( + @ImsRegistrationImplBase.ImsRegistrationTech int registrationTech, + @AccessNetworkConstants.TransportType int transportType, + int imsAttributeFlags, + @Nullable Set<String> featureTags) { + mRegistrationTech = registrationTech; + mTransportType = transportType; + mImsAttributeFlags = imsAttributeFlags; + mFeatureTags = new ArrayList<>(featureTags); + } + + /**@hide*/ + public ImsRegistrationAttributes(Parcel source) { + mRegistrationTech = source.readInt(); + mTransportType = source.readInt(); + mImsAttributeFlags = source.readInt(); + mFeatureTags = new ArrayList<>(); + source.readList(mFeatureTags, null /*classloader*/); + } + + /** + * @return The Radio Access Technology that the IMS registration has been registered over. + * @hide + */ + @SystemApi + public @ImsRegistrationImplBase.ImsRegistrationTech int getRegistrationTechnology() { + return mRegistrationTech; + } + + /** + * @return The access network transport type that IMS has been registered over. + */ + public @AccessNetworkConstants.TransportType int getTransportType() { + return mTransportType; + } + + /** + * @return A bit-mask containing attributes associated with the IMS registration. + */ + public int getAttributeFlags() { + return mImsAttributeFlags; + } + + /** + * Gets the Set of feature tags associated with the current IMS registration, if the IMS + * service supports supplying this information. + * <p> + * The format of the set of feature tags will be one feature tag key and value per entry and + * will potentially contain MMTEL and RCS feature tags, depending the configuration of the IMS + * service associated with the registration indications. Each feature tag will contain the + * feature tag name and string value (if applicable), even if they have the same feature tag + * name. For example, {@code +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg, + * urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", +g.gsma.callcomposer} will be split + * into three feature tag entries: + * {@code {+g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg", + * +g.3gpp.icsi-ref="urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session", + * +g.gsma.callcomposer}}. + * @return The Set of feature tags associated with the current IMS registration. + */ + public @NonNull Set<String> getFeatureTags() { + if (mFeatureTags == null) { + return Collections.emptySet(); + } + return new ArraySet<>(mFeatureTags); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mRegistrationTech); + dest.writeInt(mTransportType); + dest.writeInt(mImsAttributeFlags); + dest.writeList(mFeatureTags); + } + + public static final @NonNull Creator<ImsRegistrationAttributes> CREATOR = + new Creator<ImsRegistrationAttributes>() { + @Override + public ImsRegistrationAttributes createFromParcel(Parcel source) { + return new ImsRegistrationAttributes(source); + } + + @Override + public ImsRegistrationAttributes[] newArray(int size) { + return new ImsRegistrationAttributes[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ImsRegistrationAttributes that = (ImsRegistrationAttributes) o; + return mRegistrationTech == that.mRegistrationTech + && mTransportType == that.mTransportType + && mImsAttributeFlags == that.mImsAttributeFlags + && Objects.equals(mFeatureTags, that.mFeatureTags); + } + + @Override + public int hashCode() { + return Objects.hash(mRegistrationTech, mTransportType, mImsAttributeFlags, mFeatureTags); + } + + @Override + public String toString() { + return "ImsRegistrationAttributes { transportType= " + mTransportType + ", attributeFlags=" + + mImsAttributeFlags + ", featureTags=[" + mFeatureTags + "]}"; + } +} diff --git a/telephony/java/android/telephony/ims/RegistrationManager.java b/telephony/java/android/telephony/ims/RegistrationManager.java index 2c75368b86bf..c49121f4dc5d 100644 --- a/telephony/java/android/telephony/ims/RegistrationManager.java +++ b/telephony/java/android/telephony/ims/RegistrationManager.java @@ -71,7 +71,6 @@ public interface RegistrationManager { */ int REGISTRATION_STATE_REGISTERED = 2; - /**@hide*/ // Translate ImsRegistrationImplBase API to new AccessNetworkConstant because WLAN // and WWAN are more accurate constants. @@ -79,7 +78,8 @@ public interface RegistrationManager { new HashMap<Integer, Integer>() {{ // Map NONE to -1 to make sure that we handle the REGISTRATION_TECH_NONE // case, since it is defined. - put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, -1); + put(ImsRegistrationImplBase.REGISTRATION_TECH_NONE, + AccessNetworkConstants.TRANSPORT_TYPE_INVALID); put(ImsRegistrationImplBase.REGISTRATION_TECH_LTE, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); put(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, @@ -103,6 +103,20 @@ public interface RegistrationManager { } /** + * @param regtech The registration technology. + * @return The Access Network type from registration technology. + * @hide + */ + static int getAccessType(int regtech) { + if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regtech)) { + Log.w("RegistrationManager", "getAccessType - invalid regType returned: " + + regtech); + return AccessNetworkConstants.TRANSPORT_TYPE_INVALID; + } + return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regtech); + } + + /** * Callback class for receiving IMS network Registration callback events. * @see #registerImsRegistrationCallback(Executor, RegistrationCallback) * @see #unregisterImsRegistrationCallback(RegistrationCallback) @@ -119,26 +133,24 @@ public interface RegistrationManager { } @Override - public void onRegistered(int imsRadioTech) { + public void onRegistered(ImsRegistrationAttributes attr) { if (mLocalCallback == null) return; long callingIdentity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> - mLocalCallback.onRegistered(getAccessType(imsRadioTech))); + mExecutor.execute(() -> mLocalCallback.onRegistered(attr)); } finally { restoreCallingIdentity(callingIdentity); } } @Override - public void onRegistering(int imsRadioTech) { + public void onRegistering(ImsRegistrationAttributes attr) { if (mLocalCallback == null) return; long callingIdentity = Binder.clearCallingIdentity(); try { - mExecutor.execute(() -> - mLocalCallback.onRegistering(getAccessType(imsRadioTech))); + mExecutor.execute(() -> mLocalCallback.onRegistering(attr)); } finally { restoreCallingIdentity(callingIdentity); } @@ -183,15 +195,6 @@ public interface RegistrationManager { private void setExecutor(Executor executor) { mExecutor = executor; } - - private static int getAccessType(int regType) { - if (!RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.containsKey(regType)) { - Log.w("RegistrationManager", "RegistrationBinder - invalid regType returned: " - + regType); - return -1; - } - return RegistrationManager.IMS_REG_TO_ACCESS_TYPE_MAP.get(regType); - } } private final RegistrationBinder mBinder = new RegistrationBinder(this); @@ -200,19 +203,42 @@ public interface RegistrationManager { * Notifies the framework when the IMS Provider is registered to the IMS network. * * @param imsTransportType the radio access technology. + * @deprecated Use {@link #onRegistered(ImsRegistrationAttributes)} instead. */ public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) { } /** + * Notifies the framework when the IMS Provider is registered to the IMS network + * with corresponding attributes. + * + * @param attributes The attributes associated with this IMS registration. + */ + public void onRegistered(@NonNull ImsRegistrationAttributes attributes) { + // Default impl to keep backwards compatibility with old implementations + onRegistered(attributes.getTransportType()); + } + + /** * Notifies the framework when the IMS Provider is trying to register the IMS network. * * @param imsTransportType the radio access technology. + * @deprecated Use {@link #onRegistering(ImsRegistrationAttributes)} instead. */ public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) { } /** + * Notifies the framework when the IMS Provider is trying to register the IMS network. + * + * @param attributes The attributes associated with this IMS registration. + */ + public void onRegistering(@NonNull ImsRegistrationAttributes attributes) { + // Default impl to keep backwards compatibility with old implementations + onRegistering(attributes.getTransportType()); + } + + /** * Notifies the framework when the IMS Provider is unregistered from the IMS network. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. @@ -298,10 +324,10 @@ public interface RegistrationManager { * @param executor The {@link Executor} that will be used to call the IMS registration state * callback. * @param stateCallback A callback called on the supplied {@link Executor} that will contain the - * registration state of the IMS service, which will be one of the - * following: {@link #REGISTRATION_STATE_NOT_REGISTERED}, - * {@link #REGISTRATION_STATE_REGISTERING}, or - * {@link #REGISTRATION_STATE_REGISTERED}. + * registration state of the IMS service, which will be one of the + * following: {@link #REGISTRATION_STATE_NOT_REGISTERED}, + * {@link #REGISTRATION_STATE_REGISTERING}, or + * {@link #REGISTRATION_STATE_REGISTERED}. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) void getRegistrationState(@NonNull @CallbackExecutor Executor executor, diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl index 7a6c28bddd09..8931a78709ed 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl @@ -47,7 +47,7 @@ interface IImsRcsController { void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); boolean isCapable(int subId, int capability, int radioTech); - boolean isAvailable(int subId, int capability); + boolean isAvailable(int subId, int capability, int radioTech); // ImsUceAdapter specific void requestCapabilities(int subId, String callingPackage, String callingFeatureId, diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl index 749b1916962e..179407c983e5 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl @@ -21,6 +21,7 @@ import android.net.Uri; import android.telephony.ims.stub.ImsFeatureConfiguration; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; /** * See {@link ImsManager#RegistrationCallback} for more information. @@ -28,8 +29,8 @@ import android.telephony.ims.ImsReasonInfo; * {@hide} */ oneway interface IImsRegistrationCallback { - void onRegistered(int imsRadioTech); - void onRegistering(int imsRadioTech); + void onRegistered(in ImsRegistrationAttributes attr); + void onRegistering(in ImsRegistrationAttributes attr); void onDeregistered(in ImsReasonInfo info); void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info); void onSubscriberAssociatedUriChanged(in Uri[] uris); diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index 87a6873d00b2..47c56e13f829 100644 --- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -36,12 +36,9 @@ import java.util.Set; public final class CapabilityChangeRequest implements Parcelable { /** - * Contains a feature capability, defined as - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}, - * along with an associated technology, defined as + * Contains a MMTEL feature capability {@link MmTelFeature.MmTelCapabilities} and RCS feature + * capability {@link RcsFeature.RcsImsCapabilities}, along with an associated technology, + * defined as * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} */ @@ -49,7 +46,7 @@ public final class CapabilityChangeRequest implements Parcelable { private final int mCapability; private final int radioTech; - public CapabilityPair(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + public CapabilityPair(int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { this.mCapability = capability; this.radioTech = radioTech; @@ -80,13 +77,10 @@ public final class CapabilityChangeRequest implements Parcelable { } /** - * @return The stored capability, defined as - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or - * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS} + * @return The stored capability, defined as {@link MmTelFeature.MmTelCapabilities} and + * {@link RcsFeature.RcsImsCapabilities} */ - public @MmTelFeature.MmTelCapabilities.MmTelCapability int getCapability() { + public int getCapability() { return mCapability; } @@ -123,12 +117,11 @@ public final class CapabilityChangeRequest implements Parcelable { * Add one or many capabilities to the request to be enabled. * * @param capabilities A bitfield of capabilities to enable, valid values are defined in - * {@link MmTelFeature.MmTelCapabilities.MmTelCapability}. + * {@link MmTelFeature.MmTelCapabilities} and {@link RcsFeature.RcsImsCapabilities}. * @param radioTech the radio tech that these capabilities should be enabled for, valid * values are in {@link ImsRegistrationImplBase.ImsRegistrationTech}. */ - public void addCapabilitiesToEnableForTech( - @MmTelFeature.MmTelCapabilities.MmTelCapability int capabilities, + public void addCapabilitiesToEnableForTech(int capabilities, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { addAllCapabilities(mCapabilitiesToEnable, capabilities, radioTech); } @@ -136,12 +129,11 @@ public final class CapabilityChangeRequest implements Parcelable { /** * Add one or many capabilities to the request to be disabled. * @param capabilities A bitfield of capabilities to diable, valid values are defined in - * {@link MmTelFeature.MmTelCapabilities.MmTelCapability}. + * {@link MmTelFeature.MmTelCapabilities} and {@link RcsFeature.RcsImsCapabilities}. * @param radioTech the radio tech that these capabilities should be disabled for, valid * values are in {@link ImsRegistrationImplBase.ImsRegistrationTech}. */ - public void addCapabilitiesToDisableForTech( - @MmTelFeature.MmTelCapabilities.MmTelCapability int capabilities, + public void addCapabilitiesToDisableForTech(int capabilities, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { addAllCapabilities(mCapabilitiesToDisable, capabilities, radioTech); } diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 22df921c4214..85703f8de5e5 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -194,7 +194,6 @@ public class RcsFeature extends ImsFeature { * of the capability and notify the capability status as true using * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the * framework that the capability is available for usage. - * @hide */ public static class RcsImsCapabilities extends Capabilities { /** @hide*/ @@ -226,12 +225,21 @@ public class RcsFeature extends ImsFeature { */ public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1; + /** + * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. + * @param capabilities The capabilities that are supported for RCS in the form of a + * bitfield. + */ public RcsImsCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super(capabilities); } - private RcsImsCapabilities(Capabilities c) { - super(c.getMask()); + /** + * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. + * @param capabilities The capabilities instance that are supported for RCS + */ + private RcsImsCapabilities(Capabilities capabilities) { + super(capabilities.getMask()); } @Override @@ -307,7 +315,7 @@ public class RcsFeature extends ImsFeature { * set, the {@link RcsFeature} has brought up the capability and is ready for framework * requests. To change the status of the capabilities * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called. - * @hide + * @return A copy of the current RcsFeature capability status. */ @Override public @NonNull final RcsImsCapabilities queryCapabilityStatus() { @@ -318,13 +326,13 @@ public class RcsFeature extends ImsFeature { * Notify the framework that the capabilities status has changed. If a capability is enabled, * this signals to the framework that the capability has been initialized and is ready. * Call {@link #queryCapabilityStatus()} to return the current capability status. - * @hide + * @param capabilities The current capability status of the RcsFeature. */ - public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities c) { - if (c == null) { + public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities capabilities) { + if (capabilities == null) { throw new IllegalArgumentException("RcsImsCapabilities must be non-null!"); } - super.notifyCapabilitiesStatusChanged(c); + super.notifyCapabilitiesStatusChanged(capabilities); } /** @@ -333,7 +341,9 @@ public class RcsFeature extends ImsFeature { * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)} to * enable or disable capability A, this method should return the correct configuration for * capability A afterwards (until it has changed). - * @hide + * @param capability The capability that we are querying the configuration for. + * @param radioTech The radio technology type that we are querying. + * @return true if the capability is enabled, false otherwise. */ public boolean queryCapabilityConfiguration( @RcsUceAdapter.RcsImsCapabilityFlag int capability, @@ -355,11 +365,12 @@ public class RcsFeature extends ImsFeature { * If for some reason one or more of these capabilities can not be enabled/disabled, * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError(int, int, int)} should * be called for each capability change that resulted in an error. - * @hide + * @param request The request to change the capability. + * @param callback To notify the framework that the result of the capability changes. */ @Override public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, - @NonNull CapabilityCallbackProxy c) { + @NonNull CapabilityCallbackProxy callback) { // Base Implementation - Override to provide functionality } diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 088a7e26a9d0..23032f0c4d38 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -18,17 +18,18 @@ package android.telephony.ims.stub; import android.annotation.IntDef; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.Uri; import android.os.RemoteException; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.RegistrationManager; import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.util.Log; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.util.RemoteCallbackListExt; import com.android.internal.util.ArrayUtils; @@ -83,7 +84,10 @@ public class ImsRegistrationImplBase { @Override public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException { - return getConnectionType(); + synchronized (mLock) { + return (mRegistrationAttributes == null) ? REGISTRATION_TECH_NONE + : mRegistrationAttributes.getRegistrationTechnology(); + } } @Override @@ -116,8 +120,7 @@ public class ImsRegistrationImplBase { new RemoteCallbackListExt<>(); private final Object mLock = new Object(); // Locked on mLock - private @ImsRegistrationTech - int mConnectionType = REGISTRATION_TECH_NONE; + private ImsRegistrationAttributes mRegistrationAttributes; // Locked on mLock private int mRegistrationState = REGISTRATION_STATE_UNKNOWN; // Locked on mLock, create unspecified disconnect cause. @@ -195,17 +198,24 @@ public class ImsRegistrationImplBase { /** * Notify the framework that the device is connected to the IMS network. * - * @param imsRadioTech the radio access technology. Valid values are defined as - * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. + * @param imsRadioTech the radio access technology. */ public final void onRegistered(@ImsRegistrationTech int imsRadioTech) { - updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERED); + onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); + } + + /** + * Notify the framework that the device is connected to the IMS network. + * + * @param attributes The attributes associated with the IMS registration. + */ + public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) { + updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED); mCallbacks.broadcastAction((c) -> { try { - c.onRegistered(imsRadioTech); + c.onRegistered(attributes); } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "onRegistrationConnected() - Skipping " + - "callback."); + Log.w(LOG_TAG, e + "onRegistered(int, Set) - Skipping callback."); } }); } @@ -213,17 +223,24 @@ public class ImsRegistrationImplBase { /** * Notify the framework that the device is trying to connect the IMS network. * - * @param imsRadioTech the radio access technology. Valid values are defined as - * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. + * @param imsRadioTech the radio access technology. */ public final void onRegistering(@ImsRegistrationTech int imsRadioTech) { - updateToState(imsRadioTech, RegistrationManager.REGISTRATION_STATE_REGISTERING); + onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); + } + + /** + * Notify the framework that the device is trying to connect the IMS network. + * + * @param attributes The attributes associated with the IMS registration. + */ + public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) { + updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING); mCallbacks.broadcastAction((c) -> { try { - c.onRegistering(imsRadioTech); + c.onRegistering(attributes); } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "onRegistrationProcessing() - Skipping " + - "callback."); + Log.w(LOG_TAG, e + "onRegistering(int, Set) - Skipping callback."); } }); } @@ -252,8 +269,7 @@ public class ImsRegistrationImplBase { try { c.onDeregistered(reasonInfo); } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "onRegistrationDisconnected() - Skipping " + - "callback."); + Log.w(LOG_TAG, e + "onDeregistered() - Skipping callback."); } }); } @@ -272,8 +288,7 @@ public class ImsRegistrationImplBase { try { c.onTechnologyChangeFailed(imsRadioTech, reasonInfo); } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "onRegistrationChangeFailed() - Skipping " + - "callback."); + Log.w(LOG_TAG, e + "onTechnologyChangeFailed() - Skipping callback."); } }); } @@ -297,14 +312,13 @@ public class ImsRegistrationImplBase { try { callback.onSubscriberAssociatedUriChanged(uris); } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " - + "callback."); + Log.w(LOG_TAG, e + "onSubscriberAssociatedUriChanged() - Skipping callback."); } } - private void updateToState(@ImsRegistrationTech int connType, int newState) { + private void updateToState(ImsRegistrationAttributes attributes, int newState) { synchronized (mLock) { - mConnectionType = connType; + mRegistrationAttributes = attributes; mRegistrationState = newState; mLastDisconnectCause = null; } @@ -316,7 +330,7 @@ public class ImsRegistrationImplBase { mUrisSet = false; mUris = null; - updateToState(REGISTRATION_TECH_NONE, + updateToState(new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NONE).build(), RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED); if (info != null) { mLastDisconnectCause = info; @@ -328,29 +342,19 @@ public class ImsRegistrationImplBase { } /** - * @return the current registration connection type. Valid values are - * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN} - * @hide - */ - @VisibleForTesting - public final @ImsRegistrationTech int getConnectionType() { - synchronized (mLock) { - return mConnectionType; - } - } - - /** * @param c the newly registered callback that will be updated with the current registration * state. */ private void updateNewCallbackWithState(IImsRegistrationCallback c) throws RemoteException { int state; + ImsRegistrationAttributes attributes; ImsReasonInfo disconnectInfo; boolean urisSet; Uri[] uris; synchronized (mLock) { state = mRegistrationState; + attributes = mRegistrationAttributes; disconnectInfo = mLastDisconnectCause; urisSet = mUrisSet; uris = mUris; @@ -361,11 +365,11 @@ public class ImsRegistrationImplBase { break; } case RegistrationManager.REGISTRATION_STATE_REGISTERING: { - c.onRegistering(getConnectionType()); + c.onRegistering(attributes); break; } case RegistrationManager.REGISTRATION_STATE_REGISTERED: { - c.onRegistered(getConnectionType()); + c.onRegistered(attributes); break; } case REGISTRATION_STATE_UNKNOWN: { diff --git a/tests/ApkVerityTest/OWNERS b/tests/ApkVerityTest/OWNERS new file mode 100644 index 000000000000..d67285ede44a --- /dev/null +++ b/tests/ApkVerityTest/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 36824 + +victorhsieh@google.com diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt index eb04f6907748..ac9e6817a230 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt @@ -76,11 +76,11 @@ class PlatformCompatCommandNotInstalledTest { Params(enableDisable = null, targetSdk = 29, result = false), Params(enableDisable = null, targetSdk = 30, result = true), - Params(enableDisable = true, targetSdk = 29, result = true), + Params(enableDisable = true, targetSdk = 29, result = false), Params(enableDisable = true, targetSdk = 30, result = true), Params(enableDisable = false, targetSdk = 29, result = false), - Params(enableDisable = false, targetSdk = 30, result = false) + Params(enableDisable = false, targetSdk = 30, result = true) ) } diff --git a/tests/net/AndroidManifest.xml b/tests/net/AndroidManifest.xml index 009f817af407..d08b2f8d40dd 100644 --- a/tests/net/AndroidManifest.xml +++ b/tests/net/AndroidManifest.xml @@ -48,6 +48,7 @@ <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" /> <uses-permission android:name="android.permission.NETWORK_FACTORY" /> <uses-permission android:name="android.permission.NETWORK_STATS_PROVIDER" /> + <uses-permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/tests/net/common/java/android/net/CaptivePortalDataTest.kt b/tests/net/common/java/android/net/CaptivePortalDataTest.kt index b2bcfeb9019d..ad5bbf220d57 100644 --- a/tests/net/common/java/android/net/CaptivePortalDataTest.kt +++ b/tests/net/common/java/android/net/CaptivePortalDataTest.kt @@ -54,12 +54,26 @@ class CaptivePortalDataTest { } .build() + private val dataFromPasspoint = CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse("https://tc.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setVenueInfoUrl(Uri.parse("https://venue.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setCaptive(true) + .apply { + if (SdkLevel.isAtLeastS()) { + setVenueFriendlyName("venue friendly name") + } + } + .build() + private fun makeBuilder() = CaptivePortalData.Builder(data) @Test fun testParcelUnparcel() { - val fieldCount = if (SdkLevel.isAtLeastS()) 8 else 7 + val fieldCount = if (SdkLevel.isAtLeastS()) 10 else 7 assertParcelSane(data, fieldCount) + assertParcelSane(dataFromPasspoint, fieldCount) assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build()) assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build()) @@ -83,6 +97,27 @@ class CaptivePortalDataTest { assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") } assertNotEqualsAfterChange { it.setVenueFriendlyName(null) } } + + assertEquals(dataFromPasspoint, CaptivePortalData.Builder(dataFromPasspoint).build()) + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/passpoint")) } + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) } + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/other"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) } + assertNotEqualsAfterChange { it.setUserPortalUrl( + Uri.parse("https://tc.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) } + assertNotEqualsAfterChange { it.setVenueInfoUrl( + Uri.parse("https://venue.example.com/passpoint")) } + assertNotEqualsAfterChange { it.setVenueInfoUrl( + Uri.parse("https://venue.example.com/other"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) } + assertNotEqualsAfterChange { it.setVenueInfoUrl( + Uri.parse("https://venue.example.com/passpoint"), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) } } @Test @@ -130,6 +165,22 @@ class CaptivePortalDataTest { assertEquals("venue friendly name", data.venueFriendlyName) } + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + fun testGetVenueInfoUrlSource() { + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER, + data.venueInfoUrlSource) + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT, + dataFromPasspoint.venueInfoUrlSource) + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.R) + fun testGetUserPortalUrlSource() { + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER, + data.userPortalUrlSource) + assertEquals(CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT, + dataFromPasspoint.userPortalUrlSource) + } + private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) = CaptivePortalData.Builder(this).apply { mutator(this) }.build() diff --git a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java index d232a507454d..fd29a9539de8 100644 --- a/tests/net/common/java/android/net/OemNetworkPreferencesTest.java +++ b/tests/net/common/java/android/net/OemNetworkPreferencesTest.java @@ -40,7 +40,7 @@ import java.util.Map; @SmallTest public class OemNetworkPreferencesTest { - private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_DEFAULT; + private static final int TEST_PREF = OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; private static final String TEST_PACKAGE = "com.google.apps.contacts"; private final OemNetworkPreferences.Builder mBuilder = new OemNetworkPreferences.Builder(); @@ -54,7 +54,7 @@ public class OemNetworkPreferencesTest { @Test public void testBuilderRemoveNetworkPreferenceRequiresNonNullPackageName() { assertThrows(NullPointerException.class, - () -> mBuilder.removeNetworkPreference(null)); + () -> mBuilder.clearNetworkPreference(null)); } @Test @@ -129,7 +129,7 @@ public class OemNetworkPreferencesTest { assertTrue(networkPreferences.containsKey(TEST_PACKAGE)); - mBuilder.removeNetworkPreference(TEST_PACKAGE); + mBuilder.clearNetworkPreference(TEST_PACKAGE); networkPreferences = mBuilder.build().getNetworkPreferences(); assertFalse(networkPreferences.containsKey(TEST_PACKAGE)); diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index e1da3d0ae2b3..dc9e587332cb 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -17,7 +17,6 @@ package com.android.server; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -85,7 +84,6 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { final String typeName = ConnectivityManager.getNetworkTypeName(type); mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities(); mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED); - mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); mNetworkCapabilities.addTransportType(transport); switch (transport) { case TRANSPORT_ETHERNET: diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index fcfb4aa9b864..6a09b0237a38 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -35,6 +35,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST; import static android.net.NetworkRequest.Type.REQUEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -329,6 +330,9 @@ public class ConnectivityManagerTest { mustFail(() -> { manager.registerDefaultNetworkCallback(null, handler); }); mustFail(() -> { manager.registerDefaultNetworkCallback(callback, null); }); + mustFail(() -> { manager.registerSystemDefaultNetworkCallback(null, handler); }); + mustFail(() -> { manager.registerSystemDefaultNetworkCallback(callback, null); }); + mustFail(() -> { manager.unregisterNetworkCallback(nullCallback); }); mustFail(() -> { manager.unregisterNetworkCallback(nullIntent); }); mustFail(() -> { manager.releaseNetworkRequest(nullIntent); }); @@ -377,6 +381,13 @@ public class ConnectivityManagerTest { eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), eq(testPkgName), eq(testAttributionTag)); reset(mService); + + Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + manager.registerSystemDefaultNetworkCallback(callback, handler); + verify(mService).requestNetwork(eq(null), + eq(TRACK_SYSTEM_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), + eq(testPkgName), eq(testAttributionTag)); + reset(mService); } static Message makeMessage(NetworkRequest req, int messageType) { diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java index 95a794235a2e..c548e30761c9 100644 --- a/tests/net/java/android/net/VpnManagerTest.java +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -49,7 +49,7 @@ public class VpnManagerTest { private static final String IDENTITY_STRING = "Identity"; private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); - private IConnectivityManager mMockCs; + private IVpnManager mMockService; private VpnManager mVpnManager; private final MockContext mMockContext = new MockContext() { @@ -61,24 +61,26 @@ public class VpnManagerTest { @Before public void setUp() throws Exception { - mMockCs = mock(IConnectivityManager.class); - mVpnManager = new VpnManager(mMockContext, mMockCs); + mMockService = mock(IVpnManager.class); + mVpnManager = new VpnManager(mMockContext, mMockService); } @Test public void testProvisionVpnProfilePreconsented() throws Exception { final PlatformVpnProfile profile = getPlatformVpnProfile(); - when(mMockCs.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME))).thenReturn(true); + when(mMockService.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)); + verify(mMockService).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); + when(mMockService.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); @@ -88,25 +90,25 @@ public class VpnManagerTest { ComponentName.unflattenFromString( "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog"); assertEquals(expectedComponentName, intent.getComponent()); - verify(mMockCs).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); + verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME)); } @Test public void testDeleteProvisionedVpnProfile() throws Exception { mVpnManager.deleteProvisionedVpnProfile(); - verify(mMockCs).deleteVpnProfile(eq(PKG_NAME)); + verify(mMockService).deleteVpnProfile(eq(PKG_NAME)); } @Test public void testStartProvisionedVpnProfile() throws Exception { mVpnManager.startProvisionedVpnProfile(); - verify(mMockCs).startVpnProfile(eq(PKG_NAME)); + verify(mMockService).startVpnProfile(eq(PKG_NAME)); } @Test public void testStopProvisionedVpnProfile() throws Exception { mVpnManager.stopProvisionedVpnProfile(); - verify(mMockCs).stopVpnProfile(eq(PKG_NAME)); + verify(mMockService).stopVpnProfile(eq(PKG_NAME)); } private Ikev2VpnProfile getPlatformVpnProfile() throws Exception { diff --git a/tests/net/java/android/net/VpnTransportInfoTest.java b/tests/net/java/android/net/VpnTransportInfoTest.java new file mode 100644 index 000000000000..866f38c84333 --- /dev/null +++ b/tests/net/java/android/net/VpnTransportInfoTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static com.android.testutils.ParcelUtils.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VpnTransportInfoTest { + + @Test + public void testParceling() { + VpnTransportInfo v = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); + assertParcelSane(v, 1 /* fieldCount */); + } + + @Test + public void testEqualsAndHashCode() { + VpnTransportInfo v1 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); + VpnTransportInfo v2 = new VpnTransportInfo(VpnManager.TYPE_VPN_SERVICE); + VpnTransportInfo v3 = new VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM); + assertNotEquals(v1, v2); + assertEquals(v1, v3); + assertEquals(v1.hashCode(), v3.hashCode()); + } +}
\ No newline at end of file diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index edc2c636e0ee..a84e2f15657c 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -200,11 +200,13 @@ import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; +import android.net.TransportInfo; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.UnderlyingNetworkInfo; import android.net.Uri; import android.net.VpnManager; +import android.net.VpnTransportInfo; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; @@ -361,13 +363,21 @@ public class ConnectivityServiceTest { private static final String INTERFACE_NAME = "interface"; - private static final String TEST_VENUE_URL_NA = "https://android.com/"; + private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/"; + private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/"; + private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT = + "https://android.com/terms/"; + private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER = + "https://example.com/terms/"; private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/"; + private static final String TEST_USER_PORTAL_API_URL_CAPPORT = + "https://android.com/user/api/capport/"; private static final String TEST_FRIENDLY_NAME = "Network friendly name"; private static final String TEST_REDIRECT_URL = "http://example.com/firstPath"; private MockContext mServiceContext; private HandlerThread mCsHandlerThread; + private HandlerThread mVMSHandlerThread; private ConnectivityService.Dependencies mDeps; private ConnectivityService mService; private WrappedConnectivityManager mCm; @@ -382,6 +392,7 @@ public class ConnectivityServiceTest { private TestNetIdManager mNetIdManager; private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; + private VpnManagerService mVpnManagerService; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -1110,7 +1121,7 @@ public class ConnectivityServiceTest { } @Override - public int getActiveAppVpnType() { + public int getActiveVpnType() { return mVpnType; } @@ -1123,10 +1134,12 @@ public class ConnectivityServiceTest { private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp) throws Exception { if (mAgentRegistered) throw new IllegalStateException("already registered"); + updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent"); mConfig = new VpnConfig(); setUids(uids); if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); mInterface = VPN_IFNAME; + mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType())); mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, mNetworkCapabilities); mMockNetworkAgent.waitForIdle(TIMEOUT_MS); @@ -1252,24 +1265,55 @@ public class ConnectivityServiceTest { r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new); } - private void mockVpn(int uid) { - synchronized (mService.mVpns) { - int userId = UserHandle.getUserId(uid); - mMockVpn = new MockVpn(userId); - // This has no effect unless the VPN is actually connected, because things like - // getActiveNetworkForUidInternal call getNetworkAgentInfoForNetId on the VPN - // netId, and check if that network is actually connected. - mService.mVpns.put(userId, mMockVpn); - } + private VpnManagerService makeVpnManagerService() { + final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() { + public int getCallingUid() { + return mDeps.getCallingUid(); + } + + public HandlerThread makeHandlerThread() { + return mVMSHandlerThread; + } + + public KeyStore getKeyStore() { + return mKeyStore; + } + + public INetd getNetd() { + return mMockNetd; + } + + public INetworkManagementService getINetworkManagementService() { + return mNetworkManagementService; + } + }; + return new VpnManagerService(mServiceContext, deps); + } + + private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { + assertNotNull(nc); + final TransportInfo ti = nc.getTransportInfo(); + assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti, + ti instanceof VpnTransportInfo); + assertEquals(type, ((VpnTransportInfo) ti).type); + } private void processBroadcastForVpn(Intent intent) { - // The BroadcastReceiver for this broadcast checks it is being run on the handler thread. - final Handler handler = new Handler(mCsHandlerThread.getLooper()); - handler.post(() -> mServiceContext.sendBroadcast(intent)); + mServiceContext.sendBroadcast(intent); + HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS); waitForIdle(); } + private void mockVpn(int uid) { + synchronized (mVpnManagerService.mVpns) { + int userId = UserHandle.getUserId(uid); + mMockVpn = new MockVpn(userId); + // Every running user always has a Vpn in the mVpns array, even if no VPN is running. + mVpnManagerService.mVpns.put(userId, mMockVpn); + } + } + private void mockUidNetworkingBlocked() { doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, @@ -1384,6 +1428,7 @@ public class ConnectivityServiceTest { FakeSettingsProvider.clearSettingsProvider(); mServiceContext = new MockContext(InstrumentationRegistry.getContext(), new FakeSettingsProvider()); + mServiceContext.setUseRegisteredHandlers(true); LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService( NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class)); @@ -1393,6 +1438,7 @@ public class ConnectivityServiceTest { initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); mCsHandlerThread = new HandlerThread("TestConnectivityService"); + mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); mDeps = makeDependencies(); returnRealCallingUid(); mService = new ConnectivityService(mServiceContext, @@ -1415,6 +1461,8 @@ public class ConnectivityServiceTest { // getSystemService() correctly. mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); mService.systemReadyInternal(); + mVpnManagerService = makeVpnManagerService(); + mVpnManagerService.systemReady(); mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); mQosCallbackTracker = mock(QosCallbackTracker.class); @@ -1442,7 +1490,6 @@ public class ConnectivityServiceTest { doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any()); doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); - doReturn(mKeyStore).when(deps).getKeyStore(); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); @@ -2707,10 +2754,6 @@ public class ConnectivityServiceTest { NetworkCapabilities filter = new NetworkCapabilities(); filter.addCapability(capability); - // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add - // NOT_VCN_MANAGED automatically but not for NetworkCapabilities, - // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details. - filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); handlerThread.start(); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), @@ -3286,39 +3329,68 @@ public class ConnectivityServiceTest { } private class CaptivePortalTestData { - CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData, - CaptivePortalData expectedMergedData) { - mNaData = naData; + CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData, + CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData, + CaptivePortalData expectedMergedOtherData) { + mNaPasspointData = naPasspointData; mCapportData = capportData; - mExpectedMergedData = expectedMergedData; + mNaOtherData = naOtherData; + mExpectedMergedPasspointData = expectedMergedPasspointData; + mExpectedMergedOtherData = expectedMergedOtherData; } - public final CaptivePortalData mNaData; + public final CaptivePortalData mNaPasspointData; public final CaptivePortalData mCapportData; - public final CaptivePortalData mExpectedMergedData; + public final CaptivePortalData mNaOtherData; + public final CaptivePortalData mExpectedMergedPasspointData; + public final CaptivePortalData mExpectedMergedOtherData; + } private CaptivePortalTestData setupCaptivePortalData() { final CaptivePortalData capportData = new CaptivePortalData.Builder() .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) + .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT)) .setExpiryTime(1000000L) .setBytesRemaining(12345L) .build(); - final CaptivePortalData naData = new CaptivePortalData.Builder() + final CaptivePortalData naPasspointData = new CaptivePortalData.Builder() + .setBytesRemaining(80802L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + + final CaptivePortalData naOtherData = new CaptivePortalData.Builder() .setBytesRemaining(80802L) - .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA)) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) + .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); - final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder() + final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder() .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) .setBytesRemaining(12345L) .setExpiryTime(1000000L) - .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA)) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) + .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT), + CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); - return new CaptivePortalTestData(naData, capportData, expectedMergedData); + final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder() + .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) + .setBytesRemaining(12345L) + .setExpiryTime(1000000L) + .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) + .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT)) + .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); + return new CaptivePortalTestData(naPasspointData, capportData, naOtherData, + expectedMergedPasspointData, expectedMergedOtherData); } @Test @@ -3332,15 +3404,26 @@ public class ConnectivityServiceTest { captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); - // Venue URL and friendly name from Network agent, confirm that API data gets precedence - // on the bytes remaining. + // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm + // that API data gets precedence on the bytes remaining. final LinkProperties linkProperties = new LinkProperties(); - linkProperties.setCaptivePortalData(captivePortalTestData.mNaData); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the capport data is merged + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedPasspointData + .equals(lp.getCaptivePortalData())); + + // Now send this information from non-Passpoint source, confirm that Capport data takes + // precedence + linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData); mWiFiNetworkAgent.sendLinkProperties(linkProperties); // Make sure that the capport data is merged captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, - lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData())); + lp -> captivePortalTestData.mExpectedMergedOtherData + .equals(lp.getCaptivePortalData())); // Create a new LP with no Network agent capport data final LinkProperties newLps = new LinkProperties(); @@ -3357,12 +3440,12 @@ public class ConnectivityServiceTest { captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> lp.getCaptivePortalData() == null); - newLps.setCaptivePortalData(captivePortalTestData.mNaData); + newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData); mWiFiNetworkAgent.sendLinkProperties(newLps); // Make sure that only the network agent capport data is available captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, - lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData())); + lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData())); } @Test @@ -3373,12 +3456,12 @@ public class ConnectivityServiceTest { // Venue URL and friendly name from Network agent, confirm that API data gets precedence // on the bytes remaining. final LinkProperties linkProperties = new LinkProperties(); - linkProperties.setCaptivePortalData(captivePortalTestData.mNaData); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData); mWiFiNetworkAgent.sendLinkProperties(linkProperties); // Make sure that the data is saved correctly captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, - lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData())); + lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData())); // Expected merged data: Network agent data is preferred, and values that are not used by // it are merged from capport data @@ -3386,7 +3469,8 @@ public class ConnectivityServiceTest { // Make sure that the Capport data is merged correctly captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, - lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData())); + lp -> captivePortalTestData.mExpectedMergedPasspointData.equals( + lp.getCaptivePortalData())); // Now set the naData to null linkProperties.setCaptivePortalData(null); @@ -3397,6 +3481,32 @@ public class ConnectivityServiceTest { lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); } + @Test + public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport() + throws Exception { + final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); + final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); + + // Venue URL and friendly name from Network agent, confirm that API data gets precedence + // on the bytes remaining. + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData); + mWiFiNetworkAgent.sendLinkProperties(linkProperties); + + // Make sure that the data is saved correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData())); + + // Expected merged data: Network agent data is preferred, and values that are not used by + // it are merged from capport data + mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); + + // Make sure that the Capport data is merged correctly + captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, + lp -> captivePortalTestData.mExpectedMergedOtherData.equals( + lp.getCaptivePortalData())); + } + private NetworkRequest.Builder newWifiRequestBuilder() { return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI); } @@ -3649,10 +3759,19 @@ public class ConnectivityServiceTest { @Test public void testRegisterDefaultNetworkCallback() throws Exception { + // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_GRANTED); + final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultNetworkCallback); defaultNetworkCallback.assertNoCallback(); + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler); + systemDefaultCallback.assertNoCallback(); + // Create a TRANSPORT_CELLULAR request to keep the mobile interface up // whenever Wi-Fi is up. Without this, the mobile network agent is // reaped before any other activity can take place. @@ -3667,27 +3786,35 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring up wifi and expect CALLBACK_AVAILABLE. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); cellNetworkCallback.assertNoCallback(); defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring down cell. Expect no default network callback, since it wasn't the default. mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring up cell. Expect no default network callback, since it won't be the default. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); // Bring down wifi. Expect the default network callback to notified of LOST wifi // followed by AVAILABLE cell. @@ -3695,19 +3822,25 @@ public class ConnectivityServiceTest { cellNetworkCallback.assertNoCallback(); defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); mMockVpn.establishForMyUid(); assertUidRangesUpdatedForMyUid(true); defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(null, systemDefaultCallback.getLastAvailableNetwork()); mMockVpn.disconnect(); defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + systemDefaultCallback.assertNoCallback(); waitForIdle(); assertEquals(null, mCm.getActiveNetwork()); } @@ -3773,6 +3906,24 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(cellNetworkCallback); } + @Test + public void testRegisterSystemDefaultCallbackRequiresNetworkSettings() throws Exception { + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(false /* validated */); + + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final TestNetworkCallback callback = new TestNetworkCallback(); + assertThrows(SecurityException.class, + () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); + callback.assertNoCallback(); + + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_GRANTED); + mCm.registerSystemDefaultNetworkCallback(callback, handler); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + mCm.unregisterNetworkCallback(callback); + } + private void setCaptivePortalMode(int mode) { ContentResolver cr = mServiceContext.getContentResolver(); Settings.Global.putInt(cr, Settings.Global.CAPTIVE_PORTAL_MODE, mode); @@ -3962,7 +4113,6 @@ public class ConnectivityServiceTest { handlerThread.start(); NetworkCapabilities filter = new NetworkCapabilities() .addTransportType(TRANSPORT_CELLULAR) - .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addCapability(NET_CAPABILITY_INTERNET); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter); @@ -5866,7 +6016,6 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_CONGESTED) - .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setLinkDownstreamBandwidthKbps(10); final NetworkCapabilities wifiNc = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) @@ -5875,7 +6024,6 @@ public class ConnectivityServiceTest { .addCapability(NET_CAPABILITY_NOT_ROAMING) .addCapability(NET_CAPABILITY_NOT_CONGESTED) .addCapability(NET_CAPABILITY_NOT_SUSPENDED) - .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setLinkUpstreamBandwidthKbps(20); mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */); mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */); @@ -6134,6 +6282,10 @@ public class ConnectivityServiceTest { @Test public void testVpnNetworkActive() throws Exception { + // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, + PERMISSION_GRANTED); + final int uid = Process.myUid(); final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); @@ -6141,6 +6293,7 @@ public class ConnectivityServiceTest { final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build(); final NetworkRequest genericRequest = new NetworkRequest.Builder() .removeCapability(NET_CAPABILITY_NOT_VPN).build(); @@ -6154,6 +6307,8 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); mCm.registerDefaultNetworkCallback(defaultCallback); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper())); defaultCallback.assertNoCallback(); mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); @@ -6163,6 +6318,7 @@ public class ConnectivityServiceTest { genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); vpnNetworkCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); @@ -6183,7 +6339,10 @@ public class ConnectivityServiceTest { wifiNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); + assertEquals(mWiFiNetworkAgent.getNetwork(), + systemDefaultCallback.getLastAvailableNetwork()); ranges.clear(); mMockVpn.setUids(ranges); @@ -6200,6 +6359,7 @@ public class ConnectivityServiceTest { // much, but that is the reason the test here has to check for an update to the // capabilities instead of the expected LOST then AVAILABLE. defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + systemDefaultCallback.assertNoCallback(); ranges.add(new UidRange(uid, uid)); mMockVpn.setUids(ranges); @@ -6211,6 +6371,7 @@ public class ConnectivityServiceTest { // TODO : Here like above, AVAILABLE would be correct, but because this can't actually // happen outside of the test, ConnectivityService does not rematch callbacks. defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); + systemDefaultCallback.assertNoCallback(); mWiFiNetworkAgent.disconnect(); @@ -6219,6 +6380,7 @@ public class ConnectivityServiceTest { wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); vpnNetworkCallback.assertNoCallback(); defaultCallback.assertNoCallback(); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); mMockVpn.disconnect(); @@ -6227,12 +6389,14 @@ public class ConnectivityServiceTest { wifiNetworkCallback.assertNoCallback(); vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); + systemDefaultCallback.assertNoCallback(); assertEquals(null, mCm.getActiveNetwork()); mCm.unregisterNetworkCallback(genericNetworkCallback); mCm.unregisterNetworkCallback(wifiNetworkCallback); mCm.unregisterNetworkCallback(vpnNetworkCallback); mCm.unregisterNetworkCallback(defaultCallback); + mCm.unregisterNetworkCallback(systemDefaultCallback); } @Test @@ -6368,6 +6532,8 @@ public class ConnectivityServiceTest { assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED)); assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); } private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { @@ -6407,6 +6573,7 @@ public class ConnectivityServiceTest { assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); // A VPN without underlying networks is not suspended. assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); final int userId = UserHandle.getUserId(Process.myUid()); assertDefaultNetworkCapabilities(userId /* no networks */); @@ -6570,6 +6737,7 @@ public class ConnectivityServiceTest { // By default, VPN is set to track default network (i.e. its underlying networks is null). // In case of no default network, VPN is considered metered. assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Connect to Cell; Cell is the default network. mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); @@ -6627,6 +6795,7 @@ public class ConnectivityServiceTest { NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); assertNotNull("nc=" + nc, nc.getUids()); assertEquals(nc.getUids(), uidRangesForUid(uid)); + assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); // Set an underlying network and expect to see the VPN transports change. mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); @@ -6709,8 +6878,8 @@ public class ConnectivityServiceTest { // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. final ArrayList<String> allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, true /* lockdown */, - allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, + true /* lockdown */, allowList); waitForIdle(); assertNull(mCm.getActiveNetworkForUid(uid)); // This is arguably overspecified: a UID that is not running doesn't have an active network. @@ -6740,7 +6909,8 @@ public class ConnectivityServiceTest { assertNull(mCm.getActiveNetworkForUid(uid)); assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); - mService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, + allowList); waitForIdle(); } @@ -7116,7 +7286,8 @@ public class ConnectivityServiceTest { final int uid = Process.myUid(); final int userId = UserHandle.getUserId(uid); final ArrayList<String> allowList = new ArrayList<>(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1); @@ -7138,7 +7309,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown, expect to see the network unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7151,7 +7322,8 @@ public class ConnectivityServiceTest { // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked. allowList.add(TEST_PACKAGE_NAME); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); callback.assertNoCallback(); defaultCallback.assertNoCallback(); vpnUidCallback.assertNoCallback(); @@ -7184,11 +7356,12 @@ public class ConnectivityServiceTest { // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown. // Everything should now be blocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3); allowList.clear(); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); waitForIdle(); expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); @@ -7201,7 +7374,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); // Disable lockdown. Everything is unblocked. - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7213,7 +7386,8 @@ public class ConnectivityServiceTest { // Enable and disable an always-on VPN package without lockdown. Expect no changes. reset(mMockNetd); - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, + allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7224,7 +7398,7 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); callback.assertNoCallback(); defaultCallback.assertNoCallback(); @@ -7236,7 +7410,8 @@ public class ConnectivityServiceTest { assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); // Enable lockdown and connect a VPN. The VPN is not blocked. - mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList); + mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, + allowList); defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); vpnUidCallback.assertNoCallback(); @@ -7282,10 +7457,14 @@ public class ConnectivityServiceTest { when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } - private void establishLegacyLockdownVpn() throws Exception { + private void establishLegacyLockdownVpn(Network underlying) throws Exception { + // The legacy lockdown VPN only supports userId 0, and must have an underlying network. + assertNotNull(underlying); + mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY); // The legacy lockdown VPN only supports userId 0. final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); mMockVpn.registerAgent(ranges); + mMockVpn.setUnderlyingNetworks(new Network[]{underlying}); mMockVpn.connect(true); } @@ -7293,6 +7472,9 @@ public class ConnectivityServiceTest { public void testLegacyLockdownVpn() throws Exception { mServiceContext.setPermission( Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); + // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback. + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); final TestNetworkCallback callback = new TestNetworkCallback(); @@ -7301,6 +7483,10 @@ public class ConnectivityServiceTest { final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); + final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + // Pretend lockdown VPN was configured. setupLegacyLockdownVpn(); @@ -7330,6 +7516,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7341,6 +7528,8 @@ public class ConnectivityServiceTest { mCellNetworkAgent.sendLinkProperties(cellLp); callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, + mCellNetworkAgent); waitForIdle(); assertNull(mMockVpn.getAgent()); @@ -7350,6 +7539,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); b1.expectBroadcast(); // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten @@ -7359,6 +7549,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent.connect(false /* validated */); callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); b1.expectBroadcast(); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); @@ -7381,9 +7572,10 @@ public class ConnectivityServiceTest { mMockVpn.expectStartLegacyVpnRunner(); b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); - establishLegacyLockdownVpn(); + establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork()); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); b1.expectBroadcast(); b2.expectBroadcast(); @@ -7395,6 +7587,7 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY); // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. final LinkProperties wifiLp = new LinkProperties(); @@ -7422,11 +7615,10 @@ public class ConnectivityServiceTest { // fact that a VPN is connected should only result in the VPN itself being unblocked, not // any other network. Bug in isUidBlockedByVpn? callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); - callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI)); callback.expectCallback(CallbackEntry.LOST, mMockVpn); - defaultCallback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasTransport(TRANSPORT_WIFI)); defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); + systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // While the VPN is reconnecting on the new network, everything is blocked. assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); @@ -7437,9 +7629,10 @@ public class ConnectivityServiceTest { // The VPN comes up again on wifi. b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); - establishLegacyLockdownVpn(); + establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork()); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + systemDefaultCallback.assertNoCallback(); b1.expectBroadcast(); b2.expectBroadcast(); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); @@ -7453,14 +7646,10 @@ public class ConnectivityServiceTest { assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Disconnect cell. Nothing much happens since it's not the default network. - // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any - // NetworkInfo is updated. This is probably a bug. - // TODO: consider fixing this. - b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); mCellNetworkAgent.disconnect(); - b1.expectBroadcast(); callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); defaultCallback.assertNoCallback(); + systemDefaultCallback.assertNoCallback(); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); @@ -7470,6 +7659,7 @@ public class ConnectivityServiceTest { b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); + systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); b1.expectBroadcast(); callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI)); b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); @@ -7521,13 +7711,19 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.removeCapability(testCap); callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has + // it. + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + } mCellNetworkAgent.removeCapability(testCap); callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); callbackWithoutCap.assertNoCallback(); - verify(mMockNetd).networkClearDefault(); + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkClearDefault(); + } mCm.unregisterNetworkCallback(callbackWithCap); mCm.unregisterNetworkCallback(callbackWithoutCap); @@ -8521,11 +8717,7 @@ public class ConnectivityServiceTest { final int myUid = Process.myUid(); setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM); - try { - mService.getConnectionOwnerUid(getTestConnectionInfo()); - fail("Expected SecurityException for non-VpnService app"); - } catch (SecurityException expected) { - } + assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo())); } @Test @@ -8533,11 +8725,7 @@ public class ConnectivityServiceTest { final int myUid = Process.myUid(); setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE); - try { - mService.getConnectionOwnerUid(getTestConnectionInfo()); - fail("Expected SecurityException for non-VpnService app"); - } catch (SecurityException expected) { - } + assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo())); } @Test diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 799bcc82d2a9..c86224a71978 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.when; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.INetd; import android.net.InetAddresses; import android.net.IpSecAlgorithm; @@ -44,6 +45,7 @@ import android.net.IpSecTransformResponse; import android.net.IpSecTunnelInterfaceResponse; import android.net.IpSecUdpEncapResponse; import android.net.LinkAddress; +import android.net.LinkProperties; import android.net.Network; import android.os.Binder; import android.os.INetworkManagementService; @@ -53,6 +55,8 @@ import android.test.mock.MockContext; import androidx.test.filters.SmallTest; +import com.android.server.IpSecService.TunnelInterfaceRecord; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -109,6 +113,7 @@ public class IpSecServiceParameterizedTest { }; AppOpsManager mMockAppOps = mock(AppOpsManager.class); + ConnectivityManager mMockConnectivityMgr = mock(ConnectivityManager.class); MockContext mMockContext = new MockContext() { @Override @@ -116,12 +121,22 @@ public class IpSecServiceParameterizedTest { switch(name) { case Context.APP_OPS_SERVICE: return mMockAppOps; + case Context.CONNECTIVITY_SERVICE: + return mMockConnectivityMgr; default: return null; } } @Override + public String getSystemServiceName(Class<?> serviceClass) { + if (ConnectivityManager.class == serviceClass) { + return Context.CONNECTIVITY_SERVICE; + } + return null; + } + + @Override public PackageManager getPackageManager() { return mMockPkgMgr; } @@ -151,6 +166,10 @@ public class IpSecServiceParameterizedTest { new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); private static final int REMOTE_ENCAP_PORT = 4500; + private static final String BLESSED_PACKAGE = "blessedPackage"; + private static final String SYSTEM_PACKAGE = "systemPackage"; + private static final String BAD_PACKAGE = "badPackage"; + public IpSecServiceParameterizedTest( String sourceAddr, String destAddr, String localInnerAddr, int family) { mSourceAddr = sourceAddr; @@ -174,15 +193,15 @@ public class IpSecServiceParameterizedTest { when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true); // A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED. - when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage"))) - .thenReturn(AppOpsManager.MODE_ALLOWED); + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BLESSED_PACKAGE))) + .thenReturn(AppOpsManager.MODE_ALLOWED); // A system package will not be granted the app op, so this should fall back to // a permissions check, which should pass. - when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("systemPackage"))) - .thenReturn(AppOpsManager.MODE_DEFAULT); + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(SYSTEM_PACKAGE))) + .thenReturn(AppOpsManager.MODE_DEFAULT); // A mismatch between the package name and the UID will return MODE_IGNORED. - when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("badPackage"))) - .thenReturn(AppOpsManager.MODE_IGNORED); + when(mMockAppOps.noteOp(anyInt(), anyInt(), eq(BAD_PACKAGE))) + .thenReturn(AppOpsManager.MODE_IGNORED); } //TODO: Add a test to verify SPI. @@ -338,7 +357,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); @@ -352,7 +371,7 @@ public class IpSecServiceParameterizedTest { ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp); @@ -370,14 +389,14 @@ public class IpSecServiceParameterizedTest { if (mFamily == AF_INET) { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); } else { try { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); } catch (IllegalArgumentException expected) { } @@ -396,14 +415,14 @@ public class IpSecServiceParameterizedTest { if (mFamily == AF_INET) { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); verifyTransformNetdCalledForCreatingSA(ipSecConfig, createTransformResp, udpSock.port); } else { try { IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("Expected IllegalArgumentException on attempt to use UDP Encap in IPv6"); } catch (IllegalArgumentException expected) { } @@ -417,12 +436,12 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); assertEquals(IpSecManager.Status.OK, createTransformResp.status); // Attempting to create transform a second time with the same SPIs should throw an error... try { - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("IpSecService should have thrown an error for reuse of SPI"); } catch (IllegalStateException expected) { } @@ -430,7 +449,7 @@ public class IpSecServiceParameterizedTest { // ... even if the transform is deleted mIpSecService.deleteTransform(createTransformResp.resourceId); try { - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); fail("IpSecService should have thrown an error for reuse of SPI"); } catch (IllegalStateException expected) { } @@ -443,7 +462,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); assertEquals(1, userRecord.mSpiQuotaTracker.mCurrent); mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -467,7 +486,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); mIpSecService.deleteTransform(createTransformResp.resourceId); verify(mMockNetd, times(1)) @@ -515,7 +534,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); IpSecService.RefcountedResource refcountedRecord = @@ -562,7 +581,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); if (closeSpiBeforeApply) { mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -592,7 +611,7 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); // Close SPI record mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -638,7 +657,7 @@ public class IpSecServiceParameterizedTest { @Test public void testCreateTunnelInterface() throws Exception { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); // Check that we have stored the tracking object, and retrieve it IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); @@ -661,11 +680,11 @@ public class IpSecServiceParameterizedTest { @Test public void testDeleteTunnelInterface() throws Exception { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); - mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, "blessedPackage"); + mIpSecService.deleteTunnelInterface(createTunnelResp.resourceId, BLESSED_PACKAGE); // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mTunnelQuotaTracker.mCurrent); @@ -678,10 +697,73 @@ public class IpSecServiceParameterizedTest { } } + private Network createFakeUnderlyingNetwork(String interfaceName) { + final Network fakeNetwork = new Network(1000); + final LinkProperties fakeLp = new LinkProperties(); + fakeLp.setInterfaceName(interfaceName); + when(mMockConnectivityMgr.getLinkProperties(eq(fakeNetwork))).thenReturn(fakeLp); + return fakeNetwork; + } + + @Test + public void testSetNetworkForTunnelInterface() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = createFakeUnderlyingNetwork("newFakeNetworkInterface"); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + assertEquals(1, userRecord.mTunnelQuotaTracker.mCurrent); + + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + assertEquals(newFakeNetwork, tunnelInterfaceInfo.getUnderlyingNetwork()); + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final Network newFakeNetwork = new Network(1000); + + try { + mIpSecService.setNetworkForTunnelInterface( + IpSecManager.INVALID_RESOURCE_ID, newFakeNetwork, BLESSED_PACKAGE); + fail("Expected an IllegalArgumentException for invalid resource ID."); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetNetworkForTunnelInterfaceFailsWhenSettingTunnelNetwork() throws Exception { + final IpSecTunnelInterfaceResponse createTunnelResp = + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); + final int tunnelIfaceResourceId = createTunnelResp.resourceId; + final IpSecService.UserRecord userRecord = + mIpSecService.mUserResourceTracker.getUserRecord(mUid); + final TunnelInterfaceRecord tunnelInterfaceInfo = + userRecord.mTunnelInterfaceRecords.getResourceOrThrow(tunnelIfaceResourceId); + + final Network newFakeNetwork = + createFakeUnderlyingNetwork(tunnelInterfaceInfo.getInterfaceName()); + + try { + mIpSecService.setNetworkForTunnelInterface( + tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE); + fail( + "Expected an IllegalArgumentException because the underlying network is the" + + " network being exposed by this tunnel."); + } catch (IllegalArgumentException expected) { + } + } + @Test public void testTunnelInterfaceBinderDeath() throws Exception { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(mUid); IpSecService.RefcountedResource refcountedRecord = @@ -718,9 +800,9 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); if (closeSpiBeforeApply) { mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); @@ -728,8 +810,8 @@ public class IpSecServiceParameterizedTest { int transformResourceId = createTransformResp.resourceId; int tunnelResourceId = createTunnelResp.resourceId; - mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT, - transformResourceId, "blessedPackage"); + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE); for (int selAddrFamily : ADDRESS_FAMILIES) { verify(mMockNetd) @@ -758,17 +840,17 @@ public class IpSecServiceParameterizedTest { addAuthAndCryptToIpSecConfig(ipSecConfig); IpSecTransformResponse createTransformResp = - mIpSecService.createTransform(ipSecConfig, new Binder(), "blessedPackage"); + mIpSecService.createTransform(ipSecConfig, new Binder(), BLESSED_PACKAGE); IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "blessedPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE); // Close SPI record mIpSecService.releaseSecurityParameterIndex(ipSecConfig.getSpiResourceId()); int transformResourceId = createTransformResp.resourceId; int tunnelResourceId = createTunnelResp.resourceId; - mIpSecService.applyTunnelModeTransform(tunnelResourceId, IpSecManager.DIRECTION_OUT, - transformResourceId, "blessedPackage"); + mIpSecService.applyTunnelModeTransform( + tunnelResourceId, IpSecManager.DIRECTION_OUT, transformResourceId, BLESSED_PACKAGE); for (int selAddrFamily : ADDRESS_FAMILIES) { verify(mMockNetd) @@ -790,7 +872,7 @@ public class IpSecServiceParameterizedTest { @Test public void testAddRemoveAddressFromTunnelInterface() throws Exception { - for (String pkgName : new String[]{"blessedPackage", "systemPackage"}) { + for (String pkgName : new String[] {BLESSED_PACKAGE, SYSTEM_PACKAGE}) { IpSecTunnelInterfaceResponse createTunnelResp = createAndValidateTunnel(mSourceAddr, mDestinationAddr, pkgName); mIpSecService.addAddressToTunnelInterface( @@ -816,7 +898,7 @@ public class IpSecServiceParameterizedTest { public void testAddTunnelFailsForBadPackageName() throws Exception { try { IpSecTunnelInterfaceResponse createTunnelResp = - createAndValidateTunnel(mSourceAddr, mDestinationAddr, "badPackage"); + createAndValidateTunnel(mSourceAddr, mDestinationAddr, BAD_PACKAGE); fail("Expected a SecurityException for badPackage."); } catch (SecurityException expected) { } @@ -830,7 +912,7 @@ public class IpSecServiceParameterizedTest { try { String addr = Inet4Address.getLoopbackAddress().getHostAddress(); mIpSecService.createTunnelInterface( - addr, addr, new Network(0), new Binder(), "blessedPackage"); + addr, addr, new Network(0), new Binder(), BLESSED_PACKAGE); fail("Expected UnsupportedOperationException for disabled feature"); } catch (UnsupportedOperationException expected) { } diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index b47be97ed002..7e85acb05dee 100644 --- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -63,6 +63,8 @@ import java.util.List; @SmallTest public class NetworkNotificationManagerTest { + private static final String TEST_SSID = "Test SSID"; + private static final String TEST_EXTRA_INFO = "extra"; static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities(); @@ -72,6 +74,7 @@ public class NetworkNotificationManagerTest { WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + WIFI_CAPABILITIES.setSSID(TEST_SSID); // Set the underyling network to wifi. VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); @@ -112,7 +115,7 @@ public class NetworkNotificationManagerTest { when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx); when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); - when(mNetworkInfo.getExtraInfo()).thenReturn("extra"); + when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO); when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); mManager = new NetworkNotificationManager(mCtx, mTelephonyManager); @@ -125,11 +128,11 @@ public class NetworkNotificationManagerTest { .notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any()); final int transportType = NetworkNotificationManager.approximateTransportType(nai); if (transportType == NetworkCapabilities.TRANSPORT_WIFI) { - verify(mResources, times(1)).getString(title, eq(any())); + verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO)); } else { verify(mResources, times(1)).getString(title); } - verify(mResources, times(1)).getString(R.string.private_dns_broken_detailed); + verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed)); } @Test diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index 73cc9f129e79..cffd2d1d428f 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -25,6 +25,7 @@ import static android.net.ConnectivityManager.NetworkCallback; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -74,6 +75,7 @@ import android.net.UidRange; import android.net.UidRangeParcel; import android.net.VpnManager; import android.net.VpnService; +import android.net.VpnTransportInfo; import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.os.Build.VERSION_CODES; @@ -984,6 +986,13 @@ public class VpnTest { startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve } + private void assertTransportInfoMatches(NetworkCapabilities nc, int type) { + assertNotNull(nc); + VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo(); + assertNotNull(ti); + assertEquals(type, ti.type); + } + public void startRacoon(final String serverAddr, final String expectedAddr) throws Exception { final ConditionVariable legacyRunnerReady = new ConditionVariable(); @@ -1020,8 +1029,10 @@ public class VpnTest { // Now wait for the runner to be ready before testing for the route. ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); + ArgumentCaptor<NetworkCapabilities> ncCaptor = + ArgumentCaptor.forClass(NetworkCapabilities.class); verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), - lpCaptor.capture(), any(), anyInt(), any(), anyInt()); + lpCaptor.capture(), ncCaptor.capture(), anyInt(), any(), anyInt()); // In this test the expected address is always v4 so /32. // Note that the interface needs to be specified because RouteInfo objects stored in @@ -1031,6 +1042,8 @@ public class VpnTest { final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes(); assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes, actualRoutes.contains(expectedRoute)); + + assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY); } finally { // Now interrupt the thread, unblock the runner and clean up. vpn.mVpnRunner.exitVpnRunner(); diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 1102624da031..4a1f96d145bd 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -42,6 +42,8 @@ public class BroadcastInterceptingContext extends ContextWrapper { private final List<BroadcastInterceptor> mInterceptors = new ArrayList<>(); + private boolean mUseRegisteredHandlers; + public abstract class FutureIntent extends FutureTask<Intent> { public FutureIntent() { super( @@ -61,17 +63,24 @@ public class BroadcastInterceptingContext extends ContextWrapper { public class BroadcastInterceptor extends FutureIntent { private final BroadcastReceiver mReceiver; private final IntentFilter mFilter; + private final Handler mHandler; - public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter) { + public BroadcastInterceptor(BroadcastReceiver receiver, IntentFilter filter, + Handler handler) { mReceiver = receiver; mFilter = filter; + mHandler = mUseRegisteredHandlers ? handler : null; } public boolean dispatchBroadcast(Intent intent) { if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) { if (mReceiver != null) { final Context context = BroadcastInterceptingContext.this; - mReceiver.onReceive(context, intent); + if (mHandler == null) { + mReceiver.onReceive(context, intent); + } else { + mHandler.post(() -> mReceiver.onReceive(context, intent)); + } return false; } else { set(intent); @@ -116,25 +125,38 @@ public class BroadcastInterceptingContext extends ContextWrapper { } public FutureIntent nextBroadcastIntent(IntentFilter filter) { - final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter); + final BroadcastInterceptor interceptor = new BroadcastInterceptor(null, filter, null); synchronized (mInterceptors) { mInterceptors.add(interceptor); } return interceptor; } - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + /** + * Whether to send broadcasts to registered handlers. By default, receivers are called + * synchronously by sendBroadcast. If this method is called with {@code true}, the receiver is + * instead called by a runnable posted to the Handler specified when the receiver was + * registered. This method applies only to future registrations, already-registered receivers + * are unaffected. + */ + public void setUseRegisteredHandlers(boolean use) { synchronized (mInterceptors) { - mInterceptors.add(new BroadcastInterceptor(receiver, filter)); + mUseRegisteredHandlers = use; } - return null; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return registerReceiver(receiver, filter, null, null); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { - return registerReceiver(receiver, filter); + synchronized (mInterceptors) { + mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler)); + } + return null; } @Override diff --git a/tests/vcn/java/android/net/vcn/VcnManagerTest.java b/tests/vcn/java/android/net/vcn/VcnManagerTest.java index f9db408462b7..7dada9d1b6d4 100644 --- a/tests/vcn/java/android/net/vcn/VcnManagerTest.java +++ b/tests/vcn/java/android/net/vcn/VcnManagerTest.java @@ -65,7 +65,7 @@ public class VcnManagerTest { ArgumentCaptor.forClass(IVcnUnderlyingNetworkPolicyListener.class); verify(mMockVcnManagementService).addVcnUnderlyingNetworkPolicyListener(captor.capture()); - assertTrue(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener)); + assertTrue(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener)); IVcnUnderlyingNetworkPolicyListener listenerWrapper = captor.getValue(); listenerWrapper.onPolicyChanged(); @@ -78,7 +78,7 @@ public class VcnManagerTest { mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener)); + assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener)); verify(mMockVcnManagementService) .addVcnUnderlyingNetworkPolicyListener( any(IVcnUnderlyingNetworkPolicyListener.class)); @@ -88,7 +88,7 @@ public class VcnManagerTest { public void testRemoveVcnUnderlyingNetworkPolicyListenerUnknownListener() throws Exception { mVcnManager.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); - assertFalse(VcnManager.REGISTERED_POLICY_LISTENERS.containsKey(mMockPolicyListener)); + assertFalse(VcnManager.getAllPolicyListeners().containsKey(mMockPolicyListener)); verify(mMockVcnManagementService, never()) .addVcnUnderlyingNetworkPolicyListener( any(IVcnUnderlyingNetworkPolicyListener.class)); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 485964487fda..c290bff188c1 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -16,6 +16,10 @@ package com.android.server; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; import static com.android.server.vcn.VcnTestUtils.setupSystemService; @@ -106,6 +110,7 @@ public class VcnManagementServiceTest { Collections.unmodifiableMap(Collections.singletonMap(TEST_UUID_1, TEST_VCN_CONFIG)); private static final int TEST_SUBSCRIPTION_ID = 1; + private static final int TEST_SUBSCRIPTION_ID_2 = 2; private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO = new SubscriptionInfo( TEST_SUBSCRIPTION_ID /* id */, @@ -537,59 +542,121 @@ public class VcnManagementServiceTest { Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup)); } - private void verifyMergedNetworkCapabilitiesIsVcnManaged( - NetworkCapabilities mergedCapabilities, @Transport int transportType) { + private void verifyMergedNetworkCapabilities( + NetworkCapabilities mergedCapabilities, + @Transport int transportType, + boolean isVcnManaged, + boolean isRestricted) { assertTrue(mergedCapabilities.hasTransport(transportType)); - assertFalse( + assertEquals( + !isVcnManaged, mergedCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)); + assertEquals( + !isRestricted, + mergedCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)); + } + + private void setupSubscriptionAndStartVcn(int subId, ParcelUuid subGrp, boolean isVcnActive) { + setUpVcnSubscription(subId, subGrp); + final Vcn vcn = startAndGetVcnInstance(subGrp); + doReturn(isVcnActive).when(vcn).isActive(); + } + + private VcnUnderlyingNetworkPolicy startVcnAndGetPolicyForTransport( + int subId, ParcelUuid subGrp, boolean isVcnActive, int transport) { + setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive); + + final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder(); + ncBuilder.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + if (transport == TRANSPORT_CELLULAR) { + ncBuilder + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID)); + } else if (transport == TRANSPORT_WIFI) { + WifiInfo wifiInfo = mock(WifiInfo.class); + when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo); + when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID); + + ncBuilder + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setTransportInfo(wifiInfo); + } else { + throw new IllegalArgumentException("Unknown transport"); + } + + return mVcnMgmtSvc.getUnderlyingNetworkPolicy(ncBuilder.build(), new LinkProperties()); } @Test public void testGetUnderlyingNetworkPolicyCellular() throws Exception { - setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); + final VcnUnderlyingNetworkPolicy policy = + startVcnAndGetPolicyForTransport( + TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR); - NetworkCapabilities nc = - new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID)) - .build(); + assertFalse(policy.isTeardownRequested()); + verifyMergedNetworkCapabilities( + policy.getMergedNetworkCapabilities(), + TRANSPORT_CELLULAR, + true /* isVcnManaged */, + false /* isRestricted */); + } - VcnUnderlyingNetworkPolicy policy = - mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); + @Test + public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception { + final VcnUnderlyingNetworkPolicy policy = + startVcnAndGetPolicyForTransport( + TEST_SUBSCRIPTION_ID, + TEST_UUID_2, + false /* isActive */, + TRANSPORT_CELLULAR); assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilitiesIsVcnManaged( - policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR); + verifyMergedNetworkCapabilities( + policy.getMergedNetworkCapabilities(), + NetworkCapabilities.TRANSPORT_CELLULAR, + false /* isVcnManaged */, + false /* isRestricted */); } @Test public void testGetUnderlyingNetworkPolicyWifi() throws Exception { - setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); + final VcnUnderlyingNetworkPolicy policy = + startVcnAndGetPolicyForTransport( + TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI); - WifiInfo wifiInfo = mock(WifiInfo.class); - when(wifiInfo.makeCopy(anyBoolean())).thenReturn(wifiInfo); - when(mMockDeps.getSubIdForWifiInfo(eq(wifiInfo))).thenReturn(TEST_SUBSCRIPTION_ID); - NetworkCapabilities nc = - new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setTransportInfo(wifiInfo) - .build(); + assertFalse(policy.isTeardownRequested()); + verifyMergedNetworkCapabilities( + policy.getMergedNetworkCapabilities(), + NetworkCapabilities.TRANSPORT_WIFI, + true /* isVcnManaged */, + true /* isRestricted */); + } - VcnUnderlyingNetworkPolicy policy = - mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); + @Test + public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception { + final VcnUnderlyingNetworkPolicy policy = + startVcnAndGetPolicyForTransport( + TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI); assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilitiesIsVcnManaged( - policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI); + verifyMergedNetworkCapabilities( + policy.getMergedNetworkCapabilities(), + NetworkCapabilities.TRANSPORT_WIFI, + false /* isVcnManaged */, + true /* isRestricted */); } @Test public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception { + setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */); + NetworkCapabilities nc = new NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID)) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2)) .build(); VcnUnderlyingNetworkPolicy policy = diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 8643d8a2ea8a..16181b6f839a 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -16,12 +16,18 @@ package com.android.server.vcn; +import static android.net.IpSecManager.IpSecTunnelInterface; + +import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import android.net.IpSecManager; + import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -37,6 +43,11 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect public void setUp() throws Exception { super.setUp(); + final IpSecTunnelInterface tunnelIface = + mContext.getSystemService(IpSecManager.class) + .createIpSecTunnelInterface( + DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network); + mGatewayConnection.setTunnelInterface(tunnelIface); mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); mTestLooper.dispatchAll(); } diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py deleted file mode 100755 index 28ff606d0381..000000000000 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2018 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. -"""Generate API lists for non-SDK API enforcement.""" -import argparse -from collections import defaultdict, namedtuple -import functools -import os -import re -import sys - -# Names of flags recognized by the `hiddenapi` tool. -FLAG_SDK = 'sdk' -FLAG_UNSUPPORTED = 'unsupported' -FLAG_BLOCKED = 'blocked' -FLAG_MAX_TARGET_O = 'max-target-o' -FLAG_MAX_TARGET_P = 'max-target-p' -FLAG_MAX_TARGET_Q = 'max-target-q' -FLAG_MAX_TARGET_R = 'max-target-r' -FLAG_CORE_PLATFORM_API = 'core-platform-api' -FLAG_PUBLIC_API = 'public-api' -FLAG_SYSTEM_API = 'system-api' -FLAG_TEST_API = 'test-api' - -# List of all known flags. -FLAGS_API_LIST = [ - FLAG_SDK, - FLAG_UNSUPPORTED, - FLAG_BLOCKED, - FLAG_MAX_TARGET_O, - FLAG_MAX_TARGET_P, - FLAG_MAX_TARGET_Q, - FLAG_MAX_TARGET_R, -] -ALL_FLAGS = FLAGS_API_LIST + [ - FLAG_CORE_PLATFORM_API, - FLAG_PUBLIC_API, - FLAG_SYSTEM_API, - FLAG_TEST_API, -] - -FLAGS_API_LIST_SET = set(FLAGS_API_LIST) -ALL_FLAGS_SET = set(ALL_FLAGS) - -# Option specified after one of FLAGS_API_LIST to indicate that -# only known and otherwise unassigned entries should be assign the -# given flag. -# For example, the max-target-P list is checked in as it was in P, -# but signatures have changes since then. The flag instructs this -# script to skip any entries which do not exist any more. -FLAG_IGNORE_CONFLICTS = "ignore-conflicts" - -# Option specified after one of FLAGS_API_LIST to express that all -# apis within a given set of packages should be assign the given flag. -FLAG_PACKAGES = "packages" - -# Option specified after one of FLAGS_API_LIST to indicate an extra -# tag that should be added to the matching APIs. -FLAG_TAG = "tag" - -# Regex patterns of fields/methods used in serialization. These are -# considered public API despite being hidden. -SERIALIZATION_PATTERNS = [ - r'readObject\(Ljava/io/ObjectInputStream;\)V', - r'readObjectNoData\(\)V', - r'readResolve\(\)Ljava/lang/Object;', - r'serialVersionUID:J', - r'serialPersistentFields:\[Ljava/io/ObjectStreamField;', - r'writeObject\(Ljava/io/ObjectOutputStream;\)V', - r'writeReplace\(\)Ljava/lang/Object;', -] - -# Single regex used to match serialization API. It combines all the -# SERIALIZATION_PATTERNS into a single regular expression. -SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$') - -# Predicates to be used with filter_apis. -HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags) -IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api) - - -class StoreOrderedOptions(argparse.Action): - """An argparse action that stores a number of option arguments in the order that - they were specified. - """ - def __call__(self, parser, args, values, option_string = None): - items = getattr(args, self.dest, None) - if items is None: - items = [] - items.append([option_string.lstrip('-'), values]) - setattr(args, self.dest, items) - -def get_args(): - """Parses command line arguments. - - Returns: - Namespace: dictionary of parsed arguments - """ - parser = argparse.ArgumentParser() - parser.add_argument('--output', required=True) - parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE', - help='CSV files to be merged into output') - - for flag in ALL_FLAGS: - parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE', - action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"') - parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0, - action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned ' - 'entries should be assign the given flag. Must follow a list of entries and applies ' - 'to the preceding such list.') - parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0, - action=StoreOrderedOptions, help='Indicates that the previous list of entries ' - 'is a list of packages. All members in those packages will be given the flag. ' - 'Must follow a list of entries and applies to the preceding such list.') - parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1, - action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. ' - 'Must follow a list of entries and applies to the preceding such list.') - - return parser.parse_args() - - -def read_lines(filename): - """Reads entire file and return it as a list of lines. - - Lines which begin with a hash are ignored. - - Args: - filename (string): Path to the file to read from. - - Returns: - Lines of the file as a list of string. - """ - with open(filename, 'r') as f: - lines = f.readlines(); - lines = filter(lambda line: not line.startswith('#'), lines) - lines = map(lambda line: line.strip(), lines) - return set(lines) - - -def write_lines(filename, lines): - """Writes list of lines into a file, overwriting the file if it exists. - - Args: - filename (string): Path to the file to be writting into. - lines (list): List of strings to write into the file. - """ - lines = map(lambda line: line + '\n', lines) - with open(filename, 'w') as f: - f.writelines(lines) - - -def extract_package(signature): - """Extracts the package from a signature. - - Args: - signature (string): JNI signature of a method or field. - - Returns: - The package name of the class containing the field/method. - """ - full_class_name = signature.split(";->")[0] - # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy - if (full_class_name[0] != "L"): - raise ValueError("Expected to start with 'L': %s" % full_class_name) - full_class_name = full_class_name[1:] - # If full_class_name doesn't contain '/', then package_name will be ''. - package_name = full_class_name.rpartition("/")[0] - return package_name.replace('/', '.') - - -class FlagsDict: - def __init__(self): - self._dict_keyset = set() - self._dict = defaultdict(set) - - def _check_entries_set(self, keys_subset, source): - assert isinstance(keys_subset, set) - assert keys_subset.issubset(self._dict_keyset), ( - "Error: {} specifies signatures not present in code:\n" - "{}" - "Please visit go/hiddenapi for more information.").format( - source, "".join(map(lambda x: " " + str(x) + "\n", keys_subset - self._dict_keyset))) - - def _check_flags_set(self, flags_subset, source): - assert isinstance(flags_subset, set) - assert flags_subset.issubset(ALL_FLAGS_SET), ( - "Error processing: {}\n" - "The following flags were not recognized: \n" - "{}\n" - "Please visit go/hiddenapi for more information.").format( - source, "\n".join(flags_subset - ALL_FLAGS_SET)) - - def filter_apis(self, filter_fn): - """Returns APIs which match a given predicate. - - This is a helper function which allows to filter on both signatures (keys) and - flags (values). The built-in filter() invokes the lambda only with dict's keys. - - Args: - filter_fn : Function which takes two arguments (signature/flags) and returns a boolean. - - Returns: - A set of APIs which match the predicate. - """ - return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset)) - - def get_valid_subset_of_unassigned_apis(self, api_subset): - """Sanitizes a key set input to only include keys which exist in the dictionary - and have not been assigned any API list flags. - - Args: - entries_subset (set/list): Key set to be sanitized. - - Returns: - Sanitized key set. - """ - assert isinstance(api_subset, set) - return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED)) - - def generate_csv(self): - """Constructs CSV entries from a dictionary. - - Old versions of flags are used to generate the file. - - Returns: - List of lines comprising a CSV file. See "parse_and_merge_csv" for format description. - """ - lines = [] - for api in self._dict: - flags = sorted(self._dict[api]) - lines.append(",".join([api] + flags)) - return sorted(lines) - - def parse_and_merge_csv(self, csv_lines, source = "<unknown>"): - """Parses CSV entries and merges them into a given dictionary. - - The expected CSV format is: - <api signature>,<flag1>,<flag2>,...,<flagN> - - Args: - csv_lines (list of strings): Lines read from a CSV file. - source (string): Origin of `csv_lines`. Will be printed in error messages. - - Throws: - AssertionError if parsed flags are invalid. - """ - # Split CSV lines into arrays of values. - csv_values = [ line.split(',') for line in csv_lines ] - - # Update the full set of API signatures. - self._dict_keyset.update([ csv[0] for csv in csv_values ]) - - # Check that all flags are known. - csv_flags = set() - for csv in csv_values: - csv_flags.update(csv[1:]) - self._check_flags_set(csv_flags, source) - - # Iterate over all CSV lines, find entry in dict and append flags to it. - for csv in csv_values: - flags = csv[1:] - if (FLAG_PUBLIC_API in flags) or (FLAG_SYSTEM_API in flags): - flags.append(FLAG_SDK) - self._dict[csv[0]].update(flags) - - def assign_flag(self, flag, apis, source="<unknown>", tag = None): - """Assigns a flag to given subset of entries. - - Args: - flag (string): One of ALL_FLAGS. - apis (set): Subset of APIs to receive the flag. - source (string): Origin of `entries_subset`. Will be printed in error messages. - - Throws: - AssertionError if parsed API signatures of flags are invalid. - """ - # Check that all APIs exist in the dict. - self._check_entries_set(apis, source) - - # Check that the flag is known. - self._check_flags_set(set([ flag ]), source) - - # Iterate over the API subset, find each entry in dict and assign the flag to it. - for api in apis: - self._dict[api].add(flag) - if tag: - self._dict[api].add(tag) - - -FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag')) - -def parse_ordered_flags(ordered_flags): - r = [] - currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None - for flag_value in ordered_flags: - flag, value = flag_value[0], flag_value[1] - if flag in ALL_FLAGS_SET: - if currentflag: - r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag)) - ignore_conflicts, packages, tag = False, False, None - currentflag = flag - file = value - else: - if currentflag is None: - raise argparse.ArgumentError('--%s is only allowed after one of %s' % ( - flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET]))) - if flag == FLAG_IGNORE_CONFLICTS: - ignore_conflicts = True - elif flag == FLAG_PACKAGES: - packages = True - elif flag == FLAG_TAG: - tag = value[0] - - - if currentflag: - r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag)) - return r - - -def main(argv): - # Parse arguments. - args = vars(get_args()) - flagfiles = parse_ordered_flags(args['ordered_flags']) - - # Initialize API->flags dictionary. - flags = FlagsDict() - - # Merge input CSV files into the dictionary. - # Do this first because CSV files produced by parsing API stubs will - # contain the full set of APIs. Subsequent additions from text files - # will be able to detect invalid entries, and/or filter all as-yet - # unassigned entries. - for filename in args["csv"]: - flags.parse_and_merge_csv(read_lines(filename), filename) - - # Combine inputs which do not require any particular order. - # (1) Assign serialization API to SDK. - flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION)) - - # (2) Merge text files with a known flag into the dictionary. - for info in flagfiles: - if (not info.ignore_conflicts) and (not info.packages): - flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag) - - # Merge text files where conflicts should be ignored. - # This will only assign the given flag if: - # (a) the entry exists, and - # (b) it has not been assigned any other flag. - # Because of (b), this must run after all strict assignments have been performed. - for info in flagfiles: - if info.ignore_conflicts: - valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file)) - flags.assign_flag(info.flag, valid_entries, filename, info.tag) - - # All members in the specified packages will be assigned the appropriate flag. - for info in flagfiles: - if info.packages: - packages_needing_list = set(read_lines(info.file)) - should_add_signature_to_list = lambda sig,lists: extract_package( - sig) in packages_needing_list and not lists - valid_entries = flags.filter_apis(should_add_signature_to_list) - flags.assign_flag(info.flag, valid_entries, info.file, info.tag) - - # Mark all remaining entries as blocked. - flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED)) - - # Write output. - write_lines(args["output"], flags.generate_csv()) - -if __name__ == "__main__": - main(sys.argv) diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py deleted file mode 100755 index 82d117fbbbab..000000000000 --- a/tools/hiddenapi/generate_hiddenapi_lists_test.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2018 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. -"""Unit tests for Hidden API list generation.""" -import unittest -from generate_hiddenapi_lists import * - -class TestHiddenapiListGeneration(unittest.TestCase): - - def test_filter_apis(self): - # Initialize flags so that A and B are put on the whitelist and - # C, D, E are left unassigned. Try filtering for the unassigned ones. - flags = FlagsDict() - flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B,' + FLAG_SDK, - 'C', 'D', 'E']) - filter_set = flags.filter_apis(lambda api, flags: not flags) - self.assertTrue(isinstance(filter_set, set)) - self.assertEqual(filter_set, set([ 'C', 'D', 'E' ])) - - def test_get_valid_subset_of_unassigned_keys(self): - # Create flags where only A is unassigned. - flags = FlagsDict() - flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C']) - flags.assign_flag(FLAG_UNSUPPORTED, set(['C'])) - self.assertEqual(flags.generate_csv(), - [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ]) - - # Check three things: - # (1) B is selected as valid unassigned - # (2) A is not selected because it is assigned 'whitelist' - # (3) D is not selected because it is not a valid key - self.assertEqual( - flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ])) - - def test_parse_and_merge_csv(self): - flags = FlagsDict() - - # Test empty CSV entry. - self.assertEqual(flags.generate_csv(), []) - - # Test new additions. - flags.parse_and_merge_csv([ - 'A,' + FLAG_UNSUPPORTED, - 'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O, - 'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API, - 'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API, - 'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API, - ]) - self.assertEqual(flags.generate_csv(), [ - 'A,' + FLAG_UNSUPPORTED, - 'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O, - 'C,' + FLAG_SYSTEM_API + ',' + FLAG_SDK, - 'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API, - 'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API, - ]) - - # Test unknown flag. - with self.assertRaises(AssertionError): - flags.parse_and_merge_csv([ 'Z,foo' ]) - - def test_assign_flag(self): - flags = FlagsDict() - flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B']) - - # Test new additions. - flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ])) - self.assertEqual(flags.generate_csv(), - [ 'A,' + FLAG_UNSUPPORTED + "," + FLAG_SDK, 'B,' + FLAG_UNSUPPORTED ]) - - # Test invalid API signature. - with self.assertRaises(AssertionError): - flags.assign_flag(FLAG_SDK, set([ 'C' ])) - - # Test invalid flag. - with self.assertRaises(AssertionError): - flags.assign_flag('foo', set([ 'A' ])) - - def test_extract_package(self): - signature = 'Lcom/foo/bar/Baz;->method1()Lcom/bar/Baz;' - expected_package = 'com.foo.bar' - self.assertEqual(extract_package(signature), expected_package) - - signature = 'Lcom/foo1/bar/MyClass;->method2()V' - expected_package = 'com.foo1.bar' - self.assertEqual(extract_package(signature), expected_package) - - signature = 'Lcom/foo_bar/baz/MyClass;->method3()V' - expected_package = 'com.foo_bar.baz' - self.assertEqual(extract_package(signature), expected_package) - -if __name__ == '__main__': - unittest.main() diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py deleted file mode 100755 index 6a5b0e1347b2..000000000000 --- a/tools/hiddenapi/merge_csv.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2018 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. -""" -Merge multiple CSV files, possibly with different columns. -""" - -import argparse -import csv -import io - -from zipfile import ZipFile - -args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.') -args_parser.add_argument('--header', help='Comma separated field names; ' - 'if missing determines the header from input files.') -args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.') -args_parser.add_argument('--output', help='Output file for merged CSV.', - default='-', type=argparse.FileType('w')) -args_parser.add_argument('files', nargs=argparse.REMAINDER) -args = args_parser.parse_args() - - -def dict_reader(input): - return csv.DictReader(input, delimiter=',', quotechar='|') - - -if args.zip_input and len(args.files) > 0: - raise ValueError('Expecting either a single ZIP with CSV files' - ' or a list of CSV files as input; not both.') - -csv_readers = [] -if len(args.files) > 0: - for file in args.files: - csv_readers.append(dict_reader(open(file, 'r'))) -elif args.zip_input: - with ZipFile(args.zip_input) as zip: - for entry in zip.namelist(): - if entry.endswith('.uau'): - csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r')))) - -headers = set() -if args.header: - fieldnames = args.header.split(',') -else: - # Build union of all columns from source files: - for reader in csv_readers: - headers = headers.union(reader.fieldnames) - fieldnames = sorted(headers) - -# Concatenate all files to output: -writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL, - dialect='unix', fieldnames=fieldnames) -writer.writeheader() -for reader in csv_readers: - for row in reader: - writer.writerow(row) |