diff options
190 files changed, 8053 insertions, 809 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/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 405d6f6f5f63..dc2868a59840 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -20,6 +20,7 @@ import android.os.IVoldTaskListener; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.storage.DiskInfo; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; @@ -30,6 +31,8 @@ import java.util.concurrent.CompletableFuture; public final class Sm { private static final String TAG = "Sm"; + private static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.sys.vold_app_data_isolation_enabled"; IStorageManager mSm; @@ -256,6 +259,10 @@ public final class Sm { } public void runDisableAppDataIsolation() throws RemoteException { + if (!SystemProperties.getBoolean( + ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) { + throw new IllegalStateException("Storage app data isolation is not enabled."); + } final String pkgName = nextArg(); final int pid = Integer.parseInt(nextArg()); final int userId = Integer.parseInt(nextArg()); 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 07df8b37c7a8..acd3389de620 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10145,6 +10145,7 @@ package android.content { method public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyBroadcast(@RequiresPermission android.content.Intent); + method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public void sendStickyBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable android.os.Bundle); method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void sendStickyBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle); method @Deprecated @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) public abstract void sendStickyOrderedBroadcast(@RequiresPermission android.content.Intent, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @Deprecated @RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); @@ -12127,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"; @@ -12213,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 @@ -25083,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; @@ -25092,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 { } @@ -35888,6 +35899,9 @@ package android.se.omapi { method @NonNull public String getVersion(); method public boolean isConnected(); method public void shutdown(); + field public static final String ACTION_SECURE_ELEMENT_STATE_CHANGED = "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED"; + field public static final String EXTRA_READER_NAME = "android.se.omapi.extra.READER_NAME"; + field public static final String EXTRA_READER_STATE = "android.se.omapi.extra.READER_STATE"; } public static interface SEService.OnConnectedListener { @@ -36070,15 +36084,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 { @@ -36088,7 +36107,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); @@ -39577,6 +39596,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"; @@ -40252,6 +40272,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 c19dd4ca4a8e..061d4ccbc473 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -10,6 +10,18 @@ package android.app { package android.net { + public class ConnectivityManager { + 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 + } + public final class TcpRepairWindow { ctor public TcpRepairWindow(int, int, int, int, int, int); field public final int maxWindow; @@ -44,6 +56,10 @@ package android.os { method public final void markVintfStability(); } + public static class Build.VERSION { + field public static final int FIRST_SDK_INT; + } + public interface Parcelable { method public default int getStability(); } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 34e950defb6a..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"; @@ -1411,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 { @@ -1707,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"; @@ -6020,6 +6031,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); + method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); @@ -6029,6 +6041,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); + method public void unregisterQosCallback(@NonNull android.net.QosCallback); method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; @@ -6118,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 { @@ -6222,6 +6231,8 @@ package android.net { method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); method public void onAutomaticReconnectDisabled(); method public void onNetworkUnwanted(); + method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); + method public void onQosCallbackUnregistered(int); method public void onRemoveKeepalivePacketFilter(int); method public void onSaveAcceptUnvalidated(boolean); method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); @@ -6232,6 +6243,9 @@ package android.net { method public final void sendLinkProperties(@NonNull android.net.LinkProperties); method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); method public final void sendNetworkScore(@IntRange(from=0, to=99) int); + method public final void sendQosCallbackError(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); + method public final void sendQosSessionLost(int, int); method public final void sendSocketKeepaliveEvent(int, int); method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); method public void unregister(); @@ -6318,6 +6332,9 @@ package android.net { method public abstract void onRequestScores(android.net.NetworkKey[]); } + public class NetworkReleasedException extends java.lang.Exception { + } + public class NetworkRequest implements android.os.Parcelable { method @Nullable public String getRequestorPackageName(); method public int getRequestorUid(); @@ -6390,6 +6407,47 @@ package android.net { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); } + public abstract class QosCallback { + ctor public QosCallback(); + method public void onError(@NonNull android.net.QosCallbackException); + method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes); + method public void onQosSessionLost(@NonNull android.net.QosSession); + } + + public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException { + } + + public final class QosCallbackException extends java.lang.Exception { + } + + 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 { + ctor public QosSession(int, int); + method public int describeContents(); + method public int getSessionId(); + method public int getSessionType(); + method public long getUniqueId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR; + field public static final int TYPE_EPS_BEARER = 1; // 0x1 + } + + public interface QosSessionAttributes { + } + + public final class QosSocketInfo implements android.os.Parcelable { + ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; + method public int describeContents(); + method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); + method @NonNull public android.net.Network getNetwork(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR; + } + public final class RouteInfo implements android.os.Parcelable { ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); @@ -6435,6 +6493,12 @@ package android.net { field public static final int SUCCESS = 0; // 0x0 } + public class SocketLocalAddressChangedException extends java.lang.Exception { + } + + public class SocketNotBoundException extends java.lang.Exception { + } + public final class StaticIpConfiguration implements android.os.Parcelable { ctor public StaticIpConfiguration(); ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); @@ -9007,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 { @@ -10638,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; @@ -10672,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); } @@ -10744,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); } @@ -10765,6 +10843,19 @@ package android.telephony.data { field public static final int RESULT_SUCCESS = 0; // 0x0 } + public final class EpsBearerQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes { + method @NonNull public static android.telephony.data.EpsBearerQosSessionAttributes create(@NonNull android.os.Parcel); + method public int describeContents(); + method public long getGuaranteedDownlinkBitRate(); + method public long getGuaranteedUplinkBitRate(); + method public long getMaxDownlinkBitRate(); + method public long getMaxUplinkBitRate(); + method public int getQci(); + method @NonNull public java.util.List<java.net.InetSocketAddress> getRemoteAddresses(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR; + } + public abstract class QualifiedNetworksService extends android.app.Service { ctor public QualifiedNetworksService(); method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int); @@ -10779,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 { @@ -11811,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); @@ -12248,6 +12366,164 @@ package android.util { } +package android.uwb { + + public final class AngleMeasurement implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=0.0, to=1.0) public double getConfidenceLevel(); + method @FloatRange(from=0.0, to=3.141592653589793) public double getErrorRadians(); + method @FloatRange(from=-3.141592653589793, to=3.141592653589793) public double getRadians(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.uwb.AngleMeasurement> CREATOR; + } + + public static final class AngleMeasurement.Builder { + ctor public AngleMeasurement.Builder(); + method @NonNull public android.uwb.AngleMeasurement build(); + method @NonNull public android.uwb.AngleMeasurement.Builder setConfidenceLevel(double); + method @NonNull public android.uwb.AngleMeasurement.Builder setErrorRadians(double); + method @NonNull public android.uwb.AngleMeasurement.Builder setRadians(double); + } + + public final class AngleOfArrivalMeasurement implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.uwb.AngleMeasurement getAltitude(); + method @NonNull public android.uwb.AngleMeasurement getAzimuth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.uwb.AngleOfArrivalMeasurement> CREATOR; + } + + public static final class AngleOfArrivalMeasurement.Builder { + ctor public AngleOfArrivalMeasurement.Builder(); + method @NonNull public android.uwb.AngleOfArrivalMeasurement build(); + method @NonNull public android.uwb.AngleOfArrivalMeasurement.Builder setAltitude(@NonNull android.uwb.AngleMeasurement); + method @NonNull public android.uwb.AngleOfArrivalMeasurement.Builder setAzimuth(@NonNull android.uwb.AngleMeasurement); + } + + public final class DistanceMeasurement implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=0.0, to=1.0) public double getConfidenceLevel(); + method public double getErrorMeters(); + method public double getMeters(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.uwb.DistanceMeasurement> CREATOR; + } + + public static final class DistanceMeasurement.Builder { + ctor public DistanceMeasurement.Builder(); + method @NonNull public android.uwb.DistanceMeasurement build(); + method @NonNull public android.uwb.DistanceMeasurement.Builder setConfidenceLevel(double); + method @NonNull public android.uwb.DistanceMeasurement.Builder setErrorMeters(double); + method @NonNull public android.uwb.DistanceMeasurement.Builder setMeters(double); + } + + public final class RangingMeasurement implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.uwb.AngleOfArrivalMeasurement getAngleOfArrivalMeasurement(); + method @Nullable public android.uwb.DistanceMeasurement getDistanceMeasurement(); + method public long getElapsedRealtimeNanos(); + method @NonNull public android.uwb.UwbAddress getRemoteDeviceAddress(); + method public int getStatus(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.uwb.RangingMeasurement> CREATOR; + field public static final int RANGING_STATUS_FAILURE_OUT_OF_RANGE = 1; // 0x1 + field public static final int RANGING_STATUS_FAILURE_UNKNOWN_ERROR = -1; // 0xffffffff + field public static final int RANGING_STATUS_SUCCESS = 0; // 0x0 + } + + public static final class RangingMeasurement.Builder { + ctor public RangingMeasurement.Builder(); + method @NonNull public android.uwb.RangingMeasurement build(); + method @NonNull public android.uwb.RangingMeasurement.Builder setAngleOfArrivalMeasurement(@NonNull android.uwb.AngleOfArrivalMeasurement); + method @NonNull public android.uwb.RangingMeasurement.Builder setDistanceMeasurement(@NonNull android.uwb.DistanceMeasurement); + method @NonNull public android.uwb.RangingMeasurement.Builder setElapsedRealtimeNanos(long); + method @NonNull public android.uwb.RangingMeasurement.Builder setRemoteDeviceAddress(@NonNull android.uwb.UwbAddress); + method @NonNull public android.uwb.RangingMeasurement.Builder setStatus(int); + } + + public final class RangingReport implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.uwb.RangingMeasurement> getMeasurements(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.uwb.RangingReport> CREATOR; + } + + public static final class RangingReport.Builder { + ctor public RangingReport.Builder(); + method @NonNull public android.uwb.RangingReport.Builder addMeasurement(@NonNull android.uwb.RangingMeasurement); + method @NonNull public android.uwb.RangingReport.Builder addMeasurements(@NonNull java.util.List<android.uwb.RangingMeasurement>); + method @NonNull public android.uwb.RangingReport build(); + } + + public final class RangingSession implements java.lang.AutoCloseable { + method public void close(); + method public void reconfigure(@NonNull android.os.PersistableBundle); + method public void start(@NonNull android.os.PersistableBundle); + method public void stop(); + } + + public static interface RangingSession.Callback { + method public void onClosed(int, @NonNull android.os.PersistableBundle); + method public void onOpenFailed(int, @NonNull android.os.PersistableBundle); + method public void onOpened(@NonNull android.uwb.RangingSession); + method public void onReconfigureFailed(int, @NonNull android.os.PersistableBundle); + method public void onReconfigured(@NonNull android.os.PersistableBundle); + method public void onReportReceived(@NonNull android.uwb.RangingReport); + method public void onStartFailed(int, @NonNull android.os.PersistableBundle); + method public void onStarted(@NonNull android.os.PersistableBundle); + method public void onStopFailed(int, @NonNull android.os.PersistableBundle); + method public void onStopped(); + field public static final int REASON_BAD_PARAMETERS = 3; // 0x3 + field public static final int REASON_GENERIC_ERROR = 4; // 0x4 + field public static final int REASON_LOCAL_REQUEST = 1; // 0x1 + field public static final int REASON_MAX_SESSIONS_REACHED = 5; // 0x5 + field public static final int REASON_PROTOCOL_SPECIFIC_ERROR = 7; // 0x7 + field public static final int REASON_REMOTE_REQUEST = 2; // 0x2 + field public static final int REASON_SYSTEM_POLICY = 6; // 0x6 + field public static final int REASON_UNKNOWN = 0; // 0x0 + } + + public final class UwbAddress implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public static android.uwb.UwbAddress fromBytes(@NonNull byte[]); + method public int size(); + method @NonNull public byte[] toBytes(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.uwb.UwbAddress> CREATOR; + field public static final int EXTENDED_ADDRESS_BYTE_LENGTH = 8; // 0x8 + field public static final int SHORT_ADDRESS_BYTE_LENGTH = 2; // 0x2 + } + + public final class UwbManager { + method public long elapsedRealtimeResolutionNanos(); + method public int getAngleOfArrivalSupport(); + method public int getMaxRemoteDevicesPerInitiatorSession(); + method public int getMaxRemoteDevicesPerResponderSession(); + method public int getMaxSimultaneousSessions(); + method @NonNull public android.os.PersistableBundle getSpecificationInfo(); + method @NonNull public java.util.List<java.lang.Integer> getSupportedChannelNumbers(); + method @NonNull public java.util.Set<java.lang.Integer> getSupportedPreambleCodeIndices(); + method public boolean isRangingSupported(); + method @NonNull public AutoCloseable openRangingSession(@NonNull android.os.PersistableBundle, @NonNull java.util.concurrent.Executor, @NonNull android.uwb.RangingSession.Callback); + method public void registerAdapterStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.uwb.UwbManager.AdapterStateCallback); + method public void unregisterAdapterStateCallback(@NonNull android.uwb.UwbManager.AdapterStateCallback); + field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D = 2; // 0x2 + field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; // 0x3 + field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; // 0x4 + field public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE = 1; // 0x1 + } + + public static interface UwbManager.AdapterStateCallback { + method public void onStateChanged(boolean, int); + field public static final int STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED = 1; // 0x1 + field public static final int STATE_CHANGED_REASON_ERROR_UNKNOWN = 4; // 0x4 + field public static final int STATE_CHANGED_REASON_SESSION_STARTED = 0; // 0x0 + field public static final int STATE_CHANGED_REASON_SYSTEM_BOOT = 3; // 0x3 + field public static final int STATE_CHANGED_REASON_SYSTEM_POLICY = 2; // 0x2 + } + +} + package android.view { public abstract class Window { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 715a099fca4f..546e72b8f834 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -980,10 +980,6 @@ package android.media.tv { package android.net { - public class ConnectivityManager { - 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 class EthernetManager { method public void setIncludeTestInterfaces(boolean); } @@ -992,11 +988,6 @@ package android.net { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } - public final class NetworkCapabilities implements android.os.Parcelable { - method public int[] getCapabilities(); - field public static final int TRANSPORT_TEST = 7; // 0x7 - } - public class NetworkStack { method public static void setServiceForTest(@Nullable android.os.IBinder); } @@ -1735,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/ContextImpl.java b/core/java/android/app/ContextImpl.java index 602b835a03f8..12c9cd90222a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1428,6 +1428,45 @@ class ContextImpl extends Context { } } + /** + * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em> + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Override + @Deprecated + public void sendStickyBroadcast(@NonNull Intent intent, @Nullable Bundle options) { + warnIfCallingFromSystemProcess(); + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.prepareToLeaveProcess(this); + ActivityManager.getService().broadcastIntentWithFeature( + mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, + null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options, + false, true, getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override @Deprecated public void sendStickyOrderedBroadcast(Intent intent, 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/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9acf675615a6..69d387994568 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -85,8 +85,10 @@ import android.security.keystore.StrongBoxUnavailableException; import android.service.restrictions.RestrictionsReceiver; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.net.NetworkUtilsInternal; @@ -4524,30 +4526,10 @@ public class DevicePolicyManager { if (!proxySpec.type().equals(Proxy.Type.HTTP)) { throw new IllegalArgumentException(); } - InetSocketAddress sa = (InetSocketAddress)proxySpec.address(); - String hostName = sa.getHostName(); - int port = sa.getPort(); - StringBuilder hostBuilder = new StringBuilder(); - hostSpec = hostBuilder.append(hostName) - .append(":").append(Integer.toString(port)).toString(); - if (exclusionList == null) { - exclSpec = ""; - } else { - StringBuilder listBuilder = new StringBuilder(); - boolean firstDomain = true; - for (String exclDomain : exclusionList) { - if (!firstDomain) { - listBuilder = listBuilder.append(","); - } else { - firstDomain = false; - } - listBuilder = listBuilder.append(exclDomain.trim()); - } - exclSpec = listBuilder.toString(); - } - if (android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec) - != android.net.Proxy.PROXY_VALID) - throw new IllegalArgumentException(); + final Pair<String, String> proxyParams = + getProxyParameters(proxySpec, exclusionList); + hostSpec = proxyParams.first; + exclSpec = proxyParams.second; } return mService.setGlobalProxy(admin, hostSpec, exclSpec); } catch (RemoteException e) { @@ -4558,6 +4540,35 @@ public class DevicePolicyManager { } /** + * Build HTTP proxy parameters for {@link IDevicePolicyManager#setGlobalProxy}. + * @throws IllegalArgumentException Invalid proxySpec + * @hide + */ + @VisibleForTesting + public Pair<String, String> getProxyParameters(Proxy proxySpec, List<String> exclusionList) { + InetSocketAddress sa = (InetSocketAddress) proxySpec.address(); + String hostName = sa.getHostName(); + int port = sa.getPort(); + final List<String> trimmedExclList; + if (exclusionList == null) { + trimmedExclList = Collections.emptyList(); + } else { + trimmedExclList = new ArrayList<>(exclusionList.size()); + for (String exclDomain : exclusionList) { + trimmedExclList.add(exclDomain.trim()); + } + } + final ProxyInfo info = ProxyInfo.buildDirectProxy(hostName, port, trimmedExclList); + // The hostSpec is built assuming that there is a specified port and hostname, + // but ProxyInfo.isValid() accepts 0 / empty as unspecified: also reject them. + if (port == 0 || TextUtils.isEmpty(hostName) || !info.isValid()) { + throw new IllegalArgumentException(); + } + + return new Pair<>(hostName + ":" + port, TextUtils.join(",", trimmedExclList)); + } + + /** * Set a network-independent global HTTP proxy. This is not normally what you want for typical * HTTP proxies - they are generally network dependent. However if you're doing something * unusual like general internal filtering this may be useful. On a private network where the 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 e0813cc83b1d..9c8856650ae0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2602,6 +2602,36 @@ public abstract class Context { public abstract void sendStickyBroadcast(@RequiresPermission Intent intent); /** + * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em> + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.BROADCAST_STICKY) + public void sendStickyBroadcast(@RequiresPermission @NonNull Intent intent, + @Nullable Bundle options) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** * <p>Version of {@link #sendStickyBroadcast} that allows you to * receive data back from the broadcast. This is accomplished by * supplying your own BroadcastReceiver when calling, which will be @@ -4509,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/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 5bdd521e92dd..e351c244b04c 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -617,6 +617,35 @@ public class ContextWrapper extends Context { mBase.sendStickyBroadcast(intent); } + /** + * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the + * Intent you are sending stays around after the broadcast is complete, + * so that others can quickly retrieve that data through the return + * value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In + * all other ways, this behaves the same as + * {@link #sendBroadcast(Intent)}. + * + * @deprecated Sticky broadcasts should not be used. They provide no security (anyone + * can access them), no protection (anyone can modify them), and many other problems. + * The recommended pattern is to use a non-sticky broadcast to report that <em>something</em> + * has changed, with another mechanism for apps to retrieve the current value whenever + * desired. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast, and the Intent will be held to + * be re-broadcast to future receivers. + * @param options (optional) Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * + * @see #sendBroadcast(Intent) + * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle) + */ + @Override + @Deprecated + public void sendStickyBroadcast(@NonNull Intent intent, @Nullable Bundle options) { + mBase.sendStickyBroadcast(intent, options); + } + @Override @Deprecated public void sendStickyOrderedBroadcast( 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/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index ce0ed5bc0553..d107261ab2d4 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -19,6 +19,7 @@ import static android.net.IpSecManager.INVALID_RESOURCE_ID; import static android.net.NetworkRequest.Type.LISTEN; import static android.net.NetworkRequest.Type.REQUEST; import static android.net.NetworkRequest.Type.TRACK_DEFAULT; +import static android.net.QosCallback.QosCallbackRegistrationException; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -29,7 +30,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -4823,6 +4823,8 @@ public class ConnectivityManager { /** * Simulates a Data Stall for the specified Network. * + * <p>This method should only be used for tests. + * * <p>The caller must be the owner of the specified Network. * * @param detectionMethod The detection method used to identify the Data Stall. @@ -4832,7 +4834,7 @@ public class ConnectivityManager { * @throws SecurityException if the caller is not the owner of the given network. * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int detectionMethod, long timestampMillis, @@ -4848,4 +4850,118 @@ public class ConnectivityManager { Log.d(TAG, "setOemNetworkPreference called with preference: " + preference.toString()); } + + @NonNull + private final List<QosCallbackConnection> mQosCallbackConnections = new ArrayList<>(); + + /** + * Registers a {@link QosSocketInfo} with an associated {@link QosCallback}. The callback will + * receive available QoS events related to the {@link Network} and local ip + port + * specified within socketInfo. + * <p/> + * The same {@link QosCallback} must be unregistered before being registered a second time, + * otherwise {@link QosCallbackRegistrationException} is thrown. + * <p/> + * This API does not, in itself, require any permission if called with a network that is not + * restricted. However, the underlying implementation currently only supports the IMS network, + * which is always restricted. That means non-preinstalled callers can't possibly find this API + * useful, because they'd never be called back on networks that they would have access to. + * + * @throws SecurityException if {@link QosSocketInfo#getNetwork()} is restricted and the app is + * missing CONNECTIVITY_USE_RESTRICTED_NETWORKS permission. + * @throws QosCallback.QosCallbackRegistrationException if qosCallback is already registered. + * @throws RuntimeException if the app already has too many callbacks registered. + * + * Exceptions after the time of registration is passed through + * {@link QosCallback#onError(QosCallbackException)}. see: {@link QosCallbackException}. + * + * @param socketInfo the socket information used to match QoS events + * @param callback receives qos events that satisfy socketInfo + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + * + * @hide + */ + @SystemApi + public void registerQosCallback(@NonNull final QosSocketInfo socketInfo, + @NonNull final QosCallback callback, + @CallbackExecutor @NonNull final Executor executor) { + Objects.requireNonNull(socketInfo, "socketInfo must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + Objects.requireNonNull(executor, "executor must be non-null"); + + try { + synchronized (mQosCallbackConnections) { + if (getQosCallbackConnection(callback) == null) { + final QosCallbackConnection connection = + new QosCallbackConnection(this, callback, executor); + mQosCallbackConnections.add(connection); + mService.registerQosSocketCallback(socketInfo, connection); + } else { + Log.e(TAG, "registerQosCallback: Callback already registered"); + throw new QosCallbackRegistrationException(); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + + // The same unregister method method is called for consistency even though nothing + // will be sent to the ConnectivityService since the callback was never successfully + // registered. + unregisterQosCallback(callback); + e.rethrowFromSystemServer(); + } catch (final ServiceSpecificException e) { + Log.e(TAG, "registerQosCallback: Error while registering ", e); + unregisterQosCallback(callback); + throw convertServiceException(e); + } + } + + /** + * Unregisters the given {@link QosCallback}. The {@link QosCallback} will no longer receive + * events once unregistered and can be registered a second time. + * <p/> + * If the {@link QosCallback} does not have an active registration, it is a no-op. + * + * @param callback the callback being unregistered + * + * @hide + */ + @SystemApi + public void unregisterQosCallback(@NonNull final QosCallback callback) { + Objects.requireNonNull(callback, "The callback must be non-null"); + try { + synchronized (mQosCallbackConnections) { + final QosCallbackConnection connection = getQosCallbackConnection(callback); + if (connection != null) { + connection.stopReceivingMessages(); + mService.unregisterQosCallback(connection); + mQosCallbackConnections.remove(connection); + } else { + Log.d(TAG, "unregisterQosCallback: Callback not registered"); + } + } + } catch (final RemoteException e) { + Log.e(TAG, "unregisterQosCallback: Error while unregistering ", e); + e.rethrowFromSystemServer(); + } + } + + /** + * Gets the connection related to the callback. + * + * @param callback the callback to look up + * @return the related connection + */ + @Nullable + private QosCallbackConnection getQosCallbackConnection(final QosCallback callback) { + for (final QosCallbackConnection connection : mQosCallbackConnections) { + // Checking by reference here is intentional + if (connection.getCallback() == callback) { + return connection; + } + } + return null; + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 47c7a1af029b..719783163ab9 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -20,6 +20,8 @@ import android.app.PendingIntent; import android.net.ConnectionInfo; import android.net.ConnectivityDiagnosticsManager; import android.net.IConnectivityDiagnosticsCallback; +import android.net.IQosCallback; +import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgentConfig; @@ -27,9 +29,10 @@ import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkRequest; import android.net.NetworkState; -import android.net.ISocketKeepaliveCallback; import android.net.ProxyInfo; import android.net.UidRange; +import android.net.VpnInfo; +import android.net.QosSocketInfo; import android.os.Bundle; import android.os.IBinder; import android.os.INetworkActivityListener; @@ -41,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; /** @@ -239,4 +241,7 @@ interface IConnectivityManager void unregisterNetworkActivityListener(in INetworkActivityListener l); boolean isDefaultNetworkActive(); + + void registerQosSocketCallback(in QosSocketInfo socketInfo, in IQosCallback callback); + void unregisterQosCallback(in IQosCallback callback); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 792e5b410afc..29a3fdf59e8b 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -81,4 +81,5 @@ interface INetworkPolicyManager { void factoryReset(String subscriber); boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork); + boolean isUidRestrictedOnMeteredNetworks(int uid); } 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/IQosCallback.aidl b/core/java/android/net/IQosCallback.aidl new file mode 100644 index 000000000000..91c75759f85c --- /dev/null +++ b/core/java/android/net/IQosCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.os.Bundle; +import android.net.QosSession; +import android.telephony.data.EpsBearerQosSessionAttributes; + +/** + * AIDL interface for QosCallback + * + * @hide + */ +oneway interface IQosCallback +{ + void onQosEpsBearerSessionAvailable(in QosSession session, + in EpsBearerQosSessionAttributes attributes); + void onQosSessionLost(in QosSession session); + void onError(in int type); +} 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..fbca7f178e3c 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -420,7 +420,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/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 83a7d16fa2d9..d22d82d1f4d0 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; import com.android.connectivity.aidl.INetworkAgent; @@ -227,7 +228,7 @@ public abstract class NetworkAgent { */ public static final String REDIRECT_URL_KEY = "redirect URL"; - /** + /** * Sent by the NetworkAgent to ConnectivityService to indicate this network was * explicitly selected. This should be sent before the NetworkInfo is marked * CONNECTED so it can be given special treatment at that time. @@ -341,6 +342,24 @@ public abstract class NetworkAgent { */ private static final int EVENT_AGENT_DISCONNECTED = BASE + 19; + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to register a new filter with + * callback. + * + * arg1 = QoS agent callback ID + * obj = {@link QosFilter} + * @hide + */ + public static final int CMD_REGISTER_QOS_CALLBACK = BASE + 20; + + /** + * Sent by QosCallbackTracker to {@link NetworkAgent} to unregister a callback. + * + * arg1 = QoS agent callback ID + * @hide + */ + public static final int CMD_UNREGISTER_QOS_CALLBACK = BASE + 21; + private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) { // The subtype can be changed with (TODO) setLegacySubtype, but it starts // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description. @@ -520,6 +539,17 @@ public abstract class NetworkAgent { onRemoveKeepalivePacketFilter(msg.arg1 /* slot */); break; } + case CMD_REGISTER_QOS_CALLBACK: { + onQosCallbackRegistered( + msg.arg1 /* QoS callback id */, + (QosFilter) msg.obj /* QoS filter */); + break; + } + case CMD_UNREGISTER_QOS_CALLBACK: { + onQosCallbackUnregistered( + msg.arg1 /* QoS callback id */); + break; + } } } } @@ -553,6 +583,8 @@ public abstract class NetworkAgent { } private static class NetworkAgentBinder extends INetworkAgent.Stub { + private static final String LOG_TAG = NetworkAgentBinder.class.getSimpleName(); + private final Handler mHandler; private NetworkAgentBinder(Handler handler) { @@ -639,6 +671,25 @@ public abstract class NetworkAgent { mHandler.sendMessage(mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0)); } + + @Override + public void onQosFilterCallbackRegistered(final int qosCallbackId, + final QosFilterParcelable qosFilterParcelable) { + if (qosFilterParcelable.getQosFilter() != null) { + mHandler.sendMessage( + mHandler.obtainMessage(CMD_REGISTER_QOS_CALLBACK, qosCallbackId, 0, + qosFilterParcelable.getQosFilter())); + return; + } + + Log.wtf(LOG_TAG, "onQosFilterCallbackRegistered: qos filter is null."); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + mHandler.sendMessage(mHandler.obtainMessage( + CMD_UNREGISTER_QOS_CALLBACK, qosCallbackId, 0, null)); + } } /** @@ -1067,8 +1118,68 @@ public abstract class NetworkAgent { protected void preventAutomaticReconnect() { } + /** + * Called when a qos callback is registered with a filter. + * @param qosCallbackId the id for the callback registered + * @param filter the filter being registered + */ + public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) { + } + + /** + * Called when a qos callback is registered with a filter. + * <p/> + * Any QoS events that are sent with the same callback id after this method is called + * are a no-op. + * + * @param qosCallbackId the id for the callback being unregistered + */ + public void onQosCallbackUnregistered(final int qosCallbackId) { + } + + + /** + * Sends the attributes of Eps Bearer Qos Session back to the Application + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + * @param attributes the attributes of the Eps Qos Session + */ + public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId, + @NonNull final EpsBearerQosSessionAttributes attributes) { + Objects.requireNonNull(attributes, "The attributes must be non-null"); + queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER), + attributes)); + } + + /** + * Sends event that the Eps Qos Session was lost. + * + * @param qosCallbackId the callback id that the session belongs to + * @param sessionId the unique session id across all Eps Bearer Qos Sessions + */ + public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) { + queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId, + new QosSession(sessionId, QosSession.TYPE_EPS_BEARER))); + } + + /** + * Sends the exception type back to the application. + * + * The NetworkAgent should not send anymore messages with this id. + * + * @param qosCallbackId the callback id this exception belongs to + * @param exceptionType the type of exception + */ + public final void sendQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType)); + } + + /** @hide */ - protected void log(String s) { + protected void log(final String s) { Log.d(LOG_TAG, "NetworkAgent: " + s); } } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 2d9f6d806f31..0a895b98f9fd 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; import android.os.Build; @@ -576,7 +575,6 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ @UnsupportedAppUsage - @TestApi public @NetCapability int[] getCapabilities() { return BitUtils.unpackBits(mNetworkCapabilities); } @@ -821,7 +819,7 @@ public final class NetworkCapabilities implements Parcelable { * * @hide */ - @TestApi + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int TRANSPORT_TEST = 7; /** @hide */ 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/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index c029deae09df..82b035b08428 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -460,6 +460,22 @@ public class NetworkPolicyManager { } /** + * Check that the given uid is restricted from doing networking on metered networks. + * + * @param uid The target uid. + * @return true if the given uid is restricted from doing networking on metered networks. + * + * @hide + */ + public boolean isUidRestrictedOnMeteredNetworks(int uid) { + try { + return mService.isUidRestrictedOnMeteredNetworks(uid); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Get multipath preference for the given network. */ public int getMultipathPreference(Network network) { diff --git a/core/java/android/net/NetworkReleasedException.java b/core/java/android/net/NetworkReleasedException.java new file mode 100644 index 000000000000..0629b7563aea --- /dev/null +++ b/core/java/android/net/NetworkReleasedException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Indicates that the {@link Network} was released and is no longer available. + * + * @hide + */ +@SystemApi +public class NetworkReleasedException extends Exception { + /** @hide */ + public NetworkReleasedException() { + super("The network was released and is no longer available"); + } +} 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/QosCallback.java b/core/java/android/net/QosCallback.java new file mode 100644 index 000000000000..22f06bc0e690 --- /dev/null +++ b/core/java/android/net/QosCallback.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.util.concurrent.Executor; + +/** + * Receives Qos information given a {@link Network}. The callback is registered with + * {@link ConnectivityManager#registerQosCallback}. + * + * <p> + * <br/> + * The callback will no longer receive calls if any of the following takes place: + * <ol> + * <li>{@link ConnectivityManager#unregisterQosCallback(QosCallback)} is called with the same + * callback instance.</li> + * <li>{@link QosCallback#onError(QosCallbackException)} is called.</li> + * <li>A network specific issue occurs. eg. Congestion on a carrier network.</li> + * <li>The network registered with the callback has no associated QoS providers</li> + * </ul> + * {@hide} + */ +@SystemApi +public abstract class QosCallback { + /** + * Invoked after an error occurs on a registered callback. Once called, the callback is + * automatically unregistered and the callback will no longer receive calls. + * + * <p>The underlying exception can either be a runtime exception or a custom exception made for + * {@link QosCallback}. see: {@link QosCallbackException}. + * + * @param exception wraps the underlying cause + */ + public void onError(@NonNull final QosCallbackException exception) { + } + + /** + * Called when a Qos Session first becomes available to the callback or if its attributes have + * changed. + * <p> + * Note: The callback may be called multiple times with the same attributes. + * + * @param session the available session + * @param sessionAttributes the attributes of the session + */ + public void onQosSessionAvailable(@NonNull final QosSession session, + @NonNull final QosSessionAttributes sessionAttributes) { + } + + /** + * Called after a Qos Session is lost. + * <p> + * At least one call to + * {@link QosCallback#onQosSessionAvailable(QosSession, QosSessionAttributes)} + * with the same {@link QosSession} will precede a call to lost. + * + * @param session the lost session + */ + public void onQosSessionLost(@NonNull final QosSession session) { + } + + /** + * Thrown when there is a problem registering {@link QosCallback} with + * {@link ConnectivityManager#registerQosCallback(QosSocketInfo, QosCallback, Executor)}. + */ + public static class QosCallbackRegistrationException extends RuntimeException { + /** + * @hide + */ + public QosCallbackRegistrationException() { + super(); + } + } +} diff --git a/core/java/android/net/QosCallbackConnection.java b/core/java/android/net/QosCallbackConnection.java new file mode 100644 index 000000000000..bdb4ad68cd7b --- /dev/null +++ b/core/java/android/net/QosCallbackConnection.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.data.EpsBearerQosSessionAttributes; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Sends messages from {@link com.android.server.ConnectivityService} to the registered + * {@link QosCallback}. + * <p/> + * This is a satellite class of {@link ConnectivityManager} and not meant + * to be used in other contexts. + * + * @hide + */ +class QosCallbackConnection extends android.net.IQosCallback.Stub { + + @NonNull private final ConnectivityManager mConnectivityManager; + @Nullable private volatile QosCallback mCallback; + @NonNull private final Executor mExecutor; + + @VisibleForTesting + @Nullable + public QosCallback getCallback() { + return mCallback; + } + + /** + * The constructor for the connection + * + * @param connectivityManager the mgr that created this connection + * @param callback the callback to send messages back to + * @param executor The executor on which the callback will be invoked. The provided + * {@link Executor} must run callback sequentially, otherwise the order of + * callbacks cannot be guaranteed. + */ + QosCallbackConnection(@NonNull final ConnectivityManager connectivityManager, + @NonNull final QosCallback callback, + @NonNull final Executor executor) { + mConnectivityManager = Objects.requireNonNull(connectivityManager, + "connectivityManager must be non-null"); + mCallback = Objects.requireNonNull(callback, "callback must be non-null"); + mExecutor = Objects.requireNonNull(executor, "executor must be non-null"); + } + + /** + * Called when either the {@link EpsBearerQosSessionAttributes} has changed or on the first time + * the attributes have become available. + * + * @param session the session that is now available + * @param attributes the corresponding attributes of session + */ + @Override + public void onQosEpsBearerSessionAvailable(@NonNull final QosSession session, + @NonNull final EpsBearerQosSessionAttributes attributes) { + + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionAvailable(session, attributes); + } + }); + } + + /** + * Called when the session is lost. + * + * @param session the session that was lost + */ + @Override + public void onQosSessionLost(@NonNull final QosSession session) { + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + callback.onQosSessionLost(session); + } + }); + } + + /** + * Called when there is an error on the registered callback. + * + * @param errorType the type of error + */ + @Override + public void onError(@QosCallbackException.ExceptionType final int errorType) { + mExecutor.execute(() -> { + final QosCallback callback = mCallback; + if (callback != null) { + // Messages no longer need to be received since there was an error. + stopReceivingMessages(); + mConnectivityManager.unregisterQosCallback(callback); + callback.onError(QosCallbackException.createException(errorType)); + } + }); + } + + /** + * The callback will stop receiving messages. + * <p/> + * There are no synchronization guarantees on exactly when the callback will stop receiving + * messages. + */ + void stopReceivingMessages() { + mCallback = null; + } +} diff --git a/core/java/android/net/QosCallbackException.java b/core/java/android/net/QosCallbackException.java new file mode 100644 index 000000000000..7fd9a527e2ac --- /dev/null +++ b/core/java/android/net/QosCallbackException.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This is the exception type passed back through the onError method on {@link QosCallback}. + * {@link QosCallbackException#getCause()} contains the actual error that caused this exception. + * + * The possible exception types as causes are: + * 1. {@link NetworkReleasedException} + * 2. {@link SocketNotBoundException} + * 3. {@link UnsupportedOperationException} + * 4. {@link SocketLocalAddressChangedException} + * + * @hide + */ +@SystemApi +public final class QosCallbackException extends Exception { + + /** @hide */ + @IntDef(prefix = {"EX_TYPE_"}, value = { + EX_TYPE_FILTER_NONE, + EX_TYPE_FILTER_NETWORK_RELEASED, + EX_TYPE_FILTER_SOCKET_NOT_BOUND, + EX_TYPE_FILTER_NOT_SUPPORTED, + EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ExceptionType {} + + private static final String TAG = "QosCallbackException"; + + // Types of exceptions supported // + /** {@hide} */ + public static final int EX_TYPE_FILTER_NONE = 0; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_NETWORK_RELEASED = 1; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3; + + /** {@hide} */ + public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4; + + /** + * Creates exception based off of a type and message. Not all types of exceptions accept a + * custom message. + * + * {@hide} + */ + @NonNull + static QosCallbackException createException(@ExceptionType final int type) { + switch (type) { + case EX_TYPE_FILTER_NETWORK_RELEASED: + return new QosCallbackException(new NetworkReleasedException()); + case EX_TYPE_FILTER_SOCKET_NOT_BOUND: + return new QosCallbackException(new SocketNotBoundException()); + case EX_TYPE_FILTER_NOT_SUPPORTED: + return new QosCallbackException(new UnsupportedOperationException( + "This device does not support the specified filter")); + case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED: + return new QosCallbackException( + new SocketLocalAddressChangedException()); + default: + Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'"); + return new QosCallbackException( + new RuntimeException("Unknown exception code: " + type)); + } + } + + /** + * @hide + */ + public QosCallbackException(@NonNull final String message) { + super(message); + } + + /** + * @hide + */ + public QosCallbackException(@NonNull final Throwable cause) { + super(cause); + } +} diff --git a/core/java/android/net/QosFilter.java b/core/java/android/net/QosFilter.java new file mode 100644 index 000000000000..ab55002e02b3 --- /dev/null +++ b/core/java/android/net/QosFilter.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 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}. + * + * Used by the {@link com.android.server.ConnectivityService} to validate a {@link QosCallback} + * is still able to receive a {@link QosSession}. + * + * @hide + */ +@SystemApi +public abstract class QosFilter { + + /** + * The constructor is kept hidden from outside this package to ensure that all derived types + * are known and properly handled when being passed to and from {@link NetworkAgent}. + * + * @hide + */ + QosFilter() { + } + + /** + * The network used with this filter. + * + * @return the registered {@link Network} + */ + @NonNull + public abstract Network getNetwork(); + + /** + * Validates that conditions have not changed such that no further {@link QosSession}s should + * be passed back to the {@link QosCallback} associated to this filter. + * + * @return the error code when present, otherwise the filter is valid + * + * @hide + */ + @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/QosFilterParcelable.aidl b/core/java/android/net/QosFilterParcelable.aidl new file mode 100644 index 000000000000..312d6352ee92 --- /dev/null +++ b/core/java/android/net/QosFilterParcelable.aidl @@ -0,0 +1,21 @@ +/* +** +** 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; + +parcelable QosFilterParcelable; + diff --git a/core/java/android/net/QosFilterParcelable.java b/core/java/android/net/QosFilterParcelable.java new file mode 100644 index 000000000000..da3b2cf8ff7a --- /dev/null +++ b/core/java/android/net/QosFilterParcelable.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.Objects; + +/** + * Aware of how to parcel different types of {@link QosFilter}s. Any new type of qos filter must + * have a specialized case written here. + * <p/> + * Specifically leveraged when transferring {@link QosFilter} from + * {@link com.android.server.ConnectivityService} to {@link NetworkAgent} when the filter is first + * registered. + * <p/> + * This is not meant to be used in other contexts. + * + * @hide + */ +public final class QosFilterParcelable implements Parcelable { + + private static final String LOG_TAG = QosFilterParcelable.class.getSimpleName(); + + // Indicates that the filter was not successfully written to the parcel. + private static final int NO_FILTER_PRESENT = 0; + + // The parcel is of type qos socket filter. + private static final int QOS_SOCKET_FILTER = 1; + + private final QosFilter mQosFilter; + + /** + * The underlying qos filter. + * <p/> + * Null only in the case parceling failed. + */ + @Nullable + public QosFilter getQosFilter() { + return mQosFilter; + } + + public QosFilterParcelable(@NonNull final QosFilter qosFilter) { + Objects.requireNonNull(qosFilter, "qosFilter must be non-null"); + + // NOTE: Normally a type check would belong here, but doing so breaks unit tests that rely + // on mocking qos filter. + mQosFilter = qosFilter; + } + + private QosFilterParcelable(final Parcel in) { + final int filterParcelType = in.readInt(); + + switch (filterParcelType) { + case QOS_SOCKET_FILTER: { + mQosFilter = new QosSocketFilter(QosSocketInfo.CREATOR.createFromParcel(in)); + break; + } + + case NO_FILTER_PRESENT: + default: { + mQosFilter = null; + } + } + } + + public static final Creator<QosFilterParcelable> CREATOR = new Creator<QosFilterParcelable>() { + @Override + public QosFilterParcelable createFromParcel(final Parcel in) { + return new QosFilterParcelable(in); + } + + @Override + public QosFilterParcelable[] newArray(final int size) { + return new QosFilterParcelable[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + if (mQosFilter instanceof QosSocketFilter) { + dest.writeInt(QOS_SOCKET_FILTER); + final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter; + qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0); + return; + } + dest.writeInt(NO_FILTER_PRESENT); + Log.e(LOG_TAG, "Parceling failed, unknown type of filter present: " + mQosFilter); + } +} diff --git a/core/java/android/net/QosSession.aidl b/core/java/android/net/QosSession.aidl new file mode 100644 index 000000000000..c2cf36624b55 --- /dev/null +++ b/core/java/android/net/QosSession.aidl @@ -0,0 +1,21 @@ +/* +** +** 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; + +parcelable QosSession; + diff --git a/core/java/android/net/QosSession.java b/core/java/android/net/QosSession.java new file mode 100644 index 000000000000..4f3bb77c5877 --- /dev/null +++ b/core/java/android/net/QosSession.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Provides identifying information of a QoS session. Sent to an application through + * {@link QosCallback}. + * + * @hide + */ +@SystemApi +public final class QosSession implements Parcelable { + + /** + * The {@link QosSession} is a LTE EPS Session. + */ + public static final int TYPE_EPS_BEARER = 1; + + private final int mSessionId; + + private final int mSessionType; + + /** + * Gets the unique id of the session that is used to differentiate sessions across different + * types. + * <p/> + * Note: Different qos sessions can be provided by different actors. + * + * @return the unique id + */ + public long getUniqueId() { + return (long) mSessionType << 32 | mSessionId; + } + + /** + * Gets the session id that is unique within that type. + * <p/> + * Note: The session id is set by the actor providing the qos. It can be either manufactured by + * the actor, but also may have a particular meaning within that type. For example, using the + * bearer id as the session id for {@link android.telephony.data.EpsBearerQosSessionAttributes} + * is a straight forward way to keep the sessions unique from one another within that type. + * + * @return the id of the session + */ + public int getSessionId() { + return mSessionId; + } + + /** + * Gets the type of session. + */ + @QosSessionType + public int getSessionType() { + return mSessionType; + } + + /** + * Creates a {@link QosSession}. + * + * @param sessionId uniquely identifies the session across all sessions of the same type + * @param sessionType the type of session + */ + public QosSession(final int sessionId, @QosSessionType final int sessionType) { + //Ensures the session id is unique across types of sessions + mSessionId = sessionId; + mSessionType = sessionType; + } + + + @Override + public String toString() { + return "QosSession{" + + "mSessionId=" + mSessionId + + ", mSessionType=" + mSessionType + + '}'; + } + + /** + * Annotations for types of qos sessions. + */ + @IntDef(value = { + TYPE_EPS_BEARER, + }) + @interface QosSessionType {} + + private QosSession(final Parcel in) { + mSessionId = in.readInt(); + mSessionType = in.readInt(); + } + + @NonNull + public static final Creator<QosSession> CREATOR = new Creator<QosSession>() { + @NonNull + @Override + public QosSession createFromParcel(@NonNull final Parcel in) { + return new QosSession(in); + } + + @NonNull + @Override + public QosSession[] newArray(final int size) { + return new QosSession[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mSessionId); + dest.writeInt(mSessionType); + } +} diff --git a/core/java/android/net/QosSessionAttributes.java b/core/java/android/net/QosSessionAttributes.java new file mode 100644 index 000000000000..7a885942d1b5 --- /dev/null +++ b/core/java/android/net/QosSessionAttributes.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Implemented by classes that encapsulate Qos related attributes that describe a Qos Session. + * + * Use the instanceof keyword to determine the underlying type. + * + * @hide + */ +@SystemApi +public interface QosSessionAttributes { +} diff --git a/core/java/android/net/QosSocketFilter.java b/core/java/android/net/QosSocketFilter.java new file mode 100644 index 000000000000..2080e68f5fba --- /dev/null +++ b/core/java/android/net/QosSocketFilter.java @@ -0,0 +1,166 @@ +/* + * 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 android.net.QosCallbackException.EX_TYPE_FILTER_NONE; +import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.ParcelFileDescriptor; +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; +import java.util.Objects; + +/** + * Filters a {@link QosSession} according to the binding on the provided {@link Socket}. + * + * @hide + */ +public class QosSocketFilter extends QosFilter { + + private static final String TAG = QosSocketFilter.class.getSimpleName(); + + @NonNull + private final QosSocketInfo mQosSocketInfo; + + /** + * Creates a {@link QosSocketFilter} based off of {@link QosSocketInfo}. + * + * @param qosSocketInfo the information required to filter and validate + */ + public QosSocketFilter(@NonNull final QosSocketInfo qosSocketInfo) { + Objects.requireNonNull(qosSocketInfo, "qosSocketInfo must be non-null"); + mQosSocketInfo = qosSocketInfo; + } + + /** + * Gets the parcelable qos socket info that was used to create the filter. + */ + @NonNull + public QosSocketInfo getQosSocketInfo() { + return mQosSocketInfo; + } + + /** + * Performs two validations: + * 1. If the socket is not bound, then return + * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND}. This is detected + * by checking the local address on the filter which becomes null when the socket is no + * longer bound. + * 2. In the scenario that the socket is now bound to a different local address, which can + * happen in the case of UDP, then + * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned. + * @return validation error code + */ + @Override + public int validate() { + final InetSocketAddress sa = getAddressFromFileDescriptor(); + if (sa == null) { + return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND; + } + + if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) { + return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED; + } + + return EX_TYPE_FILTER_NONE; + } + + /** + * The local address of the socket's binding. + * + * Note: If the socket is no longer bound, null is returned. + * + * @return the local address + */ + @Nullable + private InetSocketAddress getAddressFromFileDescriptor() { + final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor(); + if (parcelFileDescriptor == null) return null; + + final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor(); + if (fd == null) return null; + + final SocketAddress address; + try { + address = Os.getsockname(fd); + } catch (final ErrnoException e) { + Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e); + return null; + } + if (address instanceof InetSocketAddress) { + return (InetSocketAddress) address; + } + return null; + } + + /** + * The network used with this filter. + * + * @return the registered {@link Network} + */ + @NonNull + @Override + 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/android/net/QosSocketInfo.aidl b/core/java/android/net/QosSocketInfo.aidl new file mode 100644 index 000000000000..476c0900e23e --- /dev/null +++ b/core/java/android/net/QosSocketInfo.aidl @@ -0,0 +1,21 @@ +/* +** +** 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; + +parcelable QosSocketInfo; + diff --git a/core/java/android/net/QosSocketInfo.java b/core/java/android/net/QosSocketInfo.java new file mode 100644 index 000000000000..d37c4691ddde --- /dev/null +++ b/core/java/android/net/QosSocketInfo.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.Objects; + +/** + * Used in conjunction with + * {@link ConnectivityManager#registerQosCallback} + * in order to receive Qos Sessions related to the local address and port of a bound {@link Socket}. + * + * @hide + */ +@SystemApi +public final class QosSocketInfo implements Parcelable { + + @NonNull + private final Network mNetwork; + + @NonNull + private final ParcelFileDescriptor mParcelFileDescriptor; + + @NonNull + private final InetSocketAddress mLocalSocketAddress; + + /** + * The {@link Network} the socket is on. + * + * @return the registered {@link Network} + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * The parcel file descriptor wrapped around the socket's file descriptor. + * + * @return the parcel file descriptor of the socket + */ + @NonNull + ParcelFileDescriptor getParcelFileDescriptor() { + return mParcelFileDescriptor; + } + + /** + * The local address of the socket passed into {@link QosSocketInfo(Network, Socket)}. + * The value does not reflect any changes that occur to the socket after it is first set + * in the constructor. + * + * @return the local address of the socket + */ + @NonNull + public InetSocketAddress getLocalSocketAddress() { + return mLocalSocketAddress; + } + + /** + * Creates a {@link QosSocketInfo} given a {@link Network} and bound {@link Socket}. The + * {@link Socket} must remain bound in order to receive {@link QosSession}s. + * + * @param network the network + * @param socket the bound {@link Socket} + */ + public QosSocketInfo(@NonNull final Network network, @NonNull final Socket socket) + throws IOException { + Objects.requireNonNull(socket, "socket cannot be null"); + + mNetwork = Objects.requireNonNull(network, "network cannot be null"); + mParcelFileDescriptor = ParcelFileDescriptor.dup(socket.getFileDescriptor$()); + mLocalSocketAddress = + new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort()); + } + + /* Parcelable methods */ + private QosSocketInfo(final Parcel in) { + mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in)); + mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in); + + final int addressLength = in.readInt(); + mLocalSocketAddress = readSocketAddress(in, addressLength); + } + + private InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) { + final byte[] address = new byte[addressLength]; + in.readByteArray(address); + final int port = in.readInt(); + + try { + return new InetSocketAddress(InetAddress.getByAddress(address), port); + } catch (final UnknownHostException e) { + /* The catch block was purposely left empty. UnknownHostException will never be thrown + since the address provided is numeric and non-null. */ + } + return new InetSocketAddress(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + mNetwork.writeToParcel(dest, 0); + mParcelFileDescriptor.writeToParcel(dest, 0); + + final byte[] address = mLocalSocketAddress.getAddress().getAddress(); + dest.writeInt(address.length); + dest.writeByteArray(address); + dest.writeInt(mLocalSocketAddress.getPort()); + } + + @NonNull + public static final Parcelable.Creator<QosSocketInfo> CREATOR = + new Parcelable.Creator<QosSocketInfo>() { + @NonNull + @Override + public QosSocketInfo createFromParcel(final Parcel in) { + return new QosSocketInfo(in); + } + + @NonNull + @Override + public QosSocketInfo[] newArray(final int size) { + return new QosSocketInfo[size]; + } + }; +} diff --git a/core/java/android/net/SocketLocalAddressChangedException.java b/core/java/android/net/SocketLocalAddressChangedException.java new file mode 100644 index 000000000000..9daad83fd13e --- /dev/null +++ b/core/java/android/net/SocketLocalAddressChangedException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Thrown when the local address of the socket has changed. + * + * @hide + */ +@SystemApi +public class SocketLocalAddressChangedException extends Exception { + /** @hide */ + public SocketLocalAddressChangedException() { + super("The local address of the socket changed"); + } +} diff --git a/core/java/android/net/SocketNotBoundException.java b/core/java/android/net/SocketNotBoundException.java new file mode 100644 index 000000000000..b1d7026ac981 --- /dev/null +++ b/core/java/android/net/SocketNotBoundException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.annotation.SystemApi; + +/** + * Thrown when a previously bound socket becomes unbound. + * + * @hide + */ +@SystemApi +public class SocketNotBoundException extends Exception { + /** @hide */ + public SocketNotBoundException() { + super("The socket is unbound"); + } +} 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 0d8769e7635c..10761187d969 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -317,6 +317,7 @@ public class Build { * @see #SDK_INT * @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @TestApi public static final int FIRST_SDK_INT = SystemProperties .getInt("ro.product.first_api_level", 0); diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index 8af7de597294..ff126e12cf61 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -1,7 +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/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java index a5c5c613e1f2..333af91ac872 100644 --- a/core/java/android/se/omapi/SEService.java +++ b/core/java/android/se/omapi/SEService.java @@ -22,7 +22,10 @@ package android.se.omapi; +import android.annotation.BroadcastBehavior; import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -71,6 +74,28 @@ public final class SEService { } /** + * Broadcast Action: Intent to notify if the secure element state is changed. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(registeredOnly = true, protectedBroadcast = true) + public static final String ACTION_SECURE_ELEMENT_STATE_CHANGED = + "android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED"; + + /** + * Mandatory extra containing the reader name of the state changed secure element. + * + * @see Reader#getName() + */ + public static final String EXTRA_READER_NAME = "android.se.omapi.extra.READER_NAME"; + + /** + * Mandatory extra containing the connected state of the state changed secure element. + * + * True if the secure element is connected correctly, false otherwise. + */ + public static final String EXTRA_READER_STATE = "android.se.omapi.extra.READER_STATE"; + + /** * Listener object that allows the notification of the caller if this * SEService could be bound to the backend. */ 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/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java index 93b5fd4cd4b6..9df213b2092f 100644 --- a/core/java/android/uwb/AngleMeasurement.java +++ b/core/java/android/uwb/AngleMeasurement.java @@ -18,6 +18,7 @@ package android.uwb; import android.annotation.FloatRange; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -31,6 +32,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class AngleMeasurement implements Parcelable { private final double mRadians; private final double mErrorRadians; diff --git a/core/java/android/uwb/AngleOfArrivalMeasurement.java b/core/java/android/uwb/AngleOfArrivalMeasurement.java index 20a1c7aa72d0..3d8626b98bed 100644 --- a/core/java/android/uwb/AngleOfArrivalMeasurement.java +++ b/core/java/android/uwb/AngleOfArrivalMeasurement.java @@ -18,6 +18,7 @@ package android.uwb; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,6 +29,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class AngleOfArrivalMeasurement implements Parcelable { private final AngleMeasurement mAzimuthAngleMeasurement; private final AngleMeasurement mAltitudeAngleMeasurement; @@ -53,7 +55,7 @@ public final class AngleOfArrivalMeasurement implements Parcelable { * @return the azimuth {@link AngleMeasurement} */ @NonNull - public AngleMeasurement getAzimuthAngleMeasurement() { + public AngleMeasurement getAzimuth() { return mAzimuthAngleMeasurement; } @@ -70,7 +72,7 @@ public final class AngleOfArrivalMeasurement implements Parcelable { * @return altitude {@link AngleMeasurement} or null when this is not available */ @Nullable - public AngleMeasurement getAltitudeAngleMeasurement() { + public AngleMeasurement getAltitude() { return mAltitudeAngleMeasurement; } @@ -85,8 +87,8 @@ public final class AngleOfArrivalMeasurement implements Parcelable { if (obj instanceof AngleOfArrivalMeasurement) { AngleOfArrivalMeasurement other = (AngleOfArrivalMeasurement) obj; - return mAzimuthAngleMeasurement.equals(other.getAzimuthAngleMeasurement()) - && mAltitudeAngleMeasurement.equals(other.getAltitudeAngleMeasurement()); + return mAzimuthAngleMeasurement.equals(other.getAzimuth()) + && mAltitudeAngleMeasurement.equals(other.getAltitude()); } return false; } @@ -116,11 +118,9 @@ public final class AngleOfArrivalMeasurement implements Parcelable { public AngleOfArrivalMeasurement createFromParcel(Parcel in) { Builder builder = new Builder(); - builder.setAzimuthAngleMeasurement( - in.readParcelable(AngleMeasurement.class.getClassLoader())); + builder.setAzimuth(in.readParcelable(AngleMeasurement.class.getClassLoader())); - builder.setAltitudeAngleMeasurement( - in.readParcelable(AngleMeasurement.class.getClassLoader())); + builder.setAltitude(in.readParcelable(AngleMeasurement.class.getClassLoader())); return builder.build(); } @@ -144,7 +144,7 @@ public final class AngleOfArrivalMeasurement implements Parcelable { * @param azimuthAngle azimuth angle */ @NonNull - public Builder setAzimuthAngleMeasurement(@NonNull AngleMeasurement azimuthAngle) { + public Builder setAzimuth(@NonNull AngleMeasurement azimuthAngle) { mAzimuthAngleMeasurement = azimuthAngle; return this; } @@ -155,7 +155,7 @@ public final class AngleOfArrivalMeasurement implements Parcelable { * @param altitudeAngle altitude angle */ @NonNull - public Builder setAltitudeAngleMeasurement(@NonNull AngleMeasurement altitudeAngle) { + public Builder setAltitude(@NonNull AngleMeasurement altitudeAngle) { mAltitudeAngleMeasurement = altitudeAngle; return this; } diff --git a/core/java/android/uwb/DistanceMeasurement.java b/core/java/android/uwb/DistanceMeasurement.java index 10c2172d5a6b..2a9bbdf3ec5d 100644 --- a/core/java/android/uwb/DistanceMeasurement.java +++ b/core/java/android/uwb/DistanceMeasurement.java @@ -19,6 +19,7 @@ package android.uwb; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -32,6 +33,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class DistanceMeasurement implements Parcelable { private final double mMeters; private final double mErrorMeters; diff --git a/core/java/android/uwb/RangingMeasurement.java b/core/java/android/uwb/RangingMeasurement.java index 50e5f0d8d554..249e2b746d0d 100644 --- a/core/java/android/uwb/RangingMeasurement.java +++ b/core/java/android/uwb/RangingMeasurement.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; @@ -33,6 +34,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class RangingMeasurement implements Parcelable { private final UwbAddress mRemoteDeviceAddress; private final @Status int mStatus; diff --git a/core/java/android/uwb/RangingReport.java b/core/java/android/uwb/RangingReport.java index 5b5f084914ab..7a2df8617700 100644 --- a/core/java/android/uwb/RangingReport.java +++ b/core/java/android/uwb/RangingReport.java @@ -18,6 +18,7 @@ package android.uwb; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -30,6 +31,7 @@ import java.util.Objects; * * @hide */ +@SystemApi public final class RangingReport implements Parcelable { private final List<RangingMeasurement> mRangingMeasurements; diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java index 0f87af415825..bfa8bf21ec6a 100644 --- a/core/java/android/uwb/RangingSession.java +++ b/core/java/android/uwb/RangingSession.java @@ -18,6 +18,7 @@ package android.uwb; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Binder; import android.os.PersistableBundle; import android.os.RemoteException; @@ -42,6 +43,7 @@ import java.util.concurrent.Executor; * * @hide */ +@SystemApi public final class RangingSession implements AutoCloseable { private static final String TAG = "Uwb.RangingSession"; private final SessionHandle mSessionHandle; diff --git a/core/java/android/uwb/UwbAddress.java b/core/java/android/uwb/UwbAddress.java index b9523a307c42..22883be88760 100644 --- a/core/java/android/uwb/UwbAddress.java +++ b/core/java/android/uwb/UwbAddress.java @@ -18,6 +18,7 @@ package android.uwb; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,6 +29,7 @@ import java.util.Arrays; * * @hide */ +@SystemApi public final class UwbAddress implements Parcelable { public static final int SHORT_ADDRESS_BYTE_LENGTH = 2; public static final int EXTENDED_ADDRESS_BYTE_LENGTH = 8; diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index 15ee5b5f22eb..8adfe0601b14 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.IBinder; @@ -44,6 +45,7 @@ import java.util.concurrent.Executor; * * @hide */ +@SystemApi @SystemService(Context.UWB_SERVICE) public final class UwbManager { private IUwbAdapter mUwbAdapter; 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/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 35e583f7d5d4..24dabfc02454 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -309,6 +309,9 @@ <protected-broadcast android:name="android.net.nsd.STATE_CHANGED" /> + <!-- For OMAPI --> + <protected-broadcast android:name="android.se.omapi.action.SECURE_ELEMENT_STATE_CHANGED" /> + <protected-broadcast android:name="android.nfc.action.ADAPTER_STATE_CHANGED" /> <protected-broadcast android:name="android.nfc.action.ALWAYS_ON_STATE_CHANGED" /> <protected-broadcast android:name="android.nfc.action.PREFERRED_PAYMENT_CHANGED" /> @@ -2987,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/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java index 222471a830b1..09f16a82cd7f 100644 --- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java +++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java @@ -76,6 +76,12 @@ public class BugreportManagerTest { private static final long DUMPSTATE_STARTUP_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); private static final long UIAUTOMATOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); + + // A small timeout used when waiting for the result of a BugreportCallback to be received. + // This value must be at least 1000ms since there is an intentional delay in + // BugreportManagerServiceImpl in the error case. + private static final long CALLBACK_RESULT_TIMEOUT_MS = 1500; + // Sent by Shell when its bugreport finishes (contains final bugreport/screenshot file name // associated with the bugreport). private static final String INTENT_BUGREPORT_FINISHED = @@ -185,7 +191,7 @@ public class BugreportManagerTest { ParcelFileDescriptor bugreportFd2 = parcelFd(bugreportFile2); ParcelFileDescriptor screenshotFd2 = parcelFd(screenshotFile2); mBrm.startBugreport(bugreportFd2, screenshotFd2, wifi(), mExecutor, callback2); - Thread.sleep(500 /* .5s */); + Thread.sleep(CALLBACK_RESULT_TIMEOUT_MS); // Verify #2 encounters an error. assertThat(callback2.getErrorCode()).isEqualTo( @@ -194,7 +200,7 @@ public class BugreportManagerTest { // Cancel #1 so we can move on to the next test. mBrm.cancelBugreport(); - Thread.sleep(500 /* .5s */); + waitTillDoneOrTimeout(callback); assertThat(callback.isDone()).isTrue(); assertFdsAreClosed(mBugreportFd, mScreenshotFd); } @@ -220,7 +226,7 @@ public class BugreportManagerTest { // Try again, with DUMP permission. getPermissions(); mBrm.cancelBugreport(); - Thread.sleep(500 /* .5s */); + waitTillDoneOrTimeout(callback); assertThat(callback.isDone()).isTrue(); assertFdsAreClosed(mBugreportFd, mScreenshotFd); } 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/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java index 0f17d27048f3..6be9306bbd2d 100644 --- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java @@ -254,7 +254,7 @@ public class DateIntervalFormatTest { assertEquals("19–22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 de ene. – jue., 22 de ene. de 2009", + assertEquals("lun, 19 de ene. – jue, 22 de ene. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", @@ -265,7 +265,7 @@ public class DateIntervalFormatTest { assertEquals("19 de ene. – 22 de abr. 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 de ene. – mié., 22 de abr. de 2009", + assertEquals("lun, 19 de ene. – mié, 22 de abr. de 2009", formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", @@ -286,9 +286,9 @@ public class DateIntervalFormatTest { assertEquals("19–22 de enero de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0)); - assertEquals("19–22 ene. 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, + assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene. – jue., 22 ene. 2009", + assertEquals("lun, 19 ene – jue, 22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("lunes, 19 de enero–jueves, 22 de enero de 2009", @@ -296,19 +296,19 @@ public class DateIntervalFormatTest { assertEquals("19 de enero–22 de abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0)); - assertEquals("19 ene. – 22 abr. 2009", + assertEquals("19 ene – 22 abr 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("lun., 19 ene. – mié., 22 abr. 2009", + assertEquals("lun, 19 ene – mié, 22 abr 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); assertEquals("enero–abril de 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY)); - assertEquals("19 ene. 2009 – 9 feb. 2012", + assertEquals("19 ene 2009 – 9 feb 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("ene. 2009 – feb. 2012", + assertEquals("ene 2009 – feb 2012", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL)); assertEquals("19 de enero de 2009–9 de febrero de 2012", diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java index 068d04798858..5612833e5ddd 100644 --- a/core/tests/coretests/src/android/text/format/FormatterTest.java +++ b/core/tests/coretests/src/android/text/format/FormatterTest.java @@ -212,7 +212,7 @@ public class FormatterTest { // Make sure it works on different locales. setLocale(new Locale("ru", "RU")); - assertEquals("1 мин.", Formatter.formatShortElapsedTimeRoundingUpToMinutes( + assertEquals("1 мин", Formatter.formatShortElapsedTimeRoundingUpToMinutes( mContext, 1 * SECOND)); } diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java index 4b3b5735b4f3..b3425162f48f 100644 --- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java +++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java @@ -755,8 +755,8 @@ public class RelativeDateTimeFormatterTest { final Locale locale = new Locale("fr"); android.icu.text.RelativeDateTimeFormatter icuFormatter = android.icu.text.RelativeDateTimeFormatter.getInstance(locale); - assertEquals("D à T", icuFormatter.combineDateAndTime("D", "T")); + assertEquals("D, T", icuFormatter.combineDateAndTime("D", "T")); // Ensure single quote ' and curly braces {} are not interpreted in input values. - assertEquals("D'x' à T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}")); + assertEquals("D'x', T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}")); } } diff --git a/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java index e0884e3e1c28..9394dec7f46f 100644 --- a/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java +++ b/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java @@ -42,14 +42,14 @@ public class AngleOfArrivalMeasurementTest { AngleOfArrivalMeasurement.Builder builder = new AngleOfArrivalMeasurement.Builder(); tryBuild(builder, false); - builder.setAltitudeAngleMeasurement(altitude); + builder.setAltitude(altitude); tryBuild(builder, false); - builder.setAzimuthAngleMeasurement(azimuth); + builder.setAzimuth(azimuth); AngleOfArrivalMeasurement measurement = tryBuild(builder, true); - assertEquals(azimuth, measurement.getAzimuthAngleMeasurement()); - assertEquals(altitude, measurement.getAltitudeAngleMeasurement()); + assertEquals(azimuth, measurement.getAzimuth()); + assertEquals(altitude, measurement.getAltitude()); } private AngleMeasurement getAngleMeasurement(double radian, double error, double confidence) { diff --git a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java index b4b2e303443e..8e7f7c562ade 100644 --- a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java +++ b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java @@ -42,8 +42,8 @@ public class UwbTestUtils { public static AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() { return new AngleOfArrivalMeasurement.Builder() - .setAltitudeAngleMeasurement(getAngleMeasurement()) - .setAzimuthAngleMeasurement(getAngleMeasurement()) + .setAltitude(getAngleMeasurement()) + .setAzimuth(getAngleMeasurement()) .build(); } 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/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/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/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/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp index 6c6c5c9f4e22..8a10599b498f 100644 --- a/libs/androidfw/LocaleDataTables.cpp +++ b/libs/androidfw/LocaleDataTables.cpp @@ -64,42 +64,43 @@ const char SCRIPT_CODES[][4] = { /* 60 */ {'N', 'k', 'o', 'o'}, /* 61 */ {'N', 's', 'h', 'u'}, /* 62 */ {'O', 'g', 'a', 'm'}, - /* 63 */ {'O', 'r', 'k', 'h'}, - /* 64 */ {'O', 'r', 'y', 'a'}, - /* 65 */ {'O', 's', 'g', 'e'}, - /* 66 */ {'P', 'a', 'u', 'c'}, - /* 67 */ {'P', 'h', 'l', 'i'}, - /* 68 */ {'P', 'h', 'n', 'x'}, - /* 69 */ {'P', 'l', 'r', 'd'}, - /* 70 */ {'P', 'r', 't', 'i'}, - /* 71 */ {'R', 'u', 'n', 'r'}, - /* 72 */ {'S', 'a', 'm', 'r'}, - /* 73 */ {'S', 'a', 'r', 'b'}, - /* 74 */ {'S', 'a', 'u', 'r'}, - /* 75 */ {'S', 'g', 'n', 'w'}, - /* 76 */ {'S', 'i', 'n', 'h'}, - /* 77 */ {'S', 'o', 'g', 'd'}, - /* 78 */ {'S', 'o', 'r', 'a'}, - /* 79 */ {'S', 'o', 'y', 'o'}, - /* 80 */ {'S', 'y', 'r', 'c'}, - /* 81 */ {'T', 'a', 'l', 'e'}, - /* 82 */ {'T', 'a', 'l', 'u'}, - /* 83 */ {'T', 'a', 'm', 'l'}, - /* 84 */ {'T', 'a', 'n', 'g'}, - /* 85 */ {'T', 'a', 'v', 't'}, - /* 86 */ {'T', 'e', 'l', 'u'}, - /* 87 */ {'T', 'f', 'n', 'g'}, - /* 88 */ {'T', 'h', 'a', 'a'}, - /* 89 */ {'T', 'h', 'a', 'i'}, - /* 90 */ {'T', 'i', 'b', 't'}, - /* 91 */ {'U', 'g', 'a', 'r'}, - /* 92 */ {'V', 'a', 'i', 'i'}, - /* 93 */ {'W', 'c', 'h', 'o'}, - /* 94 */ {'X', 'p', 'e', 'o'}, - /* 95 */ {'X', 's', 'u', 'x'}, - /* 96 */ {'Y', 'i', 'i', 'i'}, - /* 97 */ {'~', '~', '~', 'A'}, - /* 98 */ {'~', '~', '~', 'B'}, + /* 63 */ {'O', 'l', 'c', 'k'}, + /* 64 */ {'O', 'r', 'k', 'h'}, + /* 65 */ {'O', 'r', 'y', 'a'}, + /* 66 */ {'O', 's', 'g', 'e'}, + /* 67 */ {'P', 'a', 'u', 'c'}, + /* 68 */ {'P', 'h', 'l', 'i'}, + /* 69 */ {'P', 'h', 'n', 'x'}, + /* 70 */ {'P', 'l', 'r', 'd'}, + /* 71 */ {'P', 'r', 't', 'i'}, + /* 72 */ {'R', 'u', 'n', 'r'}, + /* 73 */ {'S', 'a', 'm', 'r'}, + /* 74 */ {'S', 'a', 'r', 'b'}, + /* 75 */ {'S', 'a', 'u', 'r'}, + /* 76 */ {'S', 'g', 'n', 'w'}, + /* 77 */ {'S', 'i', 'n', 'h'}, + /* 78 */ {'S', 'o', 'g', 'd'}, + /* 79 */ {'S', 'o', 'r', 'a'}, + /* 80 */ {'S', 'o', 'y', 'o'}, + /* 81 */ {'S', 'y', 'r', 'c'}, + /* 82 */ {'T', 'a', 'l', 'e'}, + /* 83 */ {'T', 'a', 'l', 'u'}, + /* 84 */ {'T', 'a', 'm', 'l'}, + /* 85 */ {'T', 'a', 'n', 'g'}, + /* 86 */ {'T', 'a', 'v', 't'}, + /* 87 */ {'T', 'e', 'l', 'u'}, + /* 88 */ {'T', 'f', 'n', 'g'}, + /* 89 */ {'T', 'h', 'a', 'a'}, + /* 90 */ {'T', 'h', 'a', 'i'}, + /* 91 */ {'T', 'i', 'b', 't'}, + /* 92 */ {'U', 'g', 'a', 'r'}, + /* 93 */ {'V', 'a', 'i', 'i'}, + /* 94 */ {'W', 'c', 'h', 'o'}, + /* 95 */ {'X', 'p', 'e', 'o'}, + /* 96 */ {'X', 's', 'u', 'x'}, + /* 97 */ {'Y', 'i', 'i', 'i'}, + /* 98 */ {'~', '~', '~', 'A'}, + /* 99 */ {'~', '~', '~', 'B'}, }; @@ -120,7 +121,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x80600000u, 46u}, // ada -> Latn {0x90600000u, 46u}, // ade -> Latn {0xA4600000u, 46u}, // adj -> Latn - {0xBC600000u, 90u}, // adp -> Tibt + {0xBC600000u, 91u}, // adp -> Tibt {0xE0600000u, 17u}, // ady -> Cyrl {0xE4600000u, 46u}, // adz -> Latn {0x61650000u, 4u}, // ae -> Avst @@ -138,7 +139,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB8E00000u, 0u}, // aho -> Ahom {0x99200000u, 46u}, // ajg -> Latn {0x616B0000u, 46u}, // ak -> Latn - {0xA9400000u, 95u}, // akk -> Xsux + {0xA9400000u, 96u}, // akk -> Xsux {0x81600000u, 46u}, // ala -> Latn {0xA1600000u, 46u}, // ali -> Latn {0xB5600000u, 46u}, // aln -> Latn @@ -163,7 +164,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xC9E00000u, 46u}, // aps -> Latn {0xE5E00000u, 46u}, // apz -> Latn {0x61720000u, 1u}, // ar -> Arab - {0x61725842u, 98u}, // ar-XB -> ~~~B + {0x61725842u, 99u}, // ar-XB -> ~~~B {0x8A200000u, 2u}, // arc -> Armi {0x9E200000u, 46u}, // arh -> Latn {0xB6200000u, 46u}, // arn -> Latn @@ -174,7 +175,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE6200000u, 1u}, // arz -> Arab {0x61730000u, 7u}, // as -> Beng {0x82400000u, 46u}, // asa -> Latn - {0x92400000u, 75u}, // ase -> Sgnw + {0x92400000u, 76u}, // ase -> Sgnw {0x9A400000u, 46u}, // asg -> Latn {0xBA400000u, 46u}, // aso -> Latn {0xCE400000u, 46u}, // ast -> Latn @@ -231,7 +232,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xDC810000u, 46u}, // bex -> Latn {0xE4810000u, 46u}, // bez -> Latn {0x8CA10000u, 46u}, // bfd -> Latn - {0xC0A10000u, 83u}, // bfq -> Taml + {0xC0A10000u, 84u}, // bfq -> Taml {0xCCA10000u, 1u}, // bft -> Arab {0xE0A10000u, 18u}, // bfy -> Deva {0x62670000u, 17u}, // bg -> Cyrl @@ -265,7 +266,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xC1410000u, 46u}, // bkq -> Latn {0xD1410000u, 46u}, // bku -> Latn {0xD5410000u, 46u}, // bkv -> Latn - {0xCD610000u, 85u}, // blt -> Tavt + {0xCD610000u, 86u}, // blt -> Tavt {0x626D0000u, 46u}, // bm -> Latn {0x9D810000u, 46u}, // bmh -> Latn {0xA9810000u, 46u}, // bmk -> Latn @@ -275,7 +276,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x99A10000u, 46u}, // bng -> Latn {0xB1A10000u, 46u}, // bnm -> Latn {0xBDA10000u, 46u}, // bnp -> Latn - {0x626F0000u, 90u}, // bo -> Tibt + {0x626F0000u, 91u}, // bo -> Tibt {0xA5C10000u, 46u}, // boj -> Latn {0xB1C10000u, 46u}, // bom -> Latn {0xB5C10000u, 46u}, // bon -> Latn @@ -322,6 +323,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x9F210000u, 46u}, // bzh -> Latn {0xDB210000u, 46u}, // bzw -> Latn {0x63610000u, 46u}, // ca -> Latn + {0x8C020000u, 46u}, // cad -> Latn {0xB4020000u, 46u}, // can -> Latn {0xA4220000u, 46u}, // cbj -> Latn {0x9C420000u, 46u}, // cch -> Latn @@ -346,7 +348,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE1420000u, 46u}, // cky -> Latn {0x81620000u, 46u}, // cla -> Latn {0x91820000u, 46u}, // cme -> Latn - {0x99820000u, 79u}, // cmg -> Soyo + {0x99820000u, 80u}, // cmg -> Soyo {0x636F0000u, 46u}, // co -> Latn {0xBDC20000u, 15u}, // cop -> Copt {0xC9E20000u, 46u}, // cps -> Latn @@ -360,7 +362,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x63730000u, 46u}, // cs -> Latn {0x86420000u, 46u}, // csb -> Latn {0xDA420000u, 10u}, // csw -> Cans - {0x8E620000u, 66u}, // ctd -> Pauc + {0x8E620000u, 67u}, // ctd -> Pauc {0x63750000u, 17u}, // cu -> Cyrl {0x63760000u, 17u}, // cv -> Cyrl {0x63790000u, 46u}, // cy -> Latn @@ -389,7 +391,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x91230000u, 46u}, // dje -> Latn {0xA5A30000u, 46u}, // dnj -> Latn {0x85C30000u, 46u}, // dob -> Latn - {0xA1C30000u, 1u}, // doi -> Arab + {0xA1C30000u, 18u}, // doi -> Deva {0xBDC30000u, 46u}, // dop -> Latn {0xD9C30000u, 46u}, // dow -> Latn {0x9E230000u, 56u}, // drh -> Mong @@ -404,12 +406,12 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x8A830000u, 46u}, // duc -> Latn {0x8E830000u, 46u}, // dud -> Latn {0x9A830000u, 46u}, // dug -> Latn - {0x64760000u, 88u}, // dv -> Thaa + {0x64760000u, 89u}, // dv -> Thaa {0x82A30000u, 46u}, // dva -> Latn {0xDAC30000u, 46u}, // dww -> Latn {0xBB030000u, 46u}, // dyo -> Latn {0xD3030000u, 46u}, // dyu -> Latn - {0x647A0000u, 90u}, // dz -> Tibt + {0x647A0000u, 91u}, // dz -> Tibt {0x9B230000u, 46u}, // dzg -> Latn {0xD0240000u, 46u}, // ebu -> Latn {0x65650000u, 46u}, // ee -> Latn @@ -422,7 +424,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x81840000u, 46u}, // ema -> Latn {0xA1840000u, 46u}, // emi -> Latn {0x656E0000u, 46u}, // en -> Latn - {0x656E5841u, 97u}, // en-XA -> ~~~A + {0x656E5841u, 98u}, // en-XA -> ~~~A {0xB5A40000u, 46u}, // enn -> Latn {0xC1A40000u, 46u}, // enq -> Latn {0x656F0000u, 46u}, // eo -> Latn @@ -438,6 +440,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x65750000u, 46u}, // eu -> Latn {0xBAC40000u, 46u}, // ewo -> Latn {0xCEE40000u, 46u}, // ext -> Latn + {0x83240000u, 46u}, // eza -> Latn {0x66610000u, 1u}, // fa -> Arab {0x80050000u, 46u}, // faa -> Latn {0x84050000u, 46u}, // fab -> Latn @@ -521,7 +524,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x95C60000u, 20u}, // gof -> Ethi {0xA1C60000u, 46u}, // goi -> Latn {0xB1C60000u, 18u}, // gom -> Deva - {0xB5C60000u, 86u}, // gon -> Telu + {0xB5C60000u, 87u}, // gon -> Telu {0xC5C60000u, 46u}, // gor -> Latn {0xC9C60000u, 46u}, // gos -> Latn {0xCDC60000u, 24u}, // got -> Goth @@ -566,7 +569,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xAD070000u, 46u}, // hil -> Latn {0x81670000u, 46u}, // hla -> Latn {0xD1670000u, 32u}, // hlu -> Hluw - {0x8D870000u, 69u}, // hmd -> Plrd + {0x8D870000u, 70u}, // hmd -> Plrd {0xCD870000u, 46u}, // hmt -> Latn {0x8DA70000u, 1u}, // hnd -> Arab {0x91A70000u, 18u}, // hne -> Deva @@ -601,7 +604,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x69670000u, 46u}, // ig -> Latn {0x84C80000u, 46u}, // igb -> Latn {0x90C80000u, 46u}, // ige -> Latn - {0x69690000u, 96u}, // ii -> Yiii + {0x69690000u, 97u}, // ii -> Yiii {0xA5280000u, 46u}, // ijj -> Latn {0x696B0000u, 46u}, // ik -> Latn {0xA9480000u, 46u}, // ikk -> Latn @@ -626,6 +629,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x6A610000u, 36u}, // ja -> Jpan {0x84090000u, 46u}, // jab -> Latn {0xB0090000u, 46u}, // jam -> Latn + {0xC4090000u, 46u}, // jar -> Latn {0xB8290000u, 46u}, // jbo -> Latn {0xD0290000u, 46u}, // jbu -> Latn {0xB4890000u, 46u}, // jen -> Latn @@ -661,7 +665,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x906A0000u, 46u}, // kde -> Latn {0x9C6A0000u, 1u}, // kdh -> Arab {0xAC6A0000u, 46u}, // kdl -> Latn - {0xCC6A0000u, 89u}, // kdt -> Thai + {0xCC6A0000u, 90u}, // kdt -> Thai {0x808A0000u, 46u}, // kea -> Latn {0xB48A0000u, 46u}, // ken -> Latn {0xE48A0000u, 46u}, // kez -> Latn @@ -673,7 +677,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x94CA0000u, 46u}, // kgf -> Latn {0xBCCA0000u, 46u}, // kgp -> Latn {0x80EA0000u, 46u}, // kha -> Latn - {0x84EA0000u, 82u}, // khb -> Talu + {0x84EA0000u, 83u}, // khb -> Talu {0xB4EA0000u, 18u}, // khn -> Deva {0xC0EA0000u, 46u}, // khq -> Latn {0xC8EA0000u, 46u}, // khs -> Latn @@ -766,7 +770,8 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x82EA0000u, 46u}, // kxa -> Latn {0x8AEA0000u, 20u}, // kxc -> Ethi {0x92EA0000u, 46u}, // kxe -> Latn - {0xB2EA0000u, 89u}, // kxm -> Thai + {0xAEEA0000u, 18u}, // kxl -> Deva + {0xB2EA0000u, 90u}, // kxm -> Thai {0xBEEA0000u, 1u}, // kxp -> Arab {0xDAEA0000u, 46u}, // kxw -> Latn {0xE6EA0000u, 46u}, // kxz -> Latn @@ -775,6 +780,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x6B795452u, 46u}, // ky-TR -> Latn {0x930A0000u, 46u}, // kye -> Latn {0xDF0A0000u, 46u}, // kyx -> Latn + {0x9F2A0000u, 1u}, // kzh -> Arab {0xA72A0000u, 46u}, // kzj -> Latn {0xC72A0000u, 46u}, // kzr -> Latn {0xCF2A0000u, 46u}, // kzt -> Latn @@ -790,7 +796,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xD02B0000u, 46u}, // lbu -> Latn {0xD82B0000u, 46u}, // lbw -> Latn {0xB04B0000u, 46u}, // lcm -> Latn - {0xBC4B0000u, 89u}, // lcp -> Thai + {0xBC4B0000u, 90u}, // lcp -> Thai {0x846B0000u, 46u}, // ldb -> Latn {0x8C8B0000u, 46u}, // led -> Latn {0x908B0000u, 46u}, // lee -> Latn @@ -814,7 +820,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xCD4B0000u, 46u}, // lkt -> Latn {0x916B0000u, 46u}, // lle -> Latn {0xB56B0000u, 46u}, // lln -> Latn - {0xB58B0000u, 86u}, // lmn -> Telu + {0xB58B0000u, 87u}, // lmn -> Telu {0xB98B0000u, 46u}, // lmo -> Latn {0xBD8B0000u, 46u}, // lmp -> Latn {0x6C6E0000u, 46u}, // ln -> Latn @@ -836,7 +842,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE28B0000u, 46u}, // luy -> Latn {0xE68B0000u, 1u}, // luz -> Arab {0x6C760000u, 46u}, // lv -> Latn - {0xAECB0000u, 89u}, // lwl -> Thai + {0xAECB0000u, 90u}, // lwl -> Thai {0x9F2B0000u, 28u}, // lzh -> Hans {0xE72B0000u, 46u}, // lzz -> Latn {0x8C0C0000u, 46u}, // mad -> Latn @@ -927,7 +933,6 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xBA2C0000u, 57u}, // mro -> Mroo {0x6D730000u, 46u}, // ms -> Latn {0x6D734343u, 1u}, // ms-CC -> Arab - {0x6D734944u, 1u}, // ms-ID -> Arab {0x6D740000u, 46u}, // mt -> Latn {0x8A6C0000u, 46u}, // mtc -> Latn {0x966C0000u, 46u}, // mtf -> Latn @@ -1006,11 +1011,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x9DAD0000u, 46u}, // nnh -> Latn {0xA9AD0000u, 46u}, // nnk -> Latn {0xB1AD0000u, 46u}, // nnm -> Latn - {0xBDAD0000u, 93u}, // nnp -> Wcho + {0xBDAD0000u, 94u}, // nnp -> Wcho {0x6E6F0000u, 46u}, // no -> Latn {0x8DCD0000u, 44u}, // nod -> Lana {0x91CD0000u, 18u}, // noe -> Deva - {0xB5CD0000u, 71u}, // non -> Runr + {0xB5CD0000u, 72u}, // non -> Runr {0xBDCD0000u, 46u}, // nop -> Latn {0xD1CD0000u, 46u}, // nou -> Latn {0xBA0D0000u, 60u}, // nqo -> Nkoo @@ -1044,18 +1049,18 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB5AE0000u, 46u}, // onn -> Latn {0xC9AE0000u, 46u}, // ons -> Latn {0xB1EE0000u, 46u}, // opm -> Latn - {0x6F720000u, 64u}, // or -> Orya + {0x6F720000u, 65u}, // or -> Orya {0xBA2E0000u, 46u}, // oro -> Latn {0xD22E0000u, 1u}, // oru -> Arab {0x6F730000u, 17u}, // os -> Cyrl - {0x824E0000u, 65u}, // osa -> Osge + {0x824E0000u, 66u}, // osa -> Osge {0x826E0000u, 1u}, // ota -> Arab - {0xAA6E0000u, 63u}, // otk -> Orkh + {0xAA6E0000u, 64u}, // otk -> Orkh {0xB32E0000u, 46u}, // ozm -> Latn {0x70610000u, 27u}, // pa -> Guru {0x7061504Bu, 1u}, // pa-PK -> Arab {0x980F0000u, 46u}, // pag -> Latn - {0xAC0F0000u, 67u}, // pal -> Phli + {0xAC0F0000u, 68u}, // pal -> Phli {0xB00F0000u, 46u}, // pam -> Latn {0xBC0F0000u, 46u}, // pap -> Latn {0xD00F0000u, 46u}, // pau -> Latn @@ -1065,11 +1070,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x886F0000u, 46u}, // pdc -> Latn {0xCC6F0000u, 46u}, // pdt -> Latn {0x8C8F0000u, 46u}, // ped -> Latn - {0xB88F0000u, 94u}, // peo -> Xpeo + {0xB88F0000u, 95u}, // peo -> Xpeo {0xDC8F0000u, 46u}, // pex -> Latn {0xACAF0000u, 46u}, // pfl -> Latn {0xACEF0000u, 1u}, // phl -> Arab - {0xB4EF0000u, 68u}, // phn -> Phnx + {0xB4EF0000u, 69u}, // phn -> Phnx {0xAD0F0000u, 46u}, // pil -> Latn {0xBD0F0000u, 46u}, // pip -> Latn {0x814F0000u, 8u}, // pka -> Brah @@ -1105,7 +1110,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB4D10000u, 46u}, // rgn -> Latn {0x98F10000u, 1u}, // rhg -> Arab {0x81110000u, 46u}, // ria -> Latn - {0x95110000u, 87u}, // rif -> Tfng + {0x95110000u, 88u}, // rif -> Tfng {0x95114E4Cu, 46u}, // rif-NL -> Latn {0xC9310000u, 18u}, // rjs -> Deva {0xCD510000u, 7u}, // rkt -> Beng @@ -1135,9 +1140,9 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x9C120000u, 17u}, // sah -> Cyrl {0xC0120000u, 46u}, // saq -> Latn {0xC8120000u, 46u}, // sas -> Latn - {0xCC120000u, 46u}, // sat -> Latn + {0xCC120000u, 63u}, // sat -> Olck {0xD4120000u, 46u}, // sav -> Latn - {0xE4120000u, 74u}, // saz -> Saur + {0xE4120000u, 75u}, // saz -> Saur {0x80320000u, 46u}, // sba -> Latn {0x90320000u, 46u}, // sbe -> Latn {0xBC320000u, 46u}, // sbp -> Latn @@ -1161,11 +1166,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xD8D20000u, 20u}, // sgw -> Ethi {0xE4D20000u, 46u}, // sgz -> Latn {0x73680000u, 46u}, // sh -> Latn - {0xA0F20000u, 87u}, // shi -> Tfng + {0xA0F20000u, 88u}, // shi -> Tfng {0xA8F20000u, 46u}, // shk -> Latn {0xB4F20000u, 58u}, // shn -> Mymr {0xD0F20000u, 1u}, // shu -> Arab - {0x73690000u, 76u}, // si -> Sinh + {0x73690000u, 77u}, // si -> Sinh {0x8D120000u, 46u}, // sid -> Latn {0x99120000u, 46u}, // sig -> Latn {0xAD120000u, 46u}, // sil -> Latn @@ -1184,7 +1189,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x81920000u, 46u}, // sma -> Latn {0xA5920000u, 46u}, // smj -> Latn {0xB5920000u, 46u}, // smn -> Latn - {0xBD920000u, 72u}, // smp -> Samr + {0xBD920000u, 73u}, // smp -> Samr {0xC1920000u, 46u}, // smq -> Latn {0xC9920000u, 46u}, // sms -> Latn {0x736E0000u, 46u}, // sn -> Latn @@ -1194,10 +1199,10 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xDDB20000u, 46u}, // snx -> Latn {0xE1B20000u, 46u}, // sny -> Latn {0x736F0000u, 46u}, // so -> Latn - {0x99D20000u, 77u}, // sog -> Sogd + {0x99D20000u, 78u}, // sog -> Sogd {0xA9D20000u, 46u}, // sok -> Latn {0xC1D20000u, 46u}, // soq -> Latn - {0xD1D20000u, 89u}, // sou -> Thai + {0xD1D20000u, 90u}, // sou -> Thai {0xE1D20000u, 46u}, // soy -> Latn {0x8DF20000u, 46u}, // spd -> Latn {0xADF20000u, 46u}, // spl -> Latn @@ -1208,7 +1213,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x7372524Fu, 46u}, // sr-RO -> Latn {0x73725255u, 46u}, // sr-RU -> Latn {0x73725452u, 46u}, // sr-TR -> Latn - {0x86320000u, 78u}, // srb -> Sora + {0x86320000u, 79u}, // srb -> Sora {0xB6320000u, 46u}, // srn -> Latn {0xC6320000u, 46u}, // srr -> Latn {0xDE320000u, 18u}, // srx -> Deva @@ -1235,9 +1240,9 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB6F20000u, 46u}, // sxn -> Latn {0xDAF20000u, 46u}, // sxw -> Latn {0xAF120000u, 7u}, // syl -> Beng - {0xC7120000u, 80u}, // syr -> Syrc + {0xC7120000u, 81u}, // syr -> Syrc {0xAF320000u, 46u}, // szl -> Latn - {0x74610000u, 83u}, // ta -> Taml + {0x74610000u, 84u}, // ta -> Taml {0xA4130000u, 18u}, // taj -> Deva {0xAC130000u, 46u}, // tal -> Latn {0xB4130000u, 46u}, // tan -> Latn @@ -1251,11 +1256,11 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xE4330000u, 46u}, // tbz -> Latn {0xA0530000u, 46u}, // tci -> Latn {0xE0530000u, 42u}, // tcy -> Knda - {0x8C730000u, 81u}, // tdd -> Tale + {0x8C730000u, 82u}, // tdd -> Tale {0x98730000u, 18u}, // tdg -> Deva {0x9C730000u, 18u}, // tdh -> Deva {0xD0730000u, 46u}, // tdu -> Latn - {0x74650000u, 86u}, // te -> Telu + {0x74650000u, 87u}, // te -> Telu {0x8C930000u, 46u}, // ted -> Latn {0xB0930000u, 46u}, // tem -> Latn {0xB8930000u, 46u}, // teo -> Latn @@ -1266,7 +1271,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x88D30000u, 46u}, // tgc -> Latn {0xB8D30000u, 46u}, // tgo -> Latn {0xD0D30000u, 46u}, // tgu -> Latn - {0x74680000u, 89u}, // th -> Thai + {0x74680000u, 90u}, // th -> Thai {0xACF30000u, 18u}, // thl -> Deva {0xC0F30000u, 18u}, // thq -> Deva {0xC4F30000u, 18u}, // thr -> Deva @@ -1305,14 +1310,14 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x8E530000u, 25u}, // tsd -> Grek {0x96530000u, 18u}, // tsf -> Deva {0x9A530000u, 46u}, // tsg -> Latn - {0xA6530000u, 90u}, // tsj -> Tibt + {0xA6530000u, 91u}, // tsj -> Tibt {0xDA530000u, 46u}, // tsw -> Latn {0x74740000u, 17u}, // tt -> Cyrl {0x8E730000u, 46u}, // ttd -> Latn {0x92730000u, 46u}, // tte -> Latn {0xA6730000u, 46u}, // ttj -> Latn {0xC6730000u, 46u}, // ttr -> Latn - {0xCA730000u, 89u}, // tts -> Thai + {0xCA730000u, 90u}, // tts -> Thai {0xCE730000u, 46u}, // ttt -> Latn {0x9E930000u, 46u}, // tuh -> Latn {0xAE930000u, 46u}, // tul -> Latn @@ -1323,7 +1328,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xD2B30000u, 46u}, // tvu -> Latn {0x9ED30000u, 46u}, // twh -> Latn {0xC2D30000u, 46u}, // twq -> Latn - {0x9AF30000u, 84u}, // txg -> Tang + {0x9AF30000u, 85u}, // txg -> Tang {0x74790000u, 46u}, // ty -> Latn {0x83130000u, 46u}, // tya -> Latn {0xD7130000u, 17u}, // tyv -> Cyrl @@ -1333,7 +1338,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x75670000u, 1u}, // ug -> Arab {0x75674B5Au, 17u}, // ug-KZ -> Cyrl {0x75674D4Eu, 17u}, // ug-MN -> Cyrl - {0x80D40000u, 91u}, // uga -> Ugar + {0x80D40000u, 92u}, // uga -> Ugar {0x756B0000u, 17u}, // uk -> Cyrl {0xA1740000u, 46u}, // uli -> Latn {0x85940000u, 46u}, // umb -> Latn @@ -1346,6 +1351,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xCE340000u, 46u}, // urt -> Latn {0xDA340000u, 46u}, // urw -> Latn {0x82540000u, 46u}, // usa -> Latn + {0x9E740000u, 46u}, // uth -> Latn {0xC6740000u, 46u}, // utr -> Latn {0x9EB40000u, 46u}, // uvh -> Latn {0xAEB40000u, 46u}, // uvl -> Latn @@ -1353,7 +1359,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x757A4146u, 1u}, // uz-AF -> Arab {0x757A434Eu, 17u}, // uz-CN -> Cyrl {0x98150000u, 46u}, // vag -> Latn - {0xA0150000u, 92u}, // vai -> Vaii + {0xA0150000u, 93u}, // vai -> Vaii {0xB4150000u, 46u}, // van -> Latn {0x76650000u, 46u}, // ve -> Latn {0x88950000u, 46u}, // vec -> Latn @@ -1376,7 +1382,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xB4160000u, 46u}, // wan -> Latn {0xC4160000u, 46u}, // war -> Latn {0xBC360000u, 46u}, // wbp -> Latn - {0xC0360000u, 86u}, // wbq -> Telu + {0xC0360000u, 87u}, // wbq -> Telu {0xC4360000u, 18u}, // wbr -> Deva {0xA0560000u, 46u}, // wci -> Latn {0xC4960000u, 46u}, // wer -> Latn @@ -1418,9 +1424,9 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0xC5B70000u, 18u}, // xnr -> Deva {0x99D70000u, 46u}, // xog -> Latn {0xB5D70000u, 46u}, // xon -> Latn - {0xC5F70000u, 70u}, // xpr -> Prti + {0xC5F70000u, 71u}, // xpr -> Prti {0x86370000u, 46u}, // xrb -> Latn - {0x82570000u, 73u}, // xsa -> Sarb + {0x82570000u, 74u}, // xsa -> Sarb {0xA2570000u, 46u}, // xsi -> Latn {0xB2570000u, 46u}, // xsm -> Latn {0xC6570000u, 18u}, // xsr -> Deva @@ -1461,7 +1467,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x98190000u, 46u}, // zag -> Latn {0xA4790000u, 1u}, // zdj -> Arab {0x80990000u, 46u}, // zea -> Latn - {0x9CD90000u, 87u}, // zgh -> Tfng + {0x9CD90000u, 88u}, // zgh -> Tfng {0x7A680000u, 28u}, // zh -> Hans {0x7A684155u, 29u}, // zh-AU -> Hant {0x7A68424Eu, 29u}, // zh-BN -> Hant @@ -1470,7 +1476,6 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ {0x7A68484Bu, 29u}, // zh-HK -> Hant {0x7A684944u, 29u}, // zh-ID -> Hant {0x7A684D4Fu, 29u}, // zh-MO -> Hant - {0x7A684D59u, 29u}, // zh-MY -> Hant {0x7A685041u, 29u}, // zh-PA -> Hant {0x7A685046u, 29u}, // zh-PF -> Hant {0x7A685048u, 29u}, // zh-PH -> Hant @@ -1592,6 +1597,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0xD701434D4C61746ELLU, // byv_Latn_CM 0x93214D4C4C61746ELLU, // bze_Latn_ML 0x636145534C61746ELLU, // ca_Latn_ES + 0x8C0255534C61746ELLU, // cad_Latn_US 0x9C424E474C61746ELLU, // cch_Latn_NG 0xBC42424443616B6DLLU, // ccp_Cakm_BD 0x636552554379726CLLU, // ce_Cyrl_RU @@ -1627,6 +1633,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0x637652554379726CLLU, // cv_Cyrl_RU 0x637947424C61746ELLU, // cy_Latn_GB 0x6461444B4C61746ELLU, // da_Latn_DK + 0x940343494C61746ELLU, // daf_Latn_CI 0xA80355534C61746ELLU, // dak_Latn_US 0xC40352554379726CLLU, // dar_Cyrl_RU 0xD4034B454C61746ELLU, // dav_Latn_KE @@ -1636,7 +1643,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0xC4C343414C61746ELLU, // dgr_Latn_CA 0x91234E454C61746ELLU, // dje_Latn_NE 0xA5A343494C61746ELLU, // dnj_Latn_CI - 0xA1C3494E41726162LLU, // doi_Arab_IN + 0xA1C3494E44657661LLU, // doi_Deva_IN 0x9E23434E4D6F6E67LLU, // drh_Mong_CN 0x864344454C61746ELLU, // dsb_Latn_DE 0xB2634D4C4C61746ELLU, // dtm_Latn_ML @@ -1839,6 +1846,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0xC6AA49444C61746ELLU, // kvr_Latn_ID 0xDEAA504B41726162LLU, // kvx_Arab_PK 0x6B7747424C61746ELLU, // kw_Latn_GB + 0xAEEA494E44657661LLU, // kxl_Deva_IN 0xB2EA544854686169LLU, // kxm_Thai_TH 0xBEEA504B41726162LLU, // kxp_Arab_PK 0x6B79434E41726162LLU, // ky_Arab_CN @@ -2047,7 +2055,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0x9C1252554379726CLLU, // sah_Cyrl_RU 0xC0124B454C61746ELLU, // saq_Latn_KE 0xC81249444C61746ELLU, // sas_Latn_ID - 0xCC12494E4C61746ELLU, // sat_Latn_IN + 0xCC12494E4F6C636BLLU, // sat_Olck_IN 0xD412534E4C61746ELLU, // sav_Latn_SN 0xE412494E53617572LLU, // saz_Saur_IN 0xBC32545A4C61746ELLU, // sbp_Latn_TZ @@ -2149,6 +2157,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ 0x747254524C61746ELLU, // tr_Latn_TR 0xD23354524C61746ELLU, // tru_Latn_TR 0xD63354574C61746ELLU, // trv_Latn_TW + 0xDA33504B41726162LLU, // trw_Arab_PK 0x74735A414C61746ELLU, // ts_Latn_ZA 0x8E5347524772656BLLU, // tsd_Grek_GR 0x96534E5044657661LLU, // tsf_Deva_NP diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index bce70e2aae9e..223382731bc0 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -30,6 +30,7 @@ #include <memory> #include <set> #include <type_traits> +#include <vector> #include <android-base/macros.h> #include <androidfw/ByteBucketArray.h> @@ -1029,7 +1030,7 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ // But we don't want to hit the cache, so instead we will have a // local temporary allocation for the conversions. size_t convBufferLen = strLen + 4; - char16_t* convBuffer = (char16_t*)calloc(convBufferLen, sizeof(char16_t)); + std::vector<char16_t> convBuffer(convBufferLen); ssize_t l = 0; ssize_t h = mHeader->stringCount-1; @@ -1043,8 +1044,8 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ } if (s.has_value()) { char16_t* end = utf8_to_utf16(reinterpret_cast<const uint8_t*>(s->data()), - s->size(), convBuffer, convBufferLen); - c = strzcmp16(convBuffer, end-convBuffer, str, strLen); + s->size(), convBuffer.data(), convBufferLen); + c = strzcmp16(convBuffer.data(), end-convBuffer.data(), str, strLen); } if (kDebugStringPoolNoisy) { ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n", @@ -1054,7 +1055,6 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ if (kDebugStringPoolNoisy) { ALOGI("MATCH!"); } - free(convBuffer); return mid; } else if (c < 0) { l = mid + 1; @@ -1062,7 +1062,6 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ h = mid - 1; } } - free(convBuffer); } else { // It is unusual to get the ID from an unsorted string block... // most often this happens because we want to get IDs for style 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/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 6ecf303c6dc8..249b1946d435 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -45,7 +45,7 @@ android_library { "WindowManager-Shell", "SystemUIPluginLib", "SystemUISharedLib", - "SystemUI-statsd", + "SystemUI-statsd", "SettingsLib", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", 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 adebcc7ee54e..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", @@ -203,6 +204,8 @@ filegroup { "java/com/android/server/connectivity/NetworkRanker.java", "java/com/android/server/connectivity/PermissionMonitor.java", "java/com/android/server/connectivity/ProxyTracker.java", + "java/com/android/server/connectivity/QosCallbackAgentConnection.java", + "java/com/android/server/connectivity/QosCallbackTracker.java", "java/com/android/server/connectivity/TcpKeepaliveController.java", "java/com/android/server/connectivity/Vpn.java", "java/com/android/server/connectivity/VpnIkev2Utils.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index b07e98f60237..410ec1d3a267 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -94,6 +94,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.InetAddresses; import android.net.IpMemoryStore; @@ -121,6 +122,10 @@ import android.net.NetworkUtils; import android.net.NetworkWatchlistManager; import android.net.PrivateDnsConfigParcel; import android.net.ProxyInfo; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSocketFilter; +import android.net.QosSocketInfo; import android.net.RouteInfo; import android.net.RouteInfoParcel; import android.net.SocketKeepalive; @@ -128,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; @@ -179,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; @@ -204,6 +209,7 @@ import com.android.server.connectivity.NetworkNotificationManager.NotificationTy import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; +import com.android.server.connectivity.QosCallbackTracker; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; import com.android.server.net.LockdownVpnTracker; @@ -279,6 +285,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // Default to 30s linger time-out. Modifiable only for testing. private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; private static final int DEFAULT_LINGER_DELAY_MS = 30_000; + + // The maximum number of network request allowed per uid before an exception is thrown. + private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; + @VisibleForTesting protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it. @@ -291,6 +301,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting protected final PermissionMonitor mPermissionMonitor; + private final PerUidCounter mNetworkRequestCounter; + private KeyStore mKeyStore; @VisibleForTesting @@ -313,6 +325,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean mRestrictBackground; private final Context mContext; + // The Context is created for UserHandle.ALL. + private final Context mUserAllContext; private final Dependencies mDeps; // 0 is full bad, 100 is full good private int mDefaultInetConditionPublished = 0; @@ -614,6 +628,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final LocationPermissionChecker mLocationPermissionChecker; private KeepaliveTracker mKeepaliveTracker; + private QosCallbackTracker mQosCallbackTracker; private NetworkNotificationManager mNotifier; private LingerMonitor mLingerMonitor; @@ -858,6 +873,66 @@ public class ConnectivityService extends IConnectivityManager.Stub }; /** + * Keeps track of the number of requests made under different uids. + */ + public static class PerUidCounter { + private final int mMaxCountPerUid; + + // Map from UID to number of NetworkRequests that UID has filed. + @GuardedBy("mUidToNetworkRequestCount") + private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray(); + + /** + * Constructor + * + * @param maxCountPerUid the maximum count per uid allowed + */ + public PerUidCounter(final int maxCountPerUid) { + mMaxCountPerUid = maxCountPerUid; + } + + /** + * Increments the request count of the given uid. Throws an exception if the number + * of open requests for the uid exceeds the value of maxCounterPerUid which is the value + * passed into the constructor. see: {@link #PerUidCounter(int)}. + * + * @throws ServiceSpecificException with + * {@link ConnectivityManager.Errors.TOO_MANY_REQUESTS} if the number of requests for + * the uid exceed the allowed number. + * + * @param uid the uid that the request was made under + */ + public void incrementCountOrThrow(final int uid) { + synchronized (mUidToNetworkRequestCount) { + final int networkRequests = mUidToNetworkRequestCount.get(uid, 0) + 1; + if (networkRequests >= mMaxCountPerUid) { + throw new ServiceSpecificException( + ConnectivityManager.Errors.TOO_MANY_REQUESTS); + } + mUidToNetworkRequestCount.put(uid, networkRequests); + } + } + + /** + * Decrements the request count of the given uid. + * + * @param uid the uid that the request was made under + */ + public void decrementCount(final int uid) { + synchronized (mUidToNetworkRequestCount) { + final int requests = mUidToNetworkRequestCount.get(uid, 0); + if (requests < 1) { + logwtf("BUG: too small request count " + requests + " for UID " + uid); + } else if (requests == 1) { + mUidToNetworkRequestCount.delete(uid); + } else { + mUidToNetworkRequestCount.put(uid, requests - 1); + } + } + } + } + + /** * Dependencies of ConnectivityService, for injection in tests. */ @VisibleForTesting @@ -945,6 +1020,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mSystemProperties = mDeps.getSystemProperties(); mNetIdManager = mDeps.makeNetIdManager(); mContext = Objects.requireNonNull(context, "missing Context"); + mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID); mMetricsLog = logger; mDefaultRequest = createDefaultInternetRequestForTransport(-1, NetworkRequest.Type.REQUEST); @@ -1086,8 +1162,8 @@ public class ConnectivityService extends IConnectivityManager.Stub intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); - final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); - userAllContext.registerReceiver( + mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); + mUserAllContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, @@ -1103,7 +1179,7 @@ public class ConnectivityService extends IConnectivityManager.Stub intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); - userAllContext.registerReceiver( + mUserAllContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, @@ -1112,7 +1188,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Listen to lockdown VPN reset. intentFilter = new IntentFilter(); intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); - userAllContext.registerReceiver( + mUserAllContext.registerReceiver( mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNMS); @@ -1125,6 +1201,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker = new KeepaliveTracker(mContext, mHandler); mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager); + mQosCallbackTracker = new QosCallbackTracker(mHandler, mNetworkRequestCounter); final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, @@ -1381,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())); } @@ -2275,7 +2351,7 @@ public class ConnectivityService extends IConnectivityManager.Stub intent.addFlags(Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); } try { - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options); + mUserAllContext.sendStickyBroadcast(intent, options); } finally { Binder.restoreCallingIdentity(ident); } @@ -2653,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, @@ -2777,6 +2853,7 @@ public class ConnectivityService extends IConnectivityManager.Stub updateCapabilitiesForNetwork(nai); notifyIfacesChangedForNetworkStats(); } + break; } } } @@ -3338,6 +3415,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // of rematchAllNetworksAndRequests notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST); mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK); + + mQosCallbackTracker.handleNetworkReleased(nai.network); for (String iface : nai.linkProperties.getAllInterfaceNames()) { // Disable wakeup packet monitoring for each interface. wakeupModifyInterface(iface, nai.networkCapabilities, false); @@ -3355,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); } } @@ -3436,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); } } @@ -3525,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()) { @@ -3545,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; } @@ -3570,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) { @@ -3601,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); - decrementNetworkRequestPerUidCount(nri); + if (null != nri.getActiveRequest()) { + if (nri.getActiveRequest().isRequest()) { + removeSatisfiedNetworkRequestFromNetwork(nri); + } else { + nri.setSatisfier(null, null); + } + } - 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); - } + 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 @@ -3644,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; } @@ -3660,36 +3826,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (doRemove) { - mLegacyTypeTracker.remove(nri.request.legacyType, nai, false); + mLegacyTypeTracker.remove(requestLegacyType, 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); - } - } - } - } - - private void decrementNetworkRequestPerUidCount(final NetworkRequestInfo nri) { - synchronized (mUidToNetworkRequestCount) { - final int requests = mUidToNetworkRequestCount.get(nri.mUid, 0); - if (requests < 1) { - Log.wtf(TAG, "BUG: too small request count " + requests + " for UID " + nri.mUid); - } else if (requests == 1) { - mUidToNetworkRequestCount.removeAt(mUidToNetworkRequestCount.indexOfKey(nri.mUid)); - } else { - mUidToNetworkRequestCount.put(nri.mUid, requests - 1); - } } } @@ -4519,6 +4658,10 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.w(TAG, s); } + private static void logwtf(String s) { + Log.wtf(TAG, s); + } + private static void loge(String s) { Log.e(TAG, s); } @@ -4761,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])); } /** @@ -5261,11 +5402,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>(); private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>(); - private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; - // Map from UID to number of NetworkRequests that UID has filed. - @GuardedBy("mUidToNetworkRequestCount") - private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray(); - private static class NetworkProviderInfo { public final String name; public final Messenger messenger; @@ -5351,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; @@ -5371,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; @@ -5379,20 +5534,19 @@ public class ConnectivityService extends IConnectivityManager.Stub mBinder = null; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); - enforceRequestCountLimit(); + mNetworkRequestCounter.incrementCountOrThrow(mUid); } NetworkRequestInfo(Messenger m, NetworkRequest r, IBinder binder) { super(); messenger = m; - request = r; mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); mBinder = binder; mPid = getCallingPid(); mUid = mDeps.getCallingUid(); mPendingIntent = null; - enforceRequestCountLimit(); + mNetworkRequestCounter.incrementCountOrThrow(mUid); try { mBinder.linkToDeath(this, 0); @@ -5415,31 +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; - } - - private void enforceRequestCountLimit() { - synchronized (mUidToNetworkRequestCount) { - int networkRequests = mUidToNetworkRequestCount.get(mUid, 0) + 1; - if (networkRequests >= MAX_NETWORK_REQUESTS_PER_UID) { - throw new ServiceSpecificException( - ConnectivityManager.Errors.TOO_MANY_REQUESTS); - } - mUidToNetworkRequestCount.put(mUid, networkRequests); - } - } - void unlinkDeathRecipient() { if (mBinder != null) { mBinder.unlinkToDeath(this, 0); @@ -5486,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() @@ -5525,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) { @@ -5661,9 +5796,14 @@ public class ConnectivityService extends IConnectivityManager.Stub // Policy already enforced. return; } - if (mPolicyManagerInternal.isUidRestrictedOnMeteredNetworks(uid)) { - // If UID is restricted, don't allow them to bring up metered APNs. - networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); + final long ident = Binder.clearCallingIdentity(); + try { + if (mPolicyManager.isUidRestrictedOnMeteredNetworks(uid)) { + // If UID is restricted, don't allow them to bring up metered APNs. + networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); + } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -5856,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. @@ -5998,7 +6144,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), - this, mNetd, mDnsResolver, mNMS, providerId, uid); + this, mNetd, mDnsResolver, mNMS, providerId, uid, mQosCallbackTracker); // Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says. processCapabilitiesFromAgent(nai, nc); @@ -6787,6 +6933,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; @@ -6807,21 +6986,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); } } @@ -6830,7 +7023,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); } @@ -6860,8 +7058,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 @@ -6869,8 +7068,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); @@ -6883,7 +7088,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. @@ -6902,7 +7107,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: { @@ -6921,12 +7126,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); } } @@ -7002,19 +7207,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); @@ -7026,19 +7237,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"); } @@ -7056,7 +7273,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 + "]"); @@ -7071,7 +7288,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; } @@ -7098,6 +7315,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) { @@ -7107,58 +7326,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) { @@ -7183,8 +7442,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(); @@ -7236,12 +7497,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); } } @@ -8278,7 +8539,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Decrement the reference count for this NetworkRequestInfo. The reference count is // incremented when the NetworkRequestInfo is created as part of // enforceRequestCountLimit(). - decrementNetworkRequestPerUidCount(nri); + mNetworkRequestCounter.decrementCount(nri.mUid); return; } @@ -8344,7 +8605,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Decrement the reference count for this NetworkRequestInfo. The reference count is // incremented when the NetworkRequestInfo is created as part of // enforceRequestCountLimit(). - decrementNetworkRequestPerUidCount(nri); + mNetworkRequestCounter.decrementCount(nri.mUid); iCb.unlinkToDeath(cbInfo, 0); } @@ -8565,7 +8826,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private final INetworkManagementService mNMS; LegacyNetworkActivityTracker(@NonNull Context context, - @NonNull INetworkManagementService nms) { + @NonNull INetworkManagementService nms) { mContext = context; mNMS = nms; try { @@ -8584,7 +8845,7 @@ public class ConnectivityService extends IConnectivityManager.Stub sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos); } - }; + }; // This is deprecated and only to support legacy use cases. private int transportTypeToLegacyType(int type) { @@ -8694,4 +8955,53 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } + /** + * Registers {@link QosSocketFilter} with {@link IQosCallback}. + * + * @param socketInfo the socket information + * @param callback the callback to register + */ + @Override + public void registerQosSocketCallback(@NonNull final QosSocketInfo socketInfo, + @NonNull final IQosCallback callback) { + final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(socketInfo.getNetwork()); + if (nai == null || nai.networkCapabilities == null) { + try { + callback.onError(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + } catch (final RemoteException ex) { + loge("registerQosCallbackInternal: RemoteException", ex); + } + return; + } + registerQosCallbackInternal(new QosSocketFilter(socketInfo), callback, nai); + } + + /** + * Register a {@link IQosCallback} with base {@link QosFilter}. + * + * @param filter the filter to register + * @param callback the callback to register + * @param nai the agent information related to the filter's network + */ + @VisibleForTesting + public void registerQosCallbackInternal(@NonNull final QosFilter filter, + @NonNull final IQosCallback callback, @NonNull final NetworkAgentInfo nai) { + if (filter == null) throw new IllegalArgumentException("filter must be non-null"); + if (callback == null) throw new IllegalArgumentException("callback must be non-null"); + + if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) { + enforceConnectivityRestrictedNetworksPermission(); + } + mQosCallbackTracker.registerCallback(callback, filter, nai); + } + + /** + * Unregisters the given callback. + * + * @param callback the callback to unregister + */ + @Override + public void unregisterQosCallback(@NonNull final IQosCallback callback) { + mQosCallbackTracker.unregisterCallback(callback); + } } 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/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 2fdc7965675d..76db0192cb00 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -31,16 +31,19 @@ import android.net.vcn.VcnConfig; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Looper; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -155,6 +158,11 @@ public class VcnManagementService extends IVcnManagementService.Stub { @NonNull private final PersistableBundleUtils.LockingReadWriteHelper mConfigDiskRwHelper; + @GuardedBy("mLock") + @NonNull + private final Map<IBinder, PolicyListenerBinderDeath> mRegisteredPolicyListeners = + new ArrayMap<>(); + @VisibleForTesting(visibility = Visibility.PRIVATE) VcnManagementService(@NonNull Context context, @NonNull Dependencies deps) { mContext = requireNonNull(context, "Missing context"); @@ -497,19 +505,60 @@ public class VcnManagementService extends IVcnManagementService.Stub { } } + /** Binder death recipient used to remove a registered policy listener. */ + private class PolicyListenerBinderDeath implements Binder.DeathRecipient { + @NonNull private final IVcnUnderlyingNetworkPolicyListener mListener; + + PolicyListenerBinderDeath(@NonNull IVcnUnderlyingNetworkPolicyListener listener) { + mListener = listener; + } + + @Override + public void binderDied() { + Log.e(TAG, "app died without removing VcnUnderlyingNetworkPolicyListener"); + removeVcnUnderlyingNetworkPolicyListener(mListener); + } + } + /** Adds the provided listener for receiving VcnUnderlyingNetworkPolicy updates. */ + @GuardedBy("mLock") @Override public void addVcnUnderlyingNetworkPolicyListener( - IVcnUnderlyingNetworkPolicyListener listener) { - // TODO(b/175739863): implement policy listener registration - throw new UnsupportedOperationException("Not yet implemented"); + @NonNull IVcnUnderlyingNetworkPolicyListener listener) { + requireNonNull(listener, "listener was null"); + + mContext.enforceCallingPermission( + android.Manifest.permission.NETWORK_FACTORY, + "Must have permission NETWORK_FACTORY to register a policy listener"); + + PolicyListenerBinderDeath listenerBinderDeath = new PolicyListenerBinderDeath(listener); + + synchronized (mLock) { + mRegisteredPolicyListeners.put(listener.asBinder(), listenerBinderDeath); + + try { + listener.asBinder().linkToDeath(listenerBinderDeath, 0 /* flags */); + } catch (RemoteException e) { + // Remote binder already died - cleanup registered Listener + listenerBinderDeath.binderDied(); + } + } } /** Removes the provided listener from receiving VcnUnderlyingNetworkPolicy updates. */ + @GuardedBy("mLock") @Override public void removeVcnUnderlyingNetworkPolicyListener( - IVcnUnderlyingNetworkPolicyListener listener) { - // TODO(b/175739863): implement policy listener unregistration - throw new UnsupportedOperationException("Not yet implemented"); + @NonNull IVcnUnderlyingNetworkPolicyListener listener) { + requireNonNull(listener, "listener was null"); + + synchronized (mLock) { + PolicyListenerBinderDeath listenerBinderDeath = + mRegisteredPolicyListeners.remove(listener.asBinder()); + + if (listenerBinderDeath != null) { + listener.asBinder().unlinkToDeath(listenerBinderDeath, 0 /* flags */); + } + } } } 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/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 e3663baaf9f7..ab0360b0395a 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -36,12 +36,17 @@ import android.net.NetworkInfo; import android.net.NetworkMonitorManager; import android.net.NetworkRequest; import android.net.NetworkState; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosFilterParcelable; +import android.net.QosSession; import android.net.TcpKeepalivePacketData; import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.SystemClock; +import android.telephony.data.EpsBearerQosSessionAttributes; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -321,18 +326,20 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final ConnectivityService mConnService; private final Context mContext; private final Handler mHandler; + private final QosCallbackTracker mQosCallbackTracker; public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber, - int creatorUid) { + int creatorUid, QosCallbackTracker qosCallbackTracker) { Objects.requireNonNull(net); Objects.requireNonNull(info); Objects.requireNonNull(lp); Objects.requireNonNull(nc); Objects.requireNonNull(context); Objects.requireNonNull(config); + Objects.requireNonNull(qosCallbackTracker); networkAgent = na; network = net; networkInfo = info; @@ -346,6 +353,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { networkAgentConfig = config; this.factorySerialNumber = factorySerialNumber; this.creatorUid = creatorUid; + mQosCallbackTracker = qosCallbackTracker; } private class AgentDeathMonitor implements IBinder.DeathRecipient { @@ -531,6 +539,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } } + /** + * Notify the NetworkAgent that the qos filter should be registered against the given qos + * callback id. + */ + public void onQosFilterCallbackRegistered(final int qosCallbackId, + final QosFilter qosFilter) { + try { + networkAgent.onQosFilterCallbackRegistered(qosCallbackId, + new QosFilterParcelable(qosFilter)); + } catch (final RemoteException e) { + Log.e(TAG, "Error registering a qos callback id against a qos filter", e); + } + } + + /** + * Notify the NetworkAgent that the given qos callback id should be unregistered. + */ + public void onQosCallbackUnregistered(final int qosCallbackId) { + try { + networkAgent.onQosCallbackUnregistered(qosCallbackId); + } catch (RemoteException e) { + Log.e(TAG, "Error unregistering a qos callback id", e); + } + } + // TODO: consider moving out of NetworkAgentInfo into its own class private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub { private final Handler mHandler; @@ -584,6 +617,23 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { mHandler.obtainMessage(NetworkAgent.EVENT_UNDERLYING_NETWORKS_CHANGED, new Pair<>(NetworkAgentInfo.this, networks)).sendToTarget(); } + + @Override + public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, + final EpsBearerQosSessionAttributes attributes) { + mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes); + } + + @Override + public void sendQosSessionLost(final int qosCallbackId, final QosSession session) { + mQosCallbackTracker.sendEventQosSessionLost(qosCallbackId, session); + } + + @Override + public void sendQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType); + } } /** diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java new file mode 100644 index 000000000000..816bf2be0d69 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java @@ -0,0 +1,192 @@ +/* + * 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.connectivity; + +import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE; + +import android.annotation.NonNull; +import android.net.IQosCallback; +import android.net.Network; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.util.Slog; + +import java.util.Objects; + +/** + * Wraps callback related information and sends messages between network agent and the application. + * <p/> + * This is a satellite class of {@link com.android.server.ConnectivityService} and not meant + * to be used in other contexts. + * + * @hide + */ +class QosCallbackAgentConnection implements IBinder.DeathRecipient { + private static final String TAG = QosCallbackAgentConnection.class.getSimpleName(); + private static final boolean DBG = false; + + private final int mAgentCallbackId; + @NonNull private final QosCallbackTracker mQosCallbackTracker; + @NonNull private final IQosCallback mCallback; + @NonNull private final IBinder mBinder; + @NonNull private final QosFilter mFilter; + @NonNull private final NetworkAgentInfo mNetworkAgentInfo; + + private final int mUid; + + /** + * Gets the uid + * @return uid + */ + int getUid() { + return mUid; + } + + /** + * Gets the binder + * @return binder + */ + @NonNull + IBinder getBinder() { + return mBinder; + } + + /** + * Gets the callback id + * + * @return callback id + */ + int getAgentCallbackId() { + return mAgentCallbackId; + } + + /** + * Gets the network tied to the callback of this connection + * + * @return network + */ + @NonNull + Network getNetwork() { + return mFilter.getNetwork(); + } + + QosCallbackAgentConnection(@NonNull final QosCallbackTracker qosCallbackTracker, + final int agentCallbackId, + @NonNull final IQosCallback callback, + @NonNull final QosFilter filter, + final int uid, + @NonNull final NetworkAgentInfo networkAgentInfo) { + Objects.requireNonNull(qosCallbackTracker, "qosCallbackTracker must be non-null"); + Objects.requireNonNull(callback, "callback must be non-null"); + Objects.requireNonNull(filter, "filter must be non-null"); + Objects.requireNonNull(networkAgentInfo, "networkAgentInfo must be non-null"); + + mQosCallbackTracker = qosCallbackTracker; + mAgentCallbackId = agentCallbackId; + mCallback = callback; + mFilter = filter; + mUid = uid; + mBinder = mCallback.asBinder(); + mNetworkAgentInfo = networkAgentInfo; + } + + @Override + public void binderDied() { + logw("binderDied: binder died with callback id: " + mAgentCallbackId); + mQosCallbackTracker.unregisterCallback(mCallback); + } + + void unlinkToDeathRecipient() { + mBinder.unlinkToDeath(this, 0); + } + + // Returns false if the NetworkAgent was never notified. + boolean sendCmdRegisterCallback() { + final int exceptionType = mFilter.validate(); + if (exceptionType != EX_TYPE_FILTER_NONE) { + try { + if (DBG) log("sendCmdRegisterCallback: filter validation failed"); + mCallback.onError(exceptionType); + } catch (final RemoteException e) { + loge("sendCmdRegisterCallback:", e); + } + return false; + } + + try { + mBinder.linkToDeath(this, 0); + } catch (final RemoteException e) { + loge("failed linking to death recipient", e); + return false; + } + mNetworkAgentInfo.onQosFilterCallbackRegistered(mAgentCallbackId, mFilter); + return true; + } + + void sendCmdUnregisterCallback() { + if (DBG) log("sendCmdUnregisterCallback: unregistering"); + mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId); + } + + void sendEventQosSessionAvailable(final QosSession session, + final EpsBearerQosSessionAttributes attributes) { + try { + if (DBG) log("sendEventQosSessionAvailable: sending..."); + mCallback.onQosEpsBearerSessionAvailable(session, attributes); + } catch (final RemoteException e) { + loge("sendEventQosSessionAvailable: remote exception", e); + } + } + + void sendEventQosSessionLost(@NonNull final QosSession session) { + try { + if (DBG) log("sendEventQosSessionLost: sending..."); + mCallback.onQosSessionLost(session); + } catch (final RemoteException e) { + loge("sendEventQosSessionLost: remote exception", e); + } + } + + void sendEventQosCallbackError(@QosCallbackException.ExceptionType final int exceptionType) { + try { + if (DBG) log("sendEventQosCallbackError: sending..."); + mCallback.onError(exceptionType); + } catch (final RemoteException e) { + loge("sendEventQosCallbackError: remote exception", e); + } + } + + private static void log(@NonNull final String msg) { + Slog.d(TAG, msg); + } + + private static void logw(@NonNull final String msg) { + Slog.w(TAG, msg); + } + + private static void loge(@NonNull final String msg, final Throwable t) { + Slog.e(TAG, msg, t); + } + + private static void logwtf(@NonNull final String msg) { + Slog.wtf(TAG, msg); + } +} diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java new file mode 100644 index 000000000000..87b4c162a2cc --- /dev/null +++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java @@ -0,0 +1,277 @@ +/* + * 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.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.IQosCallback; +import android.net.Network; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.telephony.data.EpsBearerQosSessionAttributes; +import android.util.Slog; + +import com.android.internal.util.CollectionUtils; +import com.android.server.ConnectivityService; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks qos callbacks and handles the communication between the network agent and application. + * <p/> + * Any method prefixed by handle must be called from the + * {@link com.android.server.ConnectivityService} handler thread. + * + * @hide + */ +public class QosCallbackTracker { + private static final String TAG = QosCallbackTracker.class.getSimpleName(); + private static final boolean DBG = true; + + @NonNull + private final Handler mConnectivityServiceHandler; + + @NonNull + private final ConnectivityService.PerUidCounter mNetworkRequestCounter; + + /** + * Each agent gets a unique callback id that is used to proxy messages back to the original + * callback. + * <p/> + * Note: The fact that this is initialized to 0 is to ensure that the thread running + * {@link #handleRegisterCallback(IQosCallback, QosFilter, int, NetworkAgentInfo)} sees the + * initialized value. This would not necessarily be the case if the value was initialized to + * the non-default value. + * <p/> + * Note: The term previous does not apply to the first callback id that is assigned. + */ + private int mPreviousAgentCallbackId = 0; + + @NonNull + private final List<QosCallbackAgentConnection> mConnections = new ArrayList<>(); + + /** + * + * @param connectivityServiceHandler must be the same handler used with + * {@link com.android.server.ConnectivityService} + * @param networkRequestCounter keeps track of the number of open requests under a given + * uid + */ + public QosCallbackTracker(@NonNull final Handler connectivityServiceHandler, + final ConnectivityService.PerUidCounter networkRequestCounter) { + mConnectivityServiceHandler = connectivityServiceHandler; + mNetworkRequestCounter = networkRequestCounter; + } + + /** + * Registers the callback with the tracker + * + * @param callback the callback to register + * @param filter the filter being registered alongside the callback + */ + public void registerCallback(@NonNull final IQosCallback callback, + @NonNull final QosFilter filter, @NonNull final NetworkAgentInfo networkAgentInfo) { + final int uid = Binder.getCallingUid(); + + // Enforce that the number of requests under this uid has exceeded the allowed number + mNetworkRequestCounter.incrementCountOrThrow(uid); + + mConnectivityServiceHandler.post( + () -> handleRegisterCallback(callback, filter, uid, networkAgentInfo)); + } + + private void handleRegisterCallback(@NonNull final IQosCallback callback, + @NonNull final QosFilter filter, final int uid, + @NonNull final NetworkAgentInfo networkAgentInfo) { + final QosCallbackAgentConnection ac = + handleRegisterCallbackInternal(callback, filter, uid, networkAgentInfo); + if (ac != null) { + if (DBG) log("handleRegisterCallback: added callback " + ac.getAgentCallbackId()); + mConnections.add(ac); + } else { + mNetworkRequestCounter.decrementCount(uid); + } + } + + private QosCallbackAgentConnection handleRegisterCallbackInternal( + @NonNull final IQosCallback callback, + @NonNull final QosFilter filter, final int uid, + @NonNull final NetworkAgentInfo networkAgentInfo) { + final IBinder binder = callback.asBinder(); + if (CollectionUtils.any(mConnections, c -> c.getBinder().equals(binder))) { + // A duplicate registration would have only made this far due to a programming error. + logwtf("handleRegisterCallback: Callbacks can only be register once."); + return null; + } + + mPreviousAgentCallbackId = mPreviousAgentCallbackId + 1; + final int newCallbackId = mPreviousAgentCallbackId; + + final QosCallbackAgentConnection ac = + new QosCallbackAgentConnection(this, newCallbackId, callback, + filter, uid, networkAgentInfo); + + final int exceptionType = filter.validate(); + if (exceptionType != QosCallbackException.EX_TYPE_FILTER_NONE) { + ac.sendEventQosCallbackError(exceptionType); + return null; + } + + // Only add to the callback maps if the NetworkAgent successfully registered it + if (!ac.sendCmdRegisterCallback()) { + // There was an issue when registering the agent + if (DBG) log("handleRegisterCallback: error sending register callback"); + mNetworkRequestCounter.decrementCount(uid); + return null; + } + return ac; + } + + /** + * Unregisters callback + * @param callback callback to unregister + */ + public void unregisterCallback(@NonNull final IQosCallback callback) { + mConnectivityServiceHandler.post(() -> handleUnregisterCallback(callback.asBinder(), true)); + } + + private void handleUnregisterCallback(@NonNull final IBinder binder, + final boolean sendToNetworkAgent) { + final QosCallbackAgentConnection agentConnection = + CollectionUtils.find(mConnections, c -> c.getBinder().equals(binder)); + if (agentConnection == null) { + logw("handleUnregisterCallback: agentConnection is null"); + return; + } + + if (DBG) { + log("handleUnregisterCallback: unregister " + + agentConnection.getAgentCallbackId()); + } + + mNetworkRequestCounter.decrementCount(agentConnection.getUid()); + mConnections.remove(agentConnection); + + if (sendToNetworkAgent) { + agentConnection.sendCmdUnregisterCallback(); + } + agentConnection.unlinkToDeathRecipient(); + } + + /** + * Called when the NetworkAgent sends the qos session available event + * + * @param qosCallbackId the callback id that the qos session is now available to + * @param session the qos session that is now available + * @param attributes the qos attributes that are now available on the qos session + */ + public void sendEventQosSessionAvailable(final int qosCallbackId, + final QosSession session, + final EpsBearerQosSessionAttributes attributes) { + runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ", + ac -> ac.sendEventQosSessionAvailable(session, attributes)); + } + + /** + * Called when the NetworkAgent sends the qos session lost event + * + * @param qosCallbackId the callback id that lost the qos session + * @param session the corresponding qos session + */ + public void sendEventQosSessionLost(final int qosCallbackId, + final QosSession session) { + runOnAgentConnection(qosCallbackId, "sendEventQosSessionLost: ", + ac -> ac.sendEventQosSessionLost(session)); + } + + /** + * Called when the NetworkAgent sends the qos session on error event + * + * @param qosCallbackId the callback id that should receive the exception + * @param exceptionType the type of exception that caused the callback to error + */ + public void sendEventQosCallbackError(final int qosCallbackId, + @QosCallbackException.ExceptionType final int exceptionType) { + runOnAgentConnection(qosCallbackId, "sendEventQosCallbackError: ", + ac -> { + ac.sendEventQosCallbackError(exceptionType); + handleUnregisterCallback(ac.getBinder(), false); + }); + } + + /** + * Unregisters all callbacks associated to this network agent + * + * Note: Must be called on the connectivity service handler thread + * + * @param network the network that was released + */ + public void handleNetworkReleased(@Nullable final Network network) { + final List<QosCallbackAgentConnection> connections = + CollectionUtils.filter(mConnections, ac -> ac.getNetwork().equals(network)); + + for (final QosCallbackAgentConnection agentConnection : connections) { + agentConnection.sendEventQosCallbackError( + QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + + // Call unregister workflow w\o sending anything to agent since it is disconnected. + handleUnregisterCallback(agentConnection.getBinder(), false); + } + } + + private interface AgentConnectionAction { + void execute(@NonNull QosCallbackAgentConnection agentConnection); + } + + @Nullable + private void runOnAgentConnection(final int qosCallbackId, + @NonNull final String logPrefix, + @NonNull final AgentConnectionAction action) { + mConnectivityServiceHandler.post(() -> { + final QosCallbackAgentConnection ac = + CollectionUtils.find(mConnections, + c -> c.getAgentCallbackId() == qosCallbackId); + if (ac == null) { + loge(logPrefix + ": " + qosCallbackId + " missing callback id"); + return; + } + + action.execute(ac); + }); + } + + private static void log(final String msg) { + Slog.d(TAG, msg); + } + + private static void logw(final String msg) { + Slog.w(TAG, msg); + } + + private static void loge(final String msg) { + Slog.e(TAG, msg); + } + + private static void logwtf(final String msg) { + Slog.wtf(TAG, msg); + } +} 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/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/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 141fa6a17873..f92f3dcd77ef 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -39,11 +39,6 @@ public abstract class NetworkPolicyManagerInternal { public abstract void resetUserState(int userId); /** - * @return true if the given uid is restricted from doing networking on metered networks. - */ - public abstract boolean isUidRestrictedOnMeteredNetworks(int uid); - - /** * Figure out if networking is blocked for a given set of conditions. * * This is used by ConnectivityService via passing stale copies of conditions, so it must not diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 1c41dc073ac8..01d4faf5c594 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -5361,7 +5361,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) { final long startTime = mStatLogger.getTime(); - enforceAnyPermissionOf(OBSERVE_NETWORK_POLICY, PERMISSION_MAINLINE_NETWORK_STACK); + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); final int uidRules; final boolean isBackgroundRestricted; synchronized (mUidRulesFirstLock) { @@ -5376,6 +5376,23 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return ret; } + @Override + public boolean isUidRestrictedOnMeteredNetworks(int uid) { + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); + final int uidRules; + final boolean isBackgroundRestricted; + synchronized (mUidRulesFirstLock) { + uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); + isBackgroundRestricted = mRestrictBackground; + } + //TODO(b/177490332): The logic here might not be correct because it doesn't consider + // RULE_REJECT_METERED condition. And it could be replaced by + // isUidNetworkingBlockedInternal(). + return isBackgroundRestricted + && !hasRule(uidRules, RULE_ALLOW_METERED) + && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); + } + private static boolean isSystem(int uid) { return uid < Process.FIRST_APPLICATION_UID; } @@ -5444,22 +5461,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - /** - * @return true if the given uid is restricted from doing networking on metered networks. - */ - @Override - public boolean isUidRestrictedOnMeteredNetworks(int uid) { - final int uidRules; - final boolean isBackgroundRestricted; - synchronized (mUidRulesFirstLock) { - uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); - isBackgroundRestricted = mRestrictBackground; - } - return isBackgroundRestricted - && !hasRule(uidRules, RULE_ALLOW_METERED) - && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); - } - @Override public void onTempPowerSaveWhitelistChange(int appId, boolean added) { synchronized (mUidRulesFirstLock) { 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/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index ef0f0eeb56f8..4ff75fa06077 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -336,6 +336,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @Override public void binderDied() { + try { + // Allow a small amount of time for any error or finished callbacks to be made. + // This ensures that the listener does not receive an erroneous runtime error + // callback. + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } synchronized (mLock) { if (!mDone) { // If we have not gotten a "done" callback this must be a crash. 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/security/OWNERS b/services/core/java/com/android/server/security/OWNERS new file mode 100644 index 000000000000..91b240bcb189 --- /dev/null +++ b/services/core/java/com/android/server/security/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 36824 + +per-file FileIntegrityService.java = victorhsieh@google.com +per-file VerityUtils.java = victorhsieh@google.com 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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 6786f6014f2d..5d06da78fe80 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -30,6 +30,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.PasswordMetrics.computeForPassword; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; +import static android.net.InetAddresses.parseNumericAddress; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; @@ -65,6 +66,8 @@ import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.testng.Assert.assertThrows; +import static java.util.Collections.emptyList; + import android.Manifest.permission; import android.app.Activity; import android.app.AppOpsManager; @@ -118,6 +121,8 @@ import org.mockito.internal.util.collections.Sets; import org.mockito.stubbing.Answer; import java.io.File; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -2246,6 +2251,48 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts); } + public void testGetProxyParameters() throws Exception { + assertThat(dpm.getProxyParameters(inetAddrProxy("192.0.2.1", 1234), emptyList())) + .isEqualTo(new Pair<>("192.0.2.1:1234", "")); + assertThat(dpm.getProxyParameters(inetAddrProxy("192.0.2.1", 1234), + listOf("one.example.com ", " two.example.com "))) + .isEqualTo(new Pair<>("192.0.2.1:1234", "one.example.com,two.example.com")); + assertThat(dpm.getProxyParameters(hostnameProxy("proxy.example.com", 1234), emptyList())) + .isEqualTo(new Pair<>("proxy.example.com:1234", "")); + assertThat(dpm.getProxyParameters(hostnameProxy("proxy.example.com", 1234), + listOf("excluded.example.com"))) + .isEqualTo(new Pair<>("proxy.example.com:1234", "excluded.example.com")); + + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + inetAddrProxy("192.0.2.1", 0), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("", 1234), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("", 0), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("invalid! hostname", 1234), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("proxy.example.com", 1234), listOf("invalid exclusion"))); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("proxy.example.com", -1), emptyList())); + assertThrows(IllegalArgumentException.class, () -> dpm.getProxyParameters( + hostnameProxy("proxy.example.com", 0xFFFF + 1), emptyList())); + } + + private static Proxy inetAddrProxy(String inetAddr, int port) { + return new Proxy( + Proxy.Type.HTTP, new InetSocketAddress(parseNumericAddress(inetAddr), port)); + } + + private static Proxy hostnameProxy(String hostname, int port) { + return new Proxy( + Proxy.Type.HTTP, InetSocketAddress.createUnresolved(hostname, port)); + } + + private static List<String> listOf(String... args) { + return Arrays.asList(args); + } + public void testSetKeyguardDisabledFeaturesWithDO() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); @@ -5156,7 +5203,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Attempt to set to empty list (which means no listener is allowlisted) mContext.binder.callingUid = adminUid; assertFalse(dpms.setPermittedCrossProfileNotificationListeners( - admin1, Collections.emptyList())); + admin1, emptyList())); assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1)); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; @@ -5248,7 +5295,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Setting an empty allowlist - only system listeners allowed mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; assertTrue(dpms.setPermittedCrossProfileNotificationListeners( - admin1, Collections.emptyList())); + admin1, emptyList())); assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size()); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; @@ -5312,7 +5359,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // all allowed in primary profile mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID; assertTrue(dpms.setPermittedCrossProfileNotificationListeners( - admin1, Collections.emptyList())); + admin1, emptyList())); assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size()); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; 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/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/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java index 250d9e8b212e..3b4cf75e7919 100644 --- a/telephony/java/android/telephony/PreciseDisconnectCause.java +++ b/telephony/java/android/telephony/PreciseDisconnectCause.java @@ -121,7 +121,7 @@ public final class PreciseDisconnectCause { public static final int BEARER_CAPABILITY_NOT_AUTHORIZED = 57; /** The requested bearer capability is not available at this time. */ public static final int BEARER_NOT_AVAIL = 58; - /** The service option is not availble at this time. */ + /** The service option is not available at this time. */ public static final int SERVICE_OPTION_NOT_AVAILABLE = 63; /** The equipment sending this cause does not support the bearer capability requested. */ public static final int BEARER_SERVICE_NOT_IMPLEMENTED = 65; diff --git a/telephony/java/android/telephony/RadioInterfaceCapabilities.java b/telephony/java/android/telephony/RadioInterfaceCapabilities.java new file mode 100644 index 000000000000..7c7eb9fbbeb2 --- /dev/null +++ b/telephony/java/android/telephony/RadioInterfaceCapabilities.java @@ -0,0 +1,53 @@ +/* + * 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; + +import android.util.ArraySet; + +/** + * Contains the set of supported capabilities that the Radio Interface supports on this device. + * + * @hide + */ +public class RadioInterfaceCapabilities { + + private final ArraySet<String> mSupportedCapabilities; + + + public RadioInterfaceCapabilities() { + mSupportedCapabilities = new ArraySet<>(); + } + + /** + * Marks a capability as supported + * + * @param capabilityName the name of the capability + */ + public void addSupportedCapability( + @TelephonyManager.RadioInterfaceCapability String capabilityName) { + mSupportedCapabilities.add(capabilityName); + } + + /** + * Whether the capability is supported + * + * @param capabilityName the name of the capability + */ + public boolean isSupported(String capabilityName) { + return mSupportedCapabilities.contains(capabilityName); + } +} diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f018f2fffc3a..646744da5336 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -14344,6 +14344,40 @@ public class TelephonyManager { return Collections.emptyList(); } + /** @hide */ + @IntDef(prefix = {"RADIO_INTERFACE_CAPABILITY_"}, + value = {}) + public @interface RadioInterfaceCapability {} + + /** + * Whether the device supports a given capability on the radio interface. + * + * If the capability is not in the set of radio interface capabilities, false is returned. + * + * @param capability the name of the capability to check for + * @return the availability of the capability + * + * @hide + */ + public boolean isRadioInterfaceCapabilitySupported( + @NonNull @RadioInterfaceCapability String capability) { + try { + if (capability == null) return false; + + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isRadioInterfaceCapabilitySupported(capability); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + if (!isSystemProcess()) { + ex.rethrowAsRuntimeException(); + } + } + return false; + } + /** * Indicates that the thermal mitigation request was completed successfully. * 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/EpsBearerQosSessionAttributes.aidl b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.aidl new file mode 100644 index 000000000000..da31f9864cf1 --- /dev/null +++ b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.aidl @@ -0,0 +1,19 @@ +/* + * 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; + + parcelable EpsBearerQosSessionAttributes;
\ No newline at end of file diff --git a/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java new file mode 100644 index 000000000000..041edc00c4d2 --- /dev/null +++ b/telephony/java/android/telephony/data/EpsBearerQosSessionAttributes.java @@ -0,0 +1,234 @@ +/* + * 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.NonNull; +import android.annotation.SystemApi; +import android.net.QosSessionAttributes; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Provides Qos attributes of an EPS bearer. + * + * {@hide} + */ +@SystemApi +public final class EpsBearerQosSessionAttributes implements Parcelable, QosSessionAttributes { + private static final String TAG = EpsBearerQosSessionAttributes.class.getSimpleName(); + private final int mQci; + private final long mMaxUplinkBitRate; + private final long mMaxDownlinkBitRate; + private final long mGuaranteedUplinkBitRate; + private final long mGuaranteedDownlinkBitRate; + @NonNull private final List<InetSocketAddress> mRemoteAddresses; + + /** + * Quality of Service Class Identifier (QCI), see 3GPP TS 23.203 and 29.212. + * The allowed values are standard values(1-9, 65-68, 69-70, 75, 79-80, 82-85) + * defined in the spec and operator specific values in the range 128-254. + * + * @return the qci of the session + */ + public int getQci() { + return mQci; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the uplink. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedUplinkBitRate() { + return mGuaranteedUplinkBitRate; + } + + /** + * Minimum bit rate in kbps that is guaranteed to be provided by the network on the downlink. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the guaranteed bit rate in kbps + */ + public long getGuaranteedDownlinkBitRate() { + return mGuaranteedDownlinkBitRate; + } + + /** + * The maximum kbps that the network will accept. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max uplink bit rate in kbps + */ + public long getMaxUplinkBitRate() { + return mMaxUplinkBitRate; + } + + /** + * The maximum kbps that the network can provide. + * + * see 3GPP TS 23.107 section 6.4.3.1 + * + * Note: The Qos Session may be shared with OTHER applications besides yours. + * + * @return the max downlink bit rate in kbps + */ + public long getMaxDownlinkBitRate() { + return mMaxDownlinkBitRate; + } + + /** + * List of remote addresses associated with the Qos Session. The given uplink bit rates apply + * to this given list of remote addresses. + * + * Note: In the event that the list is empty, it is assumed that the uplink bit rates apply to + * all remote addresses that are not contained in a different set of attributes. + * + * @return list of remote socket addresses that the attributes apply to + */ + @NonNull + public List<InetSocketAddress> getRemoteAddresses() { + return mRemoteAddresses; + } + + /** + * ..ctor for attributes + * + * @param qci quality class indicator + * @param maxDownlinkBitRate the max downlink bit rate in kbps + * @param maxUplinkBitRate the max uplink bit rate in kbps + * @param guaranteedDownlinkBitRate the guaranteed downlink bit rate in kbps + * @param guaranteedUplinkBitRate the guaranteed uplink bit rate in kbps + * @param remoteAddresses the remote addresses that the uplink bit rates apply to + * + * @hide + */ + public EpsBearerQosSessionAttributes(final int qci, + final long maxDownlinkBitRate, final long maxUplinkBitRate, + final long guaranteedDownlinkBitRate, final long guaranteedUplinkBitRate, + @NonNull final List<InetSocketAddress> remoteAddresses) { + Objects.requireNonNull(remoteAddresses, "remoteAddress must be non-null"); + mQci = qci; + mMaxDownlinkBitRate = maxDownlinkBitRate; + mMaxUplinkBitRate = maxUplinkBitRate; + mGuaranteedDownlinkBitRate = guaranteedDownlinkBitRate; + mGuaranteedUplinkBitRate = guaranteedUplinkBitRate; + + final List<InetSocketAddress> remoteAddressesTemp = copySocketAddresses(remoteAddresses); + mRemoteAddresses = Collections.unmodifiableList(remoteAddressesTemp); + } + + private static List<InetSocketAddress> copySocketAddresses( + @NonNull final List<InetSocketAddress> remoteAddresses) { + final List<InetSocketAddress> remoteAddressesTemp = new ArrayList<>(); + for (final InetSocketAddress socketAddress : remoteAddresses) { + if (socketAddress != null && socketAddress.getAddress() != null) { + remoteAddressesTemp.add(socketAddress); + } + } + return remoteAddressesTemp; + } + + private EpsBearerQosSessionAttributes(@NonNull final Parcel in) { + mQci = in.readInt(); + mMaxDownlinkBitRate = in.readLong(); + mMaxUplinkBitRate = in.readLong(); + mGuaranteedDownlinkBitRate = in.readLong(); + mGuaranteedUplinkBitRate = in.readLong(); + + final int size = in.readInt(); + final List<InetSocketAddress> remoteAddresses = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + final byte[] addressBytes = in.createByteArray(); + final int port = in.readInt(); + try { + remoteAddresses.add( + new InetSocketAddress(InetAddress.getByAddress(addressBytes), port)); + } catch (final UnknownHostException e) { + // Impossible case since we filter out null values in the ..ctor + Log.e(TAG, "unable to unparcel remote address at index: " + i, e); + } + } + mRemoteAddresses = Collections.unmodifiableList(remoteAddresses); + } + + /** + * Creates attributes based off of a parcel + * @param in the parcel + * @return the attributes + */ + @NonNull + public static EpsBearerQosSessionAttributes create(@NonNull final Parcel in) { + return new EpsBearerQosSessionAttributes(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeInt(mQci); + dest.writeLong(mMaxDownlinkBitRate); + dest.writeLong(mMaxUplinkBitRate); + dest.writeLong(mGuaranteedDownlinkBitRate); + dest.writeLong(mGuaranteedUplinkBitRate); + + final int size = mRemoteAddresses.size(); + dest.writeInt(size); + for (int i = 0; i < size; i++) { + final InetSocketAddress address = mRemoteAddresses.get(i); + dest.writeByteArray(address.getAddress().getAddress()); + dest.writeInt(address.getPort()); + } + } + + @NonNull + public static final Creator<EpsBearerQosSessionAttributes> CREATOR = + new Creator<EpsBearerQosSessionAttributes>() { + @NonNull + @Override + public EpsBearerQosSessionAttributes createFromParcel(@NonNull final Parcel in) { + return new EpsBearerQosSessionAttributes(in); + } + + @NonNull + @Override + public EpsBearerQosSessionAttributes[] newArray(final int size) { + return new EpsBearerQosSessionAttributes[size]; + } + }; +} 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 f5662692d84b..e556664bb323 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2280,6 +2280,14 @@ interface ITelephony { CarrierBandwidth getCarrierBandwidth(int subId); /** + * Checks whether the device supports the given capability on the radio interface. + * + * @param capability the name of the capability + * @return the availability of the capability + */ + boolean isRadioInterfaceCapabilitySupported(String capability); + + /** * Thermal mitigation request to control functionalities at modem. * * @param subId the id of the subscription @@ -2359,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/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 52f263fad695..76243a5799c3 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -520,6 +520,7 @@ public interface RILConstants { int RIL_REQUEST_START_HANDOVER = 217; int RIL_REQUEST_CANCEL_HANDOVER = 218; int RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS = 219; + int RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES = 220; int RIL_REQUEST_SET_DATA_THROTTLING = 221; int RIL_REQUEST_SET_ALLOWED_NETWORK_TYPE_BITMAP = 222; int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPE_BITMAP = 223; diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index 1110790c373f..d1a68d4e9cb2 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -117,6 +117,7 @@ package android.test.mock { method public void sendOrderedBroadcast(android.content.Intent, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, String, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); method public void sendStickyBroadcast(android.content.Intent); + method public void sendStickyBroadcast(android.content.Intent, android.os.Bundle); method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle); diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index cf3b03cae72e..6046d78240b6 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -493,6 +493,11 @@ public class MockContext extends Context { } @Override + public void sendStickyBroadcast(Intent intent, Bundle options) { + throw new UnsupportedOperationException(); + } + + @Override public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { 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/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index 3d4dc4d67dcc..dc9e587332cb 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -31,6 +31,7 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityManager; import android.net.LinkProperties; @@ -40,6 +41,7 @@ import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.NetworkSpecifier; +import android.net.QosFilter; import android.net.SocketKeepalive; import android.net.UidRange; import android.os.ConditionVariable; @@ -47,10 +49,12 @@ import android.os.HandlerThread; import android.os.Message; import android.util.Log; +import com.android.net.module.util.ArrayTrackRecord; import com.android.server.connectivity.ConnectivityConstants; import com.android.testutils.HandlerUtils; import com.android.testutils.TestableNetworkCallback; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -71,6 +75,8 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { // start/stop. Useful when simulate KeepaliveTracker is waiting for response from modem. private long mKeepaliveResponseDelay = 0L; private Integer mExpectedKeepaliveSlot = null; + private final ArrayTrackRecord<CallbackType>.ReadHead mCallbackHistory = + new ArrayTrackRecord<CallbackType>().newReadHead(); public NetworkAgentWrapper(int transport, LinkProperties linkProperties, NetworkCapabilities ncTemplate, Context context) throws Exception { @@ -157,6 +163,20 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { } @Override + public void onQosCallbackRegistered(final int qosCallbackId, + final @NonNull QosFilter filter) { + Log.i(mWrapper.mLogTag, "onQosCallbackRegistered"); + mWrapper.mCallbackHistory.add( + new CallbackType.OnQosCallbackRegister(qosCallbackId, filter)); + } + + @Override + public void onQosCallbackUnregistered(final int qosCallbackId) { + Log.i(mWrapper.mLogTag, "onQosCallbackUnregistered"); + mWrapper.mCallbackHistory.add(new CallbackType.OnQosCallbackUnregister(qosCallbackId)); + } + + @Override protected void preventAutomaticReconnect() { mWrapper.mPreventReconnectReceived.open(); } @@ -279,7 +299,60 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { return mNetworkCapabilities; } + public @NonNull ArrayTrackRecord<CallbackType>.ReadHead getCallbackHistory() { + return mCallbackHistory; + } + public void waitForIdle(long timeoutMs) { HandlerUtils.waitForIdle(mHandlerThread, timeoutMs); } + + abstract static class CallbackType { + final int mQosCallbackId; + + protected CallbackType(final int qosCallbackId) { + mQosCallbackId = qosCallbackId; + } + + static class OnQosCallbackRegister extends CallbackType { + final QosFilter mFilter; + OnQosCallbackRegister(final int qosCallbackId, final QosFilter filter) { + super(qosCallbackId); + mFilter = filter; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OnQosCallbackRegister that = (OnQosCallbackRegister) o; + return mQosCallbackId == that.mQosCallbackId + && Objects.equals(mFilter, that.mFilter); + } + + @Override + public int hashCode() { + return Objects.hash(mQosCallbackId, mFilter); + } + } + + static class OnQosCallbackUnregister extends CallbackType { + OnQosCallbackUnregister(final int qosCallbackId) { + super(qosCallbackId); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OnQosCallbackUnregister that = (OnQosCallbackUnregister) o; + return mQosCallbackId == that.mQosCallbackId; + } + + @Override + public int hashCode() { + return Objects.hash(mQosCallbackId); + } + } + } } 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 37307a46b8ac..ca736898065a 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -167,6 +167,7 @@ import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; import android.net.INetworkStatsService; +import android.net.IQosCallback; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; @@ -190,6 +191,9 @@ import android.net.NetworkStackClient; import android.net.NetworkState; import android.net.NetworkTestResultParcelable; import android.net.ProxyInfo; +import android.net.QosCallbackException; +import android.net.QosFilter; +import android.net.QosSession; import android.net.ResolverParamsParcel; import android.net.RouteInfo; import android.net.RouteInfoParcel; @@ -197,6 +201,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; @@ -218,6 +223,7 @@ import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; @@ -226,10 +232,12 @@ import android.security.Credentials; import android.security.KeyStore; import android.system.Os; import android.telephony.TelephonyManager; +import android.telephony.data.EpsBearerQosSessionAttributes; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import androidx.test.InstrumentationRegistry; @@ -238,7 +246,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; @@ -251,6 +258,7 @@ import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.ProxyTracker; +import com.android.server.connectivity.QosCallbackTracker; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -368,6 +376,8 @@ public class ConnectivityServiceTest { private WrappedMultinetworkPolicyTracker mPolicyTracker; private HandlerThread mAlarmManagerThread; private TestNetIdManager mNetIdManager; + private QosCallbackMockHelper mQosCallbackMockHelper; + private QosCallbackTracker mQosCallbackTracker; @Mock DeviceIdleInternal mDeviceIdleInternal; @Mock INetworkManagementService mNetworkManagementService; @@ -1395,6 +1405,7 @@ public class ConnectivityServiceTest { mService.systemReadyInternal(); mockVpn(Process.myUid()); mCm.bindProcessToNetwork(null); + mQosCallbackTracker = mock(QosCallbackTracker.class); // Ensure that the default setting for Captive Portals is used for most tests setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT); @@ -1470,6 +1481,11 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.disconnect(); mEthernetNetworkAgent = null; } + + if (mQosCallbackMockHelper != null) { + mQosCallbackMockHelper.tearDown(); + mQosCallbackMockHelper = null; + } mMockVpn.disconnect(); waitForIdle(); @@ -4379,7 +4395,7 @@ public class ConnectivityServiceTest { } private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception { - // Ensure the network is disconnected before we do anything. + // Ensure the network is disconnected before anything else occurs if (mWiFiNetworkAgent != null) { assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); } @@ -8307,8 +8323,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); } @@ -8512,7 +8527,7 @@ public class ConnectivityServiceTest { TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE)); return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(), nc, 0, mServiceContext, null, new NetworkAgentConfig(), mService, null, null, null, - 0, INVALID_UID); + 0, INVALID_UID, mQosCallbackTracker); } @Test @@ -8890,7 +8905,7 @@ public class ConnectivityServiceTest { @Test public void testInvalidRequestTypes() { - final int[] invalidReqTypeInts = new int[] {-1, NetworkRequest.Type.NONE.ordinal(), + final int[] invalidReqTypeInts = new int[]{-1, NetworkRequest.Type.NONE.ordinal(), NetworkRequest.Type.LISTEN.ordinal(), NetworkRequest.Type.values().length}; final NetworkCapabilities nc = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); @@ -8903,4 +8918,151 @@ public class ConnectivityServiceTest { ); } } + + private class QosCallbackMockHelper { + @NonNull public final QosFilter mFilter; + @NonNull public final IQosCallback mCallback; + @NonNull public final TestNetworkAgentWrapper mAgentWrapper; + @NonNull private final List<IQosCallback> mCallbacks = new ArrayList(); + + QosCallbackMockHelper() throws Exception { + Log.d(TAG, "QosCallbackMockHelper: "); + mFilter = mock(QosFilter.class); + + // Ensure the network is disconnected before anything else occurs + assertNull(mCellNetworkAgent); + + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + + verifyActiveNetwork(TRANSPORT_CELLULAR); + waitForIdle(); + final Network network = mCellNetworkAgent.getNetwork(); + + final Pair<IQosCallback, IBinder> pair = createQosCallback(); + mCallback = pair.first; + + when(mFilter.getNetwork()).thenReturn(network); + when(mFilter.validate()).thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mAgentWrapper = mCellNetworkAgent; + } + + void registerQosCallback(@NonNull final QosFilter filter, + @NonNull final IQosCallback callback) { + mCallbacks.add(callback); + final NetworkAgentInfo nai = + mService.getNetworkAgentInfoForNetwork(filter.getNetwork()); + mService.registerQosCallbackInternal(filter, callback, nai); + } + + void tearDown() { + for (int i = 0; i < mCallbacks.size(); i++) { + mService.unregisterQosCallback(mCallbacks.get(i)); + } + } + } + + private Pair<IQosCallback, IBinder> createQosCallback() { + final IQosCallback callback = mock(IQosCallback.class); + final IBinder binder = mock(Binder.class); + when(callback.asBinder()).thenReturn(binder); + when(binder.isBinderAlive()).thenReturn(true); + return new Pair<>(callback, binder); + } + + + @Test + public void testQosCallbackRegistration() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + + final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 = + (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister) + wrapper.getCallbackHistory().poll(1000, x -> true); + assertNotNull(cbRegister1); + + final int registerCallbackId = cbRegister1.mQosCallbackId; + mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback); + final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister; + cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister) + wrapper.getCallbackHistory().poll(1000, x -> true); + assertNotNull(cbUnregister); + assertEquals(registerCallbackId, cbUnregister.mQosCallbackId); + assertNull(wrapper.getCallbackHistory().poll(200, x -> true)); + } + + @Test + public void testQosCallbackNoRegistrationOnValidationError() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback) + .onError(eq(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED)); + } + + @Test + public void testQosCallbackAvailableAndLost() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + final int sessionId = 10; + final int qosCallbackId = 1; + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); + waitForIdle(); + + final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes( + 1, 2, 3, 4, 5, new ArrayList<>()); + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); + waitForIdle(); + + verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes)); + + mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() + .sendQosSessionLost(qosCallbackId, sessionId); + waitForIdle(); + verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> + session.getSessionId() == sessionId + && session.getSessionType() == QosSession.TYPE_EPS_BEARER)); + } + + @Test + public void testQosCallbackTooManyRequests() throws Exception { + mQosCallbackMockHelper = new QosCallbackMockHelper(); + + when(mQosCallbackMockHelper.mFilter.validate()) + .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); + for (int i = 0; i < 100; i++) { + final Pair<IQosCallback, IBinder> pair = createQosCallback(); + + try { + mQosCallbackMockHelper.registerQosCallback( + mQosCallbackMockHelper.mFilter, pair.first); + } catch (ServiceSpecificException e) { + assertEquals(e.errorCode, ConnectivityManager.Errors.TOO_MANY_REQUESTS); + if (i < 50) { + fail("TOO_MANY_REQUESTS thrown too early, the count is " + i); + } + + // As long as there is at least 50 requests, it is safe to assume it works. + // Note: The count isn't being tested precisely against 100 because the counter + // is shared with request network. + return; + } + } + fail("TOO_MANY_REQUESTS never thrown"); + } } diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java index 4d151afecd63..52cb836e19c8 100644 --- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java @@ -78,6 +78,7 @@ public class LingerMonitorTest { @Mock Context mCtx; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; + @Mock QosCallbackTracker mQosCallbackTracker; @Before public void setUp() { @@ -358,7 +359,7 @@ public class LingerMonitorTest { NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info, new LinkProperties(), caps, 50, mCtx, null, new NetworkAgentConfig() /* config */, mConnService, mNetd, mDnsResolver, mNMS, NetworkProvider.ID_NONE, - Binder.getCallingUid()); + Binder.getCallingUid(), mQosCallbackTracker); nai.everValidated = true; return nai; } 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/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 25bd7c06be49..1102624da031 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -197,6 +196,11 @@ public class BroadcastInterceptingContext extends ContextWrapper { } @Override + public void sendStickyBroadcast(Intent intent, Bundle options) { + sendBroadcast(intent); + } + + @Override public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { sendBroadcast(intent); } 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 696110f01869..f0cdde33f822 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -18,15 +18,21 @@ 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; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -35,8 +41,10 @@ import static org.mockito.Mockito.verify; import android.app.AppOpsManager; import android.content.Context; import android.net.ConnectivityManager; +import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; import android.net.vcn.VcnConfigTest; +import android.os.IBinder; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.Process; @@ -126,12 +134,21 @@ public class VcnManagementServiceTest { private final VcnManagementService mVcnMgmtSvc; + private final IVcnUnderlyingNetworkPolicyListener mMockPolicyListener = + mock(IVcnUnderlyingNetworkPolicyListener.class); + 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(); @@ -169,15 +186,12 @@ public class VcnManagementServiceTest { setupMockedCarrierPrivilege(true); mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); + doReturn(mMockIBinder).when(mMockPolicyListener).asBinder(); + // Make sure the profiles are loaded. 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) @@ -438,4 +452,40 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); verify(vcnInstance).teardownAsynchronously(); } + + @Test + public void testAddVcnUnderlyingNetworkPolicyListener() throws Exception { + doNothing() + .when(mMockContext) + .enforceCallingPermission(eq(android.Manifest.permission.NETWORK_FACTORY), any()); + + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + verify(mMockIBinder).linkToDeath(any(), anyInt()); + } + + @Test(expected = SecurityException.class) + public void testAddVcnUnderlyingNetworkPolicyListenerInvalidPermission() { + doThrow(new SecurityException()) + .when(mMockContext) + .enforceCallingPermission(eq(android.Manifest.permission.NETWORK_FACTORY), any()); + + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + } + + @Test + public void testRemoveVcnUnderlyingNetworkPolicyListener() { + // verify listener added + doNothing() + .when(mMockContext) + .enforceCallingPermission(eq(android.Manifest.permission.NETWORK_FACTORY), any()); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + } + + @Test + public void testRemoveVcnUnderlyingNetworkPolicyListenerNeverRegistered() { + mVcnMgmtSvc.removeVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + } } 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 |