diff options
147 files changed, 5086 insertions, 662 deletions
@@ -1,3 +1,4 @@ third_party { - license_type: NOTICE + # would be NOTICE save for libs/usb/tests/accessorytest/f_accessory.h + license_type: RESTRICTED } diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index c0560f61460f..1a2d5d5412ed 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -26,7 +26,10 @@ java_sdk_library { defaults: ["framework-module-defaults"], // Restrict access to implementation library. - impl_library_visibility: ["//frameworks/base/apex/permission:__subpackages__"], + impl_library_visibility: [ + "//frameworks/base/apex/permission:__subpackages__", + "//packages/modules/Permission:__subpackages__", + ], srcs: [ ":framework-permission-sources", diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 6e04edfe02f1..cc6f2019a253 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -27,6 +27,7 @@ java_sdk_library { "//frameworks/base/apex/permission/tests", "//frameworks/base/services/tests/mockingservicestests", "//frameworks/base/services/tests/servicestests", + "//packages/modules/Permission/tests", ], srcs: [ ":service-permission-sources", diff --git a/boot/Android.bp b/boot/Android.bp new file mode 100644 index 000000000000..dd4066a7d151 --- /dev/null +++ b/boot/Android.bp @@ -0,0 +1,18 @@ +// 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. + +boot_image { + name: "framework-boot-image", + image_name: "boot", +} diff --git a/config/OWNERS b/config/OWNERS index d59c6f2d72ba..001038d139c4 100644 --- a/config/OWNERS +++ b/config/OWNERS @@ -4,5 +4,11 @@ include /ZYGOTE_OWNERS per-file hiddenapi-* = andreionea@google.com, mathewi@google.com, satayev@google.com +# art-team@ manages the boot image profiles +per-file boot-* = calin@google.com, mathieuc@google.com, ngeoffray@google.com +per-file dirty-image-objects = calin@google.com, mathieuc@google.com, ngeoffray@google.com +per-file generate-preloaded-classes.sh = calin@google.com, mathieuc@google.com, ngeoffray@google.com +per-file preloaded-classes* = calin@google.com, mathieuc@google.com, ngeoffray@google.com + # Escalations: per-file hiddenapi-* = bdc@google.com, narayan@google.com diff --git a/core/api/current.txt b/core/api/current.txt index 047761a0cbe9..58ac5400ec3f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12128,6 +12128,8 @@ package android.content.pm { field public static final String FEATURE_GAMEPAD = "android.hardware.gamepad"; field public static final String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors"; field public static final String FEATURE_HOME_SCREEN = "android.software.home_screen"; + field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE = "android.hardware.identity_credential"; + field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = "android.hardware.identity_credential_direct_access"; field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods"; field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris"; @@ -12214,7 +12216,7 @@ package android.content.pm { field @Deprecated public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000 field public static final int GET_GIDS = 256; // 0x100 field public static final int GET_INSTRUMENTATION = 16; // 0x10 - field public static final int GET_INTENT_FILTERS = 32; // 0x20 + field @Deprecated public static final int GET_INTENT_FILTERS = 32; // 0x20 field public static final int GET_META_DATA = 128; // 0x80 field public static final int GET_PERMISSIONS = 4096; // 0x1000 field public static final int GET_PROVIDERS = 8; // 0x8 @@ -25084,6 +25086,8 @@ package android.net { method public void applyTransportModeTransform(@NonNull java.net.Socket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; method public void applyTransportModeTransform(@NonNull java.net.DatagramSocket, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; method public void applyTransportModeTransform(@NonNull java.io.FileDescriptor, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; + method @RequiresPermission("android.permission.MANAGE_IPSEC_TUNNELS") public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; + method @NonNull @RequiresPermission("android.permission.MANAGE_IPSEC_TUNNELS") public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method @NonNull public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public void removeTransportModeTransforms(@NonNull java.net.Socket) throws java.io.IOException; @@ -25093,6 +25097,12 @@ package android.net { field public static final int DIRECTION_OUT = 1; // 0x1 } + public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable { + method @RequiresPermission("android.permission.MANAGE_IPSEC_TUNNELS") public void addAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException; + method public void close(); + method @RequiresPermission("android.permission.MANAGE_IPSEC_TUNNELS") public void removeAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException; + } + public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { } @@ -29639,6 +29649,8 @@ package android.os { field @Deprecated public static final String RADIO; field @Deprecated public static final String SERIAL; field @NonNull public static final String SKU; + field @NonNull public static final String SOC_MANUFACTURER; + field @NonNull public static final String SOC_MODEL; field public static final String[] SUPPORTED_32_BIT_ABIS; field public static final String[] SUPPORTED_64_BIT_ABIS; field public static final String[] SUPPORTED_ABIS; @@ -36074,15 +36086,20 @@ package android.security.identity { public abstract class IdentityCredential { method @NonNull public abstract java.security.KeyPair createEphemeralKeyPair(); method @NonNull public abstract byte[] decryptMessageFromReader(@NonNull byte[]) throws android.security.identity.MessageDecryptionException; + method @NonNull public byte[] delete(@NonNull byte[]); method @NonNull public abstract byte[] encryptMessageToReader(@NonNull byte[]); method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getAuthKeysNeedingCertification(); method @NonNull public abstract int[] getAuthenticationDataUsageCount(); method @NonNull public abstract java.util.Collection<java.security.cert.X509Certificate> getCredentialKeyCertificateChain(); method @NonNull public abstract android.security.identity.ResultData getEntries(@Nullable byte[], @NonNull java.util.Map<java.lang.String,java.util.Collection<java.lang.String>>, @Nullable byte[], @Nullable byte[]) throws android.security.identity.EphemeralPublicKeyNotFoundException, android.security.identity.InvalidReaderSignatureException, android.security.identity.InvalidRequestMessageException, android.security.identity.NoAuthenticationKeyAvailableException, android.security.identity.SessionTranscriptMismatchException; + method @NonNull public byte[] proveOwnership(@NonNull byte[]); method public abstract void setAllowUsingExhaustedKeys(boolean); + method public void setAllowUsingExpiredKeys(boolean); method public abstract void setAvailableAuthenticationKeys(int, int); method public abstract void setReaderEphemeralPublicKey(@NonNull java.security.PublicKey) throws java.security.InvalidKeyException; - method public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; + method @Deprecated public abstract void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; + method public void storeStaticAuthenticationData(@NonNull java.security.cert.X509Certificate, @NonNull java.time.Instant, @NonNull byte[]) throws android.security.identity.UnknownAuthenticationKeyException; + method @NonNull public byte[] update(@NonNull android.security.identity.PersonalizationData); } public class IdentityCredentialException extends java.lang.Exception { @@ -36092,7 +36109,7 @@ package android.security.identity { public abstract class IdentityCredentialStore { method @NonNull public abstract android.security.identity.WritableIdentityCredential createCredential(@NonNull String, @NonNull String) throws android.security.identity.AlreadyPersonalizedException, android.security.identity.DocTypeNotSupportedException; - method @Nullable public abstract byte[] deleteCredentialByName(@NonNull String); + method @Deprecated @Nullable public abstract byte[] deleteCredentialByName(@NonNull String); method @Nullable public abstract android.security.identity.IdentityCredential getCredentialByName(@NonNull String, int) throws android.security.identity.CipherSuiteNotSupportedException; method @Nullable public static android.security.identity.IdentityCredentialStore getDirectAccessInstance(@NonNull android.content.Context); method @Nullable public static android.security.identity.IdentityCredentialStore getInstance(@NonNull android.content.Context); @@ -36308,6 +36325,7 @@ package android.security.keystore { field public static final int ORIGIN_IMPORTED = 2; // 0x2 field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8 field public static final int ORIGIN_UNKNOWN = 4; // 0x4 + field public static final int PURPOSE_AGREE_KEY = 64; // 0x40 field public static final int PURPOSE_DECRYPT = 2; // 0x2 field public static final int PURPOSE_ENCRYPT = 1; // 0x1 field public static final int PURPOSE_SIGN = 4; // 0x4 @@ -39581,6 +39599,7 @@ package android.telephony { field public static final String KEY_RTT_DOWNGRADE_SUPPORTED_BOOL = "rtt_downgrade_supported_bool"; field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool"; field public static final String KEY_RTT_SUPPORTED_FOR_VT_BOOL = "rtt_supported_for_vt_bool"; + field public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = "rtt_supported_while_roaming_bool"; field public static final String KEY_RTT_UPGRADE_SUPPORTED_BOOL = "rtt_upgrade_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; @@ -40256,6 +40275,7 @@ package android.telephony { field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22 field public static final int SIGNAL_LOST = -3; // 0xfffffffd field public static final int SIM_CARD_CHANGED = 2043; // 0x7fb + field public static final int SLICE_REJECTED = 2252; // 0x8cc field public static final int SYNCHRONIZATION_FAILURE = 2184; // 0x888 field public static final int TEST_LOOPBACK_REGULAR_DEACTIVATION = 2196; // 0x894 field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index bc4a3ca8b244..061d4ccbc473 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -14,6 +14,10 @@ package android.net { 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); } + public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { + method public int getResourceId(); + } + public final class NetworkCapabilities implements android.os.Parcelable { field public static final int TRANSPORT_TEST = 7; // 0x7 } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 9f651e70f780..b4d60168ba91 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -44,6 +44,7 @@ package android { field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE"; field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE"; field public static final String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; + field public static final String BIND_RESUME_ON_REBOOT_SERVICE = "android.permission.BIND_RESUME_ON_REBOOT_SERVICE"; field public static final String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; field public static final String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE"; @@ -200,6 +201,7 @@ package android { field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; + field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM"; field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS"; field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT"; @@ -1410,6 +1412,15 @@ package android.app.usage { } +package android.apphibernation { + + public final class AppHibernationManager { + method public boolean isHibernating(@NonNull String); + method public void setHibernating(@NonNull String, boolean); + } + +} + package android.bluetooth { public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile { @@ -1706,6 +1717,7 @@ package android.content { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); + field public static final String APP_HIBERNATION_SERVICE = "app_hibernation"; field public static final String APP_INTEGRITY_SERVICE = "app_integrity"; field public static final String APP_PREDICTION_SERVICE = "app_prediction"; field public static final String BACKUP_SERVICE = "backup"; @@ -6119,15 +6131,11 @@ package android.net { } public final class IpSecManager { - method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; } public static final class IpSecManager.IpSecTunnelInterface implements java.lang.AutoCloseable { - method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void addAddress(@NonNull java.net.InetAddress, int) throws java.io.IOException; - 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; } public static class IpSecTransform.Builder { @@ -6414,6 +6422,7 @@ package android.net { public abstract class QosFilter { method @NonNull public abstract android.net.Network getNetwork(); + method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); } public final class QosSession implements android.os.Parcelable { @@ -9062,6 +9071,18 @@ package android.service.resolver { } +package android.service.resumeonreboot { + + public abstract class ResumeOnRebootService extends android.app.Service { + ctor public ResumeOnRebootService(); + method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); + method @NonNull public abstract byte[] onUnwrap(@NonNull byte[]) throws java.io.IOException; + method @NonNull public abstract byte[] onWrap(@NonNull byte[], long) throws java.io.IOException; + field public static final String SERVICE_INTERFACE = "android.service.resumeonreboot.ResumeOnRebootService"; + } + +} + package android.service.settings.suggestions { public final class Suggestion implements android.os.Parcelable { @@ -10693,6 +10714,7 @@ package android.telephony.data { method public int getPduSessionId(); method public int getProtocolType(); method public long getRetryDurationMillis(); + method @Nullable public android.telephony.data.SliceInfo getSliceInfo(); method @Deprecated public int getSuggestedRetryTime(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.DataCallResponse> CREATOR; @@ -10727,6 +10749,7 @@ package android.telephony.data { method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setRetryDurationMillis(long); + method @NonNull public android.telephony.data.DataCallResponse.Builder setSliceInfo(@Nullable android.telephony.data.SliceInfo); method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setSuggestedRetryTime(int); } @@ -10799,7 +10822,7 @@ package android.telephony.data { method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback); - method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @IntRange(from=0, to=15) int, @NonNull android.telephony.data.DataServiceCallback); + method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @IntRange(from=0, to=15) int, @Nullable android.telephony.data.SliceInfo, @NonNull android.telephony.data.DataServiceCallback); method public void startHandover(int, @NonNull android.telephony.data.DataServiceCallback); } @@ -10847,6 +10870,32 @@ package android.telephony.data { method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>); } + public final class SliceInfo implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getMappedHplmnSliceDifferentiator(); + method public int getMappedHplmnSliceServiceType(); + method @IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) public int getSliceDifferentiator(); + method public int getSliceServiceType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.SliceInfo> CREATOR; + field public static final int MAX_SLICE_DIFFERENTIATOR = 16777214; // 0xfffffe + field public static final int MIN_SLICE_DIFFERENTIATOR = -1; // 0xffffffff + field public static final int SLICE_DIFFERENTIATOR_NO_SLICE = -1; // 0xffffffff + field public static final int SLICE_SERVICE_TYPE_EMBB = 1; // 0x1 + field public static final int SLICE_SERVICE_TYPE_MIOT = 3; // 0x3 + field public static final int SLICE_SERVICE_TYPE_NONE = 0; // 0x0 + field public static final int SLICE_SERVICE_TYPE_URLLC = 2; // 0x2 + } + + public static final class SliceInfo.Builder { + ctor public SliceInfo.Builder(); + method @NonNull public android.telephony.data.SliceInfo build(); + method @NonNull public android.telephony.data.SliceInfo.Builder setMappedHplmnSliceDifferentiator(@IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) int); + method @NonNull public android.telephony.data.SliceInfo.Builder setMappedHplmnSliceServiceType(int); + method @NonNull public android.telephony.data.SliceInfo.Builder setSliceDifferentiator(@IntRange(from=android.telephony.data.SliceInfo.MIN_SLICE_DIFFERENTIATOR, to=android.telephony.data.SliceInfo.MAX_SLICE_DIFFERENTIATOR) int); + method @NonNull public android.telephony.data.SliceInfo.Builder setSliceServiceType(int); + } + } package android.telephony.euicc { @@ -11879,6 +11928,7 @@ package android.telephony.ims { ctor public SipMessage(@NonNull String, @NonNull String, @NonNull byte[]); method public int describeContents(); method @NonNull public byte[] getContent(); + method @NonNull public byte[] getEncodedMessage(); method @NonNull public String getHeaderSection(); method @NonNull public String getStartLine(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index ea9e9268d06d..546e72b8f834 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1726,7 +1726,6 @@ package android.util { method public static java.util.Map<java.lang.String,java.lang.String> getAllFeatureFlags(); method public static boolean isEnabled(android.content.Context, String); method public static void setEnabled(android.content.Context, String, boolean); - field public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override."; field public static final String FFLAG_PREFIX = "sys.fflag."; field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; diff --git a/core/java/android/annotation/RequiresFeature.java b/core/java/android/annotation/RequiresFeature.java index fc93f03d76cf..08861d42be39 100644 --- a/core/java/android/annotation/RequiresFeature.java +++ b/core/java/android/annotation/RequiresFeature.java @@ -30,7 +30,6 @@ import java.lang.annotation.Target; * Denotes that the annotated element requires one or more device features. This * is used to auto-generate documentation. * - * @see PackageManager#hasSystemFeature(String) * @hide */ @Retention(SOURCE) @@ -38,8 +37,16 @@ import java.lang.annotation.Target; public @interface RequiresFeature { /** * The name of the device feature that is required. - * - * @see PackageManager#hasSystemFeature(String) */ String value(); + + /** + * Defines the name of the method that should be called to check whether the feature is + * available, using the same signature format as javadoc. The feature checking method can have + * multiple parameters, but the feature name parameter must be of type String and must also be + * the first String-type parameter. + * <p> + * By default, the enforcement is {@link PackageManager#hasSystemFeature(String)}. + */ + String enforcement() default("android.content.pm.PackageManager#hasSystemFeature"); } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 60bfac566879..afa1560420f7 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -50,6 +50,9 @@ per-file *StatusBar* = file:/packages/SystemUI/OWNERS # ResourcesManager per-file ResourcesManager = rtmitchell@google.com, toddke@google.com +# VoiceInteraction +per-file *VoiceInteract* = file:/core/java/android/service/voice/OWNERS + # Wallpaper per-file *Wallpaper* = file:/core/java/android/service/wallpaper/OWNERS diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java new file mode 100644 index 000000000000..8f1934c7b77a --- /dev/null +++ b/core/java/android/apphibernation/AppHibernationManager.java @@ -0,0 +1,79 @@ +/* + * 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.apphibernation; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * This class provides an API surface for system apps to manipulate the app hibernation + * state of a package for the user provided in the context. + * @hide + */ +@SystemApi +@SystemService(Context.APP_HIBERNATION_SERVICE) +public final class AppHibernationManager { + private static final String TAG = "AppHibernationManager"; + private final Context mContext; + private final IAppHibernationService mIAppHibernationService; + + /** + * Creates a new instance. + * + * @param context The current context associated with the user + * + * @hide + */ + public AppHibernationManager(@NonNull Context context) { + mContext = context; + mIAppHibernationService = IAppHibernationService.Stub.asInterface( + ServiceManager.getService(Context.APP_HIBERNATION_SERVICE)); + } + + /** + * Returns true if the package is hibernating, false otherwise. + * + * @hide + */ + @SystemApi + public boolean isHibernating(@NonNull String packageName) { + try { + return mIAppHibernationService.isHibernating(packageName, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set whether the package is hibernating. + * + * @hide + */ + @SystemApi + public void setHibernating(@NonNull String packageName, boolean isHibernating) { + try { + mIAppHibernationService.setHibernating(packageName, mContext.getUserId(), + isHibernating); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl new file mode 100644 index 000000000000..db57ecb73051 --- /dev/null +++ b/core/java/android/apphibernation/IAppHibernationService.aidl @@ -0,0 +1,26 @@ +/* + * 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.apphibernation; + +/** + * Binder interface to communicate with AppHibernationService. + * @hide + */ +interface IAppHibernationService { + boolean isHibernating(String packageName, int userId); + void setHibernating(String packageName, int userId, boolean isHibernating); +}
\ No newline at end of file diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index eecdb843774a..9c8856650ae0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4539,6 +4539,17 @@ public abstract class Context { public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller"; /** + * Use with {@link #getSystemService(String) to retrieve an + * {@link android.apphibernation.AppHibernationManager}} for + * communicating with the hibernation service. + * @hide + * + * @see #getSystemService(String) + */ + @SystemApi + public static final String APP_HIBERNATION_SERVICE = "app_hibernation"; + + /** * Use with {@link #getSystemService(String)} to retrieve an * {@link android.app.backup.IBackupManager IBackupManager} for communicating * with the backup mechanism. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 00f5fb95768f..31beb6e6a565 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -299,7 +299,10 @@ public abstract class PackageManager { /** * {@link PackageInfo} flag: return information about the * intent filters supported by the activity. + * + * @deprecated The platform does not support getting {@link IntentFilter}s for the package. */ + @Deprecated public static final int GET_INTENT_FILTERS = 0x00000020; /** @@ -2122,6 +2125,35 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports + * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware + * at the given feature version. + * + * <p>Known feature versions include: + * <ul> + * <li><code>202009</code>: corresponds to the features included in the Identity Credential + * API shipped in Android 11. + * <li><code>202101</code>: corresponds to the features included in the Identity Credential + * API shipped in Android 12. + * </ul> + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE = + "android.hardware.identity_credential"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature(String, int)}: If this feature is supported, the device supports + * {@link android.security.identity.IdentityCredentialStore} implemented in secure hardware + * with direct access at the given feature version. + * See {@link #FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known feature versions. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = + "android.hardware.identity_credential_direct_access"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports one or more methods of * reporting current location. */ diff --git a/core/java/android/graphics/fonts/OWNERS b/core/java/android/graphics/fonts/OWNERS new file mode 100644 index 000000000000..18486af9d12c --- /dev/null +++ b/core/java/android/graphics/fonts/OWNERS @@ -0,0 +1 @@ +include /graphics/java/android/graphics/fonts/OWNERS diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 6fecee632d62..719783163ab9 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -31,6 +31,7 @@ import android.net.NetworkRequest; import android.net.NetworkState; import android.net.ProxyInfo; import android.net.UidRange; +import android.net.VpnInfo; import android.net.QosSocketInfo; import android.os.Bundle; import android.os.IBinder; @@ -43,7 +44,6 @@ 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.VpnInfo; import com.android.internal.net.VpnProfile; /** diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 1a3dc974480c..d5aede71011f 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -23,11 +23,11 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.VpnInfo; import android.net.netstats.provider.INetworkStatsProvider; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.IBinder; import android.os.Messenger; -import com.android.internal.net.VpnInfo; /** {@hide} */ interface INetworkStatsService { diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index d83715c692f7..60923f5ea8c6 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -15,6 +15,8 @@ */ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; @@ -628,7 +630,7 @@ public final class IpSecManager { } /** @hide */ - @VisibleForTesting + @SystemApi(client = MODULE_LIBRARIES) public int getResourceId() { return mResourceId; } @@ -705,7 +707,7 @@ public final class IpSecManager { } /** - * This class represents an IpSecTunnelInterface + * This class represents an IpSecTunnelInterface. * * <p>IpSecTunnelInterface objects track tunnel interfaces that serve as * local endpoints for IPsec tunnels. @@ -714,9 +716,7 @@ public final class IpSecManager { * applied to provide IPsec security to packets sent through the tunnel. While a tunnel * cannot be used in standalone mode within Android, the higher layers may use the tunnel * to create Network objects which are accessible to the Android system. - * @hide */ - @SystemApi public static final class IpSecTunnelInterface implements AutoCloseable { private final String mOpPackageName; private final IIpSecService mService; @@ -727,23 +727,26 @@ public final class IpSecManager { private String mInterfaceName; private int mResourceId = INVALID_RESOURCE_ID; - /** Get the underlying SPI held by this object. */ + /** + * Get the underlying SPI held by this object. + * + * @hide + */ + @SystemApi @NonNull public String getInterfaceName() { return mInterfaceName; } /** - * Add an address to the IpSecTunnelInterface + * Add an address to the IpSecTunnelInterface. * * <p>Add an address which may be used as the local inner address for * tunneled traffic. * * @param address the local address for traffic inside the tunnel * @param prefixLen length of the InetAddress prefix - * @hide */ - @SystemApi @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void addAddress(@NonNull InetAddress address, int prefixLen) throws IOException { @@ -758,15 +761,13 @@ public final class IpSecManager { } /** - * Remove an address from the IpSecTunnelInterface + * Remove an address from the IpSecTunnelInterface. * - * <p>Remove an address which was previously added to the IpSecTunnelInterface + * <p>Remove an address which was previously added to the IpSecTunnelInterface. * * @param address to be removed * @param prefixLen length of the InetAddress prefix - * @hide */ - @SystemApi @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void removeAddress(@NonNull InetAddress address, int prefixLen) throws IOException { @@ -817,7 +818,7 @@ public final class IpSecManager { } /** - * Delete an IpSecTunnelInterface + * Delete an IpSecTunnelInterface. * * <p>Calling close will deallocate the IpSecTunnelInterface and all of its system * resources. Any packets bound for this interface either inbound or outbound will @@ -839,7 +840,12 @@ public final class IpSecManager { } } - /** Check that the Interface was closed properly. */ + + /** + * Check that the Interface was closed properly. + * + * @hide + */ @Override protected void finalize() throws Throwable { if (mCloseGuard != null) { @@ -871,17 +877,52 @@ public final class IpSecManager { * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic. * * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the - * underlying network goes away, and the onLost() callback is received. + * underlying network disconnects, and the {@link + * ConnectivityManager.NetworkCallback#onLost(Network)} callback is received. + * + * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. Packets + * that go through the tunnel will need a underlying network to transit to the IPsec peer. + * This network should almost certainly be a physical network such as WiFi. + * @return a new {@link IpSecTunnelInterface} with the specified properties + * @throws IOException indicating that the tunnel could not be created due to a lower-layer + * error + * @throws ResourceUnavailableException indicating that the number of opening tunnels has + * reached the limit. + */ + @NonNull + @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) + @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) + public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull Network underlyingNetwork) + throws ResourceUnavailableException, IOException { + + // TODO: Remove the need for adding two unused addresses with IPsec tunnels when {@link + // #createIpSecTunnelInterface(localAddress, remoteAddress, underlyingNetwork)} can be + // safely removed. + final InetAddress address = InetAddress.getLocalHost(); + return createIpSecTunnelInterface(address, address, underlyingNetwork); + } + + /** + * Create a new IpSecTunnelInterface as a local endpoint for tunneled IPsec traffic. * - * @param localAddress The local addres of the tunnel - * @param remoteAddress The local addres of the tunnel - * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. - * This network should almost certainly be a network such as WiFi with an L2 address. - * @return a new {@link IpSecManager#IpSecTunnelInterface} with the specified properties - * @throws IOException indicating that the socket could not be opened or bound - * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open + * <p>An application that creates tunnels is responsible for cleaning up the tunnel when the + * underlying network disconnects, and the {@link + * ConnectivityManager.NetworkCallback#onLost(Network)} callback is received. + * + * @param localAddress The local address of the tunnel + * @param remoteAddress The local address of the tunnel + * @param underlyingNetwork the {@link Network} that will carry traffic for this tunnel. Packets + * that go through the tunnel will need a underlying network to transit to the IPsec peer. + * This network should almost certainly be a physical network such as WiFi. + * @return a new {@link IpSecTunnelInterface} with the specified properties + * @throws IOException indicating that the tunnel could not be created due to a lower-layer + * error + * @throws ResourceUnavailableException indicating that the number of opening tunnels has + * reached the limit. * @hide + * @deprecated Callers should use {@link #createIpSecTunnelInterface(Network)} */ + @Deprecated @SystemApi @NonNull @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) @@ -905,16 +946,14 @@ public final class IpSecManager { * <p>Applications should probably not use this API directly. * * - * @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied + * @param tunnel The {@link IpSecTunnelInterface} that will use the supplied * transform. - * @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which + * @param direction the direction, {@link #DIRECTION_OUT} or {@link #DIRECTION_IN} in which * the transform will be used. * @param transform an {@link IpSecTransform} created in tunnel mode - * @throws IOException indicating that the transform could not be applied due to a lower - * layer failure. - * @hide + * @throws IOException indicating that the transform could not be applied due to a lower-layer + * error */ - @SystemApi @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS) @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull IpSecTunnelInterface tunnel, diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index f98a1f8a220d..b07bd68a0f50 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -21,6 +21,7 @@ import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; @@ -380,7 +381,13 @@ public class Network implements Parcelable { // Query a property of the underlying socket to ensure that the socket's file descriptor // exists, is available to bind to a network and is not closed. socket.getReuseAddress(); - bindSocket(socket.getFileDescriptor$()); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); } /** @@ -392,7 +399,13 @@ public class Network implements Parcelable { // Query a property of the underlying socket to ensure that the socket's file descriptor // exists, is available to bind to a network and is not closed. socket.getReuseAddress(); - bindSocket(socket.getFileDescriptor$()); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); } /** @@ -420,7 +433,7 @@ public class Network implements Parcelable { throw new SocketException("Only AF_INET/AF_INET6 sockets supported"); } - final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId); + final int err = NetworkUtils.bindSocketToNetwork(fd, netId); if (err != 0) { // bindSocketToNetwork returns negative errno. throw new ErrnoException("Binding socket to network " + netId, -err) diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 0a895b98f9fd..3843b9ab93c2 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -204,6 +204,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_TEMPORARILY_NOT_METERED, NET_CAPABILITY_OEM_PRIVATE, NET_CAPABILITY_VEHICLE_INTERNAL, + NET_CAPABILITY_NOT_VCN_MANAGED, }) public @interface NetCapability { } @@ -399,8 +400,16 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; + /** + * Indicates that this network is not managed by a Virtual Carrier Network (VCN). + * + * TODO(b/177299683): Add additional clarifying javadoc. + * @hide + */ + public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VEHICLE_INTERNAL; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -417,7 +426,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_CONGESTED) | (1 << NET_CAPABILITY_NOT_SUSPENDED) | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY) - | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED); + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -426,16 +436,21 @@ public final class NetworkCapabilities implements Parcelable { * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then * get immediately torn down because they do not have the requested capability. */ + // Note that as a historical exception, the TRUSTED and NOT_VCN_MANAGED capabilities + // are mutable but requestable. Factories are responsible for not getting + // in an infinite loop about these. private static final long NON_REQUESTABLE_CAPABILITIES = - MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_TRUSTED); + MUTABLE_CAPABILITIES + & ~(1 << NET_CAPABILITY_TRUSTED) + & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Capabilities that are set by default when the object is constructed. */ private static final long DEFAULT_CAPABILITIES = - (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED) | - (1 << NET_CAPABILITY_NOT_VPN); + (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_NOT_VPN); /** * Capabilities that suggest that a network is restricted. @@ -495,7 +510,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_VPN) | (1 << NET_CAPABILITY_NOT_ROAMING) | (1 << NET_CAPABILITY_NOT_CONGESTED) - | (1 << NET_CAPABILITY_NOT_SUSPENDED); + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Adds the given capability to this {@code NetworkCapability} instance. @@ -1982,6 +1998,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; default: return Integer.toString(capability); } } diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index a0dc72d4adbf..b644ed56ad8b 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -194,13 +194,15 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { subscriberId = state.subscriberId; if (type == TYPE_WIFI) { - if (state.networkId != null) { - networkId = state.networkId; - } else { - final WifiManager wifi = (WifiManager) context.getSystemService( - Context.WIFI_SERVICE); - final WifiInfo info = wifi.getConnectionInfo(); - networkId = info != null ? info.getSSID() : null; + if (state.networkCapabilities.getSsid() != null) { + networkId = state.networkCapabilities.getSsid(); + if (networkId == null) { + // TODO: Figure out if this code path never runs. If so, remove them. + final WifiManager wifi = (WifiManager) context.getSystemService( + Context.WIFI_SERVICE); + final WifiInfo info = wifi.getConnectionInfo(); + networkId = info != null ? info.getSSID() : null; + } } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index f0c637c76ec5..04011fc6816e 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -353,7 +353,9 @@ public class NetworkRequest implements Parcelable { * NetworkSpecifier. */ public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) { - MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(networkSpecifier); + if (networkSpecifier instanceof MatchAllNetworkSpecifier) { + throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted"); + } mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); return this; } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index b5962c5bae14..8be4af7b1396 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -81,11 +81,11 @@ public class NetworkUtils { public native static boolean bindProcessToNetworkForHostResolution(int netId); /** - * Explicitly binds {@code socketfd} to the network designated by {@code netId}. This + * Explicitly binds {@code fd} to the network designated by {@code netId}. This * overrides any binding via {@link #bindProcessToNetwork}. * @return 0 on success or negative errno on failure. */ - public native static int bindSocketToNetwork(int socketfd, int netId); + public static native int bindSocketToNetwork(FileDescriptor fd, int netId); /** * Protect {@code fd} from VPN connections. After protecting, data sent through @@ -93,9 +93,7 @@ public class NetworkUtils { * forwarded through the VPN. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static boolean protectFromVpn(FileDescriptor fd) { - return protectFromVpn(fd.getInt$()); - } + public static native boolean protectFromVpn(FileDescriptor fd); /** * Protect {@code socketfd} from VPN connections. After protecting, data sent through diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index a202d77a211a..c9bca2876b0a 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -355,7 +355,7 @@ public class ProxyInfo implements Parcelable { port = in.readInt(); } String exclList = in.readString(); - String[] parsedExclList = in.readStringArray(); + String[] parsedExclList = in.createStringArray(); ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/net/QosFilter.java b/core/java/android/net/QosFilter.java index 070546878171..ab55002e02b3 100644 --- a/core/java/android/net/QosFilter.java +++ b/core/java/android/net/QosFilter.java @@ -19,6 +19,8 @@ package android.net; import android.annotation.NonNull; import android.annotation.SystemApi; +import java.net.InetAddress; + /** * Provides the related filtering logic to the {@link NetworkAgent} to match {@link QosSession}s * to their related {@link QosCallback}. @@ -58,5 +60,16 @@ public abstract class QosFilter { */ @QosCallbackException.ExceptionType public abstract int validate(); + + /** + * Determines whether or not the parameters is a match for the filter. + * + * @param address the local address + * @param startPort the start of the port range + * @param endPort the end of the port range + * @return whether the parameters match the local address of the filter + */ + public abstract boolean matchesLocalAddress(@NonNull InetAddress address, + int startPort, int endPort); } diff --git a/core/java/android/net/QosSocketFilter.java b/core/java/android/net/QosSocketFilter.java index f51a0881e6e7..2080e68f5fba 100644 --- a/core/java/android/net/QosSocketFilter.java +++ b/core/java/android/net/QosSocketFilter.java @@ -26,7 +26,10 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.io.FileDescriptor; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; @@ -125,4 +128,39 @@ public class QosSocketFilter extends QosFilter { public Network getNetwork() { return mQosSocketInfo.getNetwork(); } + + /** + * @inheritDoc + */ + @Override + public boolean matchesLocalAddress(@NonNull final InetAddress address, final int startPort, + final int endPort) { + if (mQosSocketInfo.getLocalSocketAddress() == null) { + return false; + } + + return matchesLocalAddress(mQosSocketInfo.getLocalSocketAddress(), address, startPort, + endPort); + } + + /** + * Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)} with the + * filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}. + * <p> + * This method exists for testing purposes since {@link QosSocketInfo} couldn't be mocked + * due to being final. + * + * @param filterSocketAddress the socket address of the filter + * @param address the address to compare the filterSocketAddressWith + * @param startPort the start of the port range to check + * @param endPort the end of the port range to check + */ + @VisibleForTesting + public static boolean matchesLocalAddress(@NonNull final InetSocketAddress filterSocketAddress, + @NonNull final InetAddress address, + final int startPort, final int endPort) { + return startPort <= filterSocketAddress.getPort() + && endPort >= filterSocketAddress.getPort() + && filterSocketAddress.getAddress().equals(address); + } } diff --git a/core/java/com/android/internal/net/VpnInfo.aidl b/core/java/android/net/VpnInfo.aidl index 6fc97be4095b..8bcaa81f3992 100644 --- a/core/java/com/android/internal/net/VpnInfo.aidl +++ b/core/java/android/net/VpnInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.internal.net; +package android.net; parcelable VpnInfo; diff --git a/core/java/com/android/internal/net/VpnInfo.java b/core/java/android/net/VpnInfo.java index e74af5eb50de..cf58c570f21f 100644 --- a/core/java/com/android/internal/net/VpnInfo.java +++ b/core/java/android/net/VpnInfo.java @@ -11,11 +11,13 @@ * 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 + * limitations under the License. */ -package com.android.internal.net; +package android.net; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -23,14 +25,28 @@ import java.util.Arrays; /** * A lightweight container used to carry information of the ongoing VPN. - * Internal use only.. + * Internal use only. * * @hide */ public class VpnInfo implements Parcelable { - public int ownerUid; - public String vpnIface; - public String[] underlyingIfaces; + public final int ownerUid; + @Nullable + public final String vpnIface; + @Nullable + public final String[] underlyingIfaces; + + public VpnInfo(int ownerUid, @Nullable String vpnIface, @Nullable String[] underlyingIfaces) { + this.ownerUid = ownerUid; + this.vpnIface = vpnIface; + this.underlyingIfaces = underlyingIfaces; + } + + private VpnInfo(@NonNull Parcel in) { + this.ownerUid = in.readInt(); + this.vpnIface = in.readString(); + this.underlyingIfaces = in.createStringArray(); + } @Override public String toString() { @@ -47,22 +63,21 @@ public class VpnInfo implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(ownerUid); dest.writeString(vpnIface); dest.writeStringArray(underlyingIfaces); } + @NonNull public static final Parcelable.Creator<VpnInfo> CREATOR = new Parcelable.Creator<VpnInfo>() { + @NonNull @Override - public VpnInfo createFromParcel(Parcel source) { - VpnInfo info = new VpnInfo(); - info.ownerUid = source.readInt(); - info.vpnIface = source.readString(); - info.underlyingIfaces = source.readStringArray(); - return info; + public VpnInfo createFromParcel(@NonNull Parcel in) { + return new VpnInfo(in); } + @NonNull @Override public VpnInfo[] newArray(int size) { return new VpnInfo[size]; diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java index ede8faaaf261..5eb4ba6a2f8e 100644 --- a/core/java/android/net/vcn/VcnConfig.java +++ b/core/java/android/net/vcn/VcnConfig.java @@ -96,7 +96,11 @@ public final class VcnConfig implements Parcelable { return mPackageName; } - /** Retrieves the set of configured tunnels. */ + /** + * Retrieves the set of configured tunnels. + * + * @hide + */ @NonNull public Set<VcnGatewayConnectionConfig> getGatewayConnectionConfigs() { return Collections.unmodifiableSet(mGatewayConnectionConfigs); @@ -146,7 +150,7 @@ public final class VcnConfig implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(toPersistableBundle(), flags); } @@ -164,8 +168,12 @@ public final class VcnConfig implements Parcelable { } }; - /** This class is used to incrementally build {@link VcnConfig} objects. */ - public static class Builder { + /** + * This class is used to incrementally build {@link VcnConfig} objects. + * + * @hide + */ + public static final class Builder { @NonNull private final String mPackageName; @NonNull @@ -182,6 +190,7 @@ public final class VcnConfig implements Parcelable { * * @param gatewayConnectionConfig the configuration for an individual gateway connection * @return this {@link Builder} instance, for chaining + * @hide */ @NonNull public Builder addGatewayConnectionConfig( @@ -196,6 +205,7 @@ public final class VcnConfig implements Parcelable { * Builds and validates the VcnConfig. * * @return an immutable VcnConfig instance + * @hide */ @NonNull public VcnConfig build() { diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index d531cdb2a6e9..cead2f1caad1 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -17,6 +17,7 @@ package android.net.vcn; import static com.android.internal.annotations.VisibleForTesting.Visibility; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -25,14 +26,19 @@ import android.os.PersistableBundle; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; import com.android.server.vcn.util.PersistableBundleUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Objects; import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.TimeUnit; /** @@ -97,6 +103,26 @@ public final class VcnGatewayConnectionConfig { ALLOWED_CAPABILITIES = Collections.unmodifiableSet(allowedCaps); } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"NET_CAPABILITY_"}, + value = { + NetworkCapabilities.NET_CAPABILITY_MMS, + NetworkCapabilities.NET_CAPABILITY_SUPL, + NetworkCapabilities.NET_CAPABILITY_DUN, + NetworkCapabilities.NET_CAPABILITY_FOTA, + NetworkCapabilities.NET_CAPABILITY_IMS, + NetworkCapabilities.NET_CAPABILITY_CBS, + NetworkCapabilities.NET_CAPABILITY_IA, + NetworkCapabilities.NET_CAPABILITY_RCS, + NetworkCapabilities.NET_CAPABILITY_XCAP, + NetworkCapabilities.NET_CAPABILITY_EIMS, + NetworkCapabilities.NET_CAPABILITY_INTERNET, + NetworkCapabilities.NET_CAPABILITY_MCX, + }) + public @interface VcnSupportedCapability {} + private static final int DEFAULT_MAX_MTU = 1500; /** @@ -128,10 +154,10 @@ public final class VcnGatewayConnectionConfig { }; private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities"; - @NonNull private final Set<Integer> mExposedCapabilities; + @NonNull private final SortedSet<Integer> mExposedCapabilities; private static final String UNDERLYING_CAPABILITIES_KEY = "mUnderlyingCapabilities"; - @NonNull private final Set<Integer> mUnderlyingCapabilities; + @NonNull private final SortedSet<Integer> mUnderlyingCapabilities; // TODO: Add Ike/ChildSessionParams as a subclass - maybe VcnIkeGatewayConnectionConfig @@ -141,14 +167,14 @@ public final class VcnGatewayConnectionConfig { private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs"; @NonNull private final long[] mRetryIntervalsMs; - @VisibleForTesting(visibility = Visibility.PRIVATE) - public VcnGatewayConnectionConfig( + /** Builds a VcnGatewayConnectionConfig with the specified parameters. */ + private VcnGatewayConnectionConfig( @NonNull Set<Integer> exposedCapabilities, @NonNull Set<Integer> underlyingCapabilities, @NonNull long[] retryIntervalsMs, @IntRange(from = MIN_MTU_V6) int maxMtu) { - mExposedCapabilities = exposedCapabilities; - mUnderlyingCapabilities = underlyingCapabilities; + mExposedCapabilities = new TreeSet(exposedCapabilities); + mUnderlyingCapabilities = new TreeSet(underlyingCapabilities); mRetryIntervalsMs = retryIntervalsMs; mMaxMtu = maxMtu; @@ -163,9 +189,9 @@ public final class VcnGatewayConnectionConfig { final PersistableBundle underlyingCapsBundle = in.getPersistableBundle(UNDERLYING_CAPABILITIES_KEY); - mExposedCapabilities = new ArraySet<>(PersistableBundleUtils.toList( + mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList( exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); - mUnderlyingCapabilities = new ArraySet<>(PersistableBundleUtils.toList( + mUnderlyingCapabilities = new TreeSet<>(PersistableBundleUtils.toList( underlyingCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER)); mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY); mMaxMtu = in.getInt(MAX_MTU_KEY); @@ -219,52 +245,93 @@ public final class VcnGatewayConnectionConfig { /** * Returns all exposed capabilities. * + * <p>The returned integer-value capabilities will not contain duplicates, and will be sorted in + * ascending numerical order. + * + * @see Builder#addExposedCapability(int) + * @see Builder#clearExposedCapability(int) * @hide */ @NonNull + public int[] getExposedCapabilities() { + // Sorted set guarantees ordering + return ArrayUtils.convertToIntArray(new ArrayList<>(mExposedCapabilities)); + } + + /** + * Returns all exposed capabilities. + * + * <p>Left to prevent the need to make major changes while changes are actively in flight. + * + * @deprecated use getExposedCapabilities() instead + * @hide + */ + @Deprecated + @NonNull public Set<Integer> getAllExposedCapabilities() { return Collections.unmodifiableSet(mExposedCapabilities); } /** - * Checks if this config is configured to support/expose a specific capability. + * Returns all capabilities required of underlying networks. * - * @param capability the capability to check for + * <p>The returned integer-value capabilities will be sorted in ascending numerical order. + * + * @see Builder#addRequiredUnderlyingCapability(int) + * @see Builder#clearRequiredUnderlyingCapability(int) + * @hide */ - public boolean hasExposedCapability(int capability) { - checkValidCapability(capability); - - return mExposedCapabilities.contains(capability); + @NonNull + public int[] getRequiredUnderlyingCapabilities() { + // Sorted set guarantees ordering + return ArrayUtils.convertToIntArray(new ArrayList<>(mUnderlyingCapabilities)); } /** * Returns all capabilities required of underlying networks. * + * <p>Left to prevent the need to make major changes while changes are actively in flight. + * + * @deprecated use getRequiredUnderlyingCapabilities() instead * @hide */ + @Deprecated @NonNull public Set<Integer> getAllUnderlyingCapabilities() { return Collections.unmodifiableSet(mUnderlyingCapabilities); } /** - * Checks if this config requires an underlying network to have the specified capability. + * Retrieves the configured retry intervals. * - * @param capability the capability to check for + * @see Builder#setRetryInterval(long[]) + * @hide */ - public boolean requiresUnderlyingCapability(int capability) { - checkValidCapability(capability); - - return mUnderlyingCapabilities.contains(capability); + @NonNull + public long[] getRetryInterval() { + return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length); } - /** Retrieves the configured retry intervals. */ + /** + * Retrieves the configured retry intervals. + * + * <p>Left to prevent the need to make major changes while changes are actively in flight. + * + * @deprecated use getRequiredUnderlyingCapabilities() instead + * @hide + */ + @Deprecated @NonNull public long[] getRetryIntervalsMs() { - return Arrays.copyOf(mRetryIntervalsMs, mRetryIntervalsMs.length); + return getRetryInterval(); } - /** Retrieves the maximum MTU allowed for this Gateway Connection. */ + /** + * Retrieves the maximum MTU allowed for this Gateway Connection. + * + * @see Builder.setMaxMtu(int) + * @hide + */ @IntRange(from = MIN_MTU_V6) public int getMaxMtu() { return mMaxMtu; @@ -319,8 +386,12 @@ public final class VcnGatewayConnectionConfig { && mMaxMtu == rhs.mMaxMtu; } - /** This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects. */ - public static class Builder { + /** + * This class is used to incrementally build {@link VcnGatewayConnectionConfig} objects. + * + * @hide + */ + public static final class Builder { @NonNull private final Set<Integer> mExposedCapabilities = new ArraySet(); @NonNull private final Set<Integer> mUnderlyingCapabilities = new ArraySet(); @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; @@ -338,8 +409,10 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway * Connection + * @hide */ - public Builder addExposedCapability(int exposedCapability) { + @NonNull + public Builder addExposedCapability(@VcnSupportedCapability int exposedCapability) { checkValidCapability(exposedCapability); mExposedCapabilities.add(exposedCapability); @@ -354,8 +427,10 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be exposed by a Gateway * Connection + * @hide */ - public Builder removeExposedCapability(int exposedCapability) { + @NonNull + public Builder clearExposedCapability(@VcnSupportedCapability int exposedCapability) { checkValidCapability(exposedCapability); mExposedCapabilities.remove(exposedCapability); @@ -370,8 +445,11 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying * networks + * @hide */ - public Builder addRequiredUnderlyingCapability(int underlyingCapability) { + @NonNull + public Builder addRequiredUnderlyingCapability( + @VcnSupportedCapability int underlyingCapability) { checkValidCapability(underlyingCapability); mUnderlyingCapabilities.add(underlyingCapability); @@ -390,8 +468,11 @@ public final class VcnGatewayConnectionConfig { * @return this {@link Builder} instance, for chaining * @see VcnGatewayConnectionConfig for a list of capabilities may be required of underlying * networks + * @hide */ - public Builder removeRequiredUnderlyingCapability(int underlyingCapability) { + @NonNull + public Builder clearRequiredUnderlyingCapability( + @VcnSupportedCapability int underlyingCapability) { checkValidCapability(underlyingCapability); mUnderlyingCapabilities.remove(underlyingCapability); @@ -420,6 +501,7 @@ public final class VcnGatewayConnectionConfig { * 15m]} * @return this {@link Builder} instance, for chaining * @see VcnManager for additional discussion on fail-safe mode + * @hide */ @NonNull public Builder setRetryInterval(@NonNull long[] retryIntervalsMs) { @@ -441,6 +523,7 @@ public final class VcnGatewayConnectionConfig { * @param maxMtu the maximum MTU allowed for this Gateway Connection. Must be greater than * the IPv6 minimum MTU of 1280. Defaults to 1500. * @return this {@link Builder} instance, for chaining + * @hide */ @NonNull public Builder setMaxMtu(@IntRange(from = MIN_MTU_V6) int maxMtu) { @@ -455,6 +538,7 @@ public final class VcnGatewayConnectionConfig { * Builds and validates the VcnGatewayConnectionConfig. * * @return an immutable VcnGatewayConnectionConfig instance + * @hide */ @NonNull public VcnGatewayConnectionConfig build() { diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 2ccdc2633af0..2d0a6d74cb86 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -65,6 +65,7 @@ import java.util.concurrent.Executor; public final class VcnManager { @NonNull private static final String TAG = VcnManager.class.getSimpleName(); + /** @hide */ @VisibleForTesting public static final Map< VcnUnderlyingNetworkPolicyListener, VcnUnderlyingNetworkPolicyListenerBinder> diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 10761187d969..5ae53b502330 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -26,6 +26,7 @@ import android.app.ActivityThread; import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; import android.text.TextUtils; import android.util.Slog; @@ -87,6 +88,14 @@ public class Build { /** The end-user-visible name for the end product. */ public static final String MODEL = getString("ro.product.model"); + /** The manufacturer of the device's primary system-on-chip. */ + @NonNull + public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN); + + /** The model name of the device's primary system-on-chip. */ + @NonNull + public static final String SOC_MODEL = SocProperties.soc_model().orElse(UNKNOWN); + /** The system bootloader version number. */ public static final String BOOTLOADER = getString("ro.bootloader"); diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index 7e17a082840b..ff126e12cf61 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -1,10 +1,10 @@ # Bug component: 95221 -narayan@google.com -nandana@google.com corinac@google.com +nandana@google.com zezeozue@google.com maco@google.com sahanas@google.com abkaur@google.com chiangi@google.com +narayan@google.com diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 017f40521a81..f994d2930cd9 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -177,6 +177,7 @@ public final class KeymasterDefs { public static final int KM_PURPOSE_SIGN = KeyPurpose.SIGN; public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY; public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY; + public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY; // Key formats. public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509; diff --git a/core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl b/core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl new file mode 100644 index 000000000000..d9b403ca0609 --- /dev/null +++ b/core/java/android/service/resumeonreboot/IResumeOnRebootService.aidl @@ -0,0 +1,25 @@ +/* + * 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.service.resumeonreboot; + +import android.os.RemoteCallback; + +/** @hide */ +interface IResumeOnRebootService { + oneway void wrapSecret(in byte[] unwrappedBlob, in long lifeTimeInMillis, in RemoteCallback resultCallback); + oneway void unwrap(in byte[] wrappedBlob, in RemoteCallback resultCallback); +}
\ No newline at end of file diff --git a/core/java/android/service/resumeonreboot/ResumeOnRebootService.java b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java new file mode 100644 index 000000000000..4ebaa96f4be2 --- /dev/null +++ b/core/java/android/service/resumeonreboot/ResumeOnRebootService.java @@ -0,0 +1,164 @@ +/* + * 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.service.resumeonreboot; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.ParcelableException; +import android.os.RemoteCallback; +import android.os.RemoteException; + +import com.android.internal.os.BackgroundThread; + +import java.io.IOException; + +/** + * Base class for service that provides wrapping/unwrapping of the opaque blob needed for + * ResumeOnReboot operation. The package needs to provide a wrap/unwrap implementation for handling + * the opaque blob, that's secure even when on device keystore and clock is compromised. This can + * be achieved by using tamper-resistant hardware such as a secure element with a secure clock, or + * using a remote server to store and retrieve data and manage timing. + * + * <p>To extend this class, you must declare the service in your manifest file with the + * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE} permission, + * include an intent filter with the {@link #SERVICE_INTERFACE} action and mark the service as + * direct-boot aware. In addition, the package that contains the service must be granted + * {@link android.Manifest.permission#BIND_RESUME_ON_REBOOT_SERVICE}. + * For example:</p> + * <pre> + * <service android:name=".FooResumeOnRebootService" + * android:exported="true" + * android:priority="100" + * android:directBootAware="true" + * android:permission="android.permission.BIND_RESUME_ON_REBOOT_SERVICE"> + * <intent-filter> + * <action android:name="android.service.resumeonreboot.ResumeOnRebootService" /> + * </intent-filter> + * </service> + * </pre> + * + * //TODO: Replace this with public link when available. + * + * @hide + * @see + * <a href="https://goto.google.com/server-based-ror">https://goto.google.com/server-based-ror</a> + */ +@SystemApi +public abstract class ResumeOnRebootService extends Service { + + /** + * The intent that the service must respond to. Add it to the intent filter of the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.resumeonreboot.ResumeOnRebootService"; + /** @hide */ + public static final String UNWRAPPED_BLOB_KEY = "unrwapped_blob_key"; + /** @hide */ + public static final String WRAPPED_BLOB_KEY = "wrapped_blob_key"; + /** @hide */ + public static final String EXCEPTION_KEY = "exception_key"; + + private final Handler mHandler = BackgroundThread.getHandler(); + + /** + * Implementation for wrapping the opaque blob used for resume-on-reboot prior to + * reboot. The service should not assume any structure of the blob to be wrapped. The + * implementation should wrap the opaque blob in a reasonable time or throw {@link IOException} + * if it's unable to complete the action. + * + * @param blob The opaque blob with size on the order of 100 bytes. + * @param lifeTimeInMillis The life time of the blob. This must be strictly enforced by the + * implementation and any attempt to unWrap the wrapped blob returned by + * this function after expiration should + * fail. + * @return Wrapped blob to be persisted across reboot with size on the order of 100 bytes. + * @throws IOException if the implementation is unable to wrap the blob successfully. + */ + @NonNull + public abstract byte[] onWrap(@NonNull byte[] blob, @DurationMillisLong long lifeTimeInMillis) + throws IOException; + + /** + * Implementation for unwrapping the wrapped blob used for resume-on-reboot after reboot. This + * operation would happen after reboot during direct boot mode (i.e before device is unlocked + * for the first time). The implementation should unwrap the wrapped blob in a reasonable time + * and returns the result or throw {@link IOException} if it's unable to complete the action + * and {@link IllegalArgumentException} if {@code unwrapBlob} fails because the wrappedBlob is + * stale. + * + * @param wrappedBlob The wrapped blob with size on the order of 100 bytes. + * @return Unwrapped blob used for resume-on-reboot with the size on the order of 100 bytes. + * @throws IOException if the implementation is unable to unwrap the wrapped blob successfully. + */ + @NonNull + public abstract byte[] onUnwrap(@NonNull byte[] wrappedBlob) throws IOException; + + private final android.service.resumeonreboot.IResumeOnRebootService mInterface = + new android.service.resumeonreboot.IResumeOnRebootService.Stub() { + + @Override + public void wrapSecret(byte[] unwrappedBlob, + @DurationMillisLong long lifeTimeInMillis, + RemoteCallback resultCallback) throws RemoteException { + mHandler.post(() -> { + try { + byte[] wrappedBlob = onWrap(unwrappedBlob, + lifeTimeInMillis); + Bundle bundle = new Bundle(); + bundle.putByteArray(WRAPPED_BLOB_KEY, wrappedBlob); + resultCallback.sendResult(bundle); + } catch (Throwable e) { + Bundle bundle = new Bundle(); + bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); + resultCallback.sendResult(bundle); + } + }); + } + + @Override + public void unwrap(byte[] wrappedBlob, RemoteCallback resultCallback) + throws RemoteException { + mHandler.post(() -> { + try { + byte[] unwrappedBlob = onUnwrap(wrappedBlob); + Bundle bundle = new Bundle(); + bundle.putByteArray(UNWRAPPED_BLOB_KEY, unwrappedBlob); + resultCallback.sendResult(bundle); + } catch (Throwable e) { + Bundle bundle = new Bundle(); + bundle.putParcelable(EXCEPTION_KEY, new ParcelableException(e)); + resultCallback.sendResult(bundle); + } + }); + } + }; + + @Nullable + @Override + public IBinder onBind(@Nullable Intent intent) { + return mInterface.asBinder(); + } +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 537498c44d5e..9d0ae30f1420 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -39,7 +39,6 @@ public class FeatureFlagUtils { public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer"; public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid"; public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press"; - public static final String DYNAMIC_SYSTEM = "settings_dynamic_system"; public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2"; public static final String SETTINGS_FUSE_FLAG = "settings_fuse"; /** @hide */ @@ -53,7 +52,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_audio_switcher", "true"); DEFAULT_FLAGS.put("settings_systemui_theme", "true"); DEFAULT_FLAGS.put(SETTINGS_FUSE_FLAG, "true"); - DEFAULT_FLAGS.put(DYNAMIC_SYSTEM, "false"); DEFAULT_FLAGS.put(SEAMLESS_TRANSFER, "false"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false"); diff --git a/core/java/android/util/OWNERS b/core/java/android/util/OWNERS index 8f3d9f6f5881..14aa38682d2b 100644 --- a/core/java/android/util/OWNERS +++ b/core/java/android/util/OWNERS @@ -1,3 +1,6 @@ per-file FeatureFlagUtils.java = sbasi@google.com per-file FeatureFlagUtils.java = tmfang@google.com per-file FeatureFlagUtils.java = asapperstein@google.com + +per-file TypedValue.java = file:/core/java/android/content/res/OWNERS +per-file AttributeSet.java = file:/core/java/android/content/res/OWNERS diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index e6a166140d89..beef9825b72a 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -129,7 +129,7 @@ interface IBatteryStats { void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph); void noteWifiBatchedScanStoppedFromSource(in WorkSource ws); void noteWifiRadioPowerState(int powerState, long timestampNs, int uid); - void noteNetworkInterfaceType(String iface, int type); + void noteNetworkInterfaceForTransports(String iface, in int[] transportTypes); void noteNetworkStatsEnabled(); void noteDeviceIdleMode(int mode, String activeReason, int activeUid); void setBatteryState(int status, int health, int plugType, int level, int temp, int volt, diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS index c5a956a81d8d..99692d0736c2 100644 --- a/core/java/com/android/internal/app/OWNERS +++ b/core/java/com/android/internal/app/OWNERS @@ -3,3 +3,5 @@ per-file *Resolver* = file:/packages/SystemUI/OWNERS per-file *Chooser* = file:/packages/SystemUI/OWNERS per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS +per-file IVoice* = file:/core/java/android/service/voice/OWNERS +per-file *Hotword* = file:/core/java/android/service/voice/OWNERS diff --git a/core/java/com/android/internal/graphics/fonts/OWNERS b/core/java/com/android/internal/graphics/fonts/OWNERS new file mode 100644 index 000000000000..18486af9d12c --- /dev/null +++ b/core/java/com/android/internal/graphics/fonts/OWNERS @@ -0,0 +1 @@ +include /graphics/java/android/graphics/fonts/OWNERS diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 8bfc28ed5e52..ae680e0febac 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -23,7 +23,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.SensorManager; -import android.net.ConnectivityManager; import android.os.BatteryStats; import android.os.BatteryStats.Uid; import android.os.Build; @@ -37,6 +36,7 @@ import android.os.SELinux; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; @@ -148,12 +148,11 @@ public class BatteryStatsHelper { boolean mHasBluetoothPowerReporting = false; public static boolean checkWifiOnly(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - if (cm == null) { + final TelephonyManager tm = context.getSystemService(TelephonyManager.class); + if (tm == null) { return false; } - return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + return !tm.isDataCapable(); } public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) { diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 1071d9f2a918..2d75b70333f2 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.os.BatteryStatsManager.NUM_WIFI_STATES; import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES; @@ -32,7 +34,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.usb.UsbManager; -import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.NetworkStats; import android.net.Uri; @@ -101,6 +102,7 @@ import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; +import com.android.net.module.util.NetworkCapabilitiesUtils; import libcore.util.EmptyArray; @@ -6099,11 +6101,12 @@ public class BatteryStatsImpl extends BatteryStats { } /** @hide */ - public void noteNetworkInterfaceType(String iface, int networkType) { + public void noteNetworkInterfaceForTransports(String iface, int[] transportTypes) { if (TextUtils.isEmpty(iface)) return; + final int displayTransport = NetworkCapabilitiesUtils.getDisplayTransport(transportTypes); synchronized (mModemNetworkLock) { - if (ConnectivityManager.isNetworkTypeMobile(networkType)) { + if (displayTransport == TRANSPORT_CELLULAR) { mModemIfaces = includeInStringArray(mModemIfaces, iface); if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mModemIfaces); } else { @@ -6113,7 +6116,7 @@ public class BatteryStatsImpl extends BatteryStats { } synchronized (mWifiNetworkLock) { - if (ConnectivityManager.isNetworkTypeWifi(networkType)) { + if (displayTransport == TRANSPORT_WIFI) { mWifiIfaces = includeInStringArray(mWifiIfaces, iface); if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces); } else { diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 2155246cd544..e2af87ee1adf 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -18,6 +18,7 @@ #include <vector> +#include <android/file_descriptor_jni.h> #include <arpa/inet.h> #include <linux/filter.h> #include <linux/if_arp.h> @@ -83,7 +84,7 @@ static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, filter_code, }; - int fd = jniGetFDFromFileDescriptor(env, javaFd); + int fd = AFileDescriptor_getFD(env, javaFd); if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) { jniThrowExceptionFmt(env, "java/net/SocketException", "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno)); @@ -93,7 +94,7 @@ static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd) { int optval_ignored = 0; - int fd = jniGetFDFromFileDescriptor(env, javaFd); + int fd = AFileDescriptor_getFD(env, javaFd); if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &optval_ignored, sizeof(optval_ignored)) != 0) { jniThrowExceptionFmt(env, "java/net/SocketException", @@ -117,10 +118,9 @@ static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv * return (jboolean) !setNetworkForResolv(netId); } -static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket, - jint netId) -{ - return setNetworkForSocket(netId, socket); +static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd, + jint netId) { + return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd)); } static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint socket) @@ -128,6 +128,10 @@ static jboolean android_net_utils_protectFromVpn(JNIEnv *env, jobject thiz, jint return (jboolean) !protectFromVpn(socket); } +static jboolean android_net_utils_protectFromVpnWithFd(JNIEnv *env, jobject thiz, jobject javaFd) { + return android_net_utils_protectFromVpn(env, thiz, AFileDescriptor_getFD(env, javaFd)); +} + static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId) { return (jboolean) !queryUserAccess(uid, netId); @@ -178,7 +182,7 @@ static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint } static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) { - int fd = jniGetFDFromFileDescriptor(env, javaFd); + int fd = AFileDescriptor_getFD(env, javaFd); int rcode; std::vector<uint8_t> buf(MAXPACKETSIZE, 0); @@ -205,7 +209,7 @@ static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, job } static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) { - int fd = jniGetFDFromFileDescriptor(env, javaFd); + int fd = AFileDescriptor_getFD(env, javaFd); resNetworkCancel(fd); jniSetFileDescriptorOfFD(env, javaFd, -1); } @@ -231,7 +235,7 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j return NULL; } - int fd = jniGetFDFromFileDescriptor(env, javaFd); + int fd = AFileDescriptor_getFD(env, javaFd); struct tcp_repair_window trw = {}; socklen_t size = sizeof(trw); @@ -271,8 +275,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork }, { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess }, { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution }, - { "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork }, - { "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn }, + { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork }, + { "protectFromVpn", "(I)Z", (void*) android_net_utils_protectFromVpn }, + { "protectFromVpn", "(Ljava/io/FileDescriptor;)Z", (void*) android_net_utils_protectFromVpnWithFd }, { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess }, { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter }, { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c6a1bdd54080..24dabfc02454 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1669,6 +1669,12 @@ <permission android:name="android.permission.REQUEST_NETWORK_SCORES" android:protectionLevel="signature|setup" /> + <!-- Allows applications to restart the Wi-Fi subsystem. + @SystemApi + <p>Not for use by third-party applications. @hide --> + <permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM" + android:protectionLevel="signature|setup" /> + <!-- @SystemApi @hide Allows applications to toggle airplane mode. <p>Not for use by third-party or privileged applications. --> @@ -2984,6 +2990,12 @@ <permission android:name="android.permission.RECOVERY" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to do certain operations needed for + resume on reboot feature. + @hide --> + <permission android:name="android.permission.BIND_RESUME_ON_REBOOT_SERVICE" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to read system update info. @hide --> <permission android:name="android.permission.READ_SYSTEM_UPDATE_INFO" diff --git a/core/sysprop/WatchdogProperties.sysprop b/core/sysprop/WatchdogProperties.sysprop index 1bcc773a9a5d..93e8b788aed9 100644 --- a/core/sysprop/WatchdogProperties.sysprop +++ b/core/sysprop/WatchdogProperties.sysprop @@ -16,7 +16,7 @@ module: "android.sysprop.WatchdogProperties" owner: Platform # To escape the watchdog timeout loop, fatal reboot the system when -# watchdog timed out 'fatal_count' times in 'fatal_window_second' +# watchdog timed out 'fatal_count' times in 'fatal_window_seconds' # seconds, if both values are not 0. Default value of both is 0. prop { api_name: "fatal_count" @@ -26,8 +26,9 @@ prop { access: Readonly } +# See 'fatal_count' for documentation. prop { - api_name: "fatal_window_second" + api_name: "fatal_window_seconds" type: Integer prop_name: "framework_watchdog.fatal_window.second" scope: Internal @@ -35,9 +36,9 @@ prop { } # The fatal counting can be disabled by setting property -# 'is_fatal_ignore' to true. +# 'should_ignore_fatal_count' to true. prop { - api_name: "is_fatal_ignore" + api_name: "should_ignore_fatal_count" type: Boolean prop_name: "persist.debug.framework_watchdog.fatal_ignore" scope: Internal diff --git a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt index d901aef945c9..c8462111fa94 100644 --- a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt +++ b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt @@ -7,13 +7,13 @@ props { prop_name: "framework_watchdog.fatal_count" } prop { - api_name: "fatal_window_second" + api_name: "fatal_window_seconds" type: Integer scope: Internal prop_name: "framework_watchdog.fatal_window.second" } prop { - api_name: "is_fatal_ignore" + api_name: "should_ignore_fatal_count" scope: Internal prop_name: "persist.debug.framework_watchdog.fatal_ignore" } diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS index bd7da0c3f209..b3f399363aef 100644 --- a/core/tests/coretests/src/android/app/OWNERS +++ b/core/tests/coretests/src/android/app/OWNERS @@ -1 +1,6 @@ per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS + +# Notification, DND, Status bar +per-file *Notification* = file:/packages/SystemUI/OWNERS +per-file *Zen* = file:/packages/SystemUI/OWNERS +per-file *StatusBar* = file:/packages/SystemUI/OWNERS diff --git a/core/tests/coretests/src/android/app/people/OWNERS b/core/tests/coretests/src/android/app/people/OWNERS new file mode 100644 index 000000000000..6ec8e6aa8d81 --- /dev/null +++ b/core/tests/coretests/src/android/app/people/OWNERS @@ -0,0 +1 @@ +file:/core/java/android/app/people/OWNERS
\ No newline at end of file diff --git a/core/tests/coretests/src/android/content/pm/OWNERS b/core/tests/coretests/src/android/content/pm/OWNERS index 711f5f012b8b..7b7670696bfa 100644 --- a/core/tests/coretests/src/android/content/pm/OWNERS +++ b/core/tests/coretests/src/android/content/pm/OWNERS @@ -1,2 +1,3 @@ per-file AppSearchPersonTest.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS - +per-file SigningDetailsTest.java = mpgroover@google.com +per-file SigningDetailsTest.java = cbrubaker@google.com diff --git a/data/etc/OWNERS b/data/etc/OWNERS index 9867d810dba2..65d3a012b129 100644 --- a/data/etc/OWNERS +++ b/data/etc/OWNERS @@ -1,3 +1,4 @@ +alanstokes@google.com cbrubaker@google.com hackbod@android.com hackbod@google.com @@ -12,4 +13,4 @@ toddke@android.com toddke@google.com yamasani@google.com -per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
\ No newline at end of file +per-file preinstalled-packages* = file:/MULTIUSER_OWNERS diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a185da19e71b..30501fb53678 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -344,6 +344,7 @@ applications that come with the platform <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> <!-- Needed for test only --> + <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/> <permission name="android.permission.NETWORK_AIRPLANE_MODE"/> <permission name="android.permission.OBSERVE_APP_USAGE"/> <permission name="android.permission.NETWORK_SCAN"/> diff --git a/identity/java/android/security/identity/CredstoreIdentityCredential.java b/identity/java/android/security/identity/CredstoreIdentityCredential.java index 7c0af6def696..6398cee74cba 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredential.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredential.java @@ -37,6 +37,7 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.time.Instant; import java.util.Collection; import java.util.LinkedList; import java.util.Map; @@ -237,12 +238,18 @@ class CredstoreIdentityCredential extends IdentityCredential { } private boolean mAllowUsingExhaustedKeys = true; + private boolean mAllowUsingExpiredKeys = false; @Override public void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys) { mAllowUsingExhaustedKeys = allowUsingExhaustedKeys; } + @Override + public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) { + mAllowUsingExpiredKeys = allowUsingExpiredKeys; + } + private boolean mOperationHandleSet = false; private long mOperationHandle = 0; @@ -256,7 +263,8 @@ class CredstoreIdentityCredential extends IdentityCredential { public long getCredstoreOperationHandle() { if (!mOperationHandleSet) { try { - mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys); + mOperationHandle = mBinder.selectAuthKey(mAllowUsingExhaustedKeys, + mAllowUsingExpiredKeys); mOperationHandleSet = true; } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); @@ -306,7 +314,8 @@ class CredstoreIdentityCredential extends IdentityCredential { rnsParcels, sessionTranscript != null ? sessionTranscript : new byte[0], readerSignature != null ? readerSignature : new byte[0], - mAllowUsingExhaustedKeys); + mAllowUsingExhaustedKeys, + mAllowUsingExpiredKeys); } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); } catch (android.os.ServiceSpecificException e) { @@ -410,6 +419,34 @@ class CredstoreIdentityCredential extends IdentityCredential { } @Override + public void storeStaticAuthenticationData(X509Certificate authenticationKey, + Instant expirationDate, + byte[] staticAuthData) + throws UnknownAuthenticationKeyException { + try { + AuthKeyParcel authKeyParcel = new AuthKeyParcel(); + authKeyParcel.x509cert = authenticationKey.getEncoded(); + long millisSinceEpoch = (expirationDate.getEpochSecond() * 1000) + + (expirationDate.getNano() / 1000000); + mBinder.storeStaticAuthenticationDataWithExpiration(authKeyParcel, + millisSinceEpoch, staticAuthData); + } catch (CertificateEncodingException e) { + throw new RuntimeException("Error encoding authenticationKey", e); + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NOT_SUPPORTED) { + throw new UnsupportedOperationException("Not supported", e); + } else if (e.errorCode == ICredentialStore.ERROR_AUTHENTICATION_KEY_NOT_FOUND) { + throw new UnknownAuthenticationKeyException(e.getMessage(), e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override public @NonNull int[] getAuthenticationDataUsageCount() { try { int[] usageCount = mBinder.getAuthenticationDataUsageCount(); @@ -421,4 +458,49 @@ class CredstoreIdentityCredential extends IdentityCredential { + e.errorCode, e); } } + + @Override + public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) { + try { + byte[] proofOfOwnership = mBinder.proveOwnership(challenge); + return proofOfOwnership; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + if (e.errorCode == ICredentialStore.ERROR_NOT_SUPPORTED) { + throw new UnsupportedOperationException("Not supported", e); + } else { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + } + + @Override + public @NonNull byte[] delete(@NonNull byte[] challenge) { + try { + byte[] proofOfDeletion = mBinder.deleteWithChallenge(challenge); + return proofOfDeletion; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } + + @Override + public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) { + try { + IWritableCredential binder = mBinder.update(); + byte[] proofOfProvision = + CredstoreWritableIdentityCredential.personalize(binder, personalizationData); + return proofOfProvision; + } catch (android.os.RemoteException e) { + throw new RuntimeException("Unexpected RemoteException ", e); + } catch (android.os.ServiceSpecificException e) { + throw new RuntimeException("Unexpected ServiceSpecificException with code " + + e.errorCode, e); + } + } } diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index 129063361b35..d8d47424e2e8 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -162,5 +162,4 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { + e.errorCode, e); } } - } diff --git a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java index 725e3d8e429a..d2e7984ce19f 100644 --- a/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java +++ b/identity/java/android/security/identity/CredstoreWritableIdentityCredential.java @@ -76,7 +76,14 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential { @NonNull @Override public byte[] personalize(@NonNull PersonalizationData personalizationData) { + return personalize(mBinder, personalizationData); + } + // Used by both personalize() and CredstoreIdentityCredential.update(). + // + @NonNull + static byte[] personalize(IWritableCredential binder, + @NonNull PersonalizationData personalizationData) { Collection<AccessControlProfile> accessControlProfiles = personalizationData.getAccessControlProfiles(); @@ -144,7 +151,7 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential { secureUserId = getRootSid(); } try { - byte[] personalizationReceipt = mBinder.personalize(acpParcels, ensParcels, + byte[] personalizationReceipt = binder.personalize(acpParcels, ensParcels, secureUserId); return personalizationReceipt; } catch (android.os.RemoteException e) { @@ -164,5 +171,4 @@ class CredstoreWritableIdentityCredential extends WritableIdentityCredential { return rootSid; } - } diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index 4eb6e420c07f..8f175bb63edb 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -23,6 +23,7 @@ import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.time.Instant; import java.util.Collection; import java.util.Map; @@ -114,6 +115,25 @@ public abstract class IdentityCredential { public abstract void setAllowUsingExhaustedKeys(boolean allowUsingExhaustedKeys); /** + * Sets whether to allow using an authentication key which has been expired if no + * other key is available. This must be called prior to calling + * {@link #getEntries(byte[], Map, byte[], byte[])}. + * + * <p>By default this is set to false. + * + * <p>This is only implemented in feature version 202101 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param allowUsingExpiredKeys whether to allow using an authentication key which use count + * has been exceeded if no other key is available. + */ + public void setAllowUsingExpiredKeys(boolean allowUsingExpiredKeys) { + throw new UnsupportedOperationException(); + } + + /** * Called by android.hardware.biometrics.CryptoObject#getOpId() to get an * operation handle. * @@ -289,6 +309,21 @@ public abstract class IdentityCredential { * * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey * can be obtained using the {@link #getCredentialKeyCertificateChain()} method. + + * <p>If the implementation is feature version 202101 or later, + * each X.509 certificate contains an X.509 extension at OID 1.3.6.1.4.1.11129.2.1.26 which + * contains a DER encoded OCTET STRING with the bytes of the CBOR with the following CDDL: + * <pre> + * ProofOfBinding = [ + * "ProofOfBinding", + * bstr, // Contains SHA-256(ProofOfProvisioning) + * ] + * </pre> + * <p>This CBOR enables an issuer to determine the exact state of the credential it + * returns issuer-signed data for. + * + * <p> See {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for + * known feature versions. * * @return A collection of X.509 certificates for dynamic authentication keys that need issuer * certification. @@ -308,16 +343,136 @@ public abstract class IdentityCredential { * the authenticity * and integrity of the credential data fields. * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized. + * @deprecated Use {@link #storeStaticAuthenticationData(X509Certificate, Instant, byte[])} + * instead. */ + @Deprecated public abstract void storeStaticAuthenticationData( @NonNull X509Certificate authenticationKey, @NonNull byte[] staticAuthData) throws UnknownAuthenticationKeyException; /** + * Store authentication data associated with a dynamic authentication key. + * + * This should only be called for an authenticated key returned by + * {@link #getAuthKeysNeedingCertification()}. + * + * <p>This is only implemented in feature version 202101 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param authenticationKey The dynamic authentication key for which certification and + * associated static + * authentication data is being provided. + * @param expirationDate The expiration date of the static authentication data. + * @param staticAuthData Static authentication data provided by the issuer that validates + * the authenticity + * and integrity of the credential data fields. + * @throws UnknownAuthenticationKeyException If the given authentication key is not recognized. + */ + public void storeStaticAuthenticationData( + @NonNull X509Certificate authenticationKey, + @NonNull Instant expirationDate, + @NonNull byte[] staticAuthData) + throws UnknownAuthenticationKeyException { + throw new UnsupportedOperationException(); + } + + /** * Get the number of times the dynamic authentication keys have been used. * * @return int array of dynamic authentication key usage counts. */ public @NonNull abstract int[] getAuthenticationDataUsageCount(); + + /** + * Proves ownership of a credential. + * + * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey + * with payload set to {@code ProofOfDeletion} as defined below.</p> + * + * <p>The returned CBOR is the following:</p> + * <pre> + * ProofOfOwnership = [ + * "ProofOfOwnership", ; tstr + * tstr, ; DocType + * bstr, ; Challenge + * bool ; true if this is a test credential, should + * ; always be false. + * ] + * </pre> + * + * <p>This is only implemented in feature version 202101 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param challenge is a non-empty byte array whose contents should be unique, fresh and + * provided by the issuing authority. The value provided is embedded in the + * generated CBOR and enables the issuing authority to verify that the + * returned proof is fresh. + * @return the COSE_Sign1 data structure above + */ + public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) { + throw new UnsupportedOperationException(); + } + + /** + * Deletes a credential. + * + * <p>This method returns a COSE_Sign1 data structure signed by the CredentialKey + * with payload set to {@code ProofOfDeletion} as defined below.</p> + * + * <pre> + * ProofOfDeletion = [ + * "ProofOfDeletion", ; tstr + * tstr, ; DocType + * bstr, ; Challenge + * bool ; true if this is a test credential, should + * ; always be false. + * ] + * </pre> + * + * <p>This is only implemented in feature version 202101 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param challenge is a non-empty byte array whose contents should be unique, fresh and + * provided by the issuing authority. The value provided is embedded in the + * generated CBOR and enables the issuing authority to verify that the + * returned proof is fresh. + * @return the COSE_Sign1 data structure above + */ + public @NonNull byte[] delete(@NonNull byte[] challenge) { + throw new UnsupportedOperationException(); + } + + /** + * Updates the credential with new access control profiles and data items. + * + * <p>This method is similar to + * {@link WritableIdentityCredential#personalize(PersonalizationData)} except that it operates + * on an existing credential, see the documentation for that method for the format of the + * returned data. + * + * <p>If this call succeeds an side-effect is that all dynamic authentication keys for the + * credential are deleted. The application will need to use + * {@link #getAuthKeysNeedingCertification()} to generate replacement keys and return + * them for issuer certification. + * + * <p>This is only implemented in feature version 202101 or later. If not implemented, the call + * fails with {@link UnsupportedOperationException}. See + * {@link android.content.pm.PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} for known + * feature versions. + * + * @param personalizationData The data to update, including access control profiles + * and data elements and their values, grouped into namespaces. + * @return A COSE_Sign1 data structure, see above. + */ + public @NonNull byte[] update(@NonNull PersonalizationData personalizationData) { + throw new UnsupportedOperationException(); + } } diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java index 3843d9279900..6ccd0e892141 100644 --- a/identity/java/android/security/identity/IdentityCredentialStore.java +++ b/identity/java/android/security/identity/IdentityCredentialStore.java @@ -72,6 +72,17 @@ import java.lang.annotation.RetentionPolicy; * <p>Credentials provisioned to the direct access store should <strong>always</strong> use reader * authentication to protect data elements. The reason for this is user authentication or user * approval of data release is not possible when the device is off. + * + * <p>The Identity Credential API is designed to be able to evolve and change over time + * but still provide 100% backwards compatibility. This is complicated by the fact that + * there may be a version skew between the API used by the application and the version + * implemented in secure hardware. To solve this problem, the API provides for a way + * for the application to query which feature version the hardware implements (if any + * at all) using + * {@link android.content.pm#FEATURE_IDENTITY_CREDENTIAL_HARDWARE} and + * {@link android.content.pm#FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS}. + * Methods which only work on certain feature versions are clearly documented as + * such. */ public abstract class IdentityCredentialStore { IdentityCredentialStore() {} @@ -193,7 +204,9 @@ public abstract class IdentityCredentialStore { * @param credentialName the name of the credential to delete. * @return {@code null} if the credential was not found, the COSE_Sign1 data structure above * if the credential was found and deleted. + * @deprecated Use {@link IdentityCredential#delete(byte[])} instead. */ + @Deprecated public abstract @Nullable byte[] deleteCredentialByName(@NonNull String credentialName); /** @hide */ @@ -201,5 +214,4 @@ public abstract class IdentityCredentialStore { @Retention(RetentionPolicy.SOURCE) public @interface Ciphersuite { } - } diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index fcc518c374e3..21d23b1b2575 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -82,7 +82,7 @@ public class Authorization { * * @param locked - whether it is a lock (true) or unlock (false) event * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic - * password provided by the LockSettingService + * password provided by the LockSettingService * * @return 0 if successful or a {@code ResponseCode}. */ diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 5928540b19bf..014d6882be8d 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -67,6 +67,7 @@ public abstract class KeyProperties { PURPOSE_SIGN, PURPOSE_VERIFY, PURPOSE_WRAP_KEY, + PURPOSE_AGREE_KEY, }) public @interface PurposeEnum {} @@ -96,6 +97,11 @@ public abstract class KeyProperties { public static final int PURPOSE_WRAP_KEY = 1 << 5; /** + * Purpose of key: creating a shared ECDH secret through key agreement. + */ + public static final int PURPOSE_AGREE_KEY = 1 << 6; + + /** * @hide */ public static abstract class Purpose { @@ -113,6 +119,8 @@ public abstract class KeyProperties { return KeymasterDefs.KM_PURPOSE_VERIFY; case PURPOSE_WRAP_KEY: return KeymasterDefs.KM_PURPOSE_WRAP; + case PURPOSE_AGREE_KEY: + return KeymasterDefs.KM_PURPOSE_AGREE_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } @@ -130,6 +138,8 @@ public abstract class KeyProperties { return PURPOSE_VERIFY; case KeymasterDefs.KM_PURPOSE_WRAP: return PURPOSE_WRAP_KEY; + case KeymasterDefs.KM_PURPOSE_AGREE_KEY: + return PURPOSE_AGREE_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java index 6ddaa704afa8..b631999c2c54 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java @@ -38,9 +38,10 @@ public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey impleme public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor, @NonNull KeyMetadata metadata, + @NonNull byte[] x509EncodedForm, @NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECParameterSpec params, @NonNull ECPoint w) { - super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_EC, securityLevel); + super(descriptor, metadata, x509EncodedForm, KeyProperties.KEY_ALGORITHM_EC, securityLevel); mParams = params; mW = w; } @@ -48,7 +49,7 @@ public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey impleme public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor, @NonNull KeyMetadata metadata, @NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECPublicKey info) { - this(descriptor, metadata, securityLevel, info.getParams(), info.getW()); + this(descriptor, metadata, info.getEncoded(), securityLevel, info.getParams(), info.getW()); if (!"X.509".equalsIgnoreCase(info.getFormat())) { throw new IllegalArgumentException( "Unsupported key export format: " + info.getFormat()); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java new file mode 100644 index 000000000000..fc963a88c4d1 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java @@ -0,0 +1,240 @@ +/* + * 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.security.keystore2; + +import android.hardware.security.keymint.Algorithm; +import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.KeyPurpose; +import android.hardware.security.keymint.Tag; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keystore.KeyStoreCryptoOperation; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.KeyAgreementSpi; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link KeyAgreementSpi} which provides an ECDH implementation backed by Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi + implements KeyStoreCryptoOperation { + + private static final String TAG = "AndroidKeyStoreKeyAgreementSpi"; + + /** + * ECDH implementation. + * + * @hide + */ + public static class ECDH extends AndroidKeyStoreKeyAgreementSpi { + public ECDH() { + super(Algorithm.EC); + } + } + + private final int mKeymintAlgorithm; + + // Fields below are populated by engineInit and should be preserved after engineDoFinal. + private AndroidKeyStorePrivateKey mKey; + private PublicKey mOtherPartyKey; + + // Fields below are reset when engineDoFinal succeeds. + private KeyStoreOperation mOperation; + private long mOperationHandle; + + protected AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm) { + resetAll(); + + mKeymintAlgorithm = keymintAlgorithm; + } + + @Override + protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { + resetAll(); + + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStorePrivateKey)) { + throw new InvalidKeyException( + "Only Android KeyStore private keys supported. Key: " + key); + } + // Checking the correct KEY_PURPOSE and algorithm is done by the Keymint implementation in + // ensureKeystoreOperationInitialized() below. + mKey = (AndroidKeyStorePrivateKey) key; + + boolean success = false; + try { + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + engineInit(key, random); + } + + @Override + protected Key engineDoPhase(Key key, boolean lastPhase) + throws InvalidKeyException, IllegalStateException { + ensureKeystoreOperationInitialized(); + + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof PublicKey)) { + throw new InvalidKeyException("Only public keys supported. Key: " + key); + } else if (!lastPhase) { + throw new IllegalStateException( + "Only one other party supported. lastPhase must be set to true."); + } else if (mOtherPartyKey != null) { + throw new IllegalStateException( + "Only one other party supported. doPhase() must only be called exactly once."); + } + // The other party key will be passed as part of the doFinal() call, to prevent an + // additional IPC. + mOtherPartyKey = (PublicKey) key; + + return null; // No intermediate key + } + + @Override + protected byte[] engineGenerateSecret() throws IllegalStateException { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new IllegalStateException("Not initialized", e); + } + + if (mOtherPartyKey == null) { + throw new IllegalStateException("Other party key not provided. Call doPhase() first."); + } + byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded(); + + try { + return mOperation.finish(otherPartyKeyEncoded, null); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } finally { + resetWhilePreservingInitState(); + } + } + + @Override + protected SecretKey engineGenerateSecret(String algorithm) + throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException { + byte[] generatedSecret = engineGenerateSecret(); + + return new SecretKeySpec(generatedSecret, algorithm); + } + + @Override + protected int engineGenerateSecret(byte[] sharedSecret, int offset) + throws IllegalStateException, ShortBufferException { + byte[] generatedSecret = engineGenerateSecret(); + + if (generatedSecret.length > sharedSecret.length - offset) { + throw new ShortBufferException("Needed: " + generatedSecret.length); + } + System.arraycopy(generatedSecret, 0, sharedSecret, offset, generatedSecret.length); + return generatedSecret.length; + } + + @Override + public long getOperationHandle() { + return mOperationHandle; + } + + @Override + protected void finalize() throws Throwable { + try { + resetAll(); + } finally { + super.finalize(); + } + } + + private void resetWhilePreservingInitState() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperationHandle = 0; + mOperation = null; + mOtherPartyKey = null; + } + + private void resetAll() { + resetWhilePreservingInitState(); + mKey = null; + } + + private void ensureKeystoreOperationInitialized() + throws InvalidKeyException, IllegalStateException { + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + if (mOperation != null) { + return; + } + + // We don't need to explicitly pass in any other parameters here, as they're part of the + // private key that is available to Keymint. + List<KeyParameter> parameters = new ArrayList<>(); + parameters.add(KeyStore2ParameterUtils.makeEnum( + Tag.PURPOSE, KeyPurpose.AGREE_KEY + )); + + try { + mOperation = + mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters); + } catch (KeyStoreException keyStoreException) { + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = + KeyStoreCryptoOperationUtils.getInvalidKeyException(mKey, keyStoreException); + if (e != null) { + throw e; + } + } + + // Set the operation handle. This will be a random number, or the operation challenge if + // user authentication is required. If we got a challenge we check if the authorization can + // possibly succeed. + mOperationHandle = + KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(mOperation, mKey); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index 403da189262d..164bc8669525 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -94,6 +94,9 @@ public class AndroidKeyStoreProvider extends Provider { put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede"); } + // javax.crypto.KeyAgreement + put("KeyAgreement.ECDH", PACKAGE_NAME + ".AndroidKeyStoreKeyAgreementSpi$ECDH"); + // java.security.SecretKeyFactory putSecretKeyFactoryImpl("AES"); if (supports3DES) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java index 49dd77e3a3db..db3e567cb6b4 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java @@ -32,13 +32,15 @@ import java.security.PublicKey; public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey { private final byte[] mCertificate; private final byte[] mCertificateChain; + private final byte[] mEncoded; public AndroidKeyStorePublicKey(@NonNull KeyDescriptor descriptor, - @NonNull KeyMetadata metadata, @NonNull String algorithm, - @NonNull KeyStoreSecurityLevel securityLevel) { + @NonNull KeyMetadata metadata, @NonNull byte[] x509EncodedForm, + @NonNull String algorithm, @NonNull KeyStoreSecurityLevel securityLevel) { super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel); mCertificate = metadata.certificate; mCertificateChain = metadata.certificateChain; + mEncoded = x509EncodedForm; } abstract AndroidKeyStorePrivateKey getPrivateKey(); @@ -50,7 +52,7 @@ public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implem @Override public byte[] getEncoded() { - return ArrayUtils.cloneIfNotEmpty(mCertificate); + return ArrayUtils.cloneIfNotEmpty(mEncoded); } @Override diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java index b578ea9baa06..9fe6cf3c113f 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java @@ -36,9 +36,11 @@ public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implem public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor, @NonNull KeyMetadata metadata, + @NonNull byte[] x509EncodedForm, @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus, @NonNull BigInteger publicExponent) { - super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_RSA, securityLevel); + super(descriptor, metadata, x509EncodedForm, KeyProperties.KEY_ALGORITHM_RSA, + securityLevel); mModulus = modulus; mPublicExponent = publicExponent; } @@ -46,7 +48,8 @@ public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implem public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor, @NonNull KeyMetadata metadata, @NonNull KeyStoreSecurityLevel securityLevel, @NonNull RSAPublicKey info) { - this(descriptor, metadata, securityLevel, info.getModulus(), info.getPublicExponent()); + this(descriptor, metadata, info.getEncoded(), securityLevel, info.getModulus(), + info.getPublicExponent()); if (!"X.509".equalsIgnoreCase(info.getFormat())) { throw new IllegalArgumentException( "Unsupported key export format: " + info.getFormat()); diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp new file mode 100644 index 000000000000..8db8d7699a1e --- /dev/null +++ b/packages/Connectivity/framework/Android.bp @@ -0,0 +1,29 @@ +// +// 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. +// + +// TODO: use a java_library in the bootclasspath instead +filegroup { + name: "framework-connectivity-sources", + srcs: [ + "src/**/*.java", + "src/**/*.aidl", + ], + path: "src", + visibility: [ + "//frameworks/base", + "//packages/modules/Connectivity:__subpackages__", + ], +}
\ No newline at end of file diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl new file mode 100644 index 000000000000..64b556720cd2 --- /dev/null +++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl @@ -0,0 +1,49 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +package com.android.connectivity.aidl; + +import android.net.NattKeepalivePacketData; +import android.net.QosFilterParcelable; +import android.net.TcpKeepalivePacketData; + +import com.android.connectivity.aidl.INetworkAgentRegistry; + +/** + * Interface to notify NetworkAgent of connectivity events. + * @hide + */ +oneway interface INetworkAgent { + void onRegistered(in INetworkAgentRegistry registry); + void onDisconnected(); + void onBandwidthUpdateRequested(); + void onValidationStatusChanged(int validationStatus, + in @nullable String captivePortalUrl); + void onSaveAcceptUnvalidated(boolean acceptUnvalidated); + void onStartNattSocketKeepalive(int slot, int intervalDurationMs, + in NattKeepalivePacketData packetData); + void onStartTcpSocketKeepalive(int slot, int intervalDurationMs, + in TcpKeepalivePacketData packetData); + void onStopSocketKeepalive(int slot); + void onSignalStrengthThresholdsUpdated(in int[] thresholds); + void onPreventAutomaticReconnect(); + void onAddNattKeepalivePacketFilter(int slot, + in NattKeepalivePacketData packetData); + void onAddTcpKeepalivePacketFilter(int slot, + in TcpKeepalivePacketData packetData); + void onRemoveKeepalivePacketFilter(int slot); + void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel); + void onQosCallbackUnregistered(int qosCallbackId); +} diff --git a/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl new file mode 100644 index 000000000000..f0193db5c2e2 --- /dev/null +++ b/packages/Connectivity/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl @@ -0,0 +1,41 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ +package com.android.connectivity.aidl; + +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.QosSession; +import android.telephony.data.EpsBearerQosSessionAttributes; + +/** + * Interface for NetworkAgents to send network network properties. + * @hide + */ +oneway interface INetworkAgentRegistry { + void sendNetworkCapabilities(in NetworkCapabilities nc); + void sendLinkProperties(in LinkProperties lp); + // TODO: consider replacing this by "markConnected()" and removing + void sendNetworkInfo(in NetworkInfo info); + void sendScore(int score); + void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial); + void sendSocketKeepaliveEvent(int slot, int reason); + void sendUnderlyingNetworks(in @nullable List<Network> networks); + void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes); + void sendQosSessionLost(int qosCallbackId, in QosSession session); + void sendQosCallbackError(int qosCallbackId, int exceptionType); +} diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java index 06c52942e671..fcee98d0bd0b 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/BootCompletedReceiver.java @@ -19,11 +19,8 @@ package com.android.dynsystem; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.image.DynamicSystemClient; -import android.os.image.DynamicSystemManager; -import android.util.FeatureFlagUtils; /** @@ -43,24 +40,10 @@ public class BootCompletedReceiver extends BroadcastReceiver { return; } - DynamicSystemManager dynSystem = - (DynamicSystemManager) context.getSystemService(Context.DYNAMIC_SYSTEM_SERVICE); - - boolean isInUse = (dynSystem != null) && dynSystem.isInUse(); - - if (!isInUse && !featureFlagEnabled()) { - return; - } - Intent startServiceIntent = new Intent( context, DynamicSystemInstallationService.class); startServiceIntent.setAction(DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE); context.startServiceAsUser(startServiceIntent, UserHandle.SYSTEM); } - - private boolean featureFlagEnabled() { - return SystemProperties.getBoolean( - FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false); - } } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java index 82ea7449bf6d..64e42cc595ec 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java @@ -22,10 +22,8 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.image.DynamicSystemClient; -import android.util.FeatureFlagUtils; import android.util.Log; /** @@ -46,12 +44,6 @@ public class VerificationActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (!featureFlagEnabled()) { - Log.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; activity aborted."); - finish(); - return; - } - KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); if (km != null) { @@ -101,11 +93,6 @@ public class VerificationActivity extends Activity { startServiceAsUser(intent, UserHandle.SYSTEM); } - private boolean featureFlagEnabled() { - return SystemProperties.getBoolean( - FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false); - } - static boolean isVerified(String url) { if (url == null) return true; return sVerifiedUrl != null && sVerifiedUrl.equals(url); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 4b6862c6d186..dcbad3082bdb 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -292,6 +292,9 @@ <!-- Permission needed to test mainline permission module rollback --> <uses-permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS" /> + <!-- Permission needed to restart WiFi Subsystem --> + <uses-permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM" /> + <!-- Permission needed to read wifi network credentials for CtsNetTestCases --> <uses-permission android:name="android.permission.NETWORK_AIRPLANE_MODE" /> diff --git a/services/OWNERS b/services/OWNERS index 88d0b61a2ab6..03e0807eea62 100644 --- a/services/OWNERS +++ b/services/OWNERS @@ -1 +1,6 @@ per-file Android.bp = file:platform/build/soong:/OWNERS + +# art-team@ manages the system server profile +per-file art-profile* = calin@google.com, mathieuc@google.com, ngeoffray@google.com + +per-file java/com/android/server/* = toddke@google.com diff --git a/services/core/Android.bp b/services/core/Android.bp index 5ad5805910c2..4bebe399b8bc 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -84,6 +84,7 @@ java_library_static { ":storaged_aidl", ":vold_aidl", ":platform-compat-config", + ":platform-compat-overrides", ":display-device-config", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 9aadcf837eb8..d129b9c074a9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -133,6 +133,7 @@ import android.net.TetheringManager; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.Uri; +import android.net.VpnInfo; import android.net.VpnManager; import android.net.VpnService; import android.net.metrics.INetdEventListener; @@ -184,7 +185,6 @@ 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.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; @@ -1458,9 +1458,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } final String action = blocked ? "BLOCKED" : "UNBLOCKED"; - final NetworkRequest satisfiedRequest = nri.getSatisfiedRequest(); - final int requestId = satisfiedRequest != null - ? satisfiedRequest.requestId : nri.mRequests.get(0).requestId; + final int requestId = nri.getActiveRequest() != null + ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId; mNetworkInfoBlockingLogs.log(String.format( "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId())); } @@ -2730,7 +2729,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting NetworkRequestInfo[] requestsSortedById() { NetworkRequestInfo[] requests = new NetworkRequestInfo[0]; - requests = mNetworkRequests.values().toArray(requests); + requests = getNrisFromGlobalRequests().toArray(requests); // Sort the array based off the NRI containing the min requestId in its requests. Arrays.sort(requests, Comparator.comparingInt(nri -> Collections.min(nri.mRequests, @@ -3435,10 +3434,10 @@ public class ConnectivityService extends IConnectivityManager.Stub for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest request = nai.requestAt(i); final NetworkRequestInfo nri = mNetworkRequests.get(request); - final NetworkAgentInfo currentNetwork = nri.mSatisfier; + final NetworkAgentInfo currentNetwork = nri.getSatisfier(); if (currentNetwork != null && currentNetwork.network.getNetId() == nai.network.getNetId()) { - nri.mSatisfier = null; + nri.setSatisfier(null, null); sendUpdatedScoreToFactories(request, null); } } @@ -3516,42 +3515,63 @@ public class ConnectivityService extends IConnectivityManager.Stub return null; } - private void handleRegisterNetworkRequestWithIntent(Message msg) { + private void handleRegisterNetworkRequestWithIntent(@NonNull final Message msg) { final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj); - - NetworkRequestInfo existingRequest = findExistingNetworkRequestInfo(nri.mPendingIntent); + // handleRegisterNetworkRequestWithIntent() doesn't apply to multilayer requests. + ensureNotMultilayerRequest(nri, "handleRegisterNetworkRequestWithIntent"); + final NetworkRequestInfo existingRequest = + findExistingNetworkRequestInfo(nri.mPendingIntent); if (existingRequest != null) { // remove the existing request. - if (DBG) log("Replacing " + existingRequest.request + " with " - + nri.request + " because their intents matched."); - handleReleaseNetworkRequest(existingRequest.request, getCallingUid(), + if (DBG) { + log("Replacing " + existingRequest.mRequests.get(0) + " with " + + nri.mRequests.get(0) + " because their intents matched."); + } + handleReleaseNetworkRequest(existingRequest.mRequests.get(0), getCallingUid(), /* callOnUnavailable */ false); } handleRegisterNetworkRequest(nri); } - private void handleRegisterNetworkRequest(NetworkRequestInfo nri) { + private void handleRegisterNetworkRequest(@NonNull final NetworkRequestInfo nri) { ensureRunningOnConnectivityServiceThread(); - mNetworkRequests.put(nri.request, nri); mNetworkRequestInfoLogs.log("REGISTER " + nri); - if (nri.request.isListen()) { - for (NetworkAgentInfo network : mNetworkAgentInfos) { - if (nri.request.networkCapabilities.hasSignalStrength() && - network.satisfiesImmutableCapabilitiesOf(nri.request)) { - updateSignalStrengthThresholds(network, "REGISTER", nri.request); + 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 (nri.request.isRequest() && nri.mSatisfier == null) { - sendUpdatedScoreToFactories(nri.request, null); + // If an active request exists, return as its score has already been sent if needed. + if (null != nri.getActiveRequest()) { + 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()) { + continue; + } + sendUpdatedScoreToFactories(req, null); } } - private void handleReleaseNetworkRequestWithIntent(PendingIntent pendingIntent, - int callingUid) { - NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent); + private void handleReleaseNetworkRequestWithIntent(@NonNull final PendingIntent pendingIntent, + final int callingUid) { + final NetworkRequestInfo nri = findExistingNetworkRequestInfo(pendingIntent); if (nri != null) { - handleReleaseNetworkRequest(nri.request, callingUid, /* callOnUnavailable */ false); + // handleReleaseNetworkRequestWithIntent() paths don't apply to multilayer requests. + ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequestWithIntent"); + handleReleaseNetworkRequest( + nri.mRequests.get(0), + callingUid, + /* callOnUnavailable */ false); } } @@ -3605,6 +3625,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } for (final NetworkRequest req : nri.mRequests) { + // This multilayer listen request is satisfied therefore no further requests need to be + // evaluated deeming this network not a potential satisfier. + if (req.isListen() && nri.getActiveRequest() == req) { + return false; + } // As non-multilayer listen requests have already returned, the below would only happen // for a multilayer request therefore continue to the next request if available. if (req.isListen()) { @@ -3625,7 +3650,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // 2. Unvalidated WiFi will not be reaped when validated cellular // is currently satisfying the request. This is desirable when // WiFi ends up validating and out scoring cellular. - || nri.mSatisfier.getCurrentScore() + || nri.getSatisfier().getCurrentScore() < candidate.getCurrentScoreAsValidated(); return isNetworkNeeded; } @@ -3650,30 +3675,45 @@ public class ConnectivityService extends IConnectivityManager.Stub return nri; } - private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) { + private void ensureNotMultilayerRequest(@NonNull final NetworkRequestInfo nri, + final String callingMethod) { + if (nri.isMultilayerRequest()) { + throw new IllegalStateException( + callingMethod + " does not support multilayer requests."); + } + } + + private void handleTimedOutNetworkRequest(@NonNull final NetworkRequestInfo nri) { ensureRunningOnConnectivityServiceThread(); - if (mNetworkRequests.get(nri.request) == null) { + // handleTimedOutNetworkRequest() is part of the requestNetwork() flow which works off of a + // single NetworkRequest and thus does not apply to multilayer requests. + ensureNotMultilayerRequest(nri, "handleTimedOutNetworkRequest"); + if (mNetworkRequests.get(nri.mRequests.get(0)) == null) { return; } - if (nri.mSatisfier != null) { + if (nri.getSatisfier() != null) { return; } - if (VDBG || (DBG && nri.request.isRequest())) { - log("releasing " + nri.request + " (timeout)"); + if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) { + log("releasing " + nri.mRequests.get(0) + " (timeout)"); } handleRemoveNetworkRequest(nri); - callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0); + callCallbackForRequest( + nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0); } - private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid, - boolean callOnUnavailable) { + private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request, + final int callingUid, + final boolean callOnUnavailable) { final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, "release NetworkRequest"); if (nri == null) { return; } - if (VDBG || (DBG && nri.request.isRequest())) { - log("releasing " + nri.request + " (release request)"); + // handleReleaseNetworkRequest() paths don't apply to multilayer requests. + ensureNotMultilayerRequest(nri, "handleReleaseNetworkRequest"); + if (VDBG || (DBG && request.isRequest())) { + log("releasing " + request + " (release request)"); } handleRemoveNetworkRequest(nri); if (callOnUnavailable) { @@ -3681,42 +3721,88 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) { + private void handleRemoveNetworkRequest(@NonNull final NetworkRequestInfo nri) { ensureRunningOnConnectivityServiceThread(); nri.unlinkDeathRecipient(); - mNetworkRequests.remove(nri.request); - + for (final NetworkRequest req : nri.mRequests) { + mNetworkRequests.remove(req); + if (req.isListen()) { + removeListenRequestFromNetworks(req); + } + } mNetworkRequestCounter.decrementCount(nri.mUid); - mNetworkRequestInfoLogs.log("RELEASE " + nri); - if (nri.request.isRequest()) { - boolean wasKept = false; - final NetworkAgentInfo nai = nri.mSatisfier; - if (nai != null) { - boolean wasBackgroundNetwork = nai.isBackgroundNetwork(); - nai.removeRequest(nri.request.requestId); - if (VDBG || DDBG) { - log(" Removing from current network " + nai.toShortString() - + ", leaving " + nai.numNetworkRequests() + " requests."); - } - // If there are still lingered requests on this network, don't tear it down, - // but resume lingering instead. - final long now = SystemClock.elapsedRealtime(); - if (updateLingerState(nai, now)) { - notifyNetworkLosing(nai, now); - } - if (unneeded(nai, UnneededFor.TEARDOWN)) { - if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting"); - teardownUnneededNetwork(nai); - } else { - wasKept = true; - } - nri.mSatisfier = null; - if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) { - // Went from foreground to background. - updateCapabilitiesForNetwork(nai); - } + + if (null != nri.getActiveRequest()) { + if (nri.getActiveRequest().isRequest()) { + removeSatisfiedNetworkRequestFromNetwork(nri); + } else { + nri.setSatisfier(null, null); + } + } + + cancelNpiRequests(nri); + } + + private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) { + for (final NetworkRequest req : nri.mRequests) { + cancelNpiRequest(req); + } + } + + private void cancelNpiRequest(@NonNull final NetworkRequest req) { + if (req.isRequest()) { + for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) { + npi.cancelRequest(req); + } + } + } + + private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) { + // listens don't have a singular affected Network. Check all networks to see + // if this listen request applies and remove it. + for (final NetworkAgentInfo nai : mNetworkAgentInfos) { + nai.removeRequest(req.requestId); + if (req.networkCapabilities.hasSignalStrength() + && nai.satisfiesImmutableCapabilitiesOf(req)) { + updateSignalStrengthThresholds(nai, "RELEASE", req); + } + } + } + + /** + * Remove a NetworkRequestInfo's satisfied request from its 'satisfier' (NetworkAgentInfo) and + * manage the necessary upkeep (linger, teardown networks, etc.) when doing so. + * @param nri the NetworkRequestInfo to disassociate from its current NetworkAgentInfo + */ + private void removeSatisfiedNetworkRequestFromNetwork(@NonNull final NetworkRequestInfo nri) { + boolean wasKept = false; + final NetworkAgentInfo nai = nri.getSatisfier(); + if (nai != null) { + final int requestLegacyType = nri.getActiveRequest().legacyType; + final boolean wasBackgroundNetwork = nai.isBackgroundNetwork(); + nai.removeRequest(nri.getActiveRequest().requestId); + if (VDBG || DDBG) { + log(" Removing from current network " + nai.toShortString() + + ", leaving " + nai.numNetworkRequests() + " requests."); + } + // If there are still lingered requests on this network, don't tear it down, + // but resume lingering instead. + final long now = SystemClock.elapsedRealtime(); + if (updateLingerState(nai, now)) { + notifyNetworkLosing(nai, now); + } + if (unneeded(nai, UnneededFor.TEARDOWN)) { + if (DBG) log("no live requests for " + nai.toShortString() + "; disconnecting"); + teardownUnneededNetwork(nai); + } else { + wasKept = true; + } + nri.setSatisfier(null, null); + if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) { + // Went from foreground to background. + updateCapabilitiesForNetwork(nai); } // Maintain the illusion. When this request arrived, we might have pretended @@ -3724,15 +3810,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // connected. Now that this request has gone away, we might have to pretend // that the network disconnected. LegacyTypeTracker will generate that // phantom disconnect for this type. - if (nri.request.legacyType != TYPE_NONE && nai != null) { + if (requestLegacyType != TYPE_NONE) { boolean doRemove = true; if (wasKept) { // check if any of the remaining requests for this network are for the // same legacy type - if so, don't remove the nai for (int i = 0; i < nai.numNetworkRequests(); i++) { NetworkRequest otherRequest = nai.requestAt(i); - if (otherRequest.legacyType == nri.request.legacyType && - otherRequest.isRequest()) { + if (otherRequest.legacyType == requestLegacyType + && otherRequest.isRequest()) { if (DBG) log(" still have other legacy request - leaving"); doRemove = false; } @@ -3740,21 +3826,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (doRemove) { - mLegacyTypeTracker.remove(nri.request.legacyType, nai, false); - } - } - - for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) { - npi.cancelRequest(nri.request); - } - } else { - // listens don't have a singular affectedNetwork. Check all networks to see - // if this listen request applies and remove it. - for (NetworkAgentInfo nai : mNetworkAgentInfos) { - nai.removeRequest(nri.request.requestId); - if (nri.request.networkCapabilities.hasSignalStrength() && - nai.satisfiesImmutableCapabilitiesOf(nri.request)) { - updateSignalStrengthThresholds(nai, "RELEASE", nri.request); + mLegacyTypeTracker.remove(requestLegacyType, nai, false); } } } @@ -4832,16 +4904,14 @@ public class ConnectivityService extends IConnectivityManager.Stub if (interfaces.isEmpty()) return null; - VpnInfo info = new VpnInfo(); - info.ownerUid = nai.networkCapabilities.getOwnerUid(); - info.vpnIface = nai.linkProperties.getInterfaceName(); // Must be non-null or NetworkStatsService will crash. // Cannot happen in production code because Vpn only registers the NetworkAgent after the // tun or ipsec interface is created. - if (info.vpnIface == null) return null; - info.underlyingIfaces = interfaces.toArray(new String[0]); + if (nai.linkProperties.getInterfaceName() == null) return null; - return info; + return new VpnInfo(nai.networkCapabilities.getOwnerUid(), + nai.linkProperties.getInterfaceName(), + interfaces.toArray(new String[0])); } /** @@ -5417,18 +5487,38 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Tracks info about the requester. - * Also used to notice when the calling process dies so we can self-expire + * Also used to notice when the calling process dies so as to self-expire */ @VisibleForTesting protected class NetworkRequestInfo implements IBinder.DeathRecipient { final List<NetworkRequest> mRequests; - final NetworkRequest request; + + // mSatisfier and mActiveRequest rely on one another therefore set them together. + void setSatisfier( + @Nullable final NetworkAgentInfo satisfier, + @Nullable final NetworkRequest activeRequest) { + mSatisfier = satisfier; + mActiveRequest = activeRequest; + } // The network currently satisfying this request, or null if none. Must only be touched // on the handler thread. This only makes sense for network requests and not for listens, // as defined by NetworkRequest#isRequest(). For listens, this is always null. @Nullable - NetworkAgentInfo mSatisfier; + private NetworkAgentInfo mSatisfier; + NetworkAgentInfo getSatisfier() { + return mSatisfier; + } + + // The request in mRequests assigned to a network agent. This is null if none of the + // requests in mRequests can be satisfied. This member has the constraint of only being + // accessible on the handler thread. + @Nullable + private NetworkRequest mActiveRequest; + NetworkRequest getActiveRequest() { + return mActiveRequest; + } + final PendingIntent mPendingIntent; boolean mPendingIntentSent; private final IBinder mBinder; @@ -5437,7 +5527,6 @@ public class ConnectivityService extends IConnectivityManager.Stub final Messenger messenger; NetworkRequestInfo(NetworkRequest r, PendingIntent pi) { - request = r; mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); mPendingIntent = pi; @@ -5451,7 +5540,6 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) { super(); messenger = m; - request = r; mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; @@ -5481,20 +5569,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return Collections.unmodifiableList(tempRequests); } - private NetworkRequest getSatisfiedRequest() { - if (mSatisfier == null) { - return null; - } - - for (NetworkRequest req : mRequests) { - if (mSatisfier.isSatisfyingRequest(req.requestId)) { - return req; - } - } - - return null; - } - void unlinkDeathRecipient() { if (mBinder != null) { mBinder.unlinkToDeath(this, 0); @@ -5541,6 +5615,10 @@ public class ConnectivityService extends IConnectivityManager.Stub private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) { final SortedSet<Integer> thresholds = new TreeSet<>(); synchronized (nai) { + // mNetworkRequests may contain the same value multiple times in case of + // multilayer requests. It won't matter in this case because the thresholds + // will then be the same and be deduplicated as they enter the `thresholds` set. + // TODO : have mNetworkRequests be a Set<NetworkRequestInfo> or the like. for (final NetworkRequestInfo nri : mNetworkRequests.values()) { for (final NetworkRequest req : nri.mRequests) { if (req.networkCapabilities.hasSignalStrength() @@ -5580,7 +5658,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ns == null) { return; } - MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns); + if (ns instanceof MatchAllNetworkSpecifier) { + throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted"); + } } private void ensureValid(NetworkCapabilities nc) { @@ -5916,13 +5996,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public void declareNetworkRequestUnfulfillable(NetworkRequest request) { + public void declareNetworkRequestUnfulfillable(@NonNull final NetworkRequest request) { if (request.hasTransport(TRANSPORT_TEST)) { enforceNetworkFactoryOrTestNetworksPermission(); } else { enforceNetworkFactoryPermission(); } - mHandler.post(() -> handleReleaseNetworkRequest(request, mDeps.getCallingUid(), true)); + final NetworkRequestInfo nri = mNetworkRequests.get(request); + if (nri != null) { + // declareNetworkRequestUnfulfillable() paths don't apply to multilayer requests. + ensureNotMultilayerRequest(nri, "declareNetworkRequestUnfulfillable"); + mHandler.post(() -> handleReleaseNetworkRequest( + nri.mRequests.get(0), mDeps.getCallingUid(), true)); + } } // NOTE: Accessed on multiple threads, must be synchronized on itself. @@ -6110,7 +6196,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkAgentPortalData = lp.getCaptivePortalData(); } - private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp, + private void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp, @NonNull LinkProperties oldLp) { int netId = networkAgent.network.getNetId(); @@ -6119,8 +6205,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the LinkProperties for the network are accurate. networkAgent.clatd.fixupLinkProperties(oldLp, newLp); - updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities, - networkAgent.networkInfo.getType()); + updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities); // update filtering rules, need to happen after the interface update so netd knows about the // new interface (the interface name -> index map becomes initialized) @@ -6259,7 +6344,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateInterfaces(final @Nullable LinkProperties newLp, final @Nullable LinkProperties oldLp, final int netId, - final @Nullable NetworkCapabilities caps, final int legacyType) { + final @NonNull NetworkCapabilities caps) { final CompareResult<String> interfaceDiff = new CompareResult<>( oldLp != null ? oldLp.getAllInterfaceNames() : null, newLp != null ? newLp.getAllInterfaceNames() : null); @@ -6270,7 +6355,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Adding iface " + iface + " to network " + netId); mNetd.networkAddInterface(netId, iface); wakeupModifyInterface(iface, caps, true); - bs.noteNetworkInterfaceType(iface, legacyType); + bs.noteNetworkInterfaceForTransports(iface, caps.getTransportTypes()); } catch (Exception e) { loge("Exception adding interface: " + e); } @@ -6542,6 +6627,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, * and foreground status). */ + @NonNull private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) { // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. // Don't complain for VPNs since they're not driven by requests and there is no risk of @@ -6598,6 +6684,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + private void updateNetworkInfoForRoamingAndSuspended(NetworkAgentInfo nai, + NetworkCapabilities prevNc, NetworkCapabilities newNc) { + final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); + final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); + final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + if (prevSuspended != suspended) { + // TODO (b/73132094) : remove this call once the few users of onSuspended and + // onResumed have been removed. + notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED + : ConnectivityManager.CALLBACK_RESUMED); + } + if (prevSuspended != suspended || prevRoaming != roaming) { + // updateNetworkInfo will mix in the suspended info from the capabilities and + // take appropriate action for the network having possibly changed state. + updateNetworkInfo(nai, nai.networkInfo); + } + } + /** * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically: * @@ -6629,25 +6734,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // on this network. We might have been called by rematchNetworkAndRequests when a // network changed foreground state. processListenRequests(nai); - final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); - final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); - final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - if (prevSuspended != suspended || prevRoaming != roaming) { - // TODO (b/73132094) : remove this call once the few users of onSuspended and - // onResumed have been removed. - notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED - : ConnectivityManager.CALLBACK_RESUMED); - // updateNetworkInfo will mix in the suspended info from the capabilities and - // take appropriate action for the network having possibly changed state. - updateNetworkInfo(nai, nai.networkInfo); - } } else { // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. rematchAllNetworksAndRequests(); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } + updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc); final boolean oldMetered = prevNc.isMetered(); final boolean newMetered = newNc.isMetered(); @@ -6847,6 +6940,39 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void sendUpdatedScoreToFactories( + @NonNull final NetworkReassignment.RequestReassignment event) { + // If a request of type REQUEST is now being satisfied by a new network. + if (null != event.mNewNetworkRequest && event.mNewNetworkRequest.isRequest()) { + sendUpdatedScoreToFactories(event.mNewNetworkRequest, event.mNewNetwork); + } + + // If a previously satisfied request of type REQUEST is no longer being satisfied. + if (null != event.mOldNetworkRequest && event.mOldNetworkRequest.isRequest() + && event.mOldNetworkRequest != event.mNewNetworkRequest) { + sendUpdatedScoreToFactories(event.mOldNetworkRequest, null); + } + + cancelMultilayerLowerPriorityNpiRequests(event.mNetworkRequestInfo); + } + + /** + * Cancel with all NPIs the given NRI's multilayer requests that are a lower priority than + * its currently satisfied active request. + * @param nri the NRI to cancel lower priority requests for. + */ + private void cancelMultilayerLowerPriorityNpiRequests( + @NonNull final NetworkRequestInfo nri) { + if (!nri.isMultilayerRequest() || null == nri.mActiveRequest) { + return; + } + + final int indexOfNewRequest = nri.mRequests.indexOf(nri.mActiveRequest); + for (int i = indexOfNewRequest + 1; i < nri.mRequests.size(); i++) { + cancelNpiRequest(nri.mRequests.get(i)); + } + } + private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest, @Nullable NetworkAgentInfo nai) { final int score; @@ -6867,21 +6993,35 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** Sends all current NetworkRequests to the specified factory. */ - private void sendAllRequestsToProvider(NetworkProviderInfo npi) { + private void sendAllRequestsToProvider(@NonNull final NetworkProviderInfo npi) { ensureRunningOnConnectivityServiceThread(); - for (NetworkRequestInfo nri : mNetworkRequests.values()) { - if (nri.request.isListen()) continue; - NetworkAgentInfo nai = nri.mSatisfier; - final int score; - final int serial; - if (nai != null) { - score = nai.getCurrentScore(); - serial = nai.factorySerialNumber; - } else { - score = 0; - serial = NetworkProvider.ID_NONE; + for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) { + for (final NetworkRequest req : nri.mRequests) { + if (req.isListen() && nri.getActiveRequest() == req) { + break; + } + if (req.isListen()) { + continue; + } + // Only set the nai for the request it is satisfying. + final NetworkAgentInfo nai = + nri.getActiveRequest() == req ? nri.getSatisfier() : null; + final int score; + final int serial; + if (null != nai) { + score = nai.getCurrentScore(); + serial = nai.factorySerialNumber; + } else { + score = 0; + serial = NetworkProvider.ID_NONE; + } + npi.requestNetwork(req, score, serial); + // For multilayer requests, don't send lower priority requests if a higher priority + // request is already satisfied. + if (null != nai) { + break; + } } - npi.requestNetwork(nri.request, score, serial); } } @@ -6890,7 +7030,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) { Intent intent = new Intent(); intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.request); + // If apps could file multi-layer requests with PendingIntents, they'd need to know + // which of the layer is satisfied alongside with some ID for the request. Hence, if + // such an API is ever implemented, there is no doubt the right request to send in + // EXTRA_NETWORK_REQUEST is mActiveRequest, and whatever ID would be added would need to + // be sent as a separate extra. + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_REQUEST, nri.getActiveRequest()); nri.mPendingIntentSent = true; sendIntent(nri.mPendingIntent, intent); } @@ -6920,8 +7065,9 @@ public class ConnectivityService extends IConnectivityManager.Stub releasePendingNetworkRequestWithDelay(pendingIntent); } - private void callCallbackForRequest(NetworkRequestInfo nri, - NetworkAgentInfo networkAgent, int notificationType, int arg1) { + private void callCallbackForRequest(@NonNull final NetworkRequestInfo nri, + @NonNull final NetworkAgentInfo networkAgent, final int notificationType, + final int arg1) { if (nri.messenger == null) { // Default request has no msgr. Also prevents callbacks from being invoked for // NetworkRequestInfos registered with ConnectivityDiagnostics requests. Those callbacks @@ -6929,8 +7075,14 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } Bundle bundle = new Bundle(); + // In the case of multi-layer NRIs, the first request is not necessarily the one that + // is satisfied. This is vexing, but the ConnectivityManager code that receives this + // callback is only using the request as a token to identify the callback, so it doesn't + // matter too much at this point as long as the callback can be found. + // TODO b/177608132: make sure callbacks are indexed by NRIs and not NetworkRequest objects. // TODO: check if defensive copies of data is needed. - putParcelable(bundle, new NetworkRequest(nri.request)); + final NetworkRequest nrForCallback = new NetworkRequest(nri.mRequests.get(0)); + putParcelable(bundle, nrForCallback); Message msg = Message.obtain(); if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) { putParcelable(bundle, networkAgent.network); @@ -6943,7 +7095,7 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable( bundle, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, nri.mUid, nri.request.getRequestorPackageName())); + nc, nri.mUid, nrForCallback.getRequestorPackageName())); putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions( networkAgent.linkProperties, nri.mPid, nri.mUid)); // For this notification, arg1 contains the blocked status. @@ -6962,7 +7114,7 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable( bundle, createWithLocationInfoSanitizedIfNecessaryWhenParceled( - netCap, nri.mUid, nri.request.getRequestorPackageName())); + netCap, nri.mUid, nrForCallback.getRequestorPackageName())); break; } case ConnectivityManager.CALLBACK_IP_CHANGED: { @@ -6981,12 +7133,12 @@ public class ConnectivityService extends IConnectivityManager.Stub try { if (VDBG) { String notification = ConnectivityManager.getCallbackName(notificationType); - log("sending notification " + notification + " for " + nri.request); + log("sending notification " + notification + " for " + nrForCallback); } nri.messenger.send(msg); } catch (RemoteException e) { // may occur naturally in the race of binder death. - loge("RemoteException caught trying to send a callback msg for " + nri.request); + loge("RemoteException caught trying to send a callback msg for " + nrForCallback); } } @@ -7062,19 +7214,25 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void processNewlyLostListenRequests(@NonNull final NetworkAgentInfo nai) { - for (NetworkRequestInfo nri : mNetworkRequests.values()) { - NetworkRequest nr = nri.request; + for (final NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.isMultilayerRequest()) { + continue; + } + final NetworkRequest nr = nri.mRequests.get(0); if (!nr.isListen()) continue; if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) { - nai.removeRequest(nri.request.requestId); + nai.removeRequest(nr.requestId); callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0); } } } private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) { - for (NetworkRequestInfo nri : mNetworkRequests.values()) { - NetworkRequest nr = nri.request; + for (final NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.isMultilayerRequest()) { + continue; + } + final NetworkRequest nr = nri.mRequests.get(0); if (!nr.isListen()) continue; if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) { nai.addRequest(nr); @@ -7086,19 +7244,25 @@ public class ConnectivityService extends IConnectivityManager.Stub // An accumulator class to gather the list of changes that result from a rematch. private static class NetworkReassignment { static class RequestReassignment { - @NonNull public final NetworkRequestInfo mRequest; + @NonNull public final NetworkRequestInfo mNetworkRequestInfo; + @NonNull public final NetworkRequest mOldNetworkRequest; + @NonNull public final NetworkRequest mNewNetworkRequest; @Nullable public final NetworkAgentInfo mOldNetwork; @Nullable public final NetworkAgentInfo mNewNetwork; - RequestReassignment(@NonNull final NetworkRequestInfo request, + RequestReassignment(@NonNull final NetworkRequestInfo networkRequestInfo, + @NonNull final NetworkRequest oldNetworkRequest, + @NonNull final NetworkRequest newNetworkRequest, @Nullable final NetworkAgentInfo oldNetwork, @Nullable final NetworkAgentInfo newNetwork) { - mRequest = request; + mNetworkRequestInfo = networkRequestInfo; + mOldNetworkRequest = oldNetworkRequest; + mNewNetworkRequest = newNetworkRequest; mOldNetwork = oldNetwork; mNewNetwork = newNetwork; } public String toString() { - return mRequest.mRequests.get(0).requestId + " : " + return mNetworkRequestInfo.mRequests.get(0).requestId + " : " + (null != mOldNetwork ? mOldNetwork.network.getNetId() : "null") + " → " + (null != mNewNetwork ? mNewNetwork.network.getNetId() : "null"); } @@ -7116,7 +7280,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // sure this stays true, but without imposing this expensive check on all // reassignments on all user devices. for (final RequestReassignment existing : mReassignments) { - if (existing.mRequest.equals(reassignment.mRequest)) { + if (existing.mNetworkRequestInfo.equals(reassignment.mNetworkRequestInfo)) { throw new IllegalStateException("Trying to reassign [" + reassignment + "] but already have [" + existing + "]"); @@ -7131,7 +7295,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Nullable private RequestReassignment getReassignment(@NonNull final NetworkRequestInfo nri) { for (final RequestReassignment event : getRequestReassignments()) { - if (nri == event.mRequest) return event; + if (nri == event.mNetworkRequestInfo) return event; } return null; } @@ -7158,6 +7322,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri, + @NonNull final NetworkRequest previousRequest, + @NonNull final NetworkRequest newRequest, @Nullable final NetworkAgentInfo previousSatisfier, @Nullable final NetworkAgentInfo newSatisfier, final long now) { @@ -7167,58 +7333,98 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG || DDBG) { log(" accepting network in place of " + previousSatisfier.toShortString()); } - previousSatisfier.removeRequest(nri.request.requestId); - previousSatisfier.lingerRequest(nri.request.requestId, now, mLingerDelayMs); + previousSatisfier.removeRequest(previousRequest.requestId); + previousSatisfier.lingerRequest(previousRequest.requestId, now, mLingerDelayMs); } else { if (VDBG || DDBG) log(" accepting network in place of null"); } - newSatisfier.unlingerRequest(nri.request.requestId); - if (!newSatisfier.addRequest(nri.request)) { + newSatisfier.unlingerRequest(newRequest.requestId); + if (!newSatisfier.addRequest(newRequest)) { Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " - + nri.request); + + newRequest); } } else { if (DBG) { log("Network " + previousSatisfier.toShortString() + " stopped satisfying" - + " request " + nri.request.requestId); + + " request " + previousRequest.requestId); } - previousSatisfier.removeRequest(nri.request.requestId); + previousSatisfier.removeRequest(previousRequest.requestId); } - nri.mSatisfier = newSatisfier; + nri.setSatisfier(newSatisfier, newRequest); } + /** + * This function is triggered when something can affect what network should satisfy what + * request, and it computes the network reassignment from the passed collection of requests to + * network match to the one that the system should now have. That data is encoded in an + * object that is a list of changes, each of them having an NRI, and old satisfier, and a new + * satisfier. + * + * After the reassignment is computed, it is applied to the state objects. + * + * @param networkRequests the nri objects to evaluate for possible network reassignment + * @return NetworkReassignment listing of proposed network assignment changes + */ @NonNull - private NetworkReassignment computeNetworkReassignment() { - ensureRunningOnConnectivityServiceThread(); + private NetworkReassignment computeNetworkReassignment( + @NonNull final Collection<NetworkRequestInfo> networkRequests) { final NetworkReassignment changes = new NetworkReassignment(); // Gather the list of all relevant agents and sort them by score. final ArrayList<NetworkAgentInfo> nais = new ArrayList<>(); for (final NetworkAgentInfo nai : mNetworkAgentInfos) { - if (!nai.everConnected) continue; + if (!nai.everConnected) { + continue; + } nais.add(nai); } - for (final NetworkRequestInfo nri : mNetworkRequests.values()) { - if (nri.request.isListen()) continue; - final NetworkAgentInfo bestNetwork = mNetworkRanker.getBestNetwork(nri.request, nais); + for (final NetworkRequestInfo nri : networkRequests) { + // Non-multilayer listen requests can be ignored. + if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) { + continue; + } + NetworkAgentInfo bestNetwork = null; + NetworkRequest bestRequest = null; + for (final NetworkRequest req : nri.mRequests) { + bestNetwork = mNetworkRanker.getBestNetwork(req, nais); + // Stop evaluating as the highest possible priority request is satisfied. + if (null != bestNetwork) { + bestRequest = req; + break; + } + } if (bestNetwork != nri.mSatisfier) { // bestNetwork may be null if no network can satisfy this request. changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( - nri, nri.mSatisfier, bestNetwork)); + nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork)); } } return changes; } + private Set<NetworkRequestInfo> getNrisFromGlobalRequests() { + return new HashSet<>(mNetworkRequests.values()); + } + /** - * Attempt to rematch all Networks with NetworkRequests. This may result in Networks + * Attempt to rematch all Networks with all NetworkRequests. This may result in Networks * being disconnected. */ private void rematchAllNetworksAndRequests() { + rematchNetworksAndRequests(getNrisFromGlobalRequests()); + } + + /** + * Attempt to rematch all Networks with given NetworkRequests. This may result in Networks + * being disconnected. + */ + private void rematchNetworksAndRequests( + @NonNull final Set<NetworkRequestInfo> networkRequests) { + ensureRunningOnConnectivityServiceThread(); // TODO: This may be slow, and should be optimized. final long now = SystemClock.elapsedRealtime(); - final NetworkReassignment changes = computeNetworkReassignment(); + final NetworkReassignment changes = computeNetworkReassignment(networkRequests); if (VDBG || DDBG) { log(changes.debugString()); } else if (DBG) { @@ -7243,8 +7449,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // the linger status. for (final NetworkReassignment.RequestReassignment event : changes.getRequestReassignments()) { - updateSatisfiersForRematchRequest(event.mRequest, event.mOldNetwork, - event.mNewNetwork, now); + updateSatisfiersForRematchRequest(event.mNetworkRequestInfo, + event.mOldNetworkRequest, event.mNewNetworkRequest, + event.mOldNetwork, event.mNewNetwork, + now); } final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork(); @@ -7296,12 +7504,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // trying to connect if they know they cannot match it. // TODO - this could get expensive if there are a lot of outstanding requests for this // network. Think of a way to reduce this. Push netid->request mapping to each factory? - sendUpdatedScoreToFactories(event.mRequest.request, event.mNewNetwork); + sendUpdatedScoreToFactories(event); if (null != event.mNewNetwork) { - notifyNetworkAvailable(event.mNewNetwork, event.mRequest); + notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo); } else { - callCallbackForRequest(event.mRequest, event.mOldNetwork, + callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork, ConnectivityManager.CALLBACK_LOST, 0); } } diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index f2b63a642c29..88ce2208adcb 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -22,7 +22,6 @@ import android.gsi.AvbPublicKey; import android.gsi.GsiProgress; import android.gsi.IGsiService; import android.gsi.IGsiServiceCallback; -import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; @@ -30,7 +29,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.image.IDynamicSystemService; import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; +import android.os.storage.VolumeInfo; import android.util.Slog; import java.io.File; @@ -88,16 +87,17 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { String path = SystemProperties.get("os.aot.path"); if (path.isEmpty()) { final int userId = UserHandle.myUserId(); - final StorageVolume[] volumes = - StorageManager.getVolumeList(userId, StorageManager.FLAG_FOR_WRITE); - for (StorageVolume volume : volumes) { - if (volume.isEmulated()) continue; - if (!volume.isRemovable()) continue; - if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue; - File sdCard = volume.getPathFile(); - if (sdCard.isDirectory()) { - path = new File(sdCard, dsuSlot).getPath(); - break; + final StorageManager sm = mContext.getSystemService(StorageManager.class); + for (VolumeInfo volume : sm.getVolumes()) { + if (volume.getType() != volume.TYPE_PUBLIC) { + continue; + } + if (!volume.isMountedWritable()) { + continue; + } + File sd_internal = volume.getInternalPathForUser(userId); + if (sd_internal != null) { + path = new File(sd_internal, dsuSlot).getPath(); } } if (path.isEmpty()) { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 5c34584d0adf..4e2519b47a47 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3297,6 +3297,12 @@ class StorageManagerService extends IStorageManager.Stub enforcePermission(android.Manifest.permission.STORAGE_INTERNAL); if (isFsEncrypted) { + // When a user has secure lock screen, require secret to actually unlock. + // This check is mostly in place for emulation mode. + if (StorageManager.isFileEncryptedEmulatedOnly() && + mLockPatternUtils.isSecure(userId) && ArrayUtils.isEmpty(secret)) { + throw new IllegalStateException("Secret required to unlock secure user " + userId); + } try { mVold.unlockUserKey(userId, serialNumber, encodeBytes(token), encodeBytes(secret)); diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index e8687e57a07b..a08d066513c7 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -242,6 +242,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); nc.setNetworkSpecifier(new StringNetworkSpecifier(iface)); nc.setAdministratorUids(administratorUids); if (!isMetered) { diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 630548df4b0b..ab24015a1174 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -704,7 +704,7 @@ public class Watchdog extends Thread { WatchdogDiagnostics.diagnoseCheckers(blockedCheckers); Slog.w(TAG, "*** GOODBYE!"); if (!Build.IS_USER && isCrashLoopFound() - && !WatchdogProperties.is_fatal_ignore().orElse(false)) { + && !WatchdogProperties.should_ignore_fatal_count().orElse(false)) { breakCrashLoop(); } Process.killProcess(Process.myPid()); @@ -783,7 +783,7 @@ public class Watchdog extends Thread { private boolean isCrashLoopFound() { int fatalCount = WatchdogProperties.fatal_count().orElse(0); long fatalWindowMs = TimeUnit.SECONDS.toMillis( - WatchdogProperties.fatal_window_second().orElse(0)); + WatchdogProperties.fatal_window_seconds().orElse(0)); if (fatalCount == 0 || fatalWindowMs == 0) { if (fatalCount != fatalWindowMs) { Slog.w(TAG, String.format("sysprops '%s' and '%s' should be set or unset together", diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 3c538806be01..9986085224b1 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1066,9 +1066,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub } @Override - public void noteNetworkInterfaceType(String iface, int networkType) { + public void noteNetworkInterfaceForTransports(final String iface, int[] transportTypes) { enforceCallingPermission(); - mStats.noteNetworkInterfaceType(iface, networkType); + mStats.noteNetworkInterfaceForTransports(iface, transportTypes); } @Override diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java new file mode 100644 index 000000000000..508bb01e50a8 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -0,0 +1,349 @@ +/* + * 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.apphibernation; + +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.ACTION_USER_ADDED; +import static android.content.Intent.ACTION_USER_REMOVED; +import static android.content.Intent.EXTRA_REPLACING; +import static android.content.pm.PackageManager.MATCH_ALL; +import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.apphibernation.IAppHibernationService; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.UserInfo; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.ShellCallback; +import android.os.Trace; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.DeviceConfig; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemService; + +import java.io.FileDescriptor; +import java.util.List; +import java.util.Map; + +/** + * System service that manages app hibernation state, a state apps can enter that means they are + * not being actively used and can be optimized for storage. The actual policy for determining + * if an app should hibernate is managed by PermissionController code. + */ +public final class AppHibernationService extends SystemService { + private static final String TAG = "AppHibernationService"; + + /** + * Lock for accessing any in-memory hibernation state + */ + private final Object mLock = new Object(); + private final Context mContext; + private final IPackageManager mIPackageManager; + private final IActivityManager mIActivityManager; + private final UserManager mUserManager; + @GuardedBy("mLock") + private final SparseArray<Map<String, UserPackageState>> mUserStates = new SparseArray<>(); + + /** + * Initializes the system service. + * <p> + * Subclasses must define a single argument constructor that accepts the context + * and passes it to super. + * </p> + * + * @param context The system server context. + */ + public AppHibernationService(@NonNull Context context) { + this(context, IPackageManager.Stub.asInterface(ServiceManager.getService("package")), + ActivityManager.getService(), + context.getSystemService(UserManager.class)); + } + + @VisibleForTesting + AppHibernationService(@NonNull Context context, IPackageManager packageManager, + IActivityManager activityManager, UserManager userManager) { + super(context); + mContext = context; + mIPackageManager = packageManager; + mIActivityManager = activityManager; + mUserManager = userManager; + + final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_USER_ADDED); + intentFilter.addAction(ACTION_USER_REMOVED); + userAllContext.registerReceiver(mBroadcastReceiver, intentFilter); + + intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_PACKAGE_ADDED); + intentFilter.addAction(ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + userAllContext.registerReceiver(mBroadcastReceiver, intentFilter); + } + + @Override + public void onStart() { + publishBinderService(Context.APP_HIBERNATION_SERVICE, mServiceStub); + } + + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_BOOT_COMPLETED) { + synchronized (mLock) { + final List<UserInfo> users = mUserManager.getUsers(); + // TODO: Pull from persistent disk storage. For now, just make from scratch. + for (UserInfo user : users) { + addUserPackageStatesL(user.id); + } + } + } + } + + /** + * Whether a package is hibernating for a given user. + * + * @param packageName the package to check + * @param userId the user to check + * @return true if package is hibernating for the user + */ + public boolean isHibernating(String packageName, int userId) { + userId = handleIncomingUser(userId, "isHibernating"); + synchronized (mLock) { + final Map<String, UserPackageState> packageStates = mUserStates.get(userId); + if (packageStates == null) { + throw new IllegalArgumentException("No user associated with user id " + userId); + } + final UserPackageState pkgState = packageStates.get(packageName); + if (pkgState == null) { + throw new IllegalArgumentException( + String.format("Package %s is not installed for user %s", + packageName, userId)); + } + return pkgState != null ? pkgState.hibernated : null; + } + } + + /** + * Set whether the package is hibernating for the given user. + * + * @param packageName package to modify state + * @param userId user + * @param isHibernating new hibernation state + */ + public void setHibernating(String packageName, int userId, boolean isHibernating) { + userId = handleIncomingUser(userId, "setHibernating"); + synchronized (mLock) { + if (!mUserStates.contains(userId)) { + throw new IllegalArgumentException("No user associated with user id " + userId); + } + Map<String, UserPackageState> packageStates = mUserStates.get(userId); + UserPackageState pkgState = packageStates.get(packageName); + if (pkgState == null) { + throw new IllegalArgumentException( + String.format("Package %s is not installed for user %s", + packageName, userId)); + } + + if (pkgState.hibernated == isHibernating) { + return; + } + + + final long caller = Binder.clearCallingIdentity(); + try { + if (isHibernating) { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); + mIActivityManager.forceStopPackage(packageName, userId); + mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, + null /* observer */); + } else { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage"); + mIPackageManager.setPackageStoppedState(packageName, false, userId); + } + pkgState.hibernated = isHibernating; + } catch (RemoteException e) { + throw new IllegalStateException( + "Failed to hibernate due to manager not being available", e); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + Binder.restoreCallingIdentity(caller); + } + + // TODO: Support package level hibernation when package is hibernating for all users + } + } + + /** + * Populates {@link #mUserStates} with the users installed packages. The caller should hold + * {@link #mLock}. + * + * @param userId user id to add installed packages for + */ + private void addUserPackageStatesL(int userId) { + Map<String, UserPackageState> packages = new ArrayMap<>(); + List<PackageInfo> packageList; + try { + packageList = mIPackageManager.getInstalledPackages(MATCH_ALL, userId).getList(); + } catch (RemoteException e) { + throw new IllegalStateException("Package manager not available.", e); + } + + for (PackageInfo pkg : packageList) { + packages.put(pkg.packageName, new UserPackageState()); + } + mUserStates.put(userId, packages); + } + + private void onUserAdded(int userId) { + synchronized (mLock) { + addUserPackageStatesL(userId); + } + } + + private void onUserRemoved(int userId) { + synchronized (mLock) { + mUserStates.remove(userId); + } + } + + private void onPackageAdded(@NonNull String packageName, int userId) { + synchronized (mLock) { + mUserStates.get(userId).put(packageName, new UserPackageState()); + } + } + + private void onPackageRemoved(@NonNull String packageName, int userId) { + synchronized (mLock) { + mUserStates.get(userId).remove(packageName); + } + } + + /** + * Private helper method to get the real user id and enforce permission checks. + * + * @param userId user id to handle + * @param name name to use for exceptions + * @return real user id + */ + private int handleIncomingUser(int userId, @NonNull String name) { + int callingUid = Binder.getCallingUid(); + try { + return mIActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, + false /* allowAll */, true /* requireFull */, name, null); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this); + + static final class AppHibernationServiceStub extends IAppHibernationService.Stub { + final AppHibernationService mService; + + AppHibernationServiceStub(AppHibernationService service) { + mService = service; + } + + @Override + public boolean isHibernating(String packageName, int userId) { + return mService.isHibernating(packageName, userId); + } + + @Override + public void setHibernating(String packageName, int userId, boolean isHibernating) { + mService.setHibernating(packageName, userId, isHibernating); + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, + @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) { + new AppHibernationShellCommand(mService).exec(this, in, out, err, args, callback, + resultReceiver); + } + } + + // Broadcast receiver for user and package add/removal events + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) { + return; + } + + final String action = intent.getAction(); + if (ACTION_USER_ADDED.equals(action)) { + onUserAdded(userId); + } + if (ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + } + if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REMOVED.equals(action)) { + final String packageName = intent.getData().getSchemeSpecificPart(); + if (intent.getBooleanExtra(EXTRA_REPLACING, false)) { + // Package removal/add is part of an update, so no need to modify package state. + return; + } + + if (ACTION_PACKAGE_ADDED.equals(action)) { + onPackageAdded(packageName, userId); + } else if (ACTION_PACKAGE_REMOVED.equals(action)) { + onPackageRemoved(packageName, userId); + } + } + } + }; + + /** + * Whether app hibernation is enabled on this device. + * + * @return true if enabled, false otherwise + */ + public static boolean isAppHibernationEnabled() { + return DeviceConfig.getBoolean( + NAMESPACE_APP_HIBERNATION, + AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED, + false /* defaultValue */); + } + + /** + * Data class that contains hibernation state info of a package for a user. + */ + private static final class UserPackageState { + public boolean hibernated; + // TODO: Track whether hibernation is exempted by the user + } +} diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java b/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java new file mode 100644 index 000000000000..869885e28958 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java @@ -0,0 +1,109 @@ +/* + * 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.apphibernation; + +import android.os.ShellCommand; +import android.os.UserHandle; +import android.text.TextUtils; + +import java.io.PrintWriter; + +/** + * Shell command implementation for {@link AppHibernationService}. + */ +final class AppHibernationShellCommand extends ShellCommand { + private static final String USER_OPT = "--user"; + private static final int SUCCESS = 0; + private static final int ERROR = -1; + private final AppHibernationService mService; + + AppHibernationShellCommand(AppHibernationService service) { + mService = service; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + switch (cmd) { + case "set-state": + return runSetState(); + case "get-state": + return runGetState(); + default: + return handleDefaultCommands(cmd); + } + } + + private int runSetState() { + int userId = parseUserOption(); + + String pkg = getNextArgRequired(); + if (pkg == null) { + getErrPrintWriter().println("Error: no package specified"); + return ERROR; + } + + String newStateRaw = getNextArgRequired(); + if (newStateRaw == null) { + getErrPrintWriter().println("Error: No state to set specified"); + return ERROR; + } + boolean newState = Boolean.parseBoolean(newStateRaw); + + mService.setHibernating(pkg, userId, newState); + return SUCCESS; + } + + private int runGetState() { + int userId = parseUserOption(); + + String pkg = getNextArgRequired(); + if (pkg == null) { + getErrPrintWriter().println("Error: No package specified"); + return ERROR; + } + boolean isHibernating = mService.isHibernating(pkg, userId); + final PrintWriter pw = getOutPrintWriter(); + pw.println(isHibernating); + return SUCCESS; + } + + private int parseUserOption() { + String option = getNextOption(); + if (TextUtils.equals(option, USER_OPT)) { + return UserHandle.parseUserArg(getNextArgRequired()); + } + return UserHandle.USER_CURRENT; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("App hibernation (app_hibernation) commands: "); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(""); + pw.println(" set-state [--user USER_ID] PACKAGE true|false"); + pw.println(" Sets the hibernation state of the package to value specified"); + pw.println(""); + pw.println(" get-state [--user USER_ID] PACKAGE"); + pw.println(" Gets the hibernation state of the package"); + pw.println(""); + } +} diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 9ba957ef27ae..e3757dfc6a59 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -23,8 +23,11 @@ import android.content.pm.ApplicationInfo; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.server.compat.config.Change; +import com.android.server.compat.overrides.ChangeOverrides; +import com.android.server.compat.overrides.OverrideValue; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -253,6 +256,71 @@ public final class CompatChange extends CompatibilityChangeInfo { 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(); + } + + void loadOverrides(ChangeOverrides changeOverrides) { + if (mDeferredOverrides == null) { + mDeferredOverrides = new HashMap<>(); + } + mDeferredOverrides.clear(); + for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { + mDeferredOverrides.put(override.getPackageName(), override.getEnabled()); + } + + if (mPackageOverrides == null) { + mPackageOverrides = new HashMap<>(); + } + mPackageOverrides.clear(); + for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { + mPackageOverrides.put(override.getPackageName(), override.getEnabled()); + } + } + + ChangeOverrides saveOverrides() { + if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) { + 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(); + override.setPackageName(entry.getKey()); + override.setEnabled(entry.getValue()); + deferredList.add(override); + } + } + changeOverrides.setDeferred(deferredOverrides); + ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated(); + List<OverrideValue> validatedList = validatedOverrides.getOverrideValue(); + if (mPackageOverrides != null) { + for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) { + OverrideValue override = new OverrideValue(); + override.setPackageName(entry.getKey()); + override.setEnabled(entry.getValue()); + validatedList.add(override); + } + } + changeOverrides.setValidated(validatedOverrides); + return changeOverrides; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("ChangeId(") diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 69686a2e4678..6b77b9d4ce39 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -34,7 +34,10 @@ import com.android.internal.compat.CompatibilityChangeInfo; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; -import com.android.server.compat.config.XmlParser; +import com.android.server.compat.config.Config; +import com.android.server.compat.overrides.ChangeOverrides; +import com.android.server.compat.overrides.Overrides; +import com.android.server.compat.overrides.XmlWriter; import com.android.server.pm.ApexManager; import org.xmlpull.v1.XmlPullParserException; @@ -60,11 +63,14 @@ import javax.xml.datatype.DatatypeConfigurationException; final class CompatConfig { private static final String TAG = "CompatConfig"; + private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat"; + private static final String OVERRIDES_FILE = "compat_framework_overrides.xml"; @GuardedBy("mChanges") private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); private final OverrideValidatorImpl mOverrideValidator; + private File mOverridesFile; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { @@ -83,6 +89,8 @@ final class CompatConfig { config.initConfigFromLib(Environment.buildPath( apex.apexDirectory, "etc", "compatconfig")); } + File overridesFile = new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE); + config.initOverrides(overridesFile); config.invalidateCache(); return config; } @@ -202,6 +210,17 @@ final class CompatConfig { * @throws IllegalStateException if overriding is not allowed */ boolean addOverride(long changeId, String packageName, boolean enabled) { + boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled); + saveOverrides(); + invalidateCache(); + return alreadyKnown; + } + + /** + * Unsafe version of {@link #addOverride(long, String, boolean)}. + * It does not invalidate the cache nor save the overrides. + */ + private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) { boolean alreadyKnown = true; OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); @@ -224,7 +243,6 @@ final class CompatConfig { throw new IllegalStateException("Should only be able to override changes that " + "are allowed or can be deferred."); } - invalidateCache(); } return alreadyKnown; } @@ -282,6 +300,17 @@ final class CompatConfig { * @return {@code true} if an override existed; */ boolean removeOverride(long changeId, String packageName) { + boolean overrideExists = removeOverrideUnsafe(changeId, packageName); + saveOverrides(); + invalidateCache(); + return overrideExists; + } + + /** + * Unsafe version of {@link #removeOverride(long, String)}. + * 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); @@ -300,7 +329,6 @@ final class CompatConfig { } } } - invalidateCache(); return overrideExists; } @@ -315,12 +343,13 @@ final class CompatConfig { void addOverrides(CompatibilityChangeConfig overrides, String packageName) { synchronized (mChanges) { for (Long changeId : overrides.enabledChanges()) { - addOverride(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, true); } for (Long changeId : overrides.disabledChanges()) { - addOverride(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, false); } + saveOverrides(); invalidateCache(); } } @@ -337,8 +366,9 @@ final class CompatConfig { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { CompatChange change = mChanges.valueAt(i); - removeOverride(change.getId(), packageName); + removeOverrideUnsafe(change.getId(), packageName); } + saveOverrides(); invalidateCache(); } } @@ -372,8 +402,10 @@ final class CompatConfig { int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverride(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, true); } + saveOverrides(); + invalidateCache(); return changes.length; } @@ -386,8 +418,10 @@ final class CompatConfig { int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverride(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, false); } + saveOverrides(); + invalidateCache(); return changes.length; } @@ -494,7 +528,8 @@ final class CompatConfig { private void readConfig(File configFile) { try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { - for (Change change : XmlParser.read(in).getCompatChange()) { + Config config = com.android.server.compat.config.XmlParser.read(in); + for (Change change : config.getCompatChange()) { Slog.d(TAG, "Adding: " + change.toString()); addChange(new CompatChange(change)); } @@ -503,6 +538,65 @@ final class CompatConfig { } } + void initOverrides(File overridesFile) { + if (!overridesFile.exists()) { + mOverridesFile = overridesFile; + // There have not been any overrides added yet. + return; + } + + try (InputStream in = new BufferedInputStream(new FileInputStream(overridesFile))) { + Overrides overrides = com.android.server.compat.overrides.XmlParser.read(in); + for (ChangeOverrides changeOverrides : overrides.getChangeOverrides()) { + long changeId = changeOverrides.getChangeId(); + CompatChange compatChange = mChanges.get(changeId); + if (compatChange == null) { + Slog.w(TAG, "Change ID " + changeId + " not found. " + + "Skipping overrides for it."); + continue; + } + compatChange.loadOverrides(changeOverrides); + } + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString()); + return; + } + mOverridesFile = overridesFile; + } + + /** + * Persist compat framework overrides to /data/misc/appcompat/compat_framework_overrides.xml + */ + void saveOverrides() { + if (mOverridesFile == null) { + return; + } + synchronized (mChanges) { + // Create the file if it doesn't already exist + try { + mOverridesFile.createNewFile(); + } catch (IOException e) { + Slog.e(TAG, "Could not create override config file: " + e.toString()); + return; + } + try (PrintWriter out = new PrintWriter(mOverridesFile)) { + XmlWriter writer = new XmlWriter(out); + Overrides overrides = new Overrides(); + List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides(); + for (int idx = 0; idx < mChanges.size(); ++idx) { + CompatChange c = mChanges.valueAt(idx); + ChangeOverrides changeOverrides = c.saveOverrides(); + if (changeOverrides != null) { + changeOverridesList.add(changeOverrides); + } + } + XmlWriter.write(writer, overrides); + } catch (IOException e) { + Slog.e(TAG, e.toString()); + } + } + } + IOverrideValidator getOverrideValidator() { return mOverrideValidator; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index ab0360b0395a..b2824846008c 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -329,7 +329,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final QosCallbackTracker mQosCallbackTracker; public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, - LinkProperties lp, NetworkCapabilities nc, int score, Context context, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber, int creatorUid, QosCallbackTracker qosCallbackTracker) { diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java index a7be657ae7a3..5e6b9f39b40a 100644 --- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java +++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java @@ -686,7 +686,7 @@ public class NetworkDiagnostics { mHostname = hostname; mMeasurement.description = "DNS TLS dst{" + mTarget.getHostAddress() + "} hostname{" - + TextUtils.emptyIfNull(mHostname) + "}"; + + (mHostname == null ? "" : mHostname) + "}"; } private SSLSocket setupSSLSocket() throws IOException { diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index fb1e8197ccff..8ce6746bc7cb 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -70,6 +70,7 @@ import android.net.NetworkRequest; import android.net.RouteInfo; import android.net.UidRange; import android.net.UidRangeParcel; +import android.net.VpnInfo; import android.net.VpnManager; import android.net.VpnService; import android.net.ipsec.ike.ChildSessionCallback; @@ -109,7 +110,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; @@ -1816,18 +1816,15 @@ public class Vpn { } /** - * This method should only be called by ConnectivityService because it doesn't - * have enough data to fill VpnInfo.primaryUnderlyingIface field. + * This method should not be called if underlying interfaces field is needed, because it doesn't + * have enough data to fill VpnInfo.underlyingIfaces field. */ public synchronized VpnInfo getVpnInfo() { if (!isRunningLocked()) { return null; } - VpnInfo info = new VpnInfo(); - info.ownerUid = mOwnerUID; - info.vpnIface = mInterface; - return info; + return new VpnInfo(mOwnerUID, mInterface, null); } public synchronized boolean appliesToUid(int uid) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index ab289ea6f081..f876e1ad4b88 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -901,7 +901,6 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @ServiceThreadOnly void setArcStatus(boolean enabled) { - // TODO(shubang): add tests assertRunOnServiceThread(); HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 8e50bb4885d8..5d1c4e6715f1 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -621,7 +621,14 @@ public class HdmiControlService extends SystemService { mWakeUpMessageReceived = false; if (isTvDeviceEnabled()) { - mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup()); + boolean autoWakeupEnabled = + readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true); + boolean autoDeviceOffEnabled = + readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true); + + mCecController.setOption(OptionKey.WAKEUP, autoWakeupEnabled); + tv().setAutoWakeup(autoWakeupEnabled); + tv().setAutoDeviceOff(autoDeviceOffEnabled); } int reason = -1; switch (initiatedBy) { diff --git a/services/core/java/com/android/server/location/timezone/OWNERS b/services/core/java/com/android/server/location/timezone/OWNERS deleted file mode 100644 index 28aff188dbd8..000000000000 --- a/services/core/java/com/android/server/location/timezone/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 847766 -nfuller@google.com -include /core/java/android/app/timedetector/OWNERS diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 81d07cc11527..c4225eda7d24 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -88,6 +88,7 @@ class LockSettingsStorage { private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key"; + private static final String REBOOT_ESCROW_SERVER_BLOB = "reboot.escrow.server.blob.key"; private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; @@ -317,6 +318,22 @@ class LockSettingsStorage { deleteFile(getRebootEscrowFile(userId)); } + public void writeRebootEscrowServerBlob(byte[] serverBlob) { + writeFile(getRebootEscrowServerBlob(), serverBlob); + } + + public byte[] readRebootEscrowServerBlob() { + return readFile(getRebootEscrowServerBlob()); + } + + public boolean hasRebootEscrowServerBlob() { + return hasFile(getRebootEscrowServerBlob()); + } + + public void removeRebootEscrowServerBlob() { + deleteFile(getRebootEscrowServerBlob()); + } + public boolean hasPassword(int userId) { return hasFile(getLockPasswordFilename(userId)); } @@ -445,6 +462,12 @@ class LockSettingsStorage { return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE); } + @VisibleForTesting + String getRebootEscrowServerBlob() { + // There is a single copy of server blob for all users. + return getLockCredentialFilePathForUser(UserHandle.USER_SYSTEM, REBOOT_ESCROW_SERVER_BLOB); + } + private String getLockCredentialFilePathForUser(int userId, String basename) { String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() + SYSTEM_DIRECTORY; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index fbec91576ca1..06962d414009 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -124,26 +124,28 @@ class RebootEscrowManager { static class Injector { protected Context mContext; private final RebootEscrowKeyStoreManager mKeyStoreManager; - private final RebootEscrowProviderInterface mRebootEscrowProvider; + private final LockSettingsStorage mStorage; + private RebootEscrowProviderInterface mRebootEscrowProvider; - Injector(Context context) { + Injector(Context context, LockSettingsStorage storage) { mContext = context; + mStorage = storage; mKeyStoreManager = new RebootEscrowKeyStoreManager(); + } - RebootEscrowProviderInterface rebootEscrowProvider = null; - // TODO(xunchang) add implementation for server based ror. + private RebootEscrowProviderInterface createRebootEscrowProvider() { + RebootEscrowProviderInterface rebootEscrowProvider; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, "server_based_ror_enabled", false)) { - Slog.e(TAG, "Server based ror isn't implemented yet."); + rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { rebootEscrowProvider = new RebootEscrowProviderHalImpl(); } - if (rebootEscrowProvider != null && rebootEscrowProvider.hasRebootEscrowSupport()) { - mRebootEscrowProvider = rebootEscrowProvider; - } else { - mRebootEscrowProvider = null; + if (rebootEscrowProvider.hasRebootEscrowSupport()) { + return rebootEscrowProvider; } + return null; } public Context getContext() { @@ -159,6 +161,12 @@ class RebootEscrowManager { } public RebootEscrowProviderInterface getRebootEscrowProvider() { + // Initialize for the provider lazily. Because the device_config and service + // implementation apps may change when system server is running. + if (mRebootEscrowProvider == null) { + mRebootEscrowProvider = createRebootEscrowProvider(); + } + return mRebootEscrowProvider; } @@ -177,7 +185,7 @@ class RebootEscrowManager { } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { - this(new Injector(context), callbacks, storage); + this(new Injector(context, storage), callbacks, storage); } @VisibleForTesting diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java new file mode 100644 index 000000000000..ba1a680ba7fb --- /dev/null +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -0,0 +1,202 @@ +/* + * 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.locksettings; + +import android.annotation.Nullable; +import android.content.Context; +import android.os.RemoteException; +import android.provider.DeviceConfig; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import javax.crypto.SecretKey; + +/** + * An implementation of the {@link RebootEscrowProviderInterface} by communicating with server to + * encrypt & decrypt the blob. + */ +class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { + private static final String TAG = "RebootEscrowProvider"; + + // Timeout for service binding + private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; + + /** + * Use the default lifetime of 10 minutes. The lifetime covers the following activities: + * Server wrap secret -> device reboot -> server unwrap blob. + */ + private static final long DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS = 600_1000; + + private final LockSettingsStorage mStorage; + + private final Injector mInjector; + + static class Injector { + private ResumeOnRebootServiceConnection mServiceConnection = null; + + Injector(Context context) { + mServiceConnection = new ResumeOnRebootServiceProvider(context).getServiceConnection(); + if (mServiceConnection == null) { + Slog.e(TAG, "Failed to resolve resume on reboot server service."); + } + } + + Injector(ResumeOnRebootServiceConnection serviceConnection) { + mServiceConnection = serviceConnection; + } + + @Nullable + private ResumeOnRebootServiceConnection getServiceConnection() { + return mServiceConnection; + } + + long getServiceTimeoutInSeconds() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA, + "server_based_service_timeout_in_seconds", + DEFAULT_SERVICE_TIMEOUT_IN_SECONDS); + } + + long getServerBlobLifetimeInMillis() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA, + "server_based_server_blob_lifetime_in_millis", + DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS); + } + } + + RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage) { + this(storage, new Injector(context)); + } + + @VisibleForTesting + RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector) { + mStorage = storage; + mInjector = injector; + } + + @Override + public boolean hasRebootEscrowSupport() { + return mInjector.getServiceConnection() != null; + } + + private byte[] unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey) throws + TimeoutException, RemoteException, IOException { + ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection(); + if (serviceConnection == null) { + Slog.w(TAG, "Had reboot escrow data for users, but resume on reboot server" + + " service is unavailable"); + return null; + } + + // Decrypt with k_k from the key store first. + byte[] decryptedBlob = AesEncryptionUtil.decrypt(decryptionKey, serverBlob); + if (decryptedBlob == null) { + Slog.w(TAG, "Decrypted server blob should not be null"); + return null; + } + + // Ask the server connection service to decrypt the inner layer, to get the reboot + // escrow key (k_s). + serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds()); + byte[] escrowKeyBytes = serviceConnection.unwrap(decryptedBlob, + mInjector.getServiceTimeoutInSeconds()); + serviceConnection.unbindService(); + + return escrowKeyBytes; + } + + @Override + public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) { + byte[] serverBlob = mStorage.readRebootEscrowServerBlob(); + // Delete the server blob in storage. + mStorage.removeRebootEscrowServerBlob(); + if (serverBlob == null) { + Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); + return null; + } + + try { + byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey); + if (escrowKeyBytes == null) { + Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); + return null; + } else if (escrowKeyBytes.length != 32) { + Slog.e(TAG, "Decrypted reboot escrow key has incorrect size " + + escrowKeyBytes.length); + return null; + } + + return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); + } catch (TimeoutException | RemoteException | IOException e) { + Slog.w(TAG, "Failed to decrypt the server blob ", e); + return null; + } + } + + @Override + public void clearRebootEscrowKey() { + mStorage.removeRebootEscrowServerBlob(); + } + + private byte[] wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey) throws + TimeoutException, RemoteException, IOException { + ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection(); + if (serviceConnection == null) { + Slog.w(TAG, "Failed to encrypt the reboot escrow key: resume on reboot server" + + " service is unavailable"); + return null; + } + + serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds()); + // Ask the server connection service to encrypt the reboot escrow key. + byte[] serverEncryptedBlob = serviceConnection.wrapBlob(escrowKeyBytes, + mInjector.getServerBlobLifetimeInMillis(), mInjector.getServiceTimeoutInSeconds()); + serviceConnection.unbindService(); + + if (serverEncryptedBlob == null) { + Slog.w(TAG, "Server encrypted reboot escrow key cannot be null"); + return null; + } + + // Additionally wrap the server blob with a local key. + return AesEncryptionUtil.encrypt(encryptionKey, serverEncryptedBlob); + } + + @Override + public boolean storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey) { + mStorage.removeRebootEscrowServerBlob(); + try { + byte[] wrappedBlob = wrapEscrowKey(escrowKey.getKeyBytes(), encryptionKey); + if (wrappedBlob == null) { + Slog.w(TAG, "Failed to encrypt the reboot escrow key"); + return false; + } + mStorage.writeRebootEscrowServerBlob(wrappedBlob); + + Slog.i(TAG, "Reboot escrow key encrypted and stored."); + return true; + } catch (TimeoutException | RemoteException | IOException e) { + Slog.w(TAG, "Failed to encrypt the reboot escrow key ", e); + } + + return false; + } +} diff --git a/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java new file mode 100644 index 000000000000..8399f54764e0 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/ResumeOnRebootServiceProvider.java @@ -0,0 +1,249 @@ +/* + * 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.locksettings; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelableException; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.service.resumeonreboot.IResumeOnRebootService; +import android.service.resumeonreboot.ResumeOnRebootService; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** @hide */ +public class ResumeOnRebootServiceProvider { + + private static final String PROVIDER_PACKAGE = DeviceConfig.getString( + DeviceConfig.NAMESPACE_OTA, "resume_on_reboot_service_package", ""); + private static final String PROVIDER_REQUIRED_PERMISSION = + Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE; + private static final String TAG = "ResumeOnRebootServiceProvider"; + + private final Context mContext; + private final PackageManager mPackageManager; + + public ResumeOnRebootServiceProvider(Context context) { + this(context, context.getPackageManager()); + } + + @VisibleForTesting + public ResumeOnRebootServiceProvider(Context context, PackageManager packageManager) { + this.mContext = context; + this.mPackageManager = packageManager; + } + + @Nullable + private ServiceInfo resolveService() { + Intent intent = new Intent(); + intent.setAction(ResumeOnRebootService.SERVICE_INTERFACE); + if (PROVIDER_PACKAGE != null && !PROVIDER_PACKAGE.equals("")) { + intent.setPackage(PROVIDER_PACKAGE); + } + + List<ResolveInfo> resolvedIntents = + mPackageManager.queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY); + for (ResolveInfo resolvedInfo : resolvedIntents) { + if (resolvedInfo.serviceInfo != null + && PROVIDER_REQUIRED_PERMISSION.equals(resolvedInfo.serviceInfo.permission)) { + return resolvedInfo.serviceInfo; + } + } + return null; + } + + /** Creates a new {@link ResumeOnRebootServiceConnection} */ + @Nullable + public ResumeOnRebootServiceConnection getServiceConnection() { + ServiceInfo serviceInfo = resolveService(); + if (serviceInfo == null) { + return null; + } + return new ResumeOnRebootServiceConnection(mContext, serviceInfo.getComponentName()); + } + + /** + * Connection class used for contacting the registered {@link IResumeOnRebootService} + */ + public static class ResumeOnRebootServiceConnection { + + private static final String TAG = "ResumeOnRebootServiceConnection"; + private final Context mContext; + private final ComponentName mComponentName; + private IResumeOnRebootService mBinder; + + private ResumeOnRebootServiceConnection(Context context, + @NonNull ComponentName componentName) { + mContext = context; + mComponentName = componentName; + } + + /** Unbind from the service */ + public void unbindService() { + mContext.unbindService(new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBinder = null; + + } + }); + } + + /** Bind to the service */ + public void bindToService(long timeOut) throws TimeoutException { + if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { + CountDownLatch connectionLatch = new CountDownLatch(1); + Intent intent = new Intent(); + intent.setComponent(mComponentName); + final boolean success = mContext.bindServiceAsUser(intent, new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mBinder = IResumeOnRebootService.Stub.asInterface(service); + connectionLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + BackgroundThread.getHandler(), UserHandle.SYSTEM); + + if (!success) { + Slog.e(TAG, "Binding: " + mComponentName + " u" + UserHandle.SYSTEM + + " failed."); + return; + } + waitForLatch(connectionLatch, "serviceConnection", timeOut); + } + } + + /** Wrap opaque blob */ + public byte[] wrapBlob(byte[] unwrappedBlob, long lifeTimeInMillis, + long timeOutInMillis) + throws RemoteException, TimeoutException, IOException { + if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { + throw new RemoteException("Service not bound"); + } + CountDownLatch binderLatch = new CountDownLatch(1); + ResumeOnRebootServiceCallback + resultCallback = + new ResumeOnRebootServiceCallback( + binderLatch); + mBinder.wrapSecret(unwrappedBlob, lifeTimeInMillis, new RemoteCallback(resultCallback)); + waitForLatch(binderLatch, "wrapSecret", timeOutInMillis); + if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { + throwTypedException(resultCallback.getResult().getParcelable( + ResumeOnRebootService.EXCEPTION_KEY)); + } + return resultCallback.mResult.getByteArray(ResumeOnRebootService.WRAPPED_BLOB_KEY); + } + + /** Unwrap wrapped blob */ + public byte[] unwrap(byte[] wrappedBlob, long timeOut) + throws RemoteException, TimeoutException, IOException { + if (mBinder == null || !mBinder.asBinder().isBinderAlive()) { + throw new RemoteException("Service not bound"); + } + CountDownLatch binderLatch = new CountDownLatch(1); + ResumeOnRebootServiceCallback + resultCallback = + new ResumeOnRebootServiceCallback( + binderLatch); + mBinder.unwrap(wrappedBlob, new RemoteCallback(resultCallback)); + waitForLatch(binderLatch, "unWrapSecret", timeOut); + if (resultCallback.getResult().containsKey(ResumeOnRebootService.EXCEPTION_KEY)) { + throwTypedException(resultCallback.getResult().getParcelable( + ResumeOnRebootService.EXCEPTION_KEY)); + } + return resultCallback.getResult().getByteArray( + ResumeOnRebootService.UNWRAPPED_BLOB_KEY); + } + + private void throwTypedException( + ParcelableException exception) + throws IOException { + if (exception.getCause() instanceof IOException) { + exception.maybeRethrow(IOException.class); + } else if (exception.getCause() instanceof IllegalStateException) { + exception.maybeRethrow(IllegalStateException.class); + } else { + // This should not happen. Wrap the cause in IllegalStateException so that it + // doesn't disrupt the exception handling + throw new IllegalStateException(exception.getCause()); + } + } + + private void waitForLatch(CountDownLatch latch, String reason, long timeOut) + throws TimeoutException { + try { + if (!latch.await(timeOut, TimeUnit.SECONDS)) { + throw new TimeoutException("Latch wait for " + reason + " elapsed"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Latch wait for " + reason + " interrupted"); + } + } + } + + private static class ResumeOnRebootServiceCallback implements + RemoteCallback.OnResultListener { + + private final CountDownLatch mResultLatch; + private Bundle mResult; + + private ResumeOnRebootServiceCallback(CountDownLatch resultLatch) { + this.mResultLatch = resultLatch; + } + + @Override + public void onResult(@Nullable Bundle result) { + this.mResult = result; + mResultLatch.countDown(); + } + + private Bundle getResult() { + return mResult; + } + } +} diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java index e9868fde3059..4faa7903c630 100644 --- a/services/core/java/com/android/server/net/NetworkStatsFactory.java +++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java @@ -27,6 +27,7 @@ import static com.android.server.NetworkManagementSocketTagger.kernelToTag; import android.annotation.Nullable; import android.net.INetd; import android.net.NetworkStats; +import android.net.VpnInfo; import android.net.util.NetdService; import android.os.RemoteException; import android.os.StrictMode; @@ -34,7 +35,6 @@ import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ProcFileReader; diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 81a6641de8a4..4be7b483af16 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -105,6 +105,7 @@ import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TrafficStats; import android.net.Uri; +import android.net.VpnInfo; import android.net.netstats.provider.INetworkStatsProvider; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.net.netstats.provider.NetworkStatsProvider; @@ -143,7 +144,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FileRotator; diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java index 06706cd06e11..0ffc1ed9e90c 100644 --- a/services/core/java/com/android/server/pm/ModuleInfoProvider.java +++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java @@ -184,7 +184,7 @@ public class ModuleInfoProvider { List<PackageInfo> allPackages; try { allPackages = mPackageManager.getInstalledPackages( - flags | PackageManager.MATCH_APEX, UserHandle.USER_SYSTEM).getList(); + flags | PackageManager.MATCH_APEX, UserHandle.getCallingUserId()).getList(); } catch (RemoteException e) { Slog.w(TAG, "Unable to retrieve all package names", e); return Collections.emptyList(); diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index e1feb5aab869..6427ae2dc13c 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -24,6 +24,9 @@ import android.net.NetworkCapabilities; import android.os.Handler; import android.os.ParcelUuid; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + import java.util.Objects; /** @@ -72,7 +75,8 @@ public class UnderlyingNetworkTracker extends Handler { @NonNull public final LinkProperties linkProperties; public final boolean blocked; - private UnderlyingNetworkRecord( + @VisibleForTesting(visibility = Visibility.PRIVATE) + UnderlyingNetworkRecord( @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties, diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 4e0c0c54923b..8805fa2f4dbb 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -24,7 +24,6 @@ import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; import android.annotation.Nullable; -import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpSecManager; @@ -36,8 +35,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; import android.net.RouteInfo; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; @@ -54,7 +51,6 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; -import android.telephony.TelephonyManager; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -126,7 +122,9 @@ public class VcnGatewayConnection extends StateMachine { private static final int TOKEN_ALL = Integer.MIN_VALUE; private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; - private static final int TEARDOWN_TIMEOUT_SECONDS = 5; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int TEARDOWN_TIMEOUT_SECONDS = 5; private interface EventInfo {} @@ -360,11 +358,25 @@ public class VcnGatewayConnection extends StateMachine { */ private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8; - @NonNull private final DisconnectedState mDisconnectedState = new DisconnectedState(); - @NonNull private final DisconnectingState mDisconnectingState = new DisconnectingState(); - @NonNull private final ConnectingState mConnectingState = new ConnectingState(); - @NonNull private final ConnectedState mConnectedState = new ConnectedState(); - @NonNull private final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final DisconnectedState mDisconnectedState = new DisconnectedState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final DisconnectingState mDisconnectingState = new DisconnectingState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final ConnectingState mConnectingState = new ConnectingState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final ConnectedState mConnectedState = new ConnectedState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @@ -403,13 +415,6 @@ public class VcnGatewayConnection extends StateMachine { private int mCurrentToken = -1; /** - * The next usable token. - * - * <p>A new token MUST be used for all new IKE sessions. - */ - private int mNextToken = 0; - - /** * The number of unsuccessful attempts since the last successful connection. * * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared @@ -430,7 +435,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and * Migrating states, null otherwise. */ - private IkeSession mIkeSession; + private VcnIkeSession mIkeSession; /** * The last known child configuration. @@ -455,7 +460,8 @@ public class VcnGatewayConnection extends StateMachine { this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies()); } - private VcnGatewayConnection( + @VisibleForTesting(visibility = Visibility.PRIVATE) + VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnGatewayConnectionConfig connectionConfig, @@ -508,7 +514,6 @@ public class VcnGatewayConnection extends StateMachine { EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); - quit(); // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this // is also called asynchronously when a NetworkAgent becomes unwanted @@ -654,7 +659,7 @@ public class VcnGatewayConnection extends StateMachine { protected void teardownNetwork() { if (mNetworkAgent != null) { - mNetworkAgent.sendNetworkInfo(buildNetworkInfo(false /* isConnected */)); + mNetworkAgent.unregister(); mNetworkAgent = null; } } @@ -667,6 +672,8 @@ public class VcnGatewayConnection extends StateMachine { protected void handleDisconnectRequested(String msg) { Slog.v(TAG, "Tearing down. Cause: " + msg); + mIsRunning = false; + teardownNetwork(); teardownIke(); @@ -697,7 +704,37 @@ public class VcnGatewayConnection extends StateMachine { */ private class DisconnectedState extends BaseState { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() { + if (!mIsRunning) { + quitNow(); // Ignore all queued events; cleanup is complete. + } + + if (mIkeSession != null || mNetworkAgent != null) { + Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState"); + } + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: + // First network found; start tunnel + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + if (mUnderlying != null) { + transitionTo(mConnectingState); + } + break; + case EVENT_DISCONNECT_REQUESTED: + mIsRunning = false; + + quitNow(); + break; + default: + logUnhandledMessage(msg); + break; + } + } } private abstract class ActiveBaseState extends BaseState { @@ -732,7 +769,70 @@ public class VcnGatewayConnection extends StateMachine { */ private class DisconnectingState extends ActiveBaseState { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() throws Exception { + if (mIkeSession == null) { + Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state."); + sendMessage(EVENT_SESSION_CLOSED, mCurrentToken); + return; + } + + // If underlying network has already been lost, save some time and just kill the session + if (mUnderlying == null) { + // Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down. + mIkeSession.kill(); + return; + } + + sendMessageDelayed( + EVENT_TEARDOWN_TIMEOUT_EXPIRED, + mCurrentToken, + TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + // If we received a new underlying network, continue. + if (mUnderlying != null) { + break; + } + + // Fallthrough; no network exists to send IKE close session requests. + case EVENT_TEARDOWN_TIMEOUT_EXPIRED: + // Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED + mIkeSession.kill(); + + break; + case EVENT_DISCONNECT_REQUESTED: + teardownNetwork(); + + String reason = ((EventDisconnectRequestedInfo) msg.obj).reason; + if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { + // Will trigger EVENT_SESSION_CLOSED immediately. + mIkeSession.kill(); + break; + } + + // Otherwise we are already in the process of shutting down. + break; + case EVENT_SESSION_CLOSED: + mIkeSession = null; + + if (mIsRunning && mUnderlying != null) { + transitionTo(mRetryTimeoutState); + } else { + teardownNetwork(); + transitionTo(mDisconnectedState); + } + break; + default: + logUnhandledMessage(msg); + break; + } + } } /** @@ -769,20 +869,6 @@ public class VcnGatewayConnection extends StateMachine { protected void processStateMsg(Message msg) {} } - // TODO: Remove this when migrating to new NetworkAgent API - private static NetworkInfo buildNetworkInfo(boolean isConnected) { - NetworkInfo info = - new NetworkInfo( - ConnectivityManager.TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, - "MOBILE", - "VCN"); - info.setDetailedState( - isConnected ? DetailedState.CONNECTED : DetailedState.DISCONNECTED, null, null); - - return info; - } - @VisibleForTesting(visibility = Visibility.PRIVATE) static NetworkCapabilities buildNetworkCapabilities( @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { @@ -893,7 +979,64 @@ public class VcnGatewayConnection extends StateMachine { } } - /** External dependencies used by VcnGatewayConnection, for injection in tests. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { + return mUnderlyingNetworkTrackerCallback; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + UnderlyingNetworkRecord getUnderlyingNetwork() { + return mUnderlying; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setUnderlyingNetwork(@Nullable UnderlyingNetworkRecord record) { + mUnderlying = record; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + boolean isRunning() { + return mIsRunning; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setIsRunning(boolean isRunning) { + mIsRunning = isRunning; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + VcnIkeSession getIkeSession() { + return mIkeSession; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setIkeSession(@Nullable VcnIkeSession session) { + mIkeSession = session; + } + + private IkeSessionParams buildIkeParams() { + // TODO: Implement this with ConnectingState + return null; + } + + private ChildSessionParams buildChildParams() { + // TODO: Implement this with ConnectingState + return null; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + VcnIkeSession buildIkeSession() { + final int token = ++mCurrentToken; + + return mDeps.newIkeSession( + mVcnContext, + buildIkeParams(), + buildChildParams(), + new IkeSessionCallbackImpl(token), + new ChildSessionCallbackImpl(token)); + } + + /** External dependencies used by VcnGatewayConnection, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { /** Builds a new UnderlyingNetworkTracker. */ @@ -905,19 +1048,67 @@ public class VcnGatewayConnection extends StateMachine { } /** Builds a new IkeSession. */ - public IkeSession newIkeSession( + public VcnIkeSession newIkeSession( VcnContext vcnContext, IkeSessionParams ikeSessionParams, ChildSessionParams childSessionParams, IkeSessionCallback ikeSessionCallback, ChildSessionCallback childSessionCallback) { - return new IkeSession( - vcnContext.getContext(), + return new VcnIkeSession( + vcnContext, ikeSessionParams, childSessionParams, - new HandlerExecutor(new Handler(vcnContext.getLooper())), ikeSessionCallback, childSessionCallback); } } + + /** Proxy implementation of IKE session, used for testing. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnIkeSession { + private final IkeSession mImpl; + + public VcnIkeSession( + VcnContext vcnContext, + IkeSessionParams ikeSessionParams, + ChildSessionParams childSessionParams, + IkeSessionCallback ikeSessionCallback, + ChildSessionCallback childSessionCallback) { + mImpl = + new IkeSession( + vcnContext.getContext(), + ikeSessionParams, + childSessionParams, + new HandlerExecutor(new Handler(vcnContext.getLooper())), + ikeSessionCallback, + childSessionCallback); + } + + /** Creates a new IKE Child session. */ + public void openChildSession( + @NonNull ChildSessionParams childSessionParams, + @NonNull ChildSessionCallback childSessionCallback) { + mImpl.openChildSession(childSessionParams, childSessionCallback); + } + + /** Closes an IKE session as identified by the ChildSessionCallback. */ + public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) { + mImpl.closeChildSession(childSessionCallback); + } + + /** Gracefully closes this IKE Session, waiting for remote acknowledgement. */ + public void close() { + mImpl.close(); + } + + /** Forcibly kills this IKE Session, without waiting for a closure confirmation. */ + public void kill() { + mImpl.kill(); + } + + /** Sets the underlying network used by the IkeSession. */ + public void setNetwork(@NonNull Network network) { + mImpl.setNetwork(network); + } + } } diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index b7d6424450f3..3690afc1bc41 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -8,11 +8,19 @@ xsd_config { xsd_config { name: "platform-compat-config", - srcs: ["platform-compat-config.xsd"], - api_dir: "platform-compat-schema", + srcs: ["platform-compat/config/platform-compat-config.xsd"], + api_dir: "platform-compat/config/schema", package_name: "com.android.server.compat.config", } +xsd_config { + name: "platform-compat-overrides", + srcs: ["platform-compat/overrides/platform-compat-overrides.xsd"], + api_dir: "platform-compat/overrides/schema", + package_name: "com.android.server.compat.overrides", + gen_writer: true, +} + xsd_config { name: "display-device-config", diff --git a/services/core/xsd/platform-compat-schema/OWNERS b/services/core/xsd/platform-compat/OWNERS index f8c3520e9fa8..f8c3520e9fa8 100644 --- a/services/core/xsd/platform-compat-schema/OWNERS +++ b/services/core/xsd/platform-compat/OWNERS diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat/config/platform-compat-config.xsd index a62e2c385766..a62e2c385766 100644 --- a/services/core/xsd/platform-compat-config.xsd +++ b/services/core/xsd/platform-compat/config/platform-compat-config.xsd diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat/config/schema/current.txt index fb8bbefd8374..fb8bbefd8374 100644 --- a/services/core/xsd/platform-compat-schema/current.txt +++ b/services/core/xsd/platform-compat/config/schema/current.txt diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/config/schema/last_current.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/services/core/xsd/platform-compat-schema/last_current.txt +++ b/services/core/xsd/platform-compat/config/schema/last_current.txt diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/config/schema/last_removed.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/services/core/xsd/platform-compat-schema/last_removed.txt +++ b/services/core/xsd/platform-compat/config/schema/last_removed.txt diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/config/schema/removed.txt index d802177e249b..d802177e249b 100644 --- a/services/core/xsd/platform-compat-schema/removed.txt +++ b/services/core/xsd/platform-compat/config/schema/removed.txt diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd new file mode 100644 index 000000000000..e27e1b8ca89d --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- This defines the format of the XML file used to store compat config overrides in + ~ /data/misc/appcompat/compat_framework_overrides.xml +--> +<xs:schema version="2.0" elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + + <xs:complexType name="override-value"> + <xs:attribute type="xs:string" name="packageName" use="required" /> + <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"> + <xs:complexType> + <xs:sequence> + <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="deferred"> + <xs:complexType> + <xs:sequence> + <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:complexType> + + <xs:element name="overrides"> + <xs:complexType> + <xs:sequence> + <xs:element name="change-overrides" type="change-overrides" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt new file mode 100644 index 000000000000..08b82072747b --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/current.txt @@ -0,0 +1,51 @@ +// Signature format: 2.0 +package com.android.server.compat.overrides { + + public class ChangeOverrides { + 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.Validated getValidated(); + method public void setChangeId(long); + method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred); + method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated); + } + + public static class ChangeOverrides.Deferred { + ctor public ChangeOverrides.Deferred(); + method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); + } + + public static class ChangeOverrides.Validated { + ctor public ChangeOverrides.Validated(); + method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); + } + + public class OverrideValue { + ctor public OverrideValue(); + method public boolean getEnabled(); + method public String getPackageName(); + method public void setEnabled(boolean); + method public void setPackageName(String); + } + + public class Overrides { + ctor public Overrides(); + method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides(); + } + + 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; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + + public class XmlWriter implements java.io.Closeable { + ctor public XmlWriter(java.io.PrintWriter); + method public void close(); + method public static void write(com.android.server.compat.overrides.XmlWriter, com.android.server.compat.overrides.Overrides) throws java.io.IOException; + } + +} + diff --git a/services/core/xsd/platform-compat/overrides/schema/last_current.txt b/services/core/xsd/platform-compat/overrides/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/last_current.txt diff --git a/services/core/xsd/platform-compat/overrides/schema/last_removed.txt b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt diff --git a/services/core/xsd/platform-compat/overrides/schema/removed.txt b/services/core/xsd/platform-compat/overrides/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 516c64217177..6089a52ae0bb 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -97,6 +97,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.server.am.ActivityManagerService; import com.android.server.appbinding.AppBindingService; +import com.android.server.apphibernation.AppHibernationService; import com.android.server.attention.AttentionManagerService; import com.android.server.audio.AudioService; import com.android.server.biometrics.AuthService; @@ -220,6 +221,8 @@ public final class SystemServer { "com.android.server.appwidget.AppWidgetService"; private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS = "com.android.server.voiceinteraction.VoiceInteractionManagerService"; + private static final String APP_HIBERNATION_SERVICE_CLASS = + "com.android.server.apphibernation.AppHibernationService"; private static final String PRINT_MANAGER_SERVICE_CLASS = "com.android.server.print.PrintManagerService"; private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS = @@ -457,7 +460,7 @@ public final class SystemServer { } try { - Thread.sleep(checkInterval); + Thread.sleep(checkInterval * 1000); } catch (InterruptedException ex) { continue; } @@ -1863,6 +1866,12 @@ public final class SystemServer { mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS); t.traceEnd(); + if (AppHibernationService.isAppHibernationEnabled()) { + t.traceBegin("StartAppHibernationService"); + mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS); + t.traceEnd(); + } + if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) { t.traceBegin("StartGestureLauncher"); mSystemServiceManager.startService(GestureLauncherService.class); diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java new file mode 100644 index 000000000000..d0370b6c25e9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -0,0 +1,168 @@ +/* + * 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.apphibernation; + +import static org.junit.Assert.assertTrue; +import static org.mockito.AdditionalAnswers.returnsArgAt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +import android.app.IActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.ParceledListSlice; +import android.content.pm.UserInfo; +import android.net.Uri; +import android.os.RemoteException; +import android.os.UserManager; + +import androidx.test.filters.SmallTest; + +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link com.android.server.apphibernation.AppHibernationService} + */ +@SmallTest +public final class AppHibernationServiceTest { + private static final String PACKAGE_SCHEME = "package"; + private static final String PACKAGE_NAME_1 = "package1"; + private static final String PACKAGE_NAME_2 = "package2"; + private static final int USER_ID_1 = 1; + private static final int USER_ID_2 = 2; + + private AppHibernationService mAppHibernationService; + private BroadcastReceiver mBroadcastReceiver; + @Mock + private Context mContext; + @Mock + private IPackageManager mIPackageManager; + @Mock + private IActivityManager mIActivityManager; + @Mock + private UserManager mUserManager; + @Captor + private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor; + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); + + mAppHibernationService = new AppHibernationService(mContext, mIPackageManager, + mIActivityManager, mUserManager); + + verify(mContext, times(2)).registerReceiver(mReceiverCaptor.capture(), any()); + mBroadcastReceiver = mReceiverCaptor.getValue(); + + List<UserInfo> userList = new ArrayList<>(); + userList.add(new UserInfo(USER_ID_1, "user 1", 0 /* flags */)); + doReturn(userList).when(mUserManager).getUsers(); + + List<PackageInfo> userPackages = new ArrayList<>(); + userPackages.add(makePackageInfo(PACKAGE_NAME_1)); + + doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) + .getInstalledPackages(anyInt(), eq(USER_ID_1)); + + doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), + anyInt(), anyBoolean(), anyBoolean(), any(), any()); + + mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + } + + @Test + public void testSetHibernating_packageIsHibernating() { + // WHEN we hibernate a package for a user + mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_1, true); + + // THEN the package is marked hibernating for the user + assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_1)); + } + + @Test + public void testSetHibernating_newPackageAdded_packageIsHibernating() { + // WHEN a new package is added and it is hibernated + Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED, + Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1); + mBroadcastReceiver.onReceive(mContext, intent); + + mAppHibernationService.setHibernating(PACKAGE_NAME_2, USER_ID_1, true); + + // THEN the new package is hibernated + assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_2, USER_ID_1)); + } + + @Test + public void testSetHibernating_newUserAdded_packageIsHibernating() throws RemoteException { + // WHEN a new user is added and a package from the user is hibernated + List<PackageInfo> userPackages = new ArrayList<>(); + userPackages.add(makePackageInfo(PACKAGE_NAME_1)); + doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) + .getInstalledPackages(anyInt(), eq(USER_ID_2)); + Intent intent = new Intent(Intent.ACTION_USER_ADDED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_2); + mBroadcastReceiver.onReceive(mContext, intent); + + mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_2, true); + + // THEN the new user's package is hibernated + assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_2)); + } + + @Test + public void testIsHibernating_packageReplaced_stillReturnsHibernating() { + // GIVEN a package is currently hibernated + mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_1, true); + + // WHEN the package is removed but marked as replacing + Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED, + Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1); + intent.putExtra(Intent.EXTRA_REPLACING, true); + mBroadcastReceiver.onReceive(mContext, intent); + + // THEN the package is still hibernating + assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_1)); + } + + private static PackageInfo makePackageInfo(String packageName) { + PackageInfo pkg = new PackageInfo(); + pkg.packageName = packageName; + return pkg; + } +} 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 ac8dc341999a..a53ff9bc7fdc 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -44,6 +44,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.UUID; @RunWith(AndroidJUnit4.class) @@ -69,6 +71,10 @@ public class CompatConfigTest { os.close(); } + private String readFile(File file) throws IOException { + return new String(Files.readAllBytes(Paths.get(file.toURI()))); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -499,4 +505,86 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(1236L, ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue(); } + + @Test + public void testSaveOverrides() 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); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName("foo.bar") + .debuggable() + .build()); + when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) + .thenThrow(new NameNotFoundException()); + + compatConfig.addOverride(1L, "foo.bar", true); + compatConfig.addOverride(2L, "bar.baz", false); + + assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + + " </override-value>\n" + + " </validated>\n" + + " <deferred>\n" + + " </deferred>\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" + + " </change-overrides>\n" + + "</overrides>\n"); + } + + @Test + public void testLoadOverrides() 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>" + + "<change-overrides changeId=\"1\">" + + "<deferred/>" + + "<validated>" + + "<override-value packageName=\"foo.bar\" enabled=\"true\"/>" + + "</validated>" + + "</change-overrides>" + + "<change-overrides changeId=\"2\">" + + "<deferred>" + + "<override-value packageName=\"bar.baz\" enabled=\"false\"/>" + + "</deferred>" + + "<validated/>" + + "</change-overrides>" + + "</overrides>"; + 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") + .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(); + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index dd98c4b09b78..09dd3e3e50db 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -538,6 +538,15 @@ public class HdmiCecLocalDeviceAudioSystemTest { } @Test + public void setArcStatus() { + mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); + assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue(); + + mHdmiCecLocalDeviceAudioSystem.setArcStatus(false); + assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); + } + + @Test @Ignore("b/151150320") public void handleSystemAudioModeRequest_fromNonTV_tVNotSupport() { HdmiCecMessage message = diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS deleted file mode 100644 index 28aff188dbd8..000000000000 --- a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 847766 -nfuller@google.com -include /core/java/android/app/timedetector/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index 1581d9ac1811..691d174f55f8 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -82,6 +82,11 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { } @Override + String getRebootEscrowServerBlob() { + return makeDirs(mStorageDir, super.getRebootEscrowServerBlob()).getAbsolutePath(); + } + + @Override protected File getSyntheticPasswordDirectoryForUser(int userId) { return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser( userId).getAbsolutePath()); 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 f74e45b6e59b..a4ba4c86a8fd 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; @@ -52,6 +53,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.RebootEscrowListener; +import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; import org.junit.Before; import org.junit.Test; @@ -92,6 +94,7 @@ public class RebootEscrowManagerTests { private UserManager mUserManager; private RebootEscrowManager.Callbacks mCallbacks; private IRebootEscrow mRebootEscrow; + private ResumeOnRebootServiceConnection mServiceConnection; private RebootEscrowKeyStoreManager mKeyStoreManager; LockSettingsStorageTestable mStorage; @@ -108,6 +111,7 @@ public class RebootEscrowManagerTests { static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; + private final ResumeOnRebootServiceConnection mServiceConnection; private final RebootEscrowProviderInterface mRebootEscrowProvider; private final UserManager mUserManager; private final MockableRebootEscrowInjected mInjected; @@ -116,10 +120,11 @@ public class RebootEscrowManagerTests { MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow, RebootEscrowKeyStoreManager keyStoreManager, + LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { - super(context); + super(context, storage); mRebootEscrow = rebootEscrow; - + mServiceConnection = null; RebootEscrowProviderHalImpl.Injector halInjector = new RebootEscrowProviderHalImpl.Injector() { @Override @@ -133,6 +138,22 @@ public class RebootEscrowManagerTests { mInjected = injected; } + MockInjector(Context context, UserManager userManager, + ResumeOnRebootServiceConnection serviceConnection, + RebootEscrowKeyStoreManager keyStoreManager, + LockSettingsStorageTestable storage, + MockableRebootEscrowInjected injected) { + super(context, storage); + mServiceConnection = serviceConnection; + mRebootEscrow = null; + RebootEscrowProviderServerBasedImpl.Injector injector = + new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection); + mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(storage, injector); + mUserManager = userManager; + mKeyStoreManager = keyStoreManager; + mInjected = injected; + } + @Override public UserManager getUserManager() { return mUserManager; @@ -165,6 +186,7 @@ public class RebootEscrowManagerTests { mUserManager = mock(UserManager.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); + mServiceConnection = mock(ResumeOnRebootServiceConnection.class); mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class); mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES"); @@ -186,7 +208,12 @@ public class RebootEscrowManagerTests { when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true); mInjected = mock(MockableRebootEscrowInjected.class); mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow, - mKeyStoreManager, mInjected), mCallbacks, mStorage); + mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage); + } + + private void setServerBasedRebootEscrowProvider() throws Exception { + mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, + mServiceConnection, mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage); } @Test @@ -202,6 +229,19 @@ public class RebootEscrowManagerTests { } @Test + public void prepareRebootEscrowServerBased_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } + + @Test public void prepareRebootEscrow_ClearCredentials_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); @@ -246,6 +286,28 @@ public class RebootEscrowManagerTests { } @Test + public void armServiceServerBased_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + + assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + } + + @Test public void armService_HalFailure_NonFatal() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); @@ -346,6 +408,40 @@ public class RebootEscrowManagerTests { } @Test + public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + + when(mServiceConnection.unwrap(any(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + mService.loadRebootEscrowDataIfAvailable(); + verify(mServiceConnection).unwrap(any(), anyLong()); + assertTrue(metricsSuccessCaptor.getValue()); + verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); + } + + @Test public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception { when(mInjected.getBootCount()).thenReturn(0); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java new file mode 100644 index 000000000000..bc1e025dd99f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java @@ -0,0 +1,145 @@ +/* + * 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.locksettings; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +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; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RebootEscrowProviderServerBasedImplTests { + private SecretKey mKeyStoreEncryptionKey; + private RebootEscrowKey mRebootEscrowKey; + private ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection mServiceConnection; + private LockSettingsStorageTestable mStorage; + private RebootEscrowProviderServerBasedImpl mRebootEscrowProvider; + private Answer<byte[]> mFakeEncryption; + + private static final byte[] TEST_AES_KEY = new byte[] { + 0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31, + 0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61, + 0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09, + 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23, + }; + + @Before + public void setUp() throws Exception { + mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES"); + mRebootEscrowKey = RebootEscrowKey.generate(); + mServiceConnection = mock( + ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection.class); + + Context context = new ContextWrapper(InstrumentationRegistry.getContext()); + mStorage = new LockSettingsStorageTestable(context, + new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings")); + mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mStorage, + new RebootEscrowProviderServerBasedImpl.Injector(mServiceConnection)); + + mFakeEncryption = invocation -> { + byte[] secret = invocation.getArgument(0); + for (int i = 0; i < secret.length; i++) { + secret[i] = (byte) (secret[i] ^ 0xf); + } + return secret; + }; + } + + @Test + public void getAndClearRebootEscrowKey_loopback_success() throws Exception { + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); + when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer(mFakeEncryption); + + assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); + mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + + RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey( + mKeyStoreEncryptionKey); + assertThat(ks.getKeyBytes(), is(mRebootEscrowKey.getKeyBytes())); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } + + @Test + public void getAndClearRebootEscrowKey_WrongDecryptionMethod_failure() throws Exception { + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); + when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer( + invocation -> { + byte[] secret = invocation.getArgument(0); + for (int i = 0; i < secret.length; i++) { + secret[i] = (byte) (secret[i] ^ 0xe); + } + return secret; + } + ); + + assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); + mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // Expect to get wrong key bytes + RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey( + mKeyStoreEncryptionKey); + assertNotEquals(ks.getKeyBytes(), mRebootEscrowKey.getKeyBytes()); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } + + @Test + public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception { + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); + doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong()); + + assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); + mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // Expect to get null key bytes when the server service fails to unwrap the blob. + RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey( + mKeyStoreEncryptionKey); + assertNull(ks); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java new file mode 100644 index 000000000000..b9af82b64c02 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/ResumeOnRebootServiceProviderTests.java @@ -0,0 +1,111 @@ +/* + * 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.locksettings; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.service.resumeonreboot.ResumeOnRebootService; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@SmallTest +@RunWith(JUnit4.class) +public class ResumeOnRebootServiceProviderTests { + + @Mock + Context mMockContext; + @Mock + PackageManager mMockPackageManager; + @Mock + ResolveInfo mMockResolvedInfo; + @Mock + ServiceInfo mMockServiceInfo; + @Mock + ComponentName mMockComponentName; + @Captor + ArgumentCaptor<Intent> mIntentArgumentCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockContext.getUserId()).thenReturn(0); + when(mMockResolvedInfo.serviceInfo).thenReturn(mMockServiceInfo); + when(mMockServiceInfo.getComponentName()).thenReturn(mMockComponentName); + } + + @Test + public void noServiceFound() throws Exception { + when(mMockPackageManager.queryIntentServices(any(), + eq(PackageManager.MATCH_SYSTEM_ONLY))).thenReturn( + null); + assertThat(new ResumeOnRebootServiceProvider(mMockContext, + mMockPackageManager).getServiceConnection()).isNull(); + } + + @Test + public void serviceNotGuardedWithPermission() throws Exception { + ArrayList<ResolveInfo> resultList = new ArrayList<>(); + when(mMockServiceInfo.permission).thenReturn(""); + resultList.add(mMockResolvedInfo); + when(mMockPackageManager.queryIntentServices(any(), any())).thenReturn( + resultList); + assertThat(new ResumeOnRebootServiceProvider(mMockContext, + mMockPackageManager).getServiceConnection()).isNull(); + } + + @Test + public void serviceResolved() throws Exception { + ArrayList<ResolveInfo> resultList = new ArrayList<>(); + resultList.add(mMockResolvedInfo); + when(mMockServiceInfo.permission).thenReturn( + Manifest.permission.BIND_RESUME_ON_REBOOT_SERVICE); + when(mMockPackageManager.queryIntentServices(any(), + eq(PackageManager.MATCH_SYSTEM_ONLY))).thenReturn( + resultList); + + assertThat(new ResumeOnRebootServiceProvider(mMockContext, + mMockPackageManager).getServiceConnection()).isNotNull(); + + verify(mMockPackageManager).queryIntentServices(mIntentArgumentCaptor.capture(), + eq(PackageManager.MATCH_SYSTEM_ONLY)); + assertThat(mIntentArgumentCaptor.getValue().getAction()).isEqualTo( + ResumeOnRebootService.SERVICE_INTERFACE); + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index 4db7ce2e6ef5..df19aeb13707 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2051,6 +2051,7 @@ public class NetworkPolicyManagerServiceTest { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.setSSID(TEST_SSID); return new NetworkState(info, prop, networkCapabilities, null, null, TEST_SSID); } diff --git a/services/tests/shortcutmanagerutils/OWNERS b/services/tests/shortcutmanagerutils/OWNERS new file mode 100644 index 000000000000..d825dfd7cf00 --- /dev/null +++ b/services/tests/shortcutmanagerutils/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pm/OWNERS diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index e3eb0b582b1c..3a9896a5a91d 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2774,6 +2774,30 @@ public class CarrierConfigManager { public static final String IMSI_KEY_DOWNLOAD_URL_STRING = "imsi_key_download_url_string"; /** + * String representation of a carrier's public key used for IMSI encryption for ePDG. If this + * is provided, the device will use it as a fallback when no key exists on device, but the key + * download will still initiate. + * Example string: + * "-----BEGIN CERTIFICATE-----\nabcde12345abcde12345abcde12345abcde1234 + * 5abcde12345abcde12345\nabcde12345abcde12345abcde12345abcde12345a\n-----END CERTIFICATE-----" + * @hide + */ + public static final String IMSI_CARRIER_PUBLIC_KEY_EPDG_STRING = + "imsi_carrier_public_key_epdg_string"; + + /** + * String representation of a carrier's public key used for IMSI encryption for WLAN. If this + * is provided, the device will use it as a fallback when no key exists on device, but the key + * download will still initiate. + * Example string: + * "-----BEGIN CERTIFICATE-----\nabcde12345abcde12345abcde12345abcde1234 + * 5abcde12345abcde12345\nabcde12345abcde12345abcde12345abcde12345a\n-----END CERTIFICATE-----" + * @hide + */ + public static final String IMSI_CARRIER_PUBLIC_KEY_WLAN_STRING = + "imsi_carrier_public_key_wlan_string"; + + /** * Identifies if the key is available for WLAN or EPDG or both. The value is a bitmask. * 0 indicates that neither EPDG or WLAN is enabled. * 1 indicates that key type TelephonyManager#KEY_TYPE_EPDG is enabled. @@ -4089,6 +4113,12 @@ public class CarrierConfigManager { "default_rtt_mode_int"; /** + * Indicates whether RTT is supported while roaming. + */ + public static final String KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL = + "rtt_supported_while_roaming_bool"; + + /** * Indicates if auto-configuration server is used for the RCS config * Reference: GSMA RCC.14 */ @@ -4445,6 +4475,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false); sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0); sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null); + sDefaults.putString(IMSI_CARRIER_PUBLIC_KEY_EPDG_STRING, null); + sDefaults.putString(IMSI_CARRIER_PUBLIC_KEY_WLAN_STRING, null); sDefaults.putBoolean(KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL, false); sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); @@ -4453,6 +4485,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_HIDE_TTY_HCO_VCO_WITH_RTT_BOOL, false); + sDefaults.putBoolean(KEY_RTT_SUPPORTED_WHILE_ROAMING_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true); sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null); diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index d502da9fb9ec..99a77ae5d133 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -915,6 +915,8 @@ public final class DataFailCause { public static final int IPV6_PREFIX_UNAVAILABLE = 0x8CA; /** System preference change back to SRAT during handoff */ public static final int HANDOFF_PREFERENCE_CHANGED = 0x8CB; + /** Data call fail due to the slice not being allowed for the data call. */ + public static final int SLICE_REJECTED = 0x8CC; //IKE error notifications message as specified in 3GPP TS 24.302 (Section 8.1.2.2). @@ -985,7 +987,7 @@ public final class DataFailCause { * the authentication failed. */ public static final int IWLAN_IKEV2_AUTH_FAILURE = 0x4001; - /** IKE message timeout, tunnel setup failed due to no response from EPDG */ + /** IKE message timeout, tunnel setup failed due to no response from EPDG */ public static final int IWLAN_IKEV2_MSG_TIMEOUT = 0x4002; /** IKE Certification validation failure */ public static final int IWLAN_IKEV2_CERT_INVALID = 0x4003; @@ -1419,6 +1421,7 @@ public final class DataFailCause { sFailCauseMap.put(VSNCP_RECONNECT_NOT_ALLOWED, "VSNCP_RECONNECT_NOT_ALLOWED"); sFailCauseMap.put(IPV6_PREFIX_UNAVAILABLE, "IPV6_PREFIX_UNAVAILABLE"); sFailCauseMap.put(HANDOFF_PREFERENCE_CHANGED, "HANDOFF_PREFERENCE_CHANGED"); + sFailCauseMap.put(SLICE_REJECTED, "SLICE_REJECTED"); sFailCauseMap.put(IWLAN_PDN_CONNECTION_REJECTION, "IWLAN_PDN_CONNECTION_REJECTION"); sFailCauseMap.put(IWLAN_MAX_CONNECTION_REACHED, "IWLAN_MAX_CONNECTION_REACHED"); sFailCauseMap.put(IWLAN_SEMANTIC_ERROR_IN_THE_TFT_OPERATION, diff --git a/telephony/java/android/telephony/ImsiEncryptionInfo.java b/telephony/java/android/telephony/ImsiEncryptionInfo.java index 75a79d62d2aa..4978692d3964 100644 --- a/telephony/java/android/telephony/ImsiEncryptionInfo.java +++ b/telephony/java/android/telephony/ImsiEncryptionInfo.java @@ -163,8 +163,8 @@ public final class ImsiEncryptionInfo implements Parcelable { public String toString(){ return "[ImsiEncryptionInfo " + "mcc=" + mcc - + "mnc=" + mnc - + "publicKey=" + publicKey + + " mnc=" + mnc + + " publicKey=" + publicKey + ", keyIdentifier=" + keyIdentifier + ", keyType=" + keyType + ", expirationTime=" + expirationTime diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index 8348502586a5..46ec4a39fd21 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -136,6 +136,7 @@ public final class DataCallResponse implements Parcelable { private final int mPduSessionId; private final Qos mDefaultQos; private final List<QosSession> mQosSessions; + private final SliceInfo mSliceInfo; /** * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error. @@ -186,6 +187,7 @@ public final class DataCallResponse implements Parcelable { mPduSessionId = PDU_SESSION_ID_NOT_SET; mDefaultQos = null; mQosSessions = new ArrayList<>(); + mSliceInfo = null; } private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id, @@ -194,7 +196,8 @@ public final class DataCallResponse implements Parcelable { @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses, @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6, @HandoverFailureMode int handoverFailureMode, int pduSessionId, - @Nullable Qos defaultQos, @Nullable List<QosSession> qosSessions) { + @Nullable Qos defaultQos, @Nullable List<QosSession> qosSessions, + @Nullable SliceInfo sliceInfo) { mCause = cause; mSuggestedRetryTime = suggestedRetryTime; mId = id; @@ -216,6 +219,7 @@ public final class DataCallResponse implements Parcelable { mPduSessionId = pduSessionId; mDefaultQos = defaultQos; mQosSessions = qosSessions; + mSliceInfo = sliceInfo; } /** @hide */ @@ -243,6 +247,7 @@ public final class DataCallResponse implements Parcelable { mDefaultQos = source.readParcelable(Qos.class.getClassLoader()); mQosSessions = new ArrayList<>(); source.readList(mQosSessions, QosSession.class.getClassLoader()); + mSliceInfo = source.readParcelable(SliceInfo.class.getClassLoader()); } /** @@ -368,7 +373,7 @@ public final class DataCallResponse implements Parcelable { } /** - * @return default QOS of the data call received from the network + * @return default QOS of the data connection received from the network * * @hide */ @@ -379,16 +384,24 @@ public final class DataCallResponse implements Parcelable { } /** - * @return All the dedicated bearer QOS sessions of the data call received from the network + * @return All the dedicated bearer QOS sessions of the data connection received from the + * network. * * @hide */ - @NonNull public List<QosSession> getQosSessions() { return mQosSessions; } + /** + * @return The slice info related to this data connection. + */ + @Nullable + public SliceInfo getSliceInfo() { + return mSliceInfo; + } + @NonNull @Override public String toString() { @@ -411,6 +424,7 @@ public final class DataCallResponse implements Parcelable { .append(" pduSessionId=").append(getPduSessionId()) .append(" defaultQos=").append(mDefaultQos) .append(" qosSessions=").append(mQosSessions) + .append(" sliceInfo=").append(mSliceInfo) .append("}"); return sb.toString(); } @@ -454,7 +468,8 @@ public final class DataCallResponse implements Parcelable { && mHandoverFailureMode == other.mHandoverFailureMode && mPduSessionId == other.mPduSessionId && isQosSame - && isQosSessionsSame; + && isQosSessionsSame + && Objects.equals(mSliceInfo, other.mSliceInfo); } @Override @@ -462,7 +477,7 @@ public final class DataCallResponse implements Parcelable { return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos, - mQosSessions); + mQosSessions, mSliceInfo); } @Override @@ -493,6 +508,7 @@ public final class DataCallResponse implements Parcelable { dest.writeParcelable((NrQos)mDefaultQos, flags); } dest.writeList(mQosSessions); + dest.writeParcelable(mSliceInfo, flags); } public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR = @@ -576,6 +592,8 @@ public final class DataCallResponse implements Parcelable { private List<QosSession> mQosSessions = new ArrayList<>(); + private SliceInfo mSliceInfo; + /** * Default constructor for Builder. */ @@ -799,6 +817,21 @@ public final class DataCallResponse implements Parcelable { } /** + * The Slice used for this data connection. + * <p/> + * If a handover occurs from EPDG to 5G, + * this is the {@link SliceInfo} used in {@link DataService#setupDataCall}. + * + * @param sliceInfo the slice info for the data call + * + * @return The same instance of the builder. + */ + public @NonNull Builder setSliceInfo(@Nullable SliceInfo sliceInfo) { + mSliceInfo = sliceInfo; + return this; + } + + /** * Build the DataCallResponse. * * @return the DataCallResponse object. @@ -807,7 +840,7 @@ public final class DataCallResponse implements Parcelable { return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, - mDefaultQos, mQosSessions); + mDefaultQos, mQosSessions, mSliceInfo); } } } diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 2ec965101930..03c2ef9d9baa 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -194,13 +194,19 @@ public abstract class DataService extends Service { * The standard range of values are 1-15 while 0 means no pdu session id * was attached to this call. Reference: 3GPP TS 24.007 section * 11.2.3.1b. + * @param sliceInfo used within the data connection when a handover occurs from EPDG to 5G. + * The value is null unless the access network is + * {@link android.telephony.AccessNetworkConstants.AccessNetworkType#NGRAN} and a + * handover is occurring from EPDG to 5G. If the slice passed is rejected, then + * {@link DataCallResponse#getCause()} is + * {@link android.telephony.DataFailCause#SLICE_REJECTED}. * @param callback The result callback for this request. */ public void setupDataCall(int accessNetworkType, @NonNull DataProfile dataProfile, boolean isRoaming, boolean allowRoaming, @SetupDataReason int reason, @Nullable LinkProperties linkProperties, - @IntRange(from = 0, to = 15) int pduSessionId, + @IntRange(from = 0, to = 15) int pduSessionId, @Nullable SliceInfo sliceInfo, @NonNull DataServiceCallback callback) { /* Call the old version since the new version isn't supported */ setupDataCall(accessNetworkType, dataProfile, isRoaming, allowRoaming, reason, @@ -392,10 +398,11 @@ public abstract class DataService extends Service { public final int reason; public final LinkProperties linkProperties; public final int pduSessionId; + public final SliceInfo sliceInfo; public final IDataServiceCallback callback; SetupDataCallRequest(int accessNetworkType, DataProfile dataProfile, boolean isRoaming, boolean allowRoaming, int reason, LinkProperties linkProperties, - int pduSessionId, IDataServiceCallback callback) { + int pduSessionId, SliceInfo sliceInfo, IDataServiceCallback callback) { this.accessNetworkType = accessNetworkType; this.dataProfile = dataProfile; this.isRoaming = isRoaming; @@ -403,6 +410,7 @@ public abstract class DataService extends Service { this.linkProperties = linkProperties; this.reason = reason; this.pduSessionId = pduSessionId; + this.sliceInfo = sliceInfo; this.callback = callback; } } @@ -513,6 +521,7 @@ public abstract class DataService extends Service { setupDataCallRequest.dataProfile, setupDataCallRequest.isRoaming, setupDataCallRequest.allowRoaming, setupDataCallRequest.reason, setupDataCallRequest.linkProperties, setupDataCallRequest.pduSessionId, + setupDataCallRequest.sliceInfo, (setupDataCallRequest.callback != null) ? new DataServiceCallback(setupDataCallRequest.callback) : null); @@ -676,10 +685,12 @@ public abstract class DataService extends Service { @Override public void setupDataCall(int slotIndex, int accessNetworkType, DataProfile dataProfile, boolean isRoaming, boolean allowRoaming, int reason, - LinkProperties linkProperties, int pduSessionId, IDataServiceCallback callback) { + LinkProperties linkProperties, int pduSessionId, SliceInfo sliceInfo, + IDataServiceCallback callback) { mHandler.obtainMessage(DATA_SERVICE_REQUEST_SETUP_DATA_CALL, slotIndex, 0, new SetupDataCallRequest(accessNetworkType, dataProfile, isRoaming, - allowRoaming, reason, linkProperties, pduSessionId, callback)) + allowRoaming, reason, linkProperties, pduSessionId, sliceInfo, + callback)) .sendToTarget(); } diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl index 3f1f033d6f11..e0b9a1a9bb5a 100644 --- a/telephony/java/android/telephony/data/IDataService.aidl +++ b/telephony/java/android/telephony/data/IDataService.aidl @@ -19,6 +19,7 @@ package android.telephony.data; import android.net.LinkProperties; import android.telephony.data.DataProfile; import android.telephony.data.IDataServiceCallback; +import android.telephony.data.SliceInfo; /** * {@hide} @@ -29,7 +30,7 @@ oneway interface IDataService void removeDataServiceProvider(int slotId); void setupDataCall(int slotId, int accessNetwork, in DataProfile dataProfile, boolean isRoaming, boolean allowRoaming, int reason, in LinkProperties linkProperties, - int pduSessionId, IDataServiceCallback callback); + int pduSessionId, in SliceInfo sliceInfo, IDataServiceCallback callback); void deactivateDataCall(int slotId, int cid, int reason, IDataServiceCallback callback); void setInitialAttachApn(int slotId, in DataProfile dataProfile, boolean isRoaming, IDataServiceCallback callback); diff --git a/telephony/java/android/telephony/data/SliceInfo.aidl b/telephony/java/android/telephony/data/SliceInfo.aidl new file mode 100644 index 000000000000..286ea5e4f8c7 --- /dev/null +++ b/telephony/java/android/telephony/data/SliceInfo.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 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. + */ + +/** @hide */ +package android.telephony.data; + +parcelable SliceInfo; diff --git a/telephony/java/android/telephony/data/SliceInfo.java b/telephony/java/android/telephony/data/SliceInfo.java new file mode 100644 index 000000000000..51857a7b4908 --- /dev/null +++ b/telephony/java/android/telephony/data/SliceInfo.java @@ -0,0 +1,342 @@ +/* + * 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.telephony.data; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SuppressLint; +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; + +/** + * Represents a S-NSSAI as defined in 3GPP TS 24.501. + * + * @hide + */ +@SystemApi +public final class SliceInfo implements Parcelable { + /** + * When set on a Slice Differentiator, this value indicates that there is no corresponding + * Slice. + */ + public static final int SLICE_DIFFERENTIATOR_NO_SLICE = -1; + + /** + * Indicates that the service type is not present. + */ + public static final int SLICE_SERVICE_TYPE_NONE = 0; + + /** + * Slice suitable for the handling of 5G enhanced Mobile Broadband. + */ + public static final int SLICE_SERVICE_TYPE_EMBB = 1; + + /** + * Slice suitable for the handling of ultra-reliable low latency communications. + */ + public static final int SLICE_SERVICE_TYPE_URLLC = 2; + + /** + * Slice suitable for the handling of massive IoT. + */ + public static final int SLICE_SERVICE_TYPE_MIOT = 3; + + /** + * The min acceptable value for a Slice Differentiator + */ + @SuppressLint("MinMaxConstant") + public static final int MIN_SLICE_DIFFERENTIATOR = -1; + + /** + * The max acceptable value for a Slice Differentiator + */ + @SuppressLint("MinMaxConstant") + public static final int MAX_SLICE_DIFFERENTIATOR = 0xFFFFFE; + + /** @hide */ + @IntDef(prefix = { "SLICE_SERVICE_TYPE_" }, value = { + SLICE_SERVICE_TYPE_NONE, + SLICE_SERVICE_TYPE_EMBB, + SLICE_SERVICE_TYPE_URLLC, + SLICE_SERVICE_TYPE_MIOT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SliceServiceType {} + + + @SliceServiceType + private final int mSliceServiceType; + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + private final int mSliceDifferentiator; + @SliceServiceType + private final int mMappedHplmnSliceServiceType; + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + private final int mMappedHplmnSliceDifferentiator; + + private SliceInfo(@SliceServiceType int sliceServiceType, + int sliceDifferentiator, int mappedHplmnSliceServiceType, + int mappedHplmnSliceDifferentiator) { + mSliceServiceType = sliceServiceType; + mSliceDifferentiator = sliceDifferentiator; + mMappedHplmnSliceDifferentiator = mappedHplmnSliceDifferentiator; + mMappedHplmnSliceServiceType = mappedHplmnSliceServiceType; + } + + /** + * The type of service provided by the slice. + * <p/> + * see: 3GPP TS 24.501 Section 9.11.2.8. + */ + @SliceServiceType + public int getSliceServiceType() { + return mSliceServiceType; + } + + /** + * Identifies the slice from others with the same Slice Service Type. + * <p/> + * Returns {@link #SLICE_DIFFERENTIATOR_NO_SLICE} if {@link #getSliceServiceType} returns + * {@link #SLICE_SERVICE_TYPE_NONE}. + * <p/> + * see: 3GPP TS 24.501 Section 9.11.2.8. + */ + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + public int getSliceDifferentiator() { + return mSliceDifferentiator; + } + + /** + * Corresponds to a Slice Info (S-NSSAI) of the HPLMN. + * <p/> + * see: 3GPP TS 24.501 Section 9.11.2.8. + */ + @SliceServiceType + public int getMappedHplmnSliceServiceType() { + return mMappedHplmnSliceServiceType; + } + + /** + * This Slice Differentiator corresponds to a {@link SliceInfo} (S-NSSAI) of the HPLMN; + * {@link #getSliceDifferentiator()} is mapped to this value. + * <p/> + * Returns {@link #SLICE_DIFFERENTIATOR_NO_SLICE} if either of the following are true: + * <ul> + * <li>{@link #getSliceDifferentiator()} returns {@link #SLICE_DIFFERENTIATOR_NO_SLICE}</li> + * <li>{@link #getMappedHplmnSliceServiceType()} returns {@link #SLICE_SERVICE_TYPE_NONE}</li> + * </ul> + * <p/> + * see: 3GPP TS 24.501 Section 9.11.2.8. + */ + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + public int getMappedHplmnSliceDifferentiator() { + return mMappedHplmnSliceDifferentiator; + } + + private SliceInfo(@NonNull Parcel in) { + mSliceServiceType = in.readInt(); + mSliceDifferentiator = in.readInt(); + mMappedHplmnSliceServiceType = in.readInt(); + mMappedHplmnSliceDifferentiator = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSliceServiceType); + dest.writeInt(mSliceDifferentiator); + dest.writeInt(mMappedHplmnSliceServiceType); + dest.writeInt(mMappedHplmnSliceDifferentiator); + } + + public static final @android.annotation.NonNull Parcelable.Creator<SliceInfo> CREATOR = + new Parcelable.Creator<SliceInfo>() { + @Override + @NonNull + public SliceInfo createFromParcel(@NonNull Parcel source) { + return new SliceInfo(source); + } + + @Override + @NonNull + public SliceInfo[] newArray(int size) { + return new SliceInfo[size]; + } + }; + + @Override + public String toString() { + return "SliceInfo{" + + "mSliceServiceType=" + sliceServiceTypeToString(mSliceServiceType) + + ", mSliceDifferentiator=" + mSliceDifferentiator + + ", mMappedHplmnSliceServiceType=" + + sliceServiceTypeToString(mMappedHplmnSliceServiceType) + + ", mMappedHplmnSliceDifferentiator=" + mMappedHplmnSliceDifferentiator + + '}'; + } + + private static String sliceServiceTypeToString(@SliceServiceType int sliceServiceType) { + switch(sliceServiceType) { + case SLICE_SERVICE_TYPE_NONE: + return "NONE"; + case SLICE_SERVICE_TYPE_EMBB: + return "EMBB"; + case SLICE_SERVICE_TYPE_URLLC: + return "URLLC"; + case SLICE_SERVICE_TYPE_MIOT: + return "MIOT"; + default: + return Integer.toString(sliceServiceType); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SliceInfo sliceInfo = (SliceInfo) o; + return mSliceServiceType == sliceInfo.mSliceServiceType + && mSliceDifferentiator == sliceInfo.mSliceDifferentiator + && mMappedHplmnSliceServiceType == sliceInfo.mMappedHplmnSliceServiceType + && mMappedHplmnSliceDifferentiator == sliceInfo.mMappedHplmnSliceDifferentiator; + } + + @Override + public int hashCode() { + return Objects.hash(mSliceServiceType, mSliceDifferentiator, mMappedHplmnSliceServiceType, + mMappedHplmnSliceDifferentiator); + } + + /** + * Provides a convenient way to set the fields of a {@link SliceInfo} when creating a + * new instance. + * + * <p>The example below shows how you might create a new {@code SliceInfo}: + * + * <pre><code> + * + * SliceInfo response = new SliceInfo.Builder() + * .setSliceServiceType(SLICE_SERVICE_TYPE_URLLC) + * .build(); + * </code></pre> + */ + public static final class Builder { + @SliceServiceType + private int mSliceServiceType = SLICE_SERVICE_TYPE_NONE; + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + private int mSliceDifferentiator = SLICE_DIFFERENTIATOR_NO_SLICE; + @SliceServiceType + private int mMappedHplmnSliceServiceType = SLICE_SERVICE_TYPE_NONE; + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + private int mMappedHplmnSliceDifferentiator = SLICE_DIFFERENTIATOR_NO_SLICE; + + /** + * Default constructor for Builder. + */ + public Builder() { + } + + /** + * Set the Slice Service Type. + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setSliceServiceType(@SliceServiceType int mSliceServiceType) { + this.mSliceServiceType = mSliceServiceType; + return this; + } + + /** + * Set the Slice Differentiator. + * <p/> + * A value of {@link #SLICE_DIFFERENTIATOR_NO_SLICE} indicates that there is no + * corresponding Slice. + * + * @throws IllegalArgumentException if the parameter is not between + * {@link #MIN_SLICE_DIFFERENTIATOR} and {@link #MAX_SLICE_DIFFERENTIATOR}. + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setSliceDifferentiator( + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + int sliceDifferentiator) { + if (sliceDifferentiator < MIN_SLICE_DIFFERENTIATOR + || sliceDifferentiator > MAX_SLICE_DIFFERENTIATOR) { + throw new IllegalArgumentException("The slice diffentiator value is out of range"); + } + this.mSliceDifferentiator = sliceDifferentiator; + return this; + } + + /** + * Set the HPLMN Slice Service Type. + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setMappedHplmnSliceServiceType( + @SliceServiceType int mappedHplmnSliceServiceType) { + this.mMappedHplmnSliceServiceType = mappedHplmnSliceServiceType; + return this; + } + + /** + * Set the HPLMN Slice Differentiator. + * <p/> + * A value of {@link #SLICE_DIFFERENTIATOR_NO_SLICE} indicates that there is no + * corresponding Slice of the HPLMN. + * + * @throws IllegalArgumentException if the parameter is not between + * {@link #MIN_SLICE_DIFFERENTIATOR} and {@link #MAX_SLICE_DIFFERENTIATOR}. + * + * @return The same instance of the builder. + */ + @NonNull + public Builder setMappedHplmnSliceDifferentiator( + @IntRange(from = MIN_SLICE_DIFFERENTIATOR, to = MAX_SLICE_DIFFERENTIATOR) + int mappedHplmnSliceDifferentiator) { + if (mappedHplmnSliceDifferentiator < MIN_SLICE_DIFFERENTIATOR + || mappedHplmnSliceDifferentiator > MAX_SLICE_DIFFERENTIATOR) { + throw new IllegalArgumentException("The slice diffentiator value is out of range"); + } + this.mMappedHplmnSliceDifferentiator = mappedHplmnSliceDifferentiator; + return this; + } + + /** + * Build the {@link SliceInfo}. + * + * @return the {@link SliceInfo} object. + */ + @NonNull + public SliceInfo build() { + return new SliceInfo(this.mSliceServiceType, this.mSliceDifferentiator, + this.mMappedHplmnSliceServiceType, this.mMappedHplmnSliceDifferentiator); + } + } +} diff --git a/telephony/java/android/telephony/ims/SipMessage.java b/telephony/java/android/telephony/ims/SipMessage.java index 006cca84e44b..9cfa640fce18 100644 --- a/telephony/java/android/telephony/ims/SipMessage.java +++ b/telephony/java/android/telephony/ims/SipMessage.java @@ -16,6 +16,8 @@ package android.telephony.ims; +import static java.nio.charset.StandardCharsets.UTF_8; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Build; @@ -39,6 +41,7 @@ import java.util.Objects; public final class SipMessage implements Parcelable { // Should not be set to true for production! private static final boolean IS_DEBUGGING = Build.IS_ENG; + private static final String CRLF = "\r\n"; private final String mStartLine; private final String mHeaderSection; @@ -165,4 +168,19 @@ public final class SipMessage implements Parcelable { result = 31 * result + Arrays.hashCode(mContent); return result; } + + /** + * @return the UTF-8 encoded SIP message. + */ + public @NonNull byte[] getEncodedMessage() { + byte[] header = new StringBuilder() + .append(mStartLine) + .append(mHeaderSection) + .append(CRLF) + .toString().getBytes(UTF_8); + byte[] sipMessage = new byte[header.length + mContent.length]; + System.arraycopy(header, 0, sipMessage, 0, header.length); + System.arraycopy(mContent, 0, sipMessage, header.length, mContent.length); + return sipMessage; + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 053774241900..e556664bb323 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2367,6 +2367,11 @@ interface ITelephony { boolean setCarrierSingleRegistrationEnabledOverride(int subId, String enabled); /** + * Sends a device to device message; only for use through shell. + */ + void sendDeviceToDeviceMessage(int message, int value); + + /** * Gets the config of RCS VoLTE single registration enabled for the carrier/subscription. */ boolean getCarrierSingleRegistrationEnabled(int subId); diff --git a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt index e9227e94da98..eb04f6907748 100644 --- a/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt +++ b/tests/PlatformCompatGating/src/com/android/tests/gating/PlatformCompatCommandNotInstalledTest.kt @@ -131,6 +131,10 @@ class PlatformCompatCommandNotInstalledTest { assertThat(platformCompat.isChangeEnabled(TEST_CHANGE_ID, appInfo)).isEqualTo(params.result) } - private fun command(command: String) = - FileReader(uiAutomation.executeShellCommand(command).fileDescriptor).readText() + private fun command(command: String): String { + val fileDescriptor = uiAutomation.executeShellCommand(command) + return String(ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor).use { + inputStream -> inputStream.readBytes() + }) + } } diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index 16c486562f53..083c8c8741da 100644 --- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -38,6 +38,7 @@ import android.net.metrics.IpConnectivityLog import android.os.ConditionVariable import android.os.IBinder import android.os.INetworkManagementService +import android.os.UserHandle import android.testing.TestableContext import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -55,10 +56,13 @@ import org.junit.Before import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith +import org.mockito.AdditionalAnswers import org.mockito.Mock import org.mockito.Mockito.any +import org.mockito.Mockito.anyInt import org.mockito.Mockito.doNothing import org.mockito.Mockito.doReturn +import org.mockito.Mockito.eq import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations @@ -143,7 +147,10 @@ class ConnectivityServiceIntegrationTest { @Before fun setUp() { MockitoAnnotations.initMocks(this) - doNothing().`when`(context).sendStickyBroadcastAsUser(any(), any(), any()) + val asUserCtx = mock(Context::class.java, AdditionalAnswers.delegatesTo<Context>(context)) + doReturn(UserHandle.ALL).`when`(asUserCtx).user + doReturn(asUserCtx).`when`(context).createContextAsUser(eq(UserHandle.ALL), anyInt()) + doNothing().`when`(context).sendStickyBroadcast(any(), any()) networkStackClient = TestNetworkStackClient(realContext) networkStackClient.init() diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt index 9ba56e44fe88..91fcbc0fd5d7 100644 --- a/tests/net/java/android/net/NetworkTemplateTest.kt +++ b/tests/net/java/android/net/NetworkTemplateTest.kt @@ -67,6 +67,7 @@ class NetworkTemplateTest { val caps = NetworkCapabilities().apply { setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false) setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true) + setSSID(ssid) } return NetworkState(info, lp, caps, mock(Network::class.java), subscriberId, ssid) } diff --git a/tests/net/java/android/net/QosSocketFilterTest.java b/tests/net/java/android/net/QosSocketFilterTest.java new file mode 100644 index 000000000000..ad58960eaadd --- /dev/null +++ b/tests/net/java/android/net/QosSocketFilterTest.java @@ -0,0 +1,75 @@ +/* + * 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 static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.net.InetSocketAddress; + +@RunWith(AndroidJUnit4.class) +@androidx.test.filters.SmallTest +public class QosSocketFilterTest { + + @Test + public void testPortExactMatch() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertTrue(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 10), addressB, 10, 10)); + + } + + @Test + public void testPortLessThanStart() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertFalse(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 8), addressB, 10, 10)); + } + + @Test + public void testPortGreaterThanEnd() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertFalse(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 18), addressB, 10, 10)); + } + + @Test + public void testPortBetweenStartAndEnd() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4"); + assertTrue(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 10), addressB, 8, 18)); + } + + @Test + public void testAddressesDontMatch() { + final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4"); + final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.5"); + assertFalse(QosSocketFilter.matchesLocalAddress( + new InetSocketAddress(addressA, 10), addressB, 10, 10)); + } +} + diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index f893e9eea486..81c16887c6ee 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -64,6 +64,7 @@ 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_RCS; @@ -201,6 +202,7 @@ import android.net.SocketKeepalive; import android.net.UidRange; import android.net.UidRangeParcel; import android.net.Uri; +import android.net.VpnInfo; import android.net.VpnManager; import android.net.metrics.IpConnectivityLog; import android.net.shared.NetworkMonitorUtils; @@ -245,7 +247,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IBatteryStats; import com.android.internal.net.VpnConfig; -import com.android.internal.net.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.internal.util.ArrayUtils; import com.android.internal.util.WakeupMessage; @@ -5963,23 +5964,18 @@ public class ConnectivityServiceTest { callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) && nc.hasTransport(TRANSPORT_WIFI)); - - // BUG: the VPN is no longer suspended, so a RESUMED callback should have been sent. - // callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); callback.assertNoCallback(); assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); // BUG: VPN caps have NOT_SUSPENDED. + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - // BUG: the device has connectivity, so this should return true. - assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); - // Unsuspend cellular and then switch back to it. - // The same bug happens in the opposite direction: the VPN's capabilities correctly have - // NOT_SUSPENDED, but the VPN's NetworkInfo is in state SUSPENDED. + // Unsuspend cellular and then switch back to it. The VPN remains not suspended. mCellNetworkAgent.resume(); callback.assertNoCallback(); mWiFiNetworkAgent.disconnect(); @@ -5996,12 +5992,11 @@ public class ConnectivityServiceTest { .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); - assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); // BUG: VPN caps have NOT_SUSPENDED. + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); - // BUG: the device has connectivity, so this should return true. - assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); - // Re-suspending the current network fixes the problem. + // Suspend cellular and expect no connectivity. mCellNetworkAgent.suspend(); callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) @@ -6017,6 +6012,7 @@ public class ConnectivityServiceTest { assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + // Resume cellular and expect that connectivity comes back. mCellNetworkAgent.resume(); callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) @@ -6407,10 +6403,7 @@ public class ConnectivityServiceTest { && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - // While the SUSPENDED callback should in theory be sent here, it is not. This is - // a bug in ConnectivityService, but as the SUSPENDED and RESUMED callbacks have never - // been public and are deprecated and slated for removal, there is no sense in spending - // resources fixing this bug now. + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Use both again. @@ -6422,8 +6415,7 @@ public class ConnectivityServiceTest { && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - // As above, the RESUMED callback not being sent here is a bug, but not a bug that's - // worth anybody's time to fix. + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Disconnect cell. Receive update without even removing the dead network from the @@ -7335,39 +7327,68 @@ public class ConnectivityServiceTest { b2.expectBroadcast(); } + /** + * Test mutable and requestable network capabilities such as + * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_VCN_MANAGED}. Verify that the + * {@code ConnectivityService} re-assign the networks accordingly. + */ @Test - public final void testLoseTrusted() throws Exception { - final NetworkRequest trustedRequest = new NetworkRequest.Builder() - .addCapability(NET_CAPABILITY_TRUSTED) - .build(); - final TestNetworkCallback trustedCallback = new TestNetworkCallback(); - mCm.requestNetwork(trustedRequest, trustedCallback); - - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - mCellNetworkAgent.connect(true); - trustedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); - - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - mWiFiNetworkAgent.connect(true); - trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + public final void testLoseMutableAndRequestableCaps() throws Exception { + final int[] testCaps = new int [] { + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VCN_MANAGED + }; + for (final int testCap : testCaps) { + // Create requests with and without the testing capability. + final TestNetworkCallback callbackWithCap = new TestNetworkCallback(); + final TestNetworkCallback callbackWithoutCap = new TestNetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(testCap).build(), + callbackWithCap); + mCm.requestNetwork(new NetworkRequest.Builder().removeCapability(testCap).build(), + callbackWithoutCap); + + // Setup networks with testing capability and verify the default network changes. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(testCap); + mCellNetworkAgent.connect(true); + callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); - mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); - trustedCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(testCap); + mWiFiNetworkAgent.connect(true); + callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + // Remove the testing capability on wifi, verify the callback and default network + // changes back to cellular. + mWiFiNetworkAgent.removeCapability(testCap); + callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); + callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); + // 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(NET_CAPABILITY_TRUSTED); - trustedCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - verify(mMockNetd).networkClearDefault(); + mCellNetworkAgent.removeCapability(testCap); + callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + callbackWithoutCap.assertNoCallback(); + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkClearDefault(); + } - mCm.unregisterNetworkCallback(trustedCallback); + mCm.unregisterNetworkCallback(callbackWithCap); + mCm.unregisterNetworkCallback(callbackWithoutCap); + } } - @Ignore // 40%+ flakiness : figure out why and re-enable. @Test public final void testBatteryStatsNetworkType() throws Exception { final LinkProperties cellLp = new LinkProperties(); @@ -7375,8 +7396,8 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); mCellNetworkAgent.connect(true); waitForIdle(); - verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); reset(mBatteryStatsService); final LinkProperties wifiLp = new LinkProperties(); @@ -7384,18 +7405,20 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); mWiFiNetworkAgent.connect(true); waitForIdle(); - verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(), - TYPE_WIFI); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(wifiLp.getInterfaceName(), + new int[] { TRANSPORT_WIFI }); reset(mBatteryStatsService); mCellNetworkAgent.disconnect(); + mWiFiNetworkAgent.disconnect(); cellLp.setInterfaceName("wifi0"); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); mCellNetworkAgent.connect(true); waitForIdle(); - verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); + mCellNetworkAgent.disconnect(); } /** @@ -7468,8 +7491,8 @@ public class ConnectivityServiceTest { assertRoutesAdded(cellNetId, ipv6Subnet, defaultRoute); verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId)); verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME); - verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); @@ -7489,7 +7512,8 @@ public class ConnectivityServiceTest { // Make sure BatteryStats was not told about any v4- interfaces, as none should have // come online yet. waitForIdle(); - verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt()); + verify(mBatteryStatsService, never()).noteNetworkInterfaceForTransports(startsWith("v4-"), + any()); verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); @@ -7542,8 +7566,8 @@ public class ConnectivityServiceTest { assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8")); for (final LinkProperties stackedLp : stackedLpsAfterChange) { - verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports( + stackedLp.getInterfaceName(), new int[] { TRANSPORT_CELLULAR }); } reset(mMockNetd); when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) @@ -8323,8 +8347,7 @@ public class ConnectivityServiceTest { assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid); mMockVpn.setVpnType(vpnType); - final VpnInfo vpnInfo = new VpnInfo(); - vpnInfo.ownerUid = vpnOwnerUid; + final VpnInfo vpnInfo = new VpnInfo(vpnOwnerUid, null, null); mMockVpn.setVpnInfo(vpnInfo); } diff --git a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java index 3aafe0b075f2..1b33930e96a9 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsBaseTest.java @@ -33,8 +33,7 @@ import static android.net.NetworkStats.TAG_NONE; import static org.junit.Assert.assertEquals; import android.net.NetworkStats; - -import com.android.internal.net.VpnInfo; +import android.net.VpnInfo; /** Superclass with utilities for NetworkStats(Service|Factory)Test */ abstract class NetworkStatsBaseTest { @@ -113,10 +112,6 @@ abstract class NetworkStatsBaseTest { } static VpnInfo createVpnInfo(String vpnIface, String[] underlyingIfaces) { - VpnInfo info = new VpnInfo(); - info.ownerUid = UID_VPN; - info.vpnIface = vpnIface; - info.underlyingIfaces = underlyingIfaces; - return info; + return new VpnInfo(UID_VPN, vpnIface, underlyingIfaces); } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java index e4996d981fac..76647a69de33 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java @@ -36,13 +36,13 @@ import static org.junit.Assert.fail; import android.content.res.Resources; import android.net.NetworkStats; import android.net.TrafficStats; +import android.net.VpnInfo; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.tests.net.R; -import com.android.internal.net.VpnInfo; import libcore.io.IoUtils; import libcore.io.Streams; diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index c7836297df75..b4e37de2267f 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -21,7 +21,6 @@ import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_VPN; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.DEFAULT_NETWORK_NO; import static android.net.NetworkStats.DEFAULT_NETWORK_YES; @@ -44,6 +43,7 @@ import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateMobileWithRatType; +import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.NetworkTemplate.buildTemplateWifiWildcard; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_REMOVED; @@ -86,6 +86,7 @@ import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.VpnInfo; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.os.ConditionVariable; import android.os.Handler; @@ -104,7 +105,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; @@ -146,7 +146,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { private static final String IMSI_2 = "310260"; private static final String TEST_SSID = "AndroidAP"; - private static NetworkTemplate sTemplateWifi = buildTemplateWifiWildcard(); + private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_SSID); private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1); private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2); @@ -291,7 +291,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // verify service has empty history for wifi assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); - // modify some number on wifi, and trigger poll event incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); @@ -567,61 +566,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { } @Test - public void testUid3gWimaxCombinedByTemplate() throws Exception { - // pretend that network comes online - expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; - expectNetworkStatsSummary(buildEmptyStats()); - expectNetworkStatsUidDetail(buildEmptyStats()); - - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); - - // create some traffic - incrementCurrentTime(HOUR_IN_MILLIS); - expectDefaultSettings(); - expectNetworkStatsSummary(buildEmptyStats()); - expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); - mService.incrementOperationCount(UID_RED, 0xF00D, 5); - - forcePollAndWaitForIdle(); - - // verify service recorded history - assertUidTotal(sTemplateImsi1, UID_RED, 1024L, 8L, 1024L, 8L, 5); - - - // now switch over to wimax network - incrementCurrentTime(HOUR_IN_MILLIS); - expectDefaultSettings(); - states = new NetworkState[] {buildWimaxState(TEST_IFACE2)}; - expectNetworkStatsSummary(buildEmptyStats()); - expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); - - mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); - forcePollAndWaitForIdle(); - - - // create traffic on second network - incrementCurrentTime(HOUR_IN_MILLIS); - expectDefaultSettings(); - expectNetworkStatsSummary(buildEmptyStats()); - expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .insertEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L)); - mService.incrementOperationCount(UID_RED, 0xFAAD, 5); - - forcePollAndWaitForIdle(); - - // verify that ALL_MOBILE template combines both - assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10); - } - - @Test public void testMobileStatsByRatType() throws Exception { final NetworkTemplate template3g = buildTemplateMobileWithRatType(null, TelephonyManager.NETWORK_TYPE_UMTS); @@ -1503,6 +1447,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered); capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); capabilities.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + capabilities.setSSID(TEST_SSID); return new NetworkState(info, prop, capabilities, WIFI_NETWORK, null, TEST_SSID); } @@ -1524,17 +1469,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, subscriberId, null); } - private static NetworkState buildWimaxState(@NonNull String iface) { - final NetworkInfo info = new NetworkInfo(TYPE_WIMAX, 0, null, null); - info.setDetailedState(DetailedState.CONNECTED, null, null); - final LinkProperties prop = new LinkProperties(); - prop.setInterfaceName(iface); - final NetworkCapabilities capabilities = new NetworkCapabilities(); - capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); - capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); - return new NetworkState(info, prop, capabilities, MOBILE_NETWORK, null, null); - } - private NetworkStats buildEmptyStats() { return new NetworkStats(getElapsedRealtime(), 0); } diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index dfd0c8a75172..86a15912b6b4 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -28,6 +28,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -39,6 +40,12 @@ public class VcnGatewayConnectionConfigTest { NetworkCapabilities.NET_CAPABILITY_INTERNET, NetworkCapabilities.NET_CAPABILITY_MMS }; public static final int[] UNDERLYING_CAPS = new int[] {NetworkCapabilities.NET_CAPABILITY_DUN}; + + static { + Arrays.sort(EXPOSED_CAPS); + Arrays.sort(UNDERLYING_CAPS); + } + public static final long[] RETRY_INTERVALS_MS = new long[] { TimeUnit.SECONDS.toMillis(5), @@ -124,12 +131,13 @@ public class VcnGatewayConnectionConfigTest { public void testBuilderAndGetters() { final VcnGatewayConnectionConfig config = buildTestConfig(); - for (int cap : EXPOSED_CAPS) { - config.hasExposedCapability(cap); - } - for (int cap : UNDERLYING_CAPS) { - config.requiresUnderlyingCapability(cap); - } + int[] exposedCaps = config.getExposedCapabilities(); + Arrays.sort(exposedCaps); + assertArrayEquals(EXPOSED_CAPS, exposedCaps); + + int[] underlyingCaps = config.getRequiredUnderlyingCapabilities(); + Arrays.sort(underlyingCaps); + assertArrayEquals(UNDERLYING_CAPS, underlyingCaps); assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMs()); assertEquals(MAX_MTU, config.getMaxMtu()); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 29cfdb6fd5c6..f0cdde33f822 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -18,6 +18,7 @@ package com.android.server; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; +import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -138,11 +139,16 @@ public class VcnManagementServiceTest { private final IBinder mMockIBinder = mock(IBinder.class); public VcnManagementServiceTest() throws Exception { - setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); - setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( - mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); - setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); + mMockContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + setupSystemService( + mMockContext, mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); + setupSystemService( + mMockContext, + mSubMgr, + Context.TELEPHONY_SUBSCRIPTION_SERVICE, + SubscriptionManager.class); + setupSystemService(mMockContext, mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); @@ -186,11 +192,6 @@ public class VcnManagementServiceTest { mTestLooper.dispatchAll(); } - private void setupSystemService(Object service, String name, Class<?> serviceClass) { - doReturn(name).when(mMockContext).getSystemServiceName(serviceClass); - doReturn(service).when(mMockContext).getSystemService(name); - } - private void setupMockedCarrierPrivilege(boolean isPrivileged) { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java new file mode 100644 index 000000000000..4ecd21503165 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +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 androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for VcnGatewayConnection.DisconnectedState */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnectionTestBase { + @Before + public void setUp() throws Exception { + super.setUp(); + + mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); + mTestLooper.dispatchAll(); + } + + @Test + public void testEnterWhileNotRunningTriggersQuit() throws Exception { + final VcnGatewayConnection vgc = + new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + + vgc.setIsRunning(false); + vgc.transitionTo(vgc.mDisconnectedState); + mTestLooper.dispatchAll(); + + assertNull(vgc.getCurrentState()); + } + + @Test + public void testNetworkChangesTriggerStateTransitions() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testNullNetworkDoesNotTriggerStateTransition() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertNull(mGatewayConnection.getCurrentState()); + verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java new file mode 100644 index 000000000000..d0fec55a6827 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +/** Tests for VcnGatewayConnection.DisconnectedState */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnectionTestBase { + @Before + public void setUp() throws Exception { + super.setUp(); + + mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession()); + + mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState); + mTestLooper.dispatchAll(); + } + + @Test + public void testIkeSessionClosed() throws Exception { + getIkeSessionCallback().onClosed(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testTimeoutExpired() throws Exception { + mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + mTestLooper.dispatchAll(); + + verify(mMockIkeSession).kill(); + } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + // Should do nothing; already tearing down. + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java new file mode 100644 index 000000000000..346785907fcf --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; +import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecTunnelInterfaceResponse; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.ipsec.ike.IkeSessionCallback; +import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.os.ParcelUuid; +import android.os.test.TestLooper; + +import com.android.server.IpSecService; + +import org.junit.Before; +import org.mockito.ArgumentCaptor; + +import java.util.UUID; + +public class VcnGatewayConnectionTestBase { + protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID()); + protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 1; + protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; + protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = + new UnderlyingNetworkRecord( + new Network(0), + new NetworkCapabilities(), + new LinkProperties(), + false /* blocked */); + protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 = + new UnderlyingNetworkRecord( + new Network(1), + new NetworkCapabilities(), + new LinkProperties(), + false /* blocked */); + + @NonNull protected final Context mContext; + @NonNull protected final TestLooper mTestLooper; + @NonNull protected final VcnNetworkProvider mVcnNetworkProvider; + @NonNull protected final VcnContext mVcnContext; + @NonNull protected final VcnGatewayConnectionConfig mConfig; + @NonNull protected final VcnGatewayConnection.Dependencies mDeps; + @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + + @NonNull protected final IpSecService mIpSecSvc; + + protected VcnIkeSession mMockIkeSession; + protected VcnGatewayConnection mGatewayConnection; + + public VcnGatewayConnectionTestBase() { + mContext = mock(Context.class); + mTestLooper = new TestLooper(); + mVcnNetworkProvider = mock(VcnNetworkProvider.class); + mVcnContext = mock(VcnContext.class); + mConfig = VcnGatewayConnectionConfigTest.buildTestConfig(); + mDeps = mock(VcnGatewayConnection.Dependencies.class); + mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); + + mIpSecSvc = mock(IpSecService.class); + setupIpSecManager(mContext, mIpSecSvc); + + doReturn(mContext).when(mVcnContext).getContext(); + doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); + doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); + + doReturn(mUnderlyingNetworkTracker) + .when(mDeps) + .newUnderlyingNetworkTracker(any(), any(), any()); + } + + @Before + public void setUp() throws Exception { + IpSecTunnelInterfaceResponse resp = + new IpSecTunnelInterfaceResponse( + IpSecManager.Status.OK, + TEST_IPSEC_TUNNEL_RESOURCE_ID, + TEST_IPSEC_TUNNEL_IFACE); + doReturn(resp).when(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any()); + + mMockIkeSession = mock(VcnIkeSession.class); + doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any()); + + mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + } + + protected IkeSessionCallback getIkeSessionCallback() { + ArgumentCaptor<IkeSessionCallback> captor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + verify(mDeps).newIkeSession(any(), any(), any(), captor.capture(), any()); + return captor.getValue(); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java b/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java new file mode 100644 index 000000000000..2b1080650d6d --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static org.mockito.Mockito.doReturn; + +import android.content.Context; +import android.net.IpSecManager; + +import com.android.server.IpSecService; + +public class VcnTestUtils { + /** Mock system services by directly mocking the *Manager interface. */ + public static void setupSystemService( + Context mockContext, Object service, String name, Class<?> serviceClass) { + doReturn(name).when(mockContext).getSystemServiceName(serviceClass); + doReturn(service).when(mockContext).getSystemService(name); + } + + /** Mock IpSecService by mocking the underlying service binder. */ + public static IpSecManager setupIpSecManager(Context mockContext, IpSecService service) { + doReturn(Context.IPSEC_SERVICE).when(mockContext).getSystemServiceName(IpSecManager.class); + + final IpSecManager ipSecMgr = new IpSecManager(mockContext, service); + doReturn(ipSecMgr).when(mockContext).getSystemService(Context.IPSEC_SERVICE); + + // Return to ensure this doesn't get reaped. + return ipSecMgr; + } +} diff --git a/tools/stringslint/stringslint.py b/tools/stringslint/stringslint.py index afe91cda37b0..15088fc81e88 100644 --- a/tools/stringslint/stringslint.py +++ b/tools/stringslint/stringslint.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +#-*- coding: utf-8 -*- # Copyright (C) 2018 The Android Open Source Project # @@ -33,9 +34,6 @@ In general: import re, sys, codecs import lxml.etree as ET -reload(sys) -sys.setdefaultencoding('utf8') - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False): @@ -118,7 +116,7 @@ def lint(path): raw = f.read() if len(raw.strip()) == 0: return warnings - tree = ET.fromstring(raw) + tree = ET.fromstring(bytes(raw, encoding='utf-8')) root = tree #tree.getroot() last_comment = None @@ -231,6 +229,6 @@ for b in before: if len(after) > 0: for a in sorted(after.keys()): - print after[a] - print + print(after[a]) + print() sys.exit(1) diff --git a/tools/stringslint/stringslint_sha.sh b/tools/stringslint/stringslint_sha.sh index bd80bb4e6f3f..bd0569873197 100755 --- a/tools/stringslint/stringslint_sha.sh +++ b/tools/stringslint/stringslint_sha.sh @@ -1,5 +1,5 @@ #!/bin/bash LOCAL_DIR="$( dirname ${BASH_SOURCE} )" git show --name-only --pretty=format: $1 | grep values/strings.xml | while read file; do - python $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file) + python3 $LOCAL_DIR/stringslint.py <(git show $1:$file) <(git show $1^:$file) done |