diff options
57 files changed, 3631 insertions, 202 deletions
diff --git a/api/current.txt b/api/current.txt index ec30bb0c26d3..9b6dcc70a1ec 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9824,6 +9824,7 @@ package android.content { field public static final String CARRIER_CONFIG_SERVICE = "carrier_config"; field public static final String CLIPBOARD_SERVICE = "clipboard"; field public static final String COMPANION_DEVICE_SERVICE = "companiondevice"; + field public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics"; field public static final String CONNECTIVITY_SERVICE = "connectivity"; field public static final String CONSUMER_IR_SERVICE = "consumer_ir"; field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2 @@ -9882,6 +9883,7 @@ package android.content { field public static final String USB_SERVICE = "usb"; field public static final String USER_SERVICE = "user"; field public static final String VIBRATOR_SERVICE = "vibrator"; + field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management"; field public static final String WALLPAPER_SERVICE = "wallpaper"; field public static final String WIFI_AWARE_SERVICE = "wifiaware"; field public static final String WIFI_P2P_SERVICE = "wifip2p"; @@ -28691,8 +28693,6 @@ package android.net { public class ConnectivityDiagnosticsManager { method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); - field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 - field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 } public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { @@ -28702,21 +28702,44 @@ package android.net { method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); } - public static class ConnectivityDiagnosticsManager.ConnectivityReport { + public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - field @NonNull public final android.os.PersistableBundle additionalInfo; - field @NonNull public final android.net.LinkProperties linkProperties; - field @NonNull public final android.net.Network network; - field @NonNull public final android.net.NetworkCapabilities networkCapabilities; - field public final long reportTimestamp; - } - - public static class ConnectivityDiagnosticsManager.DataStallReport { + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getAdditionalInfo(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; + field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttemped"; + field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; + field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + field public static final int NETWORK_PROBE_DNS = 4; // 0x4 + field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 + field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 + field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 + field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 + field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 + field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 + field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 + field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 + } + + public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.os.PersistableBundle); - field public final int detectionMethod; - field @NonNull public final android.net.Network network; - field public final long reportTimestamp; - field @NonNull public final android.os.PersistableBundle stallDetails; + method public int describeContents(); + method public int getDetectionMethod(); + method @NonNull public android.net.Network getNetwork(); + method public long getReportTimestamp(); + method @NonNull public android.os.PersistableBundle getStallDetails(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR; + field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 + field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 + field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; + field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; } public class ConnectivityManager { @@ -28859,6 +28882,35 @@ package android.net { field public final int code; } + public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile { + method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms(); + method public int getMaxMtu(); + method @Nullable public String getPassword(); + method @Nullable public byte[] getPresharedKey(); + method @Nullable public android.net.ProxyInfo getProxyInfo(); + method @Nullable public java.security.PrivateKey getRsaPrivateKey(); + method @NonNull public String getServerAddr(); + method @Nullable public java.security.cert.X509Certificate getServerRootCaCert(); + method @Nullable public java.security.cert.X509Certificate getUserCert(); + method @NonNull public String getUserIdentity(); + method @Nullable public String getUsername(); + method public boolean isBypassable(); + method public boolean isMetered(); + } + + public static final class Ikev2VpnProfile.Builder { + ctor public Ikev2VpnProfile.Builder(@NonNull String, @NonNull String); + method @NonNull public android.net.Ikev2VpnProfile build(); + method @NonNull public android.net.Ikev2VpnProfile.Builder setAllowedAlgorithms(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthDigitalSignature(@NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey, @Nullable java.security.cert.X509Certificate); + method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthPsk(@NonNull byte[]); + method @NonNull public android.net.Ikev2VpnProfile.Builder setAuthUsernamePassword(@NonNull String, @NonNull String, @Nullable java.security.cert.X509Certificate); + method @NonNull public android.net.Ikev2VpnProfile.Builder setBypassable(boolean); + method @NonNull public android.net.Ikev2VpnProfile.Builder setMaxMtu(int); + method @NonNull public android.net.Ikev2VpnProfile.Builder setMetered(boolean); + method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo); + } + public class InetAddresses { method public static boolean isNumericAddress(@NonNull String); method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); @@ -29205,6 +29257,14 @@ package android.net { field public String response; } + public abstract class PlatformVpnProfile { + method public final int getType(); + method @NonNull public final String getTypeString(); + field public static final int TYPE_IKEV2_IPSEC_PSK = 7; // 0x7 + field public static final int TYPE_IKEV2_IPSEC_RSA = 8; // 0x8 + field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6 + } + public final class Proxy { ctor public Proxy(); method @Deprecated public static String getDefaultHost(); @@ -29485,6 +29545,13 @@ package android.net { method public String sanitize(String); } + public class VpnManager { + method public void deleteProvisionedVpnProfile(); + method @Nullable public android.content.Intent provisionVpnProfile(@NonNull android.net.PlatformVpnProfile); + method public void startProvisionedVpnProfile(); + method public void stopProvisionedVpnProfile(); + } + public class VpnService extends android.app.Service { ctor public VpnService(); method public final boolean isAlwaysOn(); @@ -43608,6 +43675,7 @@ package android.telecom { field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 + field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800 field public static final int PROPERTY_RTT = 1024; // 0x400 @@ -43685,6 +43753,7 @@ package android.telecom { public abstract class Conference extends android.telecom.Conferenceable { ctor public Conference(android.telecom.PhoneAccountHandle); method public final boolean addConnection(android.telecom.Connection); + method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle); method public final void destroy(); method public final android.telecom.CallAudioState getCallAudioState(); method public final java.util.List<android.telecom.Connection> getConferenceableConnections(); @@ -43699,6 +43768,8 @@ package android.telecom { method public final android.telecom.StatusHints getStatusHints(); method public android.telecom.Connection.VideoProvider getVideoProvider(); method public int getVideoState(); + method public final boolean isRingbackRequested(); + method public void onAnswer(int); method public void onCallAudioStateChanged(android.telecom.CallAudioState); method public void onConnectionAdded(android.telecom.Connection); method public void onDisconnect(); @@ -43707,6 +43778,7 @@ package android.telecom { method public void onMerge(android.telecom.Connection); method public void onMerge(); method public void onPlayDtmfTone(char); + method public void onReject(); method public void onSeparate(android.telecom.Connection); method public void onStopDtmfTone(); method public void onSwap(); @@ -43726,6 +43798,8 @@ package android.telecom { method public final void setDisconnected(android.telecom.DisconnectCause); method public final void setExtras(@Nullable android.os.Bundle); method public final void setOnHold(); + method public final void setRingbackRequested(boolean); + method public final void setRinging(); method public final void setStatusHints(android.telecom.StatusHints); method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider); method public final void setVideoState(android.telecom.Connection, int); @@ -43883,6 +43957,7 @@ package android.telecom { field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4 + field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 4096; // 0x1000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10 field public static final int PROPERTY_IS_RTT = 256; // 0x100 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400 @@ -43955,8 +44030,10 @@ package android.telecom { method public android.telecom.PhoneAccountHandle getAccountHandle(); method public android.net.Uri getAddress(); method public android.os.Bundle getExtras(); + method @Nullable public java.util.List<android.net.Uri> getParticipants(); method public android.telecom.Connection.RttTextStream getRttTextStream(); method public int getVideoState(); + method public boolean isAdhocConferenceCall(); method public boolean isRequestingRtt(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ConnectionRequest> CREATOR; @@ -43976,9 +44053,13 @@ package android.telecom { method public void onConference(android.telecom.Connection, android.telecom.Connection); method public void onConnectionServiceFocusGained(); method public void onConnectionServiceFocusLost(); + method @Nullable public android.telecom.Conference onCreateIncomingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); + method public void onCreateIncomingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateIncomingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); + method @Nullable public android.telecom.Conference onCreateOutgoingConference(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); + method public void onCreateOutgoingConferenceFailed(@Nullable android.telecom.PhoneAccountHandle, @Nullable android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingHandoverConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); @@ -44093,6 +44174,7 @@ package android.telecom { method public boolean supportsUriScheme(String); method public android.telecom.PhoneAccount.Builder toBuilder(); method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 16384; // 0x4000 field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2 field public static final int CAPABILITY_CALL_SUBJECT = 64; // 0x40 field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1 @@ -44288,6 +44370,7 @@ package android.telecom { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int); method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); + method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification(); method public android.content.Intent createManageBlockedNumbersIntent(); method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); @@ -44315,6 +44398,7 @@ package android.telecom { method public void registerPhoneAccount(android.telecom.PhoneAccount); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); + method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER"; field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; @@ -44793,6 +44877,7 @@ package android.telephony { field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; + field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool"; field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool"; diff --git a/api/system-current.txt b/api/system-current.txt index 132956b0e3b0..fe4bd863e7bb 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1440,12 +1440,13 @@ package android.bluetooth { field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; } - public final class BluetoothPan implements android.bluetooth.BluetoothProfile { - method protected void finalize(); + public final class BluetoothPan implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public void close(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) protected void finalize(); method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); - method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); - method public boolean isTetheringOn(); - method public void setBluetoothTethering(boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isTetheringOn(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void setBluetoothTethering(boolean); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; @@ -4051,8 +4052,8 @@ package android.media.tv { public final class DvbDeviceInfo implements android.os.Parcelable { ctor public DvbDeviceInfo(int, int); method public int describeContents(); - method public int getAdapterId(); - method public int getDeviceId(); + method @IntRange(from=0) public int getAdapterId(); + method @IntRange(from=0) public int getDeviceId(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.DvbDeviceInfo> CREATOR; } @@ -4532,9 +4533,11 @@ package android.net { public final class NetworkCapabilities implements android.os.Parcelable { method public boolean deduceRestrictedCapability(); + method @NonNull public java.util.List<java.lang.Integer> getAdministratorUids(); method @Nullable public String getSSID(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); + method public void setAdministratorUids(@NonNull java.util.List<java.lang.Integer>); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); method @NonNull public android.net.NetworkCapabilities setTransportInfo(@NonNull android.net.TransportInfo); field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 @@ -4707,15 +4710,6 @@ package android.net { method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); } - public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { - ctor public StringNetworkSpecifier(@NonNull String); - method public int describeContents(); - method public boolean satisfiedBy(android.net.NetworkSpecifier); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR; - field @NonNull public final String specifier; - } - public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { method public boolean satisfiedBy(android.net.NetworkSpecifier); } @@ -4909,7 +4903,7 @@ package android.net.ipsec.ike { } public final class IkeSessionConfiguration { - ctor public IkeSessionConfiguration(); + method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); method @NonNull public String getRemoteApplicationVersion(); method @NonNull public java.util.List<byte[]> getRemoteVendorIDs(); method public boolean isIkeExtensionEnabled(int); @@ -4918,6 +4912,7 @@ package android.net.ipsec.ike { } public final class IkeSessionParams { + method @NonNull public java.util.List<android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest> getConfigurationRequests(); method public long getHardLifetime(); method @NonNull public android.net.ipsec.ike.IkeSessionParams.IkeAuthConfig getLocalAuthConfig(); method @NonNull public android.net.ipsec.ike.IkeIdentification getLocalIdentification(); @@ -4931,6 +4926,8 @@ package android.net.ipsec.ike { public static final class IkeSessionParams.Builder { ctor public IkeSessionParams.Builder(); + method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addPcscfServerRequest(@NonNull java.net.InetAddress); + method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addPcscfServerRequest(int); method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder addSaProposal(@NonNull android.net.ipsec.ike.IkeSaProposal); method @NonNull public android.net.ipsec.ike.IkeSessionParams build(); method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setAuthDigitalSignature(@Nullable java.security.cert.X509Certificate, @NonNull java.security.cert.X509Certificate, @NonNull java.security.PrivateKey); @@ -4944,6 +4941,14 @@ package android.net.ipsec.ike { method @NonNull public android.net.ipsec.ike.IkeSessionParams.Builder setUdpEncapsulationSocket(@NonNull android.net.IpSecManager.UdpEncapsulationSocket); } + public static interface IkeSessionParams.ConfigRequestIpv4PcscfServer extends android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest { + method @Nullable public java.net.Inet4Address getAddress(); + } + + public static interface IkeSessionParams.ConfigRequestIpv6PcscfServer extends android.net.ipsec.ike.IkeSessionParams.IkeConfigRequest { + method @Nullable public java.net.Inet6Address getAddress(); + } + public abstract static class IkeSessionParams.IkeAuthConfig { } @@ -4965,6 +4970,9 @@ package android.net.ipsec.ike { method @NonNull public byte[] getPsk(); } + public static interface IkeSessionParams.IkeConfigRequest { + } + public final class IkeTrafficSelector { ctor public IkeTrafficSelector(int, int, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress); field public final int endPort; @@ -5012,7 +5020,7 @@ package android.net.ipsec.ike { } public final class TunnelModeChildSessionParams extends android.net.ipsec.ike.ChildSessionParams { - method @NonNull public java.util.List<android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest> getConfigurationRequests(); + method @NonNull public java.util.List<android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest> getConfigurationRequests(); } public static final class TunnelModeChildSessionParams.Builder { @@ -5029,33 +5037,33 @@ package android.net.ipsec.ike { method @NonNull public android.net.ipsec.ike.TunnelModeChildSessionParams.Builder setLifetime(long, long); } - public static interface TunnelModeChildSessionParams.ConfigRequest { - } - - public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest { + public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest { method @Nullable public java.net.Inet4Address getAddress(); } - public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest { + public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest { method @Nullable public java.net.Inet4Address getAddress(); } - public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest { + public static interface TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest { method @Nullable public java.net.Inet4Address getAddress(); } - public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest { + public static interface TunnelModeChildSessionParams.ConfigRequestIpv4Netmask extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest { } - public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest { + public static interface TunnelModeChildSessionParams.ConfigRequestIpv6Address extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest { method @Nullable public java.net.Inet6Address getAddress(); method public int getPrefixLength(); } - public static interface TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequest { + public static interface TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer extends android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest { method @Nullable public java.net.Inet6Address getAddress(); } + public static interface TunnelModeChildSessionParams.TunnelModeChildConfigRequest { + } + } package android.net.ipsec.ike.exceptions { @@ -5539,7 +5547,7 @@ package android.net.wifi { method public boolean isPortableHotspotSupported(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled(); method public boolean isWifiScannerSupported(); - method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerSoftApCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback); + method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback); method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int); @@ -7100,6 +7108,14 @@ package android.provider { } +package android.se.omapi { + + public final class Reader { + method @RequiresPermission(android.Manifest.permission.SECURE_ELEMENT_PRIVILEGED) public boolean reset(); + } + +} + package android.security.keystore { public abstract class AttestationUtils { @@ -10657,6 +10673,7 @@ package android.telephony.ims.stub { method public int transact(android.os.Bundle); method public int updateCallBarring(int, int, String[]); method public int updateCallBarringForServiceClass(int, int, String[], int); + method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String); method public int updateCallForward(int, int, String, int, int); method public int updateCallWaiting(boolean, int); method public int updateClip(boolean); diff --git a/api/test-current.txt b/api/test-current.txt index 26b710bbe9e8..98b224d366b9 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2884,6 +2884,8 @@ package android.telecom { method @NonNull public android.telecom.ConnectionRequest.Builder setAccountHandle(@NonNull android.telecom.PhoneAccountHandle); method @NonNull public android.telecom.ConnectionRequest.Builder setAddress(@NonNull android.net.Uri); method @NonNull public android.telecom.ConnectionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.telecom.ConnectionRequest.Builder setIsAdhocConferenceCall(boolean); + method @NonNull public android.telecom.ConnectionRequest.Builder setParticipants(@Nullable java.util.List<android.net.Uri>); method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeFromInCall(@NonNull android.os.ParcelFileDescriptor); method @NonNull public android.telecom.ConnectionRequest.Builder setRttPipeToInCall(@NonNull android.os.ParcelFileDescriptor); method @NonNull public android.telecom.ConnectionRequest.Builder setShouldShowIncomingCallUi(boolean); @@ -3931,6 +3933,7 @@ package android.telephony.ims.stub { method public int transact(android.os.Bundle); method public int updateCallBarring(int, int, String[]); method public int updateCallBarringForServiceClass(int, int, String[], int); + method public int updateCallBarringWithPassword(int, int, @Nullable String[], int, @NonNull String); method public int updateCallForward(int, int, String, int, int); method public int updateCallWaiting(boolean, int); method public int updateClip(boolean); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 88976e182e6d..81f6d28db9fe 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -98,6 +98,7 @@ import android.media.session.MediaSessionManager; import android.media.soundtrigger.SoundTriggerManager; import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; +import android.net.ConnectivityDiagnosticsManager; import android.net.ConnectivityManager; import android.net.ConnectivityThread; import android.net.EthernetManager; @@ -112,6 +113,7 @@ import android.net.NetworkScoreManager; import android.net.NetworkWatchlistManager; import android.net.TestNetworkManager; import android.net.TetheringManager; +import android.net.VpnManager; import android.net.lowpan.ILowpanManager; import android.net.lowpan.LowpanManager; import android.net.nsd.INsdManager; @@ -369,6 +371,27 @@ final class SystemServiceRegistry { return new IpSecManager(ctx, service); }}); + registerService(Context.VPN_MANAGEMENT_SERVICE, VpnManager.class, + new CachedServiceFetcher<VpnManager>() { + @Override + public VpnManager createService(ContextImpl ctx) throws ServiceNotFoundException { + IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + return new VpnManager(ctx, service); + }}); + + registerService(Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, + ConnectivityDiagnosticsManager.class, + new CachedServiceFetcher<ConnectivityDiagnosticsManager>() { + @Override + public ConnectivityDiagnosticsManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + // ConnectivityDiagnosticsManager is backed by ConnectivityService + IBinder b = ServiceManager.getServiceOrThrow(Context.CONNECTIVITY_SERVICE); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + return new ConnectivityDiagnosticsManager(ctx, service); + }}); + registerService( Context.TEST_NETWORK_SERVICE, TestNetworkManager.class, diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index 8993de0939e6..ee2cc6d14712 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -297,7 +297,8 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -345,7 +346,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(@Nullable BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 1869c6fa7621..bd0a39c06b26 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1222,6 +1222,7 @@ public final class BluetoothAdapter { if (mService != null) { return mService.factoryReset(); } + Log.e(TAG, "factoryReset(): IBluetooth Service is null"); SystemProperties.set("persist.bluetooth.factoryreset", "true"); } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1239,7 +1240,7 @@ public final class BluetoothAdapter { */ @UnsupportedAppUsage @RequiresPermission(Manifest.permission.BLUETOOTH) - public @NonNull ParcelUuid[] getUuids() { + public @Nullable ParcelUuid[] getUuids() { if (getState() != STATE_ON) { return null; } diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java index 6de1ffb6344e..fbda9e9d6dd7 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -584,7 +584,8 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothHeadsetClient service = getService(); @@ -633,7 +634,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothHeadsetClient service = getService(); diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java index c1233b800a51..26e3e271bffa 100644 --- a/core/java/android/bluetooth/BluetoothHidHost.java +++ b/core/java/android/bluetooth/BluetoothHidHost.java @@ -416,7 +416,8 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -464,7 +465,7 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(@Nullable BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 467470674286..1c62faa97ee6 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -340,7 +340,8 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -388,7 +389,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(@Nullable BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 0aa5aac5d8f6..8d2aaddd38d2 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -271,7 +271,8 @@ public final class BluetoothMapClient implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -319,7 +320,7 @@ public final class BluetoothMapClient implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 024bb06098ab..ec63fd058b16 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -30,6 +30,7 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.util.CloseGuard; import android.util.Log; import java.lang.annotation.Retention; @@ -50,10 +51,11 @@ import java.util.List; * @hide */ @SystemApi -public final class BluetoothPan implements BluetoothProfile { +public final class BluetoothPan implements BluetoothProfile, AutoCloseable { private static final String TAG = "BluetoothPan"; private static final boolean DBG = true; private static final boolean VDBG = false; + private CloseGuard mCloseGuard; /** * Intent used to broadcast the change in connection state of the Pan @@ -166,10 +168,15 @@ public final class BluetoothPan implements BluetoothProfile { mAdapter = BluetoothAdapter.getDefaultAdapter(); mContext = context; mProfileConnector.connect(context, listener); + mCloseGuard = new CloseGuard(); + mCloseGuard.open("close"); } - @UnsupportedAppUsage - /*package*/ void close() { + /** + * Closes the connection to the service and unregisters callbacks + */ + @RequiresPermission(Manifest.permission.BLUETOOTH) + public void close() { if (VDBG) log("close()"); mProfileConnector.disconnect(); } @@ -178,8 +185,11 @@ public final class BluetoothPan implements BluetoothProfile { return mProfileConnector.getService(); } - + @RequiresPermission(Manifest.permission.BLUETOOTH) protected void finalize() { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } close(); } @@ -316,6 +326,7 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothPan service = getService(); @@ -335,6 +346,7 @@ public final class BluetoothPan implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH) public int getConnectionState(@Nullable BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); final IBluetoothPan service = getService(); @@ -355,6 +367,7 @@ public final class BluetoothPan implements BluetoothProfile { * * @param value is whether to enable or disable bluetooth tethering */ + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) public void setBluetoothTethering(boolean value) { String pkgName = mContext.getOpPackageName(); if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); @@ -373,6 +386,7 @@ public final class BluetoothPan implements BluetoothProfile { * * @return true if tethering is on, false if not or some error occurred */ + @RequiresPermission(Manifest.permission.BLUETOOTH) public boolean isTetheringOn() { if (VDBG) log("isTetheringOn()"); final IBluetoothPan service = getService(); diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java index 9618ba01572e..9563c68ce657 100644 --- a/core/java/android/bluetooth/BluetoothPbapClient.java +++ b/core/java/android/bluetooth/BluetoothPbapClient.java @@ -271,7 +271,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) { log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); } @@ -323,7 +324,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { if (VDBG) { log("getConnectionPolicy(" + device + ")"); } diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java index 8bf1b588c89a..bfc3a4d25c23 100644 --- a/core/java/android/bluetooth/BluetoothSap.java +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -330,7 +330,8 @@ public final class BluetoothSap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothSap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -378,7 +379,7 @@ public final class BluetoothSap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothSap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cd4af968ebfc..472d9567e2ad 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3296,6 +3296,7 @@ public abstract class Context { CONNECTIVITY_SERVICE, //@hide: IP_MEMORY_STORE_SERVICE, IPSEC_SERVICE, + VPN_MANAGEMENT_SERVICE, TEST_NETWORK_SERVICE, //@hide: UPDATE_LOCK_SERVICE, //@hide: NETWORKMANAGEMENT_SERVICE, @@ -3880,6 +3881,24 @@ public abstract class Context { public static final String IPSEC_SERVICE = "ipsec"; /** + * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.VpnManager} to + * manage profiles for the platform built-in VPN. + * + * @see #getSystemService(String) + */ + public static final String VPN_MANAGEMENT_SERVICE = "vpn_management"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a {@link + * android.net.ConnectivityDiagnosticsManager} for performing network connectivity diagnostics + * as well as receiving network connectivity information from the system. + * + * @see #getSystemService(String) + * @see android.net.ConnectivityDiagnosticsManager + */ + public static final String CONNECTIVITY_DIAGNOSTICS_SERVICE = "connectivity_diagnostics"; + + /** * Use with {@link #getSystemService(String)} to retrieve a {@link * android.net.TestNetworkManager} for building TUNs and limited-use Networks * diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.aidl b/core/java/android/net/ConnectivityDiagnosticsManager.aidl new file mode 100644 index 000000000000..82ba0ca113c5 --- /dev/null +++ b/core/java/android/net/ConnectivityDiagnosticsManager.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 ConnectivityDiagnosticsManager.ConnectivityReport; +parcelable ConnectivityDiagnosticsManager.DataStallReport;
\ No newline at end of file diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 6afdb5ef1b16..456481386cc9 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -18,10 +18,19 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import android.os.PersistableBundle; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -47,38 +56,169 @@ import java.util.concurrent.Executor; * </ul> */ public class ConnectivityDiagnosticsManager { - public static final int DETECTION_METHOD_DNS_EVENTS = 1; - public static final int DETECTION_METHOD_TCP_METRICS = 2; + private final Context mContext; + private final IConnectivityManager mService; /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = {"DETECTION_METHOD_"}, - value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) - public @interface DetectionMethod {} + public ConnectivityDiagnosticsManager(Context context, IConnectivityManager service) { + mContext = Preconditions.checkNotNull(context, "missing context"); + mService = Preconditions.checkNotNull(service, "missing IConnectivityManager"); + } /** @hide */ - public ConnectivityDiagnosticsManager() {} + @VisibleForTesting + public static boolean persistableBundleEquals( + @Nullable PersistableBundle a, @Nullable PersistableBundle b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (!Objects.equals(a.keySet(), b.keySet())) return false; + for (String key : a.keySet()) { + if (!Objects.equals(a.get(key), b.get(key))) return false; + } + return true; + } /** Class that includes connectivity information for a specific Network at a specific time. */ - public static class ConnectivityReport { + public static final class ConnectivityReport implements Parcelable { + /** + * The overall status of the network is that it is invalid; it neither provides + * connectivity nor has been exempted from validation. + */ + public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; + + /** + * The overall status of the network is that it is valid, this may be because it provides + * full Internet access (all probes succeeded), or because other properties of the network + * caused probes not to be run. + */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID + public static final int NETWORK_VALIDATION_RESULT_VALID = 1; + + /** + * The overall status of the network is that it provides partial connectivity; some + * probed services succeeded but others failed. + */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; + public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; + + /** + * Due to the properties of the network, validation was not performed. + */ + public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; + + /** @hide */ + @IntDef( + prefix = {"NETWORK_VALIDATION_RESULT_"}, + value = { + NETWORK_VALIDATION_RESULT_INVALID, + NETWORK_VALIDATION_RESULT_VALID, + NETWORK_VALIDATION_RESULT_PARTIALLY_VALID, + NETWORK_VALIDATION_RESULT_SKIPPED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkValidationResult {} + + /** + * The overall validation result for the Network being reported on. + * + * <p>The possible values for this key are: + * {@link #NETWORK_VALIDATION_RESULT_INVALID}, + * {@link #NETWORK_VALIDATION_RESULT_VALID}, + * {@link #NETWORK_VALIDATION_RESULT_PARTIALLY_VALID}, + * {@link #NETWORK_VALIDATION_RESULT_SKIPPED}. + * + * @see android.net.NetworkCapabilities#CAPABILITY_VALIDATED + */ + @NetworkValidationResult + public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + + /** DNS probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS + public static final int NETWORK_PROBE_DNS = 0x04; + + /** HTTP probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP + public static final int NETWORK_PROBE_HTTP = 0x08; + + /** HTTPS probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; + public static final int NETWORK_PROBE_HTTPS = 0x10; + + /** Captive portal fallback probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_FALLBACK + public static final int NETWORK_PROBE_FALLBACK = 0x20; + + /** Private DNS (DNS over TLS) probd. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS + public static final int NETWORK_PROBE_PRIVATE_DNS = 0x40; + + /** @hide */ + @IntDef( + prefix = {"NETWORK_PROBE_"}, + value = { + NETWORK_PROBE_DNS, + NETWORK_PROBE_HTTP, + NETWORK_PROBE_HTTPS, + NETWORK_PROBE_FALLBACK, + NETWORK_PROBE_PRIVATE_DNS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkProbe {} + + /** + * A bitmask of network validation probes that succeeded. + * + * <p>The possible bits values reported by this key are: + * {@link #NETWORK_PROBE_DNS}, + * {@link #NETWORK_PROBE_HTTP}, + * {@link #NETWORK_PROBE_HTTPS}, + * {@link #NETWORK_PROBE_FALLBACK}, + * {@link #NETWORK_PROBE_PRIVATE_DNS}. + */ + @NetworkProbe + public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = + "networkProbesSucceeded"; + + /** + * A bitmask of network validation probes that were attempted. + * + * <p>These probes may have failed or may be incomplete at the time of this report. + * + * <p>The possible bits values reported by this key are: + * {@link #NETWORK_PROBE_DNS}, + * {@link #NETWORK_PROBE_HTTP}, + * {@link #NETWORK_PROBE_HTTPS}, + * {@link #NETWORK_PROBE_FALLBACK}, + * {@link #NETWORK_PROBE_PRIVATE_DNS}. + */ + @NetworkProbe + public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = + "networkProbesAttemped"; + + /** @hide */ + @StringDef(prefix = {"KEY_"}, value = { + KEY_NETWORK_VALIDATION_RESULT, KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, + KEY_NETWORK_PROBES_ATTEMPTED_BITMASK}) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectivityReportBundleKeys {} + /** The Network for which this ConnectivityReport applied */ - @NonNull public final Network network; + @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ - public final long reportTimestamp; + private final long mReportTimestamp; /** LinkProperties available on the Network at the reported timestamp */ - @NonNull public final LinkProperties linkProperties; + @NonNull private final LinkProperties mLinkProperties; /** NetworkCapabilities available on the Network at the reported timestamp */ - @NonNull public final NetworkCapabilities networkCapabilities; + @NonNull private final NetworkCapabilities mNetworkCapabilities; /** PersistableBundle that may contain additional info about the report */ - @NonNull public final PersistableBundle additionalInfo; + @NonNull private final PersistableBundle mAdditionalInfo; /** * Constructor for ConnectivityReport. @@ -101,30 +241,191 @@ public class ConnectivityDiagnosticsManager { @NonNull LinkProperties linkProperties, @NonNull NetworkCapabilities networkCapabilities, @NonNull PersistableBundle additionalInfo) { - this.network = network; - this.reportTimestamp = reportTimestamp; - this.linkProperties = linkProperties; - this.networkCapabilities = networkCapabilities; - this.additionalInfo = additionalInfo; + mNetwork = network; + mReportTimestamp = reportTimestamp; + mLinkProperties = linkProperties; + mNetworkCapabilities = networkCapabilities; + mAdditionalInfo = additionalInfo; + } + + /** + * Returns the Network for this ConnectivityReport. + * + * @return The Network for which this ConnectivityReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the LinkProperties available when this report was taken. + * + * @return LinkProperties available on the Network at the reported timestamp + */ + @NonNull + public LinkProperties getLinkProperties() { + return new LinkProperties(mLinkProperties); + } + + /** + * Returns the NetworkCapabilities when this report was taken. + * + * @return NetworkCapabilities available on the Network at the reported timestamp + */ + @NonNull + public NetworkCapabilities getNetworkCapabilities() { + return new NetworkCapabilities(mNetworkCapabilities); + } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * @return PersistableBundle that may contain additional info about the report + */ + @NonNull + public PersistableBundle getAdditionalInfo() { + return new PersistableBundle(mAdditionalInfo); } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof ConnectivityReport)) return false; + final ConnectivityReport that = (ConnectivityReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mNetwork.equals(that.mNetwork) + && mLinkProperties.equals(that.mLinkProperties) + && mNetworkCapabilities.equals(that.mNetworkCapabilities) + && persistableBundleEquals(mAdditionalInfo, that.mAdditionalInfo); + } + + @Override + public int hashCode() { + return Objects.hash( + mNetwork, + mReportTimestamp, + mLinkProperties, + mNetworkCapabilities, + mAdditionalInfo); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeParcelable(mLinkProperties, flags); + dest.writeParcelable(mNetworkCapabilities, flags); + dest.writeParcelable(mAdditionalInfo, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator<ConnectivityReport> CREATOR = + new Creator<ConnectivityReport>() { + public ConnectivityReport createFromParcel(Parcel in) { + return new ConnectivityReport( + in.readParcelable(null), + in.readLong(), + in.readParcelable(null), + in.readParcelable(null), + in.readParcelable(null)); + } + + public ConnectivityReport[] newArray(int size) { + return new ConnectivityReport[size]; + } + }; } /** Class that includes information for a suspected data stall on a specific Network */ - public static class DataStallReport { + public static final class DataStallReport implements Parcelable { + public static final int DETECTION_METHOD_DNS_EVENTS = 1; + public static final int DETECTION_METHOD_TCP_METRICS = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DETECTION_METHOD_"}, + value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) + public @interface DetectionMethod {} + + /** + * This key represents the period in milliseconds over which other included TCP metrics + * were measured. + * + * <p>This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_TCP_METRICS}. + * + * <p>This value is an int. + */ + public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = + "tcpMetricsCollectionPeriodMillis"; + + /** + * This key represents the fail rate of TCP packets when the suspected data stall was + * detected. + * + * <p>This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_TCP_METRICS}. + * + * <p>This value is an int percentage between 0 and 100. + */ + public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; + + /** + * This key represents the consecutive number of DNS timeouts that have occurred. + * + * <p>The consecutive count will be reset any time a DNS response is received. + * + * <p>This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_DNS_EVENTS}. + * + * <p>This value is an int. + */ + public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = {"KEY_"}, value = { + KEY_TCP_PACKET_FAIL_RATE, + KEY_DNS_CONSECUTIVE_TIMEOUTS + }) + public @interface DataStallReportBundleKeys {} + /** The Network for which this DataStallReport applied */ - @NonNull public final Network network; + @NonNull private final Network mNetwork; /** * The timestamp for the report. The timestamp is taken from {@link * System#currentTimeMillis}. */ - public final long reportTimestamp; + private long mReportTimestamp; /** The detection method used to identify the suspected data stall */ - @DetectionMethod public final int detectionMethod; + @DetectionMethod private final int mDetectionMethod; /** PersistableBundle that may contain additional information on the suspected data stall */ - @NonNull public final PersistableBundle stallDetails; + @NonNull private final PersistableBundle mStallDetails; /** * Constructor for DataStallReport. @@ -143,11 +444,104 @@ public class ConnectivityDiagnosticsManager { long reportTimestamp, @DetectionMethod int detectionMethod, @NonNull PersistableBundle stallDetails) { - this.network = network; - this.reportTimestamp = reportTimestamp; - this.detectionMethod = detectionMethod; - this.stallDetails = stallDetails; + mNetwork = network; + mReportTimestamp = reportTimestamp; + mDetectionMethod = detectionMethod; + mStallDetails = stallDetails; + } + + /** + * Returns the Network for this DataStallReport. + * + * @return The Network for which this DataStallReport applied + */ + @NonNull + public Network getNetwork() { + return mNetwork; + } + + /** + * Returns the epoch timestamp (milliseconds) for when this report was taken. + * + * @return The timestamp for the report. Taken from {@link System#currentTimeMillis}. + */ + public long getReportTimestamp() { + return mReportTimestamp; + } + + /** + * Returns the detection method used to identify this suspected data stall. + * + * @return The detection method used to identify the suspected data stall + */ + public int getDetectionMethod() { + return mDetectionMethod; } + + /** + * Returns a PersistableBundle with additional info for this report. + * + * <p>Gets a bundle with details about the suspected data stall including information + * specific to the monitoring method that detected the data stall. + * + * @return PersistableBundle that may contain additional information on the suspected data + * stall + */ + @NonNull + public PersistableBundle getStallDetails() { + return new PersistableBundle(mStallDetails); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof DataStallReport)) return false; + final DataStallReport that = (DataStallReport) o; + + // PersistableBundle is optimized to avoid unparcelling data unless fields are + // referenced. Because of this, use {@link ConnectivityDiagnosticsManager#equals} over + // {@link PersistableBundle#kindofEquals}. + return mReportTimestamp == that.mReportTimestamp + && mDetectionMethod == that.mDetectionMethod + && mNetwork.equals(that.mNetwork) + && persistableBundleEquals(mStallDetails, that.mStallDetails); + } + + @Override + public int hashCode() { + return Objects.hash(mNetwork, mReportTimestamp, mDetectionMethod, mStallDetails); + } + + /** {@inheritDoc} */ + @Override + public int describeContents() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mNetwork, flags); + dest.writeLong(mReportTimestamp); + dest.writeInt(mDetectionMethod); + dest.writeParcelable(mStallDetails, flags); + } + + /** Implement the Parcelable interface */ + public static final @NonNull Creator<DataStallReport> CREATOR = + new Creator<DataStallReport>() { + public DataStallReport createFromParcel(Parcel in) { + return new DataStallReport( + in.readParcelable(null), + in.readLong(), + in.readInt(), + in.readParcelable(null)); + } + + public DataStallReport[] newArray(int size) { + return new DataStallReport[size]; + } + }; } /** diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java new file mode 100644 index 000000000000..42b4da14d879 --- /dev/null +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -0,0 +1,728 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK; +import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA; +import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; +import static com.android.internal.util.Preconditions.checkStringNotEmpty; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.Credentials; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.net.VpnProfile; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * The Ikev2VpnProfile is a configuration for the platform setup of IKEv2/IPsec VPNs. + * + * <p>Together with VpnManager, this allows apps to provision IKEv2/IPsec VPNs that do not require + * the VPN app to constantly run in the background. + * + * @see VpnManager + * @see <a href="https://tools.ietf.org/html/rfc7296#section-3.3.2">RFC 7296 - Internet Key + * Exchange, Version 2 (IKEv2)</a> + */ +public final class Ikev2VpnProfile extends PlatformVpnProfile { + private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s"; + private static final String EMPTY_CERT = ""; + + @NonNull private final String mServerAddr; + @NonNull private final String mUserIdentity; + + // PSK authentication + @Nullable private final byte[] mPresharedKey; + + // Username/Password, RSA authentication + @Nullable private final X509Certificate mServerRootCaCert; + + // Username/Password authentication + @Nullable private final String mUsername; + @Nullable private final String mPassword; + + // RSA Certificate authentication + @Nullable private final PrivateKey mRsaPrivateKey; + @Nullable private final X509Certificate mUserCert; + + @Nullable private final ProxyInfo mProxyInfo; + @NonNull private final List<String> mAllowedAlgorithms; + private final boolean mIsBypassable; // Defaults in builder + private final boolean mIsMetered; // Defaults in builder + private final int mMaxMtu; // Defaults in builder + + private Ikev2VpnProfile( + int type, + @NonNull String serverAddr, + @NonNull String userIdentity, + @Nullable byte[] presharedKey, + @Nullable X509Certificate serverRootCaCert, + @Nullable String username, + @Nullable String password, + @Nullable PrivateKey rsaPrivateKey, + @Nullable X509Certificate userCert, + @Nullable ProxyInfo proxyInfo, + @NonNull List<String> allowedAlgorithms, + boolean isBypassable, + boolean isMetered, + int maxMtu) { + super(type); + + checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address"); + checkNotNull(userIdentity, MISSING_PARAM_MSG_TMPL, "User Identity"); + checkNotNull(allowedAlgorithms, MISSING_PARAM_MSG_TMPL, "Allowed Algorithms"); + + mServerAddr = serverAddr; + mUserIdentity = userIdentity; + mPresharedKey = + presharedKey == null ? null : Arrays.copyOf(presharedKey, presharedKey.length); + mServerRootCaCert = serverRootCaCert; + mUsername = username; + mPassword = password; + mRsaPrivateKey = rsaPrivateKey; + mUserCert = userCert; + mProxyInfo = new ProxyInfo(proxyInfo); + + // UnmodifiableList doesn't make a defensive copy by default. + mAllowedAlgorithms = Collections.unmodifiableList(new ArrayList<>(allowedAlgorithms)); + + mIsBypassable = isBypassable; + mIsMetered = isMetered; + mMaxMtu = maxMtu; + + validate(); + } + + private void validate() { + // Server Address not validated except to check an address was provided. This allows for + // dual-stack servers and hostname based addresses. + checkStringNotEmpty(mServerAddr, MISSING_PARAM_MSG_TMPL, "Server Address"); + checkStringNotEmpty(mUserIdentity, MISSING_PARAM_MSG_TMPL, "User Identity"); + + // IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6 + // networks, the VPN must provide a link fulfilling the stricter of the two conditions + // (at least that of the IPv6 MTU). + if (mMaxMtu < LinkProperties.MIN_MTU_V6) { + throw new IllegalArgumentException( + "Max MTU must be at least" + LinkProperties.MIN_MTU_V6); + } + + switch (mType) { + case TYPE_IKEV2_IPSEC_USER_PASS: + checkNotNull(mUsername, MISSING_PARAM_MSG_TMPL, "Username"); + checkNotNull(mPassword, MISSING_PARAM_MSG_TMPL, "Password"); + + if (mServerRootCaCert != null) checkCert(mServerRootCaCert); + + break; + case TYPE_IKEV2_IPSEC_PSK: + checkNotNull(mPresharedKey, MISSING_PARAM_MSG_TMPL, "Preshared Key"); + break; + case TYPE_IKEV2_IPSEC_RSA: + checkNotNull(mUserCert, MISSING_PARAM_MSG_TMPL, "User cert"); + checkNotNull(mRsaPrivateKey, MISSING_PARAM_MSG_TMPL, "RSA Private key"); + + checkCert(mUserCert); + if (mServerRootCaCert != null) checkCert(mServerRootCaCert); + + break; + default: + throw new IllegalArgumentException("Invalid auth method set"); + } + + VpnProfile.validateAllowedAlgorithms(mAllowedAlgorithms); + } + + /** Retrieves the server address string. */ + @NonNull + public String getServerAddr() { + return mServerAddr; + } + + /** Retrieves the user identity. */ + @NonNull + public String getUserIdentity() { + return mUserIdentity; + } + + /** + * Retrieves the pre-shared key. + * + * <p>May be null if the profile is not using Pre-shared key authentication. + */ + @Nullable + public byte[] getPresharedKey() { + return mPresharedKey == null ? null : Arrays.copyOf(mPresharedKey, mPresharedKey.length); + } + + /** + * Retrieves the certificate for the server's root CA. + * + * <p>May be null if the profile is not using RSA Digital Signature Authentication or + * Username/Password authentication + */ + @Nullable + public X509Certificate getServerRootCaCert() { + return mServerRootCaCert; + } + + /** + * Retrieves the username. + * + * <p>May be null if the profile is not using Username/Password authentication + */ + @Nullable + public String getUsername() { + return mUsername; + } + + /** + * Retrieves the password. + * + * <p>May be null if the profile is not using Username/Password authentication + */ + @Nullable + public String getPassword() { + return mPassword; + } + + /** + * Retrieves the RSA private key. + * + * <p>May be null if the profile is not using RSA Digital Signature authentication + */ + @Nullable + public PrivateKey getRsaPrivateKey() { + return mRsaPrivateKey; + } + + /** Retrieves the user certificate, if any was set. */ + @Nullable + public X509Certificate getUserCert() { + return mUserCert; + } + + /** Retrieves the proxy information if any was set */ + @Nullable + public ProxyInfo getProxyInfo() { + return mProxyInfo; + } + + /** Returns all the algorithms allowed by this VPN profile. */ + @NonNull + public List<String> getAllowedAlgorithms() { + return mAllowedAlgorithms; + } + + /** Returns whether or not the VPN profile should be bypassable. */ + public boolean isBypassable() { + return mIsBypassable; + } + + /** Returns whether or not the VPN profile should be always considered metered. */ + public boolean isMetered() { + return mIsMetered; + } + + /** Retrieves the maximum MTU set for this VPN profile. */ + public int getMaxMtu() { + return mMaxMtu; + } + + @Override + public int hashCode() { + return Objects.hash( + mType, + mServerAddr, + mUserIdentity, + Arrays.hashCode(mPresharedKey), + mServerRootCaCert, + mUsername, + mPassword, + mRsaPrivateKey, + mUserCert, + mProxyInfo, + mAllowedAlgorithms, + mIsBypassable, + mIsMetered, + mMaxMtu); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Ikev2VpnProfile)) { + return false; + } + + final Ikev2VpnProfile other = (Ikev2VpnProfile) obj; + return mType == other.mType + && Objects.equals(mServerAddr, other.mServerAddr) + && Objects.equals(mUserIdentity, other.mUserIdentity) + && Arrays.equals(mPresharedKey, other.mPresharedKey) + && Objects.equals(mServerRootCaCert, other.mServerRootCaCert) + && Objects.equals(mUsername, other.mUsername) + && Objects.equals(mPassword, other.mPassword) + && Objects.equals(mRsaPrivateKey, other.mRsaPrivateKey) + && Objects.equals(mUserCert, other.mUserCert) + && Objects.equals(mProxyInfo, other.mProxyInfo) + && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) + && mIsBypassable == other.mIsBypassable + && mIsMetered == other.mIsMetered + && mMaxMtu == other.mMaxMtu; + } + + /** + * Builds a VpnProfile instance for internal use, based on the stored IKEv2/IPsec parameters. + * + * <p>Redundant authentication information (from previous calls to other setAuth* methods) will + * be discarded. + * + * @hide + */ + @NonNull + public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException { + final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */); + profile.type = mType; + profile.server = mServerAddr; + profile.ipsecIdentifier = mUserIdentity; + profile.proxy = mProxyInfo; + profile.setAllowedAlgorithms(mAllowedAlgorithms); + profile.isBypassable = mIsBypassable; + profile.isMetered = mIsMetered; + profile.maxMtu = mMaxMtu; + profile.areAuthParamsInline = true; + profile.saveLogin = true; + + switch (mType) { + case TYPE_IKEV2_IPSEC_USER_PASS: + profile.username = mUsername; + profile.password = mPassword; + profile.ipsecCaCert = + mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); + break; + case TYPE_IKEV2_IPSEC_PSK: + profile.ipsecSecret = encodeForIpsecSecret(mPresharedKey); + break; + case TYPE_IKEV2_IPSEC_RSA: + profile.ipsecUserCert = certificateToPemString(mUserCert); + profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded()); + profile.ipsecCaCert = + mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert); + break; + default: + throw new IllegalArgumentException("Invalid auth method set"); + } + + return profile; + } + + /** + * Constructs a Ikev2VpnProfile from an internal-use VpnProfile instance. + * + * <p>Redundant authentication information (not related to profile type) will be discarded. + * + * @hide + */ + @NonNull + public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile) + throws IOException, GeneralSecurityException { + final Builder builder = new Builder(profile.server, profile.ipsecIdentifier); + builder.setProxy(profile.proxy); + builder.setAllowedAlgorithms(profile.getAllowedAlgorithms()); + builder.setBypassable(profile.isBypassable); + builder.setMetered(profile.isMetered); + builder.setMaxMtu(profile.maxMtu); + + switch (profile.type) { + case TYPE_IKEV2_IPSEC_USER_PASS: + builder.setAuthUsernamePassword( + profile.username, + profile.password, + certificateFromPemString(profile.ipsecCaCert)); + break; + case TYPE_IKEV2_IPSEC_PSK: + builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret)); + break; + case TYPE_IKEV2_IPSEC_RSA: + final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert); + final PrivateKey key = getPrivateKey(profile.ipsecSecret); + final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert); + builder.setAuthDigitalSignature(userCert, key, serverRootCa); + break; + default: + throw new IllegalArgumentException("Invalid auth method set"); + } + + return builder.build(); + } + + /** + * Converts a X509 Certificate to a PEM-formatted string. + * + * <p>Must be public due to runtime-package restrictions. + * + * @hide + */ + @NonNull + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static String certificateToPemString(@Nullable X509Certificate cert) + throws IOException, CertificateEncodingException { + if (cert == null) { + return EMPTY_CERT; + } + + // Credentials.convertToPem outputs ASCII bytes. + return new String(Credentials.convertToPem(cert), StandardCharsets.US_ASCII); + } + + /** + * Decodes the provided Certificate(s). + * + * <p>Will use the first one if the certStr encodes more than one certificate. + */ + @Nullable + private static X509Certificate certificateFromPemString(@Nullable String certStr) + throws CertificateException { + if (certStr == null || EMPTY_CERT.equals(certStr)) { + return null; + } + + try { + final List<X509Certificate> certs = + Credentials.convertFromPem(certStr.getBytes(StandardCharsets.US_ASCII)); + return certs.isEmpty() ? null : certs.get(0); + } catch (IOException e) { + throw new CertificateException(e); + } + } + + /** @hide */ + @NonNull + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static String encodeForIpsecSecret(@NonNull byte[] secret) { + checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret"); + + return Base64.getEncoder().encodeToString(secret); + } + + @NonNull + private static byte[] decodeFromIpsecSecret(@NonNull String encoded) { + checkNotNull(encoded, MISSING_PARAM_MSG_TMPL, "encoded"); + + return Base64.getDecoder().decode(encoded); + } + + @NonNull + private static PrivateKey getPrivateKey(@NonNull String keyStr) + throws InvalidKeySpecException, NoSuchAlgorithmException { + final PKCS8EncodedKeySpec privateKeySpec = + new PKCS8EncodedKeySpec(decodeFromIpsecSecret(keyStr)); + final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(privateKeySpec); + } + + private static void checkCert(@NonNull X509Certificate cert) { + try { + certificateToPemString(cert); + } catch (GeneralSecurityException | IOException e) { + throw new IllegalArgumentException("Certificate could not be encoded"); + } + } + + private static @NonNull <T> T checkNotNull( + final T reference, final String messageTemplate, final Object... messageArgs) { + return Objects.requireNonNull(reference, String.format(messageTemplate, messageArgs)); + } + + /** A incremental builder for IKEv2 VPN profiles */ + public static final class Builder { + private int mType = -1; + @NonNull private final String mServerAddr; + @NonNull private final String mUserIdentity; + + // PSK authentication + @Nullable private byte[] mPresharedKey; + + // Username/Password, RSA authentication + @Nullable private X509Certificate mServerRootCaCert; + + // Username/Password authentication + @Nullable private String mUsername; + @Nullable private String mPassword; + + // RSA Certificate authentication + @Nullable private PrivateKey mRsaPrivateKey; + @Nullable private X509Certificate mUserCert; + + @Nullable private ProxyInfo mProxyInfo; + @NonNull private List<String> mAllowedAlgorithms = new ArrayList<>(); + private boolean mIsBypassable = false; + private boolean mIsMetered = true; + private int mMaxMtu = 1360; + + /** + * Creates a new builder with the basic parameters of an IKEv2/IPsec VPN. + * + * @param serverAddr the server that the VPN should connect to + * @param identity the identity string to be used for IKEv2 authentication + */ + public Builder(@NonNull String serverAddr, @NonNull String identity) { + checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "serverAddr"); + checkNotNull(identity, MISSING_PARAM_MSG_TMPL, "identity"); + + mServerAddr = serverAddr; + mUserIdentity = identity; + } + + private void resetAuthParams() { + mPresharedKey = null; + mServerRootCaCert = null; + mUsername = null; + mPassword = null; + mRsaPrivateKey = null; + mUserCert = null; + } + + /** + * Set the IKEv2 authentication to use the provided username/password. + * + * <p>Setting this will configure IKEv2 authentication using EAP-MSCHAPv2. Only one + * authentication method may be set. This method will overwrite any previously set + * authentication method. + * + * @param user the username to be used for EAP-MSCHAPv2 authentication + * @param pass the password to be used for EAP-MSCHAPv2 authentication + * @param serverRootCa the root certificate to be used for verifying the identity of the + * server + * @return this {@link Builder} object to facilitate chaining of method calls + * @throws IllegalArgumentException if any of the certificates were invalid or of an + * unrecognized format + */ + @NonNull + public Builder setAuthUsernamePassword( + @NonNull String user, + @NonNull String pass, + @Nullable X509Certificate serverRootCa) { + checkNotNull(user, MISSING_PARAM_MSG_TMPL, "user"); + checkNotNull(pass, MISSING_PARAM_MSG_TMPL, "pass"); + + // Test to make sure all auth params can be encoded safely. + if (serverRootCa != null) checkCert(serverRootCa); + + resetAuthParams(); + mUsername = user; + mPassword = pass; + mServerRootCaCert = serverRootCa; + mType = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + return this; + } + + /** + * Set the IKEv2 authentication to use Digital Signature Authentication with the given key. + * + * <p>Setting this will configure IKEv2 authentication using a Digital Signature scheme. + * Only one authentication method may be set. This method will overwrite any previously set + * authentication method. + * + * @param userCert the username to be used for RSA Digital signiture authentication + * @param key the PrivateKey instance associated with the user ceritificate, used for + * constructing the signature + * @param serverRootCa the root certificate to be used for verifying the identity of the + * server + * @return this {@link Builder} object to facilitate chaining of method calls + * @throws IllegalArgumentException if any of the certificates were invalid or of an + * unrecognized format + */ + @NonNull + public Builder setAuthDigitalSignature( + @NonNull X509Certificate userCert, + @NonNull PrivateKey key, + @Nullable X509Certificate serverRootCa) { + checkNotNull(userCert, MISSING_PARAM_MSG_TMPL, "userCert"); + checkNotNull(key, MISSING_PARAM_MSG_TMPL, "key"); + + // Test to make sure all auth params can be encoded safely. + checkCert(userCert); + if (serverRootCa != null) checkCert(serverRootCa); + + resetAuthParams(); + mUserCert = userCert; + mRsaPrivateKey = key; + mServerRootCaCert = serverRootCa; + mType = VpnProfile.TYPE_IKEV2_IPSEC_RSA; + return this; + } + + /** + * Set the IKEv2 authentication to use Preshared keys. + * + * <p>Setting this will configure IKEv2 authentication using a Preshared Key. Only one + * authentication method may be set. This method will overwrite any previously set + * authentication method. + * + * @param psk the key to be used for Pre-Shared Key authentication + * @return this {@link Builder} object to facilitate chaining of method calls + */ + @NonNull + public Builder setAuthPsk(@NonNull byte[] psk) { + checkNotNull(psk, MISSING_PARAM_MSG_TMPL, "psk"); + + resetAuthParams(); + mPresharedKey = psk; + mType = VpnProfile.TYPE_IKEV2_IPSEC_PSK; + return this; + } + + /** + * Sets whether apps can bypass this VPN connection. + * + * <p>By default, all traffic from apps are forwarded through the VPN interface and it is + * not possible for unprivileged apps to side-step the VPN. If a VPN is set to bypassable, + * apps may use methods such as {@link Network#getSocketFactory} or {@link + * Network#openConnection} to instead send/receive directly over the underlying network or + * any other network they have permissions for. + * + * @param isBypassable Whether or not the VPN should be considered bypassable. Defaults to + * {@code false}. + * @return this {@link Builder} object to facilitate chaining of method calls + */ + @NonNull + public Builder setBypassable(boolean isBypassable) { + mIsBypassable = isBypassable; + return this; + } + + /** + * Sets a proxy for the VPN network. + * + * <p>Note that this proxy is only a recommendation and it may be ignored by apps. + * + * @param proxy the ProxyInfo to be set for the VPN network + * @return this {@link Builder} object to facilitate chaining of method calls + */ + @NonNull + public Builder setProxy(@Nullable ProxyInfo proxy) { + mProxyInfo = proxy; + return this; + } + + /** + * Set the upper bound of the maximum transmission unit (MTU) of the VPN interface. + * + * <p>If it is not set, a safe value will be used. Additionally, the actual link MTU will be + * dynamically calculated/updated based on the underlying link's mtu. + * + * @param mtu the MTU (in bytes) of the VPN interface + * @return this {@link Builder} object to facilitate chaining of method calls + * @throws IllegalArgumentException if the value is not at least the minimum IPv6 MTU (1280) + */ + @NonNull + public Builder setMaxMtu(int mtu) { + // IPv6 MTU is greater; since profiles may be started by the system on IPv4 and IPv6 + // networks, the VPN must provide a link fulfilling the stricter of the two conditions + // (at least that of the IPv6 MTU). + if (mtu < LinkProperties.MIN_MTU_V6) { + throw new IllegalArgumentException( + "Max MTU must be at least " + LinkProperties.MIN_MTU_V6); + } + mMaxMtu = mtu; + return this; + } + + /** + * Marks the VPN network as metered. + * + * <p>A VPN network is classified as metered when the user is sensitive to heavy data usage + * due to monetary costs and/or data limitations. In such cases, you should set this to + * {@code true} so that apps on the system can avoid doing large data transfers. Otherwise, + * set this to {@code false}. Doing so would cause VPN network to inherit its meteredness + * from the underlying network. + * + * @param isMetered {@code true} if the VPN network should be treated as metered regardless + * of underlying network meteredness. Defaults to {@code true}. + * @return this {@link Builder} object to facilitate chaining of method calls + * @see NetworkCapabilities.NET_CAPABILITY_NOT_METERED + */ + @NonNull + public Builder setMetered(boolean isMetered) { + mIsMetered = isMetered; + return this; + } + + /** + * Sets the allowable set of IPsec algorithms + * + * <p>A list of allowed IPsec algorithms as defined in {@link IpSecAlgorithm} + * + * @param algorithmNames the list of supported IPsec algorithms + * @return this {@link Builder} object to facilitate chaining of method calls + * @see IpSecAlgorithm + */ + @NonNull + public Builder setAllowedAlgorithms(@NonNull List<String> algorithmNames) { + checkNotNull(algorithmNames, MISSING_PARAM_MSG_TMPL, "algorithmNames"); + VpnProfile.validateAllowedAlgorithms(algorithmNames); + + mAllowedAlgorithms = algorithmNames; + return this; + } + + /** + * Validates, builds and provisions the VpnProfile. + * + * @throws IllegalArgumentException if any of the required keys or values were invalid + */ + @NonNull + public Ikev2VpnProfile build() { + return new Ikev2VpnProfile( + mType, + mServerAddr, + mUserIdentity, + mPresharedKey, + mServerRootCaCert, + mUsername, + mPassword, + mRsaPrivateKey, + mUserCert, + mProxyInfo, + mAllowedAlgorithms, + mIsBypassable, + mIsMetered, + mMaxMtu); + } + } +} diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index ec773ef4a1e2..d25ee0e69e88 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -80,7 +80,8 @@ public final class LinkProperties implements Parcelable { private final transient boolean mParcelSensitiveFields; private static final int MIN_MTU = 68; - private static final int MIN_MTU_V6 = 1280; + /* package-visibility - Used in other files (such as Ikev2VpnProfile) as minimum iface MTU. */ + static final int MIN_MTU_V6 = 1280; private static final int MAX_MTU = 10000; private static final int INET6_ADDR_LENGTH = 16; diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 8ebd1392240d..6207661e47ce 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -35,6 +35,9 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; @@ -83,6 +86,7 @@ public final class NetworkCapabilities implements Parcelable { mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; mUids = null; mEstablishingVpnAppUid = INVALID_UID; + mAdministratorUids.clear(); mSSID = null; mPrivateDnsBroken = false; } @@ -101,6 +105,7 @@ public final class NetworkCapabilities implements Parcelable { mSignalStrength = nc.mSignalStrength; setUids(nc.mUids); // Will make the defensive copy mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; + setAdministratorUids(nc.mAdministratorUids); mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mSSID = nc.mSSID; mPrivateDnsBroken = nc.mPrivateDnsBroken; @@ -833,6 +838,56 @@ public final class NetworkCapabilities implements Parcelable { } /** + * UIDs of packages that are administrators of this network, or empty if none. + * + * <p>This field tracks the UIDs of packages that have permission to manage this network. + * + * <p>Network owners will also be listed as administrators. + * + * <p>For NetworkCapability instances being sent from the System Server, this value MUST be + * empty unless the destination is 1) the System Server, or 2) Telephony. In either case, the + * receiving entity must have the ACCESS_FINE_LOCATION permission and target R+. + */ + private final List<Integer> mAdministratorUids = new ArrayList<>(); + + /** + * Sets the list of UIDs that are administrators of this network. + * + * <p>UIDs included in administratorUids gain administrator privileges over this Network. + * Examples of UIDs that should be included in administratorUids are: + * <ul> + * <li>Carrier apps with privileges for the relevant subscription + * <li>Active VPN apps + * <li>Other application groups with a particular Network-related role + * </ul> + * + * <p>In general, user-supplied networks (such as WiFi networks) do not have an administrator. + * + * <p>An app is granted owner privileges over Networks that it supplies. Owner privileges + * implicitly include administrator privileges. + * + * @param administratorUids the UIDs to be set as administrators of this Network. + * @hide + */ + @SystemApi + public void setAdministratorUids(@NonNull final List<Integer> administratorUids) { + mAdministratorUids.clear(); + mAdministratorUids.addAll(administratorUids); + } + + /** + * Retrieves the list of UIDs that are administrators of this Network. + * + * @return the List of UIDs that are administrators of this Network + * @hide + */ + @NonNull + @SystemApi + public List<Integer> getAdministratorUids() { + return Collections.unmodifiableList(mAdministratorUids); + } + + /** * Value indicating that link bandwidth is unspecified. * @hide */ @@ -1471,6 +1526,7 @@ public final class NetworkCapabilities implements Parcelable { public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mNetworkCapabilities); @@ -1484,6 +1540,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeArraySet(mUids); dest.writeString(mSSID); dest.writeBoolean(mPrivateDnsBroken); + dest.writeList(mAdministratorUids); } public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR = @@ -1504,6 +1561,7 @@ public final class NetworkCapabilities implements Parcelable { null /* ClassLoader, null for default */); netCap.mSSID = in.readString(); netCap.mPrivateDnsBroken = in.readBoolean(); + netCap.setAdministratorUids(in.readArrayList(null)); return netCap; } @Override @@ -1557,6 +1615,10 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid); } + if (!mAdministratorUids.isEmpty()) { + sb.append(" AdministratorUids: ").append(mAdministratorUids); + } + if (null != mSSID) { sb.append(" SSID: ").append(mSSID); } diff --git a/core/java/android/net/PlatformVpnProfile.java b/core/java/android/net/PlatformVpnProfile.java new file mode 100644 index 000000000000..fbae63707be2 --- /dev/null +++ b/core/java/android/net/PlatformVpnProfile.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_PSK; +import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_RSA; +import static android.net.PlatformVpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + +import android.annotation.IntDef; +import android.annotation.NonNull; + +import com.android.internal.net.VpnProfile; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.GeneralSecurityException; + +/** + * PlatformVpnProfile represents a configuration for a platform-based VPN implementation. + * + * <p>Platform-based VPNs allow VPN applications to provide configuration and authentication options + * to leverage the Android OS' implementations of well-defined control plane (authentication, key + * negotiation) and data plane (per-packet encryption) protocols to simplify the creation of VPN + * tunnels. In contrast, {@link VpnService} based VPNs must implement both the control and data + * planes on a per-app basis. + * + * @see Ikev2VpnProfile + */ +public abstract class PlatformVpnProfile { + /** + * Alias to platform VPN related types from VpnProfile, for API use. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + TYPE_IKEV2_IPSEC_USER_PASS, + TYPE_IKEV2_IPSEC_PSK, + TYPE_IKEV2_IPSEC_RSA, + }) + public static @interface PlatformVpnType {} + + public static final int TYPE_IKEV2_IPSEC_USER_PASS = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + public static final int TYPE_IKEV2_IPSEC_PSK = VpnProfile.TYPE_IKEV2_IPSEC_PSK; + public static final int TYPE_IKEV2_IPSEC_RSA = VpnProfile.TYPE_IKEV2_IPSEC_RSA; + + /** @hide */ + @PlatformVpnType protected final int mType; + + /** @hide */ + PlatformVpnProfile(@PlatformVpnType int type) { + mType = type; + } + /** Returns the profile integer type. */ + @PlatformVpnType + public final int getType() { + return mType; + } + + /** Returns a type string describing the VPN profile type */ + @NonNull + public final String getTypeString() { + switch (mType) { + case TYPE_IKEV2_IPSEC_USER_PASS: + return "IKEv2/IPsec Username/Password"; + case TYPE_IKEV2_IPSEC_PSK: + return "IKEv2/IPsec Preshared key"; + case TYPE_IKEV2_IPSEC_RSA: + return "IKEv2/IPsec RSA Digital Signature"; + default: + return "Unknown VPN profile type"; + } + } + + /** @hide */ + @NonNull + public abstract VpnProfile toVpnProfile() throws IOException, GeneralSecurityException; + + /** @hide */ + @NonNull + public static PlatformVpnProfile fromVpnProfile(@NonNull VpnProfile profile) + throws IOException, GeneralSecurityException { + switch (profile.type) { + case TYPE_IKEV2_IPSEC_USER_PASS: // fallthrough + case TYPE_IKEV2_IPSEC_PSK: // fallthrough + case TYPE_IKEV2_IPSEC_RSA: + return Ikev2VpnProfile.fromVpnProfile(profile); + default: + throw new IllegalArgumentException("Unknown VPN Profile type"); + } + } +} diff --git a/core/java/android/net/StringNetworkSpecifier.java b/core/java/android/net/StringNetworkSpecifier.java index 83dbc637fb65..6ae59716cfd8 100644 --- a/core/java/android/net/StringNetworkSpecifier.java +++ b/core/java/android/net/StringNetworkSpecifier.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -27,7 +26,6 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** @hide */ -@SystemApi public final class StringNetworkSpecifier extends NetworkSpecifier implements Parcelable { /** * Arbitrary string used to pass (additional) information to the network factory. diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java new file mode 100644 index 000000000000..f95807a14f00 --- /dev/null +++ b/core/java/android/net/VpnManager.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; + +/** + * This class provides an interface for apps to manage platform VPN profiles + * + * <p>Apps can use this API to provide profiles with which the platform can set up a VPN without + * further app intermediation. When a VPN profile is present and the app is selected as an always-on + * VPN, the platform will directly trigger the negotiation of the VPN without starting or waking the + * app (unlike VpnService). + * + * <p>VPN apps using supported protocols should preferentially use this API over the {@link + * VpnService} API for ease-of-development and reduced maintainance burden. This also give the user + * the guarantee that VPN network traffic is not subjected to on-device packet interception. + * + * @see Ikev2VpnProfile + */ +public class VpnManager { + @NonNull private final Context mContext; + @NonNull private final IConnectivityManager mService; + + /** + * Create an instance of the VpnManger with the given context. + * + * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the + * {@link Context.getSystemService()} method call. + * + * @hide + */ + public VpnManager(@NonNull Context ctx, @NonNull IConnectivityManager service) { + mContext = checkNotNull(ctx, "missing Context"); + mService = checkNotNull(service, "missing IConnectivityManager"); + } + + /** + * Install a VpnProfile configuration keyed on the calling app's package name. + * + * @param profile the PlatformVpnProfile provided by this package. Will override any previous + * PlatformVpnProfile stored for this package. + * @return an intent to request user consent if needed (null otherwise). + */ + @Nullable + public Intent provisionVpnProfile(@NonNull PlatformVpnProfile profile) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** Delete the VPN profile configuration that was provisioned by the calling app */ + public void deleteProvisionedVpnProfile() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** + * Request the startup of a previously provisioned VPN. + * + * @throws SecurityException exception if user or device settings prevent this VPN from being + * setup, or if user consent has not been granted + */ + public void startProvisionedVpnProfile() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** Tear down the VPN provided by the calling app (if any) */ + public void stopProvisionedVpnProfile() { + throw new UnsupportedOperationException("Not yet implemented"); + } +} diff --git a/core/java/android/se/omapi/ISecureElementReader.aidl b/core/java/android/se/omapi/ISecureElementReader.aidl index a312c445395a..41244ab058e0 100644 --- a/core/java/android/se/omapi/ISecureElementReader.aidl +++ b/core/java/android/se/omapi/ISecureElementReader.aidl @@ -48,4 +48,10 @@ interface ISecureElementReader { */ void closeSessions(); + /** + * Closes all the sessions opened on this reader and resets the reader. + * All the channels opened by all these sessions will be closed. + * @return true if the reset is successful, false otherwise. + */ + boolean reset(); } diff --git a/core/java/android/se/omapi/Reader.java b/core/java/android/se/omapi/Reader.java index 80262f7533c8..7f68d9188650 100644 --- a/core/java/android/se/omapi/Reader.java +++ b/core/java/android/se/omapi/Reader.java @@ -23,6 +23,8 @@ package android.se.omapi; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -150,4 +152,25 @@ public final class Reader { } catch (RemoteException ignore) { } } } + + /** + * Close all the sessions opened on this reader and reset the reader. + * All the channels opened by all these sessions will be closed. + * @return <code>true</code> if reset success, <code>false</code> otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.SECURE_ELEMENT_PRIVILEGED) + public boolean reset() { + if (!mService.isConnected()) { + Log.e(TAG, "service is not connected"); + return false; + } + synchronized (mLock) { + try { + closeSessions(); + return mReader.reset(); + } catch (RemoteException ignore) {return false;} + } + } } diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java index 4bb012aac769..bbae0273ef4e 100644 --- a/core/java/com/android/internal/net/VpnProfile.java +++ b/core/java/com/android/internal/net/VpnProfile.java @@ -16,6 +16,7 @@ package com.android.internal.net; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.net.ProxyInfo; import android.os.Build; @@ -23,21 +24,34 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.annotations.VisibleForTesting; + import java.net.InetAddress; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; /** - * Parcel-like entity class for VPN profiles. To keep things simple, all - * fields are package private. Methods are provided for serialization, so - * storage can be implemented easily. Two rules are set for this class. - * First, all fields must be kept non-null. Second, always make a copy - * using clone() before modifying. + * Profile storage class for a platform VPN. + * + * <p>This class supports both the Legacy VPN, as well as application-configurable platform VPNs + * (such as IKEv2/IPsec). + * + * <p>This class is serialized and deserialized via the {@link #encode()} and {@link #decode()} + * functions for persistent storage in the Android Keystore. The encoding is entirely custom, but + * must be kept for backward compatibility for devices upgrading between Android versions. * * @hide */ -public class VpnProfile implements Cloneable, Parcelable { +public final class VpnProfile implements Cloneable, Parcelable { private static final String TAG = "VpnProfile"; + @VisibleForTesting static final String VALUE_DELIMITER = "\0"; + @VisibleForTesting static final String LIST_DELIMITER = ","; + // Match these constants with R.array.vpn_types. public static final int TYPE_PPTP = 0; public static final int TYPE_L2TP_IPSEC_PSK = 1; @@ -45,39 +59,85 @@ public class VpnProfile implements Cloneable, Parcelable { public static final int TYPE_IPSEC_XAUTH_PSK = 3; public static final int TYPE_IPSEC_XAUTH_RSA = 4; public static final int TYPE_IPSEC_HYBRID_RSA = 5; - public static final int TYPE_MAX = 5; + public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; + public static final int TYPE_IKEV2_IPSEC_PSK = 7; + public static final int TYPE_IKEV2_IPSEC_RSA = 8; + public static final int TYPE_MAX = 8; // Match these constants with R.array.vpn_proxy_settings. public static final int PROXY_NONE = 0; public static final int PROXY_MANUAL = 1; + private static final String ENCODED_NULL_PROXY_INFO = "\0\0\0\0"; + // Entity fields. @UnsupportedAppUsage - public final String key; // -1 + public final String key; // -1 + @UnsupportedAppUsage - public String name = ""; // 0 + public String name = ""; // 0 + @UnsupportedAppUsage - public int type = TYPE_PPTP; // 1 + public int type = TYPE_PPTP; // 1 + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public String server = ""; // 2 + public String server = ""; // 2 + @UnsupportedAppUsage - public String username = ""; // 3 - public String password = ""; // 4 - public String dnsServers = ""; // 5 - public String searchDomains = ""; // 6 - public String routes = ""; // 7 - public boolean mppe = true; // 8 - public String l2tpSecret = ""; // 9 - public String ipsecIdentifier = "";// 10 - public String ipsecSecret = ""; // 11 - public String ipsecUserCert = ""; // 12 - public String ipsecCaCert = ""; // 13 - public String ipsecServerCert = "";// 14 - public ProxyInfo proxy = null; // 15~18 + public String username = ""; // 3 + public String password = ""; // 4 + public String dnsServers = ""; // 5 + public String searchDomains = ""; // 6 + public String routes = ""; // 7 + public boolean mppe = true; // 8 + public String l2tpSecret = ""; // 9 + public String ipsecIdentifier = ""; // 10 + + /** + * The RSA private key or pre-shared key used for authentication. + * + * <p>If areAuthParamsInline is {@code true}, this String will be either: + * + * <ul> + * <li>If this is an IKEv2 RSA profile: a PKCS#8 encoded {@link java.security.PrivateKey} + * <li>If this is an IKEv2 PSK profile: a string value representing the PSK. + * </ul> + */ + public String ipsecSecret = ""; // 11 + + /** + * The RSA certificate to be used for digital signature authentication. + * + * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link + * java.security.X509Certificate} + */ + public String ipsecUserCert = ""; // 12 + + /** + * The RSA certificate that should be used to verify the server's end/target certificate. + * + * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link + * java.security.X509Certificate} + */ + public String ipsecCaCert = ""; // 13 + public String ipsecServerCert = ""; // 14 + public ProxyInfo proxy = null; // 15~18 + + /** + * The list of allowable algorithms. + * + * <p>This list is validated in the setter to ensure that encoding characters (list, value + * delimiters) are not present in the algorithm names. See {@link #validateAllowedAlgorithms()} + */ + private List<String> mAllowedAlgorithms = new ArrayList<>(); // 19 + public boolean isBypassable = false; // 20 + public boolean isMetered = false; // 21 + public int maxMtu = 1400; // 22 + public boolean areAuthParamsInline = false; // 23 // Helper fields. @UnsupportedAppUsage - public boolean saveLogin = false; + public transient boolean saveLogin = false; public VpnProfile(String key) { this.key = key; @@ -103,6 +163,34 @@ public class VpnProfile implements Cloneable, Parcelable { ipsecServerCert = in.readString(); saveLogin = in.readInt() != 0; proxy = in.readParcelable(null); + mAllowedAlgorithms = new ArrayList<>(); + in.readList(mAllowedAlgorithms, null); + isBypassable = in.readBoolean(); + isMetered = in.readBoolean(); + maxMtu = in.readInt(); + areAuthParamsInline = in.readBoolean(); + } + + /** + * Retrieves the list of allowed algorithms. + * + * <p>The contained elements are as listed in {@link IpSecAlgorithm} + */ + public List<String> getAllowedAlgorithms() { + return Collections.unmodifiableList(mAllowedAlgorithms); + } + + /** + * Validates and sets the list of algorithms that can be used for the IPsec transforms. + * + * @param allowedAlgorithms the list of allowable algorithms, as listed in {@link + * IpSecAlgorithm}. + * @throws IllegalArgumentException if any delimiters are used in algorithm names. See {@link + * #VALUE_DELIMITER} and {@link LIST_DELIMITER}. + */ + public void setAllowedAlgorithms(List<String> allowedAlgorithms) { + validateAllowedAlgorithms(allowedAlgorithms); + mAllowedAlgorithms = allowedAlgorithms; } @Override @@ -125,8 +213,18 @@ public class VpnProfile implements Cloneable, Parcelable { out.writeString(ipsecServerCert); out.writeInt(saveLogin ? 1 : 0); out.writeParcelable(proxy, flags); + out.writeList(mAllowedAlgorithms); + out.writeBoolean(isBypassable); + out.writeBoolean(isMetered); + out.writeInt(maxMtu); + out.writeBoolean(areAuthParamsInline); } + /** + * Decodes a VpnProfile instance from the encoded byte array. + * + * <p>See {@link #encode()} + */ @UnsupportedAppUsage public static VpnProfile decode(String key, byte[] value) { try { @@ -134,9 +232,11 @@ public class VpnProfile implements Cloneable, Parcelable { return null; } - String[] values = new String(value, StandardCharsets.UTF_8).split("\0", -1); - // There can be 14 - 19 Bytes in values.length. - if (values.length < 14 || values.length > 19) { + String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); + // Acceptable numbers of values are: + // 14-19: Standard profile, with option for serverCert, proxy + // 24: Standard profile with serverCert, proxy and platform-VPN parameters. + if ((values.length < 14 || values.length > 19) && values.length != 24) { return null; } @@ -164,13 +264,23 @@ public class VpnProfile implements Cloneable, Parcelable { String port = (values.length > 16) ? values[16] : ""; String exclList = (values.length > 17) ? values[17] : ""; String pacFileUrl = (values.length > 18) ? values[18] : ""; - if (pacFileUrl.isEmpty()) { + if (!host.isEmpty() || !port.isEmpty() || !exclList.isEmpty()) { profile.proxy = new ProxyInfo(host, port.isEmpty() ? 0 : Integer.parseInt(port), exclList); - } else { + } else if (!pacFileUrl.isEmpty()) { profile.proxy = new ProxyInfo(pacFileUrl); } - } // else profle.proxy = null + } // else profile.proxy = null + + // Either all must be present, or none must be. + if (values.length >= 24) { + profile.mAllowedAlgorithms = Arrays.asList(values[19].split(LIST_DELIMITER)); + profile.isBypassable = Boolean.parseBoolean(values[20]); + profile.isMetered = Boolean.parseBoolean(values[21]); + profile.maxMtu = Integer.parseInt(values[22]); + profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); + } + profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); return profile; } catch (Exception e) { @@ -179,36 +289,52 @@ public class VpnProfile implements Cloneable, Parcelable { return null; } + /** + * Encodes a VpnProfile instance to a byte array for storage. + * + * <p>See {@link #decode(String, byte[])} + */ public byte[] encode() { StringBuilder builder = new StringBuilder(name); - builder.append('\0').append(type); - builder.append('\0').append(server); - builder.append('\0').append(saveLogin ? username : ""); - builder.append('\0').append(saveLogin ? password : ""); - builder.append('\0').append(dnsServers); - builder.append('\0').append(searchDomains); - builder.append('\0').append(routes); - builder.append('\0').append(mppe); - builder.append('\0').append(l2tpSecret); - builder.append('\0').append(ipsecIdentifier); - builder.append('\0').append(ipsecSecret); - builder.append('\0').append(ipsecUserCert); - builder.append('\0').append(ipsecCaCert); - builder.append('\0').append(ipsecServerCert); + builder.append(VALUE_DELIMITER).append(type); + builder.append(VALUE_DELIMITER).append(server); + builder.append(VALUE_DELIMITER).append(saveLogin ? username : ""); + builder.append(VALUE_DELIMITER).append(saveLogin ? password : ""); + builder.append(VALUE_DELIMITER).append(dnsServers); + builder.append(VALUE_DELIMITER).append(searchDomains); + builder.append(VALUE_DELIMITER).append(routes); + builder.append(VALUE_DELIMITER).append(mppe); + builder.append(VALUE_DELIMITER).append(l2tpSecret); + builder.append(VALUE_DELIMITER).append(ipsecIdentifier); + builder.append(VALUE_DELIMITER).append(ipsecSecret); + builder.append(VALUE_DELIMITER).append(ipsecUserCert); + builder.append(VALUE_DELIMITER).append(ipsecCaCert); + builder.append(VALUE_DELIMITER).append(ipsecServerCert); if (proxy != null) { - builder.append('\0').append(proxy.getHost() != null ? proxy.getHost() : ""); - builder.append('\0').append(proxy.getPort()); - builder.append('\0').append(proxy.getExclusionListAsString() != null ? - proxy.getExclusionListAsString() : ""); - builder.append('\0').append(proxy.getPacFileUrl().toString()); + builder.append(VALUE_DELIMITER).append(proxy.getHost() != null ? proxy.getHost() : ""); + builder.append(VALUE_DELIMITER).append(proxy.getPort()); + builder.append(VALUE_DELIMITER) + .append( + proxy.getExclusionListAsString() != null + ? proxy.getExclusionListAsString() + : ""); + builder.append(VALUE_DELIMITER).append(proxy.getPacFileUrl().toString()); + } else { + builder.append(ENCODED_NULL_PROXY_INFO); } + + builder.append(VALUE_DELIMITER).append(String.join(LIST_DELIMITER, mAllowedAlgorithms)); + builder.append(VALUE_DELIMITER).append(isBypassable); + builder.append(VALUE_DELIMITER).append(isMetered); + builder.append(VALUE_DELIMITER).append(maxMtu); + builder.append(VALUE_DELIMITER).append(areAuthParamsInline); + return builder.toString().getBytes(StandardCharsets.UTF_8); } /** - * Tests if profile is valid for lockdown, which requires IPv4 address for - * both server and DNS. Server hostnames would require using DNS before - * connection. + * Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS. + * Server hostnames would require using DNS before connection. */ public boolean isValidLockdownProfile() { return isTypeValidForLockdown() @@ -238,10 +364,7 @@ public class VpnProfile implements Cloneable, Parcelable { return !TextUtils.isEmpty(dnsServers); } - /** - * Returns {@code true} if all DNS servers have numeric addresses, - * e.g. 8.8.8.8 - */ + /** Returns {@code true} if all DNS servers have numeric addresses, e.g. 8.8.8.8 */ public boolean areDnsAddressesNumeric() { try { for (String dnsServer : dnsServers.split(" +")) { @@ -253,6 +376,62 @@ public class VpnProfile implements Cloneable, Parcelable { return true; } + /** + * Validates that the provided list of algorithms does not contain illegal characters. + * + * @param allowedAlgorithms The list to be validated + */ + public static void validateAllowedAlgorithms(List<String> allowedAlgorithms) { + for (final String alg : allowedAlgorithms) { + if (alg.contains(VALUE_DELIMITER) || alg.contains(LIST_DELIMITER)) { + throw new IllegalArgumentException( + "Algorithm contained illegal ('\0' or ',') character"); + } + } + } + + /** Generates a hashcode over the VpnProfile. */ + @Override + public int hashCode() { + return Objects.hash( + key, type, server, username, password, dnsServers, searchDomains, routes, mppe, + l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, + proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline); + } + + /** Checks VPN profiles for interior equality. */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof VpnProfile)) { + return false; + } + + final VpnProfile other = (VpnProfile) obj; + return Objects.equals(key, other.key) + && Objects.equals(name, other.name) + && type == other.type + && Objects.equals(server, other.server) + && Objects.equals(username, other.username) + && Objects.equals(password, other.password) + && Objects.equals(dnsServers, other.dnsServers) + && Objects.equals(searchDomains, other.searchDomains) + && Objects.equals(routes, other.routes) + && mppe == other.mppe + && Objects.equals(l2tpSecret, other.l2tpSecret) + && Objects.equals(ipsecIdentifier, other.ipsecIdentifier) + && Objects.equals(ipsecSecret, other.ipsecSecret) + && Objects.equals(ipsecUserCert, other.ipsecUserCert) + && Objects.equals(ipsecCaCert, other.ipsecCaCert) + && Objects.equals(ipsecServerCert, other.ipsecServerCert) + && Objects.equals(proxy, other.proxy) + && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) + && isBypassable == other.isBypassable + && isMetered == other.isMetered + && maxMtu == other.maxMtu + && areAuthParamsInline == other.areAuthParamsInline; + } + + @NonNull public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() { @Override public VpnProfile createFromParcel(Parcel in) { diff --git a/media/java/android/media/tv/DvbDeviceInfo.java b/media/java/android/media/tv/DvbDeviceInfo.java index 96c852812a74..54fc39e9db9e 100644 --- a/media/java/android/media/tv/DvbDeviceInfo.java +++ b/media/java/android/media/tv/DvbDeviceInfo.java @@ -16,6 +16,7 @@ package android.media.tv; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; @@ -23,9 +24,13 @@ import android.os.Parcelable; import android.util.Log; /** - * Simple container for information about DVB device. - * Not for third-party developers. + * A digital video broadcasting (DVB) device. * + * <p> Simple wrapper around a <a href="https://www.linuxtv.org/docs/dvbapi/dvbapi.html">Linux DVB + * v3</a> device. + * + * @see TvInputManager#getDvbDeviceList() + * @see TvInputManager#openDvbDevice(DvbDeviceInfo, int) * @hide */ @SystemApi @@ -67,17 +72,19 @@ public final class DvbDeviceInfo implements Parcelable { } /** - * Returns the adapter ID of DVB device, in terms of enumerating the DVB device adapters - * installed in the system. The adapter ID counts from zero. + * Returns the adapter ID. + * + * <p>DVB Adapters contain one or more devices. */ + @IntRange(from = 0) public int getAdapterId() { return mAdapterId; } /** - * Returns the device ID of DVB device, in terms of enumerating the DVB devices attached to - * the same device adapter. The device ID counts from zero. + * Returns the device ID. */ + @IntRange(from = 0) public int getDeviceId() { return mDeviceId; } diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 854ea43f17c3..ed4fe4bc6404 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1664,7 +1664,7 @@ public final class TvInputManager { } /** - * Returns the list of currently available DVB devices on the system. + * Returns the list of currently available DVB frontend devices on the system. * * @return the list of {@link DvbDeviceInfo} objects representing available DVB devices. * @hide @@ -1681,16 +1681,17 @@ public final class TvInputManager { } /** - * Returns a {@link ParcelFileDescriptor} of a specified DVB device for a given - * {@link DvbDeviceInfo} + * Returns a {@link ParcelFileDescriptor} of a specified DVB device of a given type for a given + * {@link DvbDeviceInfo}. * * @param info A {@link DvbDeviceInfo} to open a DVB device. - * @param deviceType A DVB device type. The type can be {@link #DVB_DEVICE_DEMUX}, - * {@link #DVB_DEVICE_DVR} or {@link #DVB_DEVICE_FRONTEND}. + * @param deviceType A DVB device type. * @return a {@link ParcelFileDescriptor} of a specified DVB device for a given - * {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo} - * failed to open. + * {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo} + * failed to open. * @throws IllegalArgumentException if {@code deviceType} is invalid or the device is not found. + + * @see <a href="https://www.linuxtv.org/docs/dvbapi/dvbapi.html">Linux DVB API v3</a> * @hide */ @SystemApi diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index 4318a0ae7d06..d4c4a62932e6 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -352,8 +352,7 @@ public final class TvTrackInfo implements Parcelable { if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType || !TextUtils.equals(mLanguage, obj.mLanguage) || !TextUtils.equals(mDescription, obj.mDescription) - || mEncrypted != obj.mEncrypted - || !Objects.equals(mExtra, obj.mExtra)) { + || mEncrypted != obj.mEncrypted) { return false; } @@ -381,7 +380,16 @@ public final class TvTrackInfo implements Parcelable { @Override public int hashCode() { - return Objects.hashCode(mId); + int result = Objects.hash(mId, mType, mLanguage, mDescription); + + if (mType == TYPE_AUDIO) { + result = Objects.hash(result, mAudioChannelCount, mAudioSampleRate); + } else if (mType == TYPE_VIDEO) { + result = Objects.hash(result, mVideoWidth, mVideoHeight, mVideoFrameRate, + mVideoPixelAspectRatio); + } + + return result; } public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR = diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 76c119db0ad9..11f0a04fa8dc 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -212,6 +212,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.HashMap; @@ -1634,6 +1635,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (newNc.getNetworkSpecifier() != null) { newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact()); } + newNc.setAdministratorUids(Collections.EMPTY_LIST); return newNc; } @@ -1664,6 +1666,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!checkSettingsPermission()) { nc.setSingleUid(Binder.getCallingUid()); } + nc.setAdministratorUids(Collections.EMPTY_LIST); } private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) { diff --git a/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS index c7c6d5658d1d..0e795b9a5ef8 100644 --- a/services/core/java/com/android/server/lights/OWNERS +++ b/services/core/java/com/android/server/lights/OWNERS @@ -1,2 +1,3 @@ michaelwr@google.com -dangittik@google.com +santoscordon@google.com +flc@google.com diff --git a/services/core/java/com/android/server/policy/GlobalKeyManager.java b/services/core/java/com/android/server/policy/GlobalKeyManager.java index e08c004866ea..157f8256ce50 100644 --- a/services/core/java/com/android/server/policy/GlobalKeyManager.java +++ b/services/core/java/com/android/server/policy/GlobalKeyManager.java @@ -74,7 +74,7 @@ final class GlobalKeyManager { Intent intent = new Intent(Intent.ACTION_GLOBAL_BUTTON) .setComponent(component) .setFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(Intent.EXTRA_KEY_EVENT, event); + .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(event)); context.sendBroadcastAsUser(intent, UserHandle.CURRENT, null); return true; } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 826a89eb38bb..acf51f3856d3 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -547,8 +547,14 @@ public final class Call { */ public static final int PROPERTY_VOIP_AUDIO_MODE = 0x00001000; + /** + * Indicates that the call is an adhoc conference call. This property can be set for both + * incoming and outgoing calls. + */ + public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000; + //****************************************************************************************** - // Next PROPERTY value: 0x00002000 + // Next PROPERTY value: 0x00004000 //****************************************************************************************** private final String mTelecomCallId; @@ -726,6 +732,9 @@ public final class Call { if (hasProperty(properties, PROPERTY_VOIP_AUDIO_MODE)) { builder.append(" PROPERTY_VOIP_AUDIO_MODE"); } + if (hasProperty(properties, PROPERTY_IS_ADHOC_CONFERENCE)) { + builder.append(" PROPERTY_IS_ADHOC_CONFERENCE"); + } builder.append("]"); return builder.toString(); } diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index 456290cd772a..6b0845f5d12b 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -69,6 +69,7 @@ public abstract class Conference extends Conferenceable { public void onConnectionEvent(Conference c, String event, Bundle extras) {} public void onCallerDisplayNameChanged( Conference c, String callerDisplayName, int presentation) {} + public void onRingbackRequested(Conference c, boolean ringback) {} } private final Set<Listener> mListeners = new CopyOnWriteArraySet<>(); @@ -97,6 +98,7 @@ public abstract class Conference extends Conferenceable { private int mAddressPresentation; private String mCallerDisplayName; private int mCallerDisplayNamePresentation; + private boolean mRingbackRequested = false; private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { @Override @@ -170,6 +172,14 @@ public abstract class Conference extends Conferenceable { } /** + * Returns whether this conference is requesting that the system play a ringback tone + * on its behalf. + */ + public final boolean isRingbackRequested() { + return mRingbackRequested; + } + + /** * Returns the capabilities of the conference. See {@code CAPABILITY_*} constants in class * {@link Connection} for valid values. * @@ -308,6 +318,35 @@ public abstract class Conference extends Conferenceable { public void onConnectionAdded(Connection connection) {} /** + * Notifies this Conference, which is in {@code STATE_RINGING}, of + * a request to accept. + * For managed {@link ConnectionService}s, this will be called when the user answers a call via + * the default dialer's {@link InCallService}. + * + * @param videoState The video state in which to answer the connection. + */ + public void onAnswer(int videoState) {} + + /** + * Notifies this Conference, which is in {@code STATE_RINGING}, of + * a request to accept. + * For managed {@link ConnectionService}s, this will be called when the user answers a call via + * the default dialer's {@link InCallService}. + * @hide + */ + public final void onAnswer() { + onAnswer(VideoProfile.STATE_AUDIO_ONLY); + } + + /** + * Notifies this Conference, which is in {@code STATE_RINGING}, of + * a request to reject. + * For managed {@link ConnectionService}s, this will be called when the user rejects a call via + * the default dialer's {@link InCallService}. + */ + public void onReject() {} + + /** * Sets state to be on hold. */ public final void setOnHold() { @@ -322,9 +361,17 @@ public abstract class Conference extends Conferenceable { } /** + * Sets state to be ringing. + */ + public final void setRinging() { + setState(Connection.STATE_RINGING); + } + + /** * Sets state to be active. */ public final void setActive() { + setRingbackRequested(false); setState(Connection.STATE_ACTIVE); } @@ -436,6 +483,21 @@ public abstract class Conference extends Conferenceable { } /** + * Requests that the framework play a ringback tone. This is to be invoked by implementations + * that do not play a ringback tone themselves in the conference's audio stream. + * + * @param ringback Whether the ringback tone is to be played. + */ + public final void setRingbackRequested(boolean ringback) { + if (mRingbackRequested != ringback) { + mRingbackRequested = ringback; + for (Listener l : mListeners) { + l.onRingbackRequested(this, ringback); + } + } + } + + /** * Set the video state for the conference. * Valid values: {@link VideoProfile#STATE_AUDIO_ONLY}, * {@link VideoProfile#STATE_BIDIRECTIONAL}, @@ -640,14 +702,6 @@ public abstract class Conference extends Conferenceable { } private void setState(int newState) { - if (newState != Connection.STATE_ACTIVE && - newState != Connection.STATE_HOLDING && - newState != Connection.STATE_DISCONNECTED) { - Log.w(this, "Unsupported state transition for Conference call.", - Connection.stateToString(newState)); - return; - } - if (mState != newState) { int oldState = mState; mState = newState; @@ -657,6 +711,37 @@ public abstract class Conference extends Conferenceable { } } + private static class FailureSignalingConference extends Conference { + private boolean mImmutable = false; + public FailureSignalingConference(DisconnectCause disconnectCause, + PhoneAccountHandle phoneAccount) { + super(phoneAccount); + setDisconnected(disconnectCause); + mImmutable = true; + } + public void checkImmutable() { + if (mImmutable) { + throw new UnsupportedOperationException("Conference is immutable"); + } + } + } + + /** + * Return a {@code Conference} which represents a failed conference attempt. The returned + * {@code Conference} will have a {@link android.telecom.DisconnectCause} and as specified, + * and a {@link #getState()} of {@code STATE_DISCONNECTED}. + * <p> + * The returned {@code Conference} can be assumed to {@link #destroy()} itself when appropriate, + * so users of this method need not maintain a reference to its return value to destroy it. + * + * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}). + * @return A {@code Conference} which indicates failure. + */ + public @NonNull static Conference createFailedConference( + @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) { + return new FailureSignalingConference(disconnectCause, phoneAccount); + } + private final void clearConferenceableList() { for (Connection c : mConferenceableConnections) { c.removeConnectionListener(mConnectionDeathListener); @@ -667,11 +752,13 @@ public abstract class Conference extends Conferenceable { @Override public String toString() { return String.format(Locale.US, - "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s, ThisObject %s]", + "[State: %s,Capabilites: %s, VideoState: %s, VideoProvider: %s," + + "isRingbackRequested: %s, ThisObject %s]", Connection.stateToString(mState), Call.Details.capabilitiesToString(mConnectionCapabilities), getVideoState(), getVideoProvider(), + isRingbackRequested() ? "Y" : "N", super.toString()); } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index f205ec64f49b..c934625f588b 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -497,8 +497,17 @@ public abstract class Connection extends Conferenceable { @TestApi public static final int PROPERTY_REMOTELY_HOSTED = 1 << 11; + /** + * Set by the framework to indicate that it is an adhoc conference call. + * <p> + * This is used for Outgoing and incoming conference calls. + * + */ + public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12; + + //********************************************************************************************** - // Next PROPERTY value: 1<<12 + // Next PROPERTY value: 1<<13 //********************************************************************************************** /** @@ -1018,6 +1027,10 @@ public abstract class Connection extends Conferenceable { builder.append(isLong ? " PROPERTY_REMOTELY_HOSTED" : " remote_hst"); } + if ((properties & PROPERTY_IS_ADHOC_CONFERENCE) == PROPERTY_IS_ADHOC_CONFERENCE) { + builder.append(isLong ? " PROPERTY_IS_ADHOC_CONFERENCE" : " adhoc_conf"); + } + builder.append("]"); return builder.toString(); } diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java index 221f8f129744..6d7ceca0a2cd 100644 --- a/telecomm/java/android/telecom/ConnectionRequest.java +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -26,6 +26,9 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.List; + /** * Simple data container encapsulating a request to some entity to * create a new {@link Connection}. @@ -46,6 +49,8 @@ public final class ConnectionRequest implements Parcelable { private boolean mShouldShowIncomingCallUi = false; private ParcelFileDescriptor mRttPipeToInCall; private ParcelFileDescriptor mRttPipeFromInCall; + private List<Uri> mParticipants; + private boolean mIsAdhocConference = false; public Builder() { } @@ -59,6 +64,15 @@ public final class ConnectionRequest implements Parcelable { } /** + * Sets the participants for the resulting {@link ConnectionRequest} + * @param participants The participants to which the {@link Connection} is to connect. + */ + public @NonNull Builder setParticipants(@Nullable List<Uri> participants) { + this.mParticipants = participants; + return this; + } + + /** * Sets the address for the resulting {@link ConnectionRequest} * @param address The address(e.g., phone number) to which the {@link Connection} is to * connect. @@ -108,6 +122,16 @@ public final class ConnectionRequest implements Parcelable { } /** + * Sets isAdhocConference for the resulting {@link ConnectionRequest} + * @param isAdhocConference {@code true} if it is a adhoc conference call + * {@code false}, if not a adhoc conference call + */ + public @NonNull Builder setIsAdhocConferenceCall(boolean isAdhocConference) { + this.mIsAdhocConference = isAdhocConference; + return this; + } + + /** * Sets the RTT pipe for transferring text into the {@link ConnectionService} for the * resulting {@link ConnectionRequest} * @param rttPipeFromInCall The data pipe to read from. @@ -141,7 +165,9 @@ public final class ConnectionRequest implements Parcelable { mTelecomCallId, mShouldShowIncomingCallUi, mRttPipeFromInCall, - mRttPipeToInCall); + mRttPipeToInCall, + mParticipants, + mIsAdhocConference); } } @@ -155,6 +181,8 @@ public final class ConnectionRequest implements Parcelable { private final ParcelFileDescriptor mRttPipeFromInCall; // Cached return value of getRttTextStream -- we don't want to wrap it more than once. private Connection.RttTextStream mRttTextStream; + private List<Uri> mParticipants; + private final boolean mIsAdhocConference; /** * @param accountHandle The accountHandle which should be used to place the call. @@ -214,6 +242,21 @@ public final class ConnectionRequest implements Parcelable { boolean shouldShowIncomingCallUi, ParcelFileDescriptor rttPipeFromInCall, ParcelFileDescriptor rttPipeToInCall) { + this(accountHandle, handle, extras, videoState, telecomCallId, + shouldShowIncomingCallUi, rttPipeFromInCall, rttPipeToInCall, null, false); + } + + private ConnectionRequest( + PhoneAccountHandle accountHandle, + Uri handle, + Bundle extras, + int videoState, + String telecomCallId, + boolean shouldShowIncomingCallUi, + ParcelFileDescriptor rttPipeFromInCall, + ParcelFileDescriptor rttPipeToInCall, + List<Uri> participants, + boolean isAdhocConference) { mAccountHandle = accountHandle; mAddress = handle; mExtras = extras; @@ -222,6 +265,8 @@ public final class ConnectionRequest implements Parcelable { mShouldShowIncomingCallUi = shouldShowIncomingCallUi; mRttPipeFromInCall = rttPipeFromInCall; mRttPipeToInCall = rttPipeToInCall; + mParticipants = participants; + mIsAdhocConference = isAdhocConference; } private ConnectionRequest(Parcel in) { @@ -233,6 +278,11 @@ public final class ConnectionRequest implements Parcelable { mShouldShowIncomingCallUi = in.readInt() == 1; mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader()); mRttPipeToInCall = in.readParcelable(getClass().getClassLoader()); + + mParticipants = new ArrayList<Uri>(); + in.readList(mParticipants, getClass().getClassLoader()); + + mIsAdhocConference = in.readInt() == 1; } /** @@ -246,6 +296,11 @@ public final class ConnectionRequest implements Parcelable { public Uri getAddress() { return mAddress; } /** + * The participants to which the {@link Connection} is to connect. + */ + public @Nullable List<Uri> getParticipants() { return mParticipants; } + + /** * Application-specific extra data. Used for passing back information from an incoming * call {@code Intent}, and for any proprietary extensions arranged between a client * and servant {@code ConnectionService} which agree on a vocabulary for such data. @@ -290,6 +345,13 @@ public final class ConnectionRequest implements Parcelable { } /** + * @return {@code true} if the call is a adhoc conference call else @return {@code false} + */ + public boolean isAdhocConferenceCall() { + return mIsAdhocConference; + } + + /** * Gets the {@link ParcelFileDescriptor} that is used to send RTT text from the connection * service to the in-call UI. In order to obtain an * {@link java.io.InputStream} from this {@link ParcelFileDescriptor}, use @@ -345,11 +407,12 @@ public final class ConnectionRequest implements Parcelable { @Override public String toString() { - return String.format("ConnectionRequest %s %s", + return String.format("ConnectionRequest %s %s isAdhocConf: %s", mAddress == null ? Uri.EMPTY : Connection.toLogSafePhoneNumber(mAddress.toString()), - bundleToString(mExtras)); + bundleToString(mExtras), + isAdhocConferenceCall() ? "Y" : "N"); } private static String bundleToString(Bundle extras){ @@ -406,5 +469,7 @@ public final class ConnectionRequest implements Parcelable { destination.writeInt(mShouldShowIncomingCallUi ? 1 : 0); destination.writeParcelable(mRttPipeFromInCall, 0); destination.writeParcelable(mRttPipeToInCall, 0); + destination.writeList(mParticipants); + destination.writeInt(mIsAdhocConference ? 1 : 0); } } diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 3a0494e17db9..440f044fdcf7 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -154,6 +154,9 @@ public abstract class ConnectionService extends Service { private static final String SESSION_CONNECTION_SERVICE_FOCUS_LOST = "CS.cSFL"; private static final String SESSION_CONNECTION_SERVICE_FOCUS_GAINED = "CS.cSFG"; private static final String SESSION_HANDOVER_FAILED = "CS.haF"; + private static final String SESSION_CREATE_CONF = "CS.crConf"; + private static final String SESSION_CREATE_CONF_COMPLETE = "CS.crConfC"; + private static final String SESSION_CREATE_CONF_FAILED = "CS.crConfF"; private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; private static final int MSG_CREATE_CONNECTION = 2; @@ -188,6 +191,9 @@ public abstract class ConnectionService extends Service { private static final int MSG_HANDOVER_FAILED = 32; private static final int MSG_HANDOVER_COMPLETE = 33; private static final int MSG_DEFLECT = 34; + private static final int MSG_CREATE_CONFERENCE = 35; + private static final int MSG_CREATE_CONFERENCE_COMPLETE = 36; + private static final int MSG_CREATE_CONFERENCE_FAILED = 37; private static Connection sNullConnection; @@ -291,6 +297,63 @@ public abstract class ConnectionService extends Service { } @Override + public void createConference( + PhoneAccountHandle connectionManagerPhoneAccount, + String id, + ConnectionRequest request, + boolean isIncoming, + boolean isUnknown, + Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_CREATE_CONF); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = connectionManagerPhoneAccount; + args.arg2 = id; + args.arg3 = request; + args.arg4 = Log.createSubsession(); + args.argi1 = isIncoming ? 1 : 0; + args.argi2 = isUnknown ? 1 : 0; + mHandler.obtainMessage(MSG_CREATE_CONFERENCE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override + public void createConferenceComplete(String id, Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_CREATE_CONF_COMPLETE); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = id; + args.arg2 = Log.createSubsession(); + mHandler.obtainMessage(MSG_CREATE_CONFERENCE_COMPLETE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override + public void createConferenceFailed( + PhoneAccountHandle connectionManagerPhoneAccount, + String callId, + ConnectionRequest request, + boolean isIncoming, + Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_CREATE_CONF_FAILED); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = request; + args.arg3 = Log.createSubsession(); + args.arg4 = connectionManagerPhoneAccount; + args.argi1 = isIncoming ? 1 : 0; + mHandler.obtainMessage(MSG_CREATE_CONFERENCE_FAILED, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override public void handoverFailed(String callId, ConnectionRequest request, int reason, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_HANDOVER_FAILED); @@ -802,6 +865,106 @@ public abstract class ConnectionService extends Service { } break; } + case MSG_CREATE_CONFERENCE: { + SomeArgs args = (SomeArgs) msg.obj; + Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN); + try { + final PhoneAccountHandle connectionManagerPhoneAccount = + (PhoneAccountHandle) args.arg1; + final String id = (String) args.arg2; + final ConnectionRequest request = (ConnectionRequest) args.arg3; + final boolean isIncoming = args.argi1 == 1; + final boolean isUnknown = args.argi2 == 1; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-initconference request %s", id); + mPreInitializationConnectionRequests.add( + new android.telecom.Logging.Runnable( + SESSION_HANDLER + SESSION_CREATE_CONF + ".pIConfR", + null /*lock*/) { + @Override + public void loggedRun() { + createConference(connectionManagerPhoneAccount, + id, + request, + isIncoming, + isUnknown); + } + }.prepare()); + } else { + createConference(connectionManagerPhoneAccount, + id, + request, + isIncoming, + isUnknown); + } + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_CREATE_CONFERENCE_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + Log.continueSession((Session) args.arg2, + SESSION_HANDLER + SESSION_CREATE_CONN_COMPLETE); + try { + final String id = (String) args.arg1; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-init conference request %s", id); + mPreInitializationConnectionRequests.add( + new android.telecom.Logging.Runnable( + SESSION_HANDLER + SESSION_CREATE_CONF_COMPLETE + + ".pIConfR", + null /*lock*/) { + @Override + public void loggedRun() { + notifyCreateConferenceComplete(id); + } + }.prepare()); + } else { + notifyCreateConferenceComplete(id); + } + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_CREATE_CONFERENCE_FAILED: { + SomeArgs args = (SomeArgs) msg.obj; + Log.continueSession((Session) args.arg3, SESSION_HANDLER + + SESSION_CREATE_CONN_FAILED); + try { + final String id = (String) args.arg1; + final ConnectionRequest request = (ConnectionRequest) args.arg2; + final boolean isIncoming = args.argi1 == 1; + final PhoneAccountHandle connectionMgrPhoneAccount = + (PhoneAccountHandle) args.arg4; + if (!mAreAccountsInitialized) { + Log.d(this, "Enqueueing pre-init conference request %s", id); + mPreInitializationConnectionRequests.add( + new android.telecom.Logging.Runnable( + SESSION_HANDLER + SESSION_CREATE_CONF_FAILED + + ".pIConfR", + null /*lock*/) { + @Override + public void loggedRun() { + createConferenceFailed(connectionMgrPhoneAccount, id, + request, isIncoming); + } + }.prepare()); + } else { + Log.i(this, "createConferenceFailed %s", id); + createConferenceFailed(connectionMgrPhoneAccount, id, request, + isIncoming); + } + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_HANDOVER_FAILED: { SomeArgs args = (SomeArgs) msg.obj; Log.continueSession((Session) args.arg3, SESSION_HANDLER + @@ -1162,6 +1325,12 @@ public abstract class ConnectionService extends Service { public void onStateChanged(Conference conference, int oldState, int newState) { String id = mIdByConference.get(conference); switch (newState) { + case Connection.STATE_RINGING: + mAdapter.setRinging(id); + break; + case Connection.STATE_DIALING: + mAdapter.setDialing(id); + break; case Connection.STATE_ACTIVE: mAdapter.setActive(id); break; @@ -1292,6 +1461,13 @@ public abstract class ConnectionService extends Service { mAdapter.onConnectionEvent(id, event, extras); } } + + @Override + public void onRingbackRequested(Conference c, boolean ringback) { + String id = mIdByConference.get(c); + Log.d(this, "Adapter conference onRingback %b", ringback); + mAdapter.setRingbackRequested(id, ringback); + } }; private final Connection.Listener mConnectionListener = new Connection.Listener() { @@ -1534,6 +1710,70 @@ public abstract class ConnectionService extends Service { return super.onUnbind(intent); } + + /** + * This can be used by telecom to either create a new outgoing conference call or attach + * to an existing incoming conference call. In either case, telecom will cycle through a + * set of services and call createConference until a connection service cancels the process + * or completes it successfully. + */ + private void createConference( + final PhoneAccountHandle callManagerAccount, + final String callId, + final ConnectionRequest request, + boolean isIncoming, + boolean isUnknown) { + + Conference conference = null; + conference = isIncoming ? onCreateIncomingConference(callManagerAccount, request) + : onCreateOutgoingConference(callManagerAccount, request); + + Log.d(this, "createConference, conference: %s", conference); + if (conference == null) { + Log.i(this, "createConference, implementation returned null conference."); + conference = Conference.createFailedConference( + new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"), + request.getAccountHandle()); + } + if (conference.getExtras() != null) { + conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + } + mConferenceById.put(callId, conference); + mIdByConference.put(conference, callId); + conference.addListener(mConferenceListener); + ParcelableConference parcelableConference = new ParcelableConference( + request.getAccountHandle(), + conference.getState(), + conference.getConnectionCapabilities(), + conference.getConnectionProperties(), + Collections.<String>emptyList(), //connectionIds + conference.getVideoProvider() == null ? + null : conference.getVideoProvider().getInterface(), + conference.getVideoState(), + conference.getConnectTimeMillis(), + conference.getConnectionStartElapsedRealTime(), + conference.getStatusHints(), + conference.getExtras(), + conference.getAddress(), + conference.getAddressPresentation(), + conference.getCallerDisplayName(), + conference.getCallerDisplayNamePresentation(), + conference.getDisconnectCause(), + conference.isRingbackRequested()); + if (conference.getState() != Connection.STATE_DISCONNECTED) { + conference.setTelecomCallId(callId); + mAdapter.setVideoProvider(callId, conference.getVideoProvider()); + mAdapter.setVideoState(callId, conference.getVideoState()); + onConferenceAdded(conference); + } + + Log.d(this, "createConference, calling handleCreateConferenceSuccessful %s", callId); + mAdapter.handleCreateConferenceComplete( + callId, + request, + parcelableConference); + } + /** * This can be used by telecom to either create a new outgoing call or attach to an existing * incoming call. In either case, telecom will cycle through a set of services and call @@ -1645,6 +1885,18 @@ public abstract class ConnectionService extends Service { } } + private void createConferenceFailed(final PhoneAccountHandle callManagerAccount, + final String callId, final ConnectionRequest request, + boolean isIncoming) { + + Log.i(this, "createConferenceFailed %s", callId); + if (isIncoming) { + onCreateIncomingConferenceFailed(callManagerAccount, request); + } else { + onCreateOutgoingConferenceFailed(callManagerAccount, request); + } + } + private void handoverFailed(final String callId, final ConnectionRequest request, int reason) { @@ -1669,6 +1921,24 @@ public abstract class ConnectionService extends Service { "notifyCreateConnectionComplete")); } + /** + * Called by Telecom when the creation of a new Conference has completed and it is now added + * to Telecom. + * @param callId The ID of the connection. + */ + private void notifyCreateConferenceComplete(final String callId) { + Log.i(this, "notifyCreateConferenceComplete %s", callId); + if (callId == null) { + // This could happen if the conference fails quickly and is removed from the + // ConnectionService before Telecom sends the create conference complete callback. + Log.w(this, "notifyCreateConferenceComplete: callId is null."); + return; + } + onCreateConferenceComplete(findConferenceForAction(callId, + "notifyCreateConferenceComplete")); + } + + private void abort(String callId) { Log.d(this, "abort %s", callId); findConnectionForAction(callId, "abort").onAbort(); @@ -1676,12 +1946,20 @@ public abstract class ConnectionService extends Service { private void answerVideo(String callId, int videoState) { Log.d(this, "answerVideo %s", callId); - findConnectionForAction(callId, "answer").onAnswer(videoState); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "answer").onAnswer(videoState); + } else { + findConferenceForAction(callId, "answer").onAnswer(videoState); + } } private void answer(String callId) { Log.d(this, "answer %s", callId); - findConnectionForAction(callId, "answer").onAnswer(); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "answer").onAnswer(); + } else { + findConferenceForAction(callId, "answer").onAnswer(); + } } private void deflect(String callId, Uri address) { @@ -1691,7 +1969,11 @@ public abstract class ConnectionService extends Service { private void reject(String callId) { Log.d(this, "reject %s", callId); - findConnectionForAction(callId, "reject").onReject(); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "reject").onReject(); + } else { + findConferenceForAction(callId, "reject").onReject(); + } } private void reject(String callId, String rejectWithMessage) { @@ -2198,6 +2480,21 @@ public abstract class ConnectionService extends Service { ConnectionRequest request) { return null; } + /** + * Create a {@code Connection} given an incoming request. This is used to attach to existing + * incoming conference call. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request Details about the incoming call. + * @return The {@code Connection} object to satisfy this call, or {@code null} to + * not handle the call. + */ + public @Nullable Conference onCreateIncomingConference( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + return null; + } /** * Called after the {@link Connection} returned by @@ -2212,6 +2509,19 @@ public abstract class ConnectionService extends Service { } /** + * Called after the {@link Conference} returned by + * {@link #onCreateIncomingConference(PhoneAccountHandle, ConnectionRequest)} + * or {@link #onCreateOutgoingConference(PhoneAccountHandle, ConnectionRequest)} has been + * added to the {@link ConnectionService} and sent to Telecom. + * + * @param conference the {@link Conference}. + * @hide + */ + public void onCreateConferenceComplete(Conference conference) { + } + + + /** * Called by Telecom to inform the {@link ConnectionService} that its request to create a new * incoming {@link Connection} was denied. * <p> @@ -2250,6 +2560,47 @@ public abstract class ConnectionService extends Service { } /** + * Called by Telecom to inform the {@link ConnectionService} that its request to create a new + * incoming {@link Conference} was denied. + * <p> + * Used when a self-managed {@link ConnectionService} attempts to create a new incoming + * {@link Conference}, but Telecom has determined that the call cannot be allowed at this time. + * The {@link ConnectionService} is responsible for silently rejecting the new incoming + * {@link Conference}. + * <p> + * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request The incoming connection request. + */ + public void onCreateIncomingConferenceFailed( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + } + + /** + * Called by Telecom to inform the {@link ConnectionService} that its request to create a new + * outgoing {@link Conference} was denied. + * <p> + * Used when a self-managed {@link ConnectionService} attempts to create a new outgoing + * {@link Conference}, but Telecom has determined that the call cannot be placed at this time. + * The {@link ConnectionService} is responisible for informing the user that the + * {@link Conference} cannot be made at this time. + * <p> + * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information. + * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. + * @param request The outgoing connection request. + */ + public void onCreateOutgoingConferenceFailed( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + } + + + /** * Trigger recalculate functinality for conference calls. This is used when a Telephony * Connection is part of a conference controller but is not yet added to Connection * Service and hence cannot be added to the conference call. @@ -2289,6 +2640,36 @@ public abstract class ConnectionService extends Service { } /** + * Create a {@code Conference} given an outgoing request. This is used to initiate new + * outgoing conference call. + * + * @param connectionManagerPhoneAccount The connection manager account to use for managing + * this call. + * <p> + * If this parameter is not {@code null}, it means that this {@code ConnectionService} + * has registered one or more {@code PhoneAccount}s having + * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}. This parameter will contain + * one of these {@code PhoneAccount}s, while the {@code request} will contain another + * (usually but not always distinct) {@code PhoneAccount} to be used for actually + * making the connection. + * <p> + * If this parameter is {@code null}, it means that this {@code ConnectionService} is + * being asked to make a direct connection. The + * {@link ConnectionRequest#getAccountHandle()} of parameter {@code request} will be + * a {@code PhoneAccount} registered by this {@code ConnectionService} to use for + * making the connection. + * @param request Details about the outgoing call. + * @return The {@code Conference} object to satisfy this call, or the result of an invocation + * of {@link Connection#createFailedConnection(DisconnectCause)} to not handle the call. + */ + public @Nullable Conference onCreateOutgoingConference( + @Nullable PhoneAccountHandle connectionManagerPhoneAccount, + @Nullable ConnectionRequest request) { + return null; + } + + + /** * Called by Telecom to request that a {@link ConnectionService} creates an instance of an * outgoing handover {@link Connection}. * <p> diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index 04e930ccd954..8f273233044e 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -100,6 +100,19 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } + void handleCreateConferenceComplete( + String id, + ConnectionRequest request, + ParcelableConference conference) { + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.handleCreateConferenceComplete(id, request, conference, + Log.getExternalSession()); + } catch (RemoteException e) { + } + } + } + /** * Sets a call's state to active (e.g., an ongoing call where two parties can actively * communicate). diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index 60b2172fdeca..79ad51b92b81 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -75,6 +75,7 @@ final class ConnectionServiceAdapterServant { private static final int MSG_SET_PHONE_ACCOUNT_CHANGED = 34; private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35; private static final int MSG_SET_CONFERENCE_STATE = 36; + private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37; private final IConnectionServiceAdapter mDelegate; @@ -103,6 +104,19 @@ final class ConnectionServiceAdapterServant { } break; } + case MSG_HANDLE_CREATE_CONFERENCE_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + mDelegate.handleCreateConferenceComplete( + (String) args.arg1, + (ConnectionRequest) args.arg2, + (ParcelableConference) args.arg3, + null /*Session.Info*/); + } finally { + args.recycle(); + } + break; + } case MSG_SET_ACTIVE: mDelegate.setActive((String) msg.obj, null /*Session.Info*/); break; @@ -366,6 +380,20 @@ final class ConnectionServiceAdapterServant { } @Override + public void handleCreateConferenceComplete( + String id, + ConnectionRequest request, + ParcelableConference conference, + Session.Info sessionInfo) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = id; + args.arg2 = request; + args.arg3 = conference; + mHandler.obtainMessage(MSG_HANDLE_CREATE_CONFERENCE_COMPLETE, args).sendToTarget(); + } + + + @Override public void setActive(String connectionId, Session.Info sessionInfo) { mHandler.obtainMessage(MSG_SET_ACTIVE, connectionId).sendToTarget(); } diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java index ede05943772e..90b69a338c7e 100644 --- a/telecomm/java/android/telecom/ParcelableConference.java +++ b/telecomm/java/android/telecom/ParcelableConference.java @@ -47,6 +47,34 @@ public final class ParcelableConference implements Parcelable { private final int mAddressPresentation; private final String mCallerDisplayName; private final int mCallerDisplayNamePresentation; + private DisconnectCause mDisconnectCause; + private boolean mRingbackRequested; + + public ParcelableConference( + PhoneAccountHandle phoneAccount, + int state, + int connectionCapabilities, + int connectionProperties, + List<String> connectionIds, + IVideoProvider videoProvider, + int videoState, + long connectTimeMillis, + long connectElapsedTimeMillis, + StatusHints statusHints, + Bundle extras, + Uri address, + int addressPresentation, + String callerDisplayName, + int callerDisplayNamePresentation, + DisconnectCause disconnectCause, + boolean ringbackRequested) { + this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds, + videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, + statusHints, extras, address, addressPresentation, callerDisplayName, + callerDisplayNamePresentation); + mDisconnectCause = disconnectCause; + mRingbackRequested = ringbackRequested; + } public ParcelableConference( PhoneAccountHandle phoneAccount, @@ -79,6 +107,8 @@ public final class ParcelableConference implements Parcelable { mAddressPresentation = addressPresentation; mCallerDisplayName = callerDisplayName; mCallerDisplayNamePresentation = callerDisplayNamePresentation; + mDisconnectCause = null; + mRingbackRequested = false; } @Override @@ -100,6 +130,10 @@ public final class ParcelableConference implements Parcelable { .append(mVideoState) .append(", VideoProvider: ") .append(mVideoProvider) + .append(", isRingbackRequested: ") + .append(mRingbackRequested) + .append(", disconnectCause: ") + .append(mDisconnectCause) .toString(); } @@ -151,6 +185,13 @@ public final class ParcelableConference implements Parcelable { return mAddress; } + public final DisconnectCause getDisconnectCause() { + return mDisconnectCause; + } + + public boolean isRingbackRequested() { + return mRingbackRequested; + } public int getHandlePresentation() { return mAddressPresentation; } @@ -177,11 +218,14 @@ public final class ParcelableConference implements Parcelable { int addressPresentation = source.readInt(); String callerDisplayName = source.readString(); int callerDisplayNamePresentation = source.readInt(); + DisconnectCause disconnectCause = source.readParcelable(classLoader); + boolean isRingbackRequested = source.readInt() == 1; return new ParcelableConference(phoneAccount, state, capabilities, properties, connectionIds, videoCallProvider, videoState, connectTimeMillis, connectElapsedTimeMillis, statusHints, extras, address, addressPresentation, - callerDisplayName, callerDisplayNamePresentation); + callerDisplayName, callerDisplayNamePresentation, disconnectCause, + isRingbackRequested); } @Override @@ -215,5 +259,7 @@ public final class ParcelableConference implements Parcelable { destination.writeInt(mAddressPresentation); destination.writeString(mCallerDisplayName); destination.writeInt(mCallerDisplayNamePresentation); + destination.writeParcelable(mDisconnectCause, 0); + destination.writeInt(mRingbackRequested ? 1 : 0); } } diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index bb858cb0761b..abb210f13376 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -331,7 +331,17 @@ public final class PhoneAccount implements Parcelable { */ public static final int CAPABILITY_EMERGENCY_PREFERRED = 0x2000; - /* NEXT CAPABILITY: 0x4000 */ + /** + * An adhoc conference call is established by providing a list of addresses to + * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the + * {@link ConnectionService} is responsible for connecting all indicated participants + * to a conference simultaneously. + * This is in contrast to conferences formed by merging calls together (e.g. using + * {@link android.telecom.Call#mergeConference()}). + */ + public static final int CAPABILITY_ADHOC_CONFERENCE_CALLING = 0x4000; + + /* NEXT CAPABILITY: 0x8000 */ /** * URI scheme for telephone number URIs. @@ -1054,6 +1064,9 @@ public final class PhoneAccount implements Parcelable { if (hasCapabilities(CAPABILITY_RTT)) { sb.append("Rtt"); } + if (hasCapabilities(CAPABILITY_ADHOC_CONFERENCE_CALLING)) { + sb.append("AdhocConf"); + } return sb.toString(); } diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 1e73bd61d68e..76640e036eeb 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -101,6 +101,14 @@ final class RemoteConnectionService { } @Override + public void handleCreateConferenceComplete( + String id, + ConnectionRequest request, + ParcelableConference parcel, + Session.Info info) { + } + + @Override public void setActive(String callId, Session.Info sessionInfo) { if (mConnectionById.containsKey(callId)) { findConnectionForAction(callId, "setActive") diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index ffb27797d340..f1dca0387369 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1803,6 +1803,45 @@ public class TelecomManager { } /** + * Registers a new incoming conference. A {@link ConnectionService} should invoke this method + * when it has an incoming conference. For managed {@link ConnectionService}s, the specified + * {@link PhoneAccountHandle} must have been registered with {@link #registerPhoneAccount} and + * the user must have enabled the corresponding {@link PhoneAccount}. This can be checked using + * {@link #getPhoneAccount}. Self-managed {@link ConnectionService}s must have + * {@link android.Manifest.permission#MANAGE_OWN_CALLS} to add a new incoming call. + * <p> + * The incoming conference you are adding is assumed to have a video state of + * {@link VideoProfile#STATE_AUDIO_ONLY}, unless the extra value + * {@link #EXTRA_INCOMING_VIDEO_STATE} is specified. + * <p> + * Once invoked, this method will cause the system to bind to the {@link ConnectionService} + * associated with the {@link PhoneAccountHandle} and request additional information about the + * call (See {@link ConnectionService#onCreateIncomingConference}) before starting the incoming + * call UI. + * <p> + * For a managed {@link ConnectionService}, a {@link SecurityException} will be thrown if either + * the {@link PhoneAccountHandle} does not correspond to a registered {@link PhoneAccount} or + * the associated {@link PhoneAccount} is not currently enabled by the user. + * + * @param phoneAccount A {@link PhoneAccountHandle} registered with + * {@link #registerPhoneAccount}. + * @param extras A bundle that will be passed through to + * {@link ConnectionService#onCreateIncomingConference}. + */ + + public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount, + @NonNull Bundle extras) { + try { + if (isServiceConnected()) { + getTelecomService().addNewIncomingConference( + phoneAccount, extras == null ? new Bundle() : extras); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException adding a new incoming conference: " + phoneAccount, e); + } + } + + /** * Registers a new unknown call with Telecom. This can only be called by the system Telephony * service. This is invoked when Telephony detects a new unknown connection that was neither * a new incoming call, nor an user-initiated outgoing call. @@ -2006,6 +2045,42 @@ public class TelecomManager { } } + + /** + * Place a new conference call with the provided participants using the system telecom service + * This method doesn't support placing of emergency calls. + * + * An adhoc conference call is established by providing a list of addresses to + * {@code TelecomManager#startConference(List<Uri>, int videoState)} where the + * {@link ConnectionService} is responsible for connecting all indicated participants + * to a conference simultaneously. + * This is in contrast to conferences formed by merging calls together (e.g. using + * {@link android.telecom.Call#mergeConference()}). + * + * The following keys are supported in the supplied extras. + * <ul> + * <li>{@link #EXTRA_PHONE_ACCOUNT_HANDLE}</li> + * <li>{@link #EXTRA_START_CALL_WITH_SPEAKERPHONE}</li> + * <li>{@link #EXTRA_START_CALL_WITH_VIDEO_STATE}</li> + * </ul> + * + * @param participants List of participants to start conference with + * @param extras Bundle of extras to use with the call + */ + @RequiresPermission(android.Manifest.permission.CALL_PHONE) + public void startConference(@NonNull List<Uri> participants, + @NonNull Bundle extras) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + service.startConference(participants, extras, + mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#placeCall", e); + } + } + } + /** * Enables and disables specified phone account. * diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index e35093c9656a..96f2483f32f9 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -53,6 +53,20 @@ oneway interface IConnectionService { void createConnectionFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId, in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo); + void createConference( + in PhoneAccountHandle connectionManagerPhoneAccount, + String callId, + in ConnectionRequest request, + boolean isIncoming, + boolean isUnknown, + in Session.Info sessionInfo); + + void createConferenceComplete(String callId, in Session.Info sessionInfo); + + void createConferenceFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId, + in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo); + + void abort(String callId, in Session.Info sessionInfo); void answerVideo(String callId, int videoState, in Session.Info sessionInfo); diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl index 9cf098c75177..4f63e08abce6 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -44,6 +44,12 @@ oneway interface IConnectionServiceAdapter { in ParcelableConnection connection, in Session.Info sessionInfo); + void handleCreateConferenceComplete( + String callId, + in ConnectionRequest request, + in ParcelableConference connection, + in Session.Info sessionInfo); + void setActive(String callId, in Session.Info sessionInfo); void setRinging(String callId, in Session.Info sessionInfo); diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 204c37e9aa38..9a47ae15e64a 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -242,11 +242,22 @@ interface ITelecomService { void addNewIncomingCall(in PhoneAccountHandle phoneAccount, in Bundle extras); /** + * @see TelecomServiceImpl#addNewIncomingConference + */ + void addNewIncomingConference(in PhoneAccountHandle phoneAccount, in Bundle extras); + + /** * @see TelecomServiceImpl#addNewUnknownCall */ void addNewUnknownCall(in PhoneAccountHandle phoneAccount, in Bundle extras); /** + * @see TelecomServiceImpl#startConference + */ + void startConference(in List<Uri> participants, in Bundle extras, + String callingPackage); + + /** * @see TelecomServiceImpl#placeCall */ void placeCall(in Uri handle, in Bundle extras, String callingPackage); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d863090a18d1..71aaa6e95352 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1079,6 +1079,14 @@ public class CarrierConfigManager { public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool"; + + /** + * Determines whether adhoc conference calls are supported by a carrier. When {@code true}, + * adhoc conference calling is supported, {@code false otherwise}. + */ + public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = + "support_adhoc_conference_calls_bool"; + /** * Determines whether conference calls are supported by a carrier. When {@code true}, * conference calling is supported, {@code false otherwise}. @@ -3522,6 +3530,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false); sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false); sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0); + sDefaults.putBoolean(KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL, false); sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ef631b8a05dc..839889f91c4a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2346,7 +2346,7 @@ public class TelephonyManager { @UnsupportedAppUsage public boolean isNetworkRoaming(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); - return getTelephonyProperty(subId, TelephonyProperties.operator_is_roaming(), false); + return getTelephonyProperty(phoneId, TelephonyProperties.operator_is_roaming(), false); } /** diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index 3ec4f3468497..f13371c1d0fa 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -17,6 +17,8 @@ package android.telephony.ims.stub; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Bundle; @@ -206,6 +208,13 @@ public class ImsUtImplBase { return ImsUtImplBase.this.updateCallBarringForServiceClass( cbType, action, barrList, serviceClass); } + + @Override + public int updateCallBarringWithPassword(int cbType, int action, String[] barrList, + int serviceClass, String password) throws RemoteException { + return ImsUtImplBase.this.updateCallBarringWithPassword( + cbType, action, barrList, serviceClass, password); + } }; /** @@ -328,6 +337,14 @@ public class ImsUtImplBase { } /** + * Updates the configuration of the call barring for specified service class with password. + */ + public int updateCallBarringWithPassword(int cbType, int action, @Nullable String[] barrList, + int serviceClass, @NonNull String password) { + return -1; + } + + /** * Updates the configuration of the call forward. */ public int updateCallForward(int action, int condition, String number, int serviceClass, diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java index 15f837189843..4a5380e4551b 100644 --- a/telephony/java/com/android/ims/ImsUtInterface.java +++ b/telephony/java/com/android/ims/ImsUtInterface.java @@ -166,6 +166,12 @@ public interface ImsUtInterface { String[] barrList, int serviceClass); /** + * Modifies the configuration of the call barring for specified service class with password. + */ + public void updateCallBarring(int cbType, int action, Message result, + String[] barrList, int serviceClass, String password); + + /** * Modifies the configuration of the call forward. */ public void updateCallForward(int action, int condition, String number, diff --git a/telephony/java/com/android/ims/internal/IImsUt.aidl b/telephony/java/com/android/ims/internal/IImsUt.aidl index 4f97cc5cfb22..302be65070f7 100644 --- a/telephony/java/com/android/ims/internal/IImsUt.aidl +++ b/telephony/java/com/android/ims/internal/IImsUt.aidl @@ -122,4 +122,10 @@ interface IImsUt { */ int updateCallBarringForServiceClass(int cbType, int action, in String[] barrList, int serviceClass); + + /** + * Updates the configuration of the call barring for specified service class with password. + */ + int updateCallBarringWithPassword(int cbType, int action, in String[] barrList, + int serviceClass, String password); } diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 15691127cab7..797fd83321f7 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -271,7 +271,7 @@ public class NetworkCapabilitiesTest { .addCapability(NET_CAPABILITY_NOT_METERED); assertParcelingIsLossless(netCap); netCap.setSSID(TEST_SSID); - assertParcelSane(netCap, 12); + assertParcelSane(netCap, 13); } @Test diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java new file mode 100644 index 000000000000..065add4fc253 --- /dev/null +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -0,0 +1,196 @@ +/* + * 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.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.ConnectivityDiagnosticsManager.DataStallReport; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.os.PersistableBundle; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ConnectivityDiagnosticsManagerTest { + private static final int NET_ID = 1; + private static final int DETECTION_METHOD = 2; + private static final long TIMESTAMP = 10L; + private static final String INTERFACE_NAME = "interface"; + private static final String BUNDLE_KEY = "key"; + private static final String BUNDLE_VALUE = "value"; + + private ConnectivityReport createSampleConnectivityReport() { + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + return new ConnectivityReport( + new Network(NET_ID), TIMESTAMP, linkProperties, networkCapabilities, bundle); + } + + private ConnectivityReport createDefaultConnectivityReport() { + return new ConnectivityReport( + new Network(0), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY); + } + + @Test + public void testPersistableBundleEquals() { + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + null, PersistableBundle.EMPTY)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, null)); + assertTrue( + ConnectivityDiagnosticsManager.persistableBundleEquals( + PersistableBundle.EMPTY, PersistableBundle.EMPTY)); + + final PersistableBundle a = new PersistableBundle(); + a.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle b = new PersistableBundle(); + b.putString(BUNDLE_KEY, BUNDLE_VALUE); + + final PersistableBundle c = new PersistableBundle(); + c.putString(BUNDLE_KEY, null); + + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(PersistableBundle.EMPTY, a)); + assertFalse( + ConnectivityDiagnosticsManager.persistableBundleEquals(a, PersistableBundle.EMPTY)); + + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(a, b)); + assertTrue(ConnectivityDiagnosticsManager.persistableBundleEquals(b, a)); + + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(a, c)); + assertFalse(ConnectivityDiagnosticsManager.persistableBundleEquals(c, a)); + } + + @Test + public void testConnectivityReportEquals() { + assertEquals(createSampleConnectivityReport(), createSampleConnectivityReport()); + assertEquals(createDefaultConnectivityReport(), createDefaultConnectivityReport()); + + final LinkProperties linkProperties = new LinkProperties(); + linkProperties.setInterfaceName(INTERFACE_NAME); + + final NetworkCapabilities networkCapabilities = new NetworkCapabilities(); + networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(NET_ID), + 0L, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + 0L, + linkProperties, + new NetworkCapabilities(), + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + networkCapabilities, + PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultConnectivityReport(), + new ConnectivityReport( + new Network(0), + TIMESTAMP, + new LinkProperties(), + new NetworkCapabilities(), + bundle)); + } + + @Test + public void testConnectivityReportParcelUnparcel() { + assertParcelSane(createSampleConnectivityReport(), 5); + } + + private DataStallReport createSampleDataStallReport() { + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + return new DataStallReport(new Network(NET_ID), TIMESTAMP, DETECTION_METHOD, bundle); + } + + private DataStallReport createDefaultDataStallReport() { + return new DataStallReport(new Network(0), 0L, 0, PersistableBundle.EMPTY); + } + + @Test + public void testDataStallReportEquals() { + assertEquals(createSampleDataStallReport(), createSampleDataStallReport()); + assertEquals(createDefaultDataStallReport(), createDefaultDataStallReport()); + + final PersistableBundle bundle = new PersistableBundle(); + bundle.putString(BUNDLE_KEY, BUNDLE_VALUE); + + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(NET_ID), 0L, 0, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(0), TIMESTAMP, 0, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), + new DataStallReport(new Network(0), 0L, DETECTION_METHOD, PersistableBundle.EMPTY)); + assertNotEquals( + createDefaultDataStallReport(), new DataStallReport(new Network(0), 0L, 0, bundle)); + } + + @Test + public void testDataStallReportParcelUnparcel() { + assertParcelSane(createSampleDataStallReport(), 4); + } +} diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java new file mode 100644 index 000000000000..d6a2176d7e81 --- /dev/null +++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.net.VpnProfile; +import com.android.org.bouncycastle.x509.X509V1CertificateGenerator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.x500.X500Principal; + +/** Unit tests for {@link Ikev2VpnProfile.Builder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class Ikev2VpnProfileTest { + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final String USERNAME_STRING = "username"; + private static final String PASSWORD_STRING = "pa55w0rd"; + private static final String EXCL_LIST = "exclList"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + private static final int TEST_MTU = 1300; + + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + private final ProxyInfo mProxy = new ProxyInfo(SERVER_ADDR_STRING, -1, EXCL_LIST); + + private X509Certificate mUserCert; + private X509Certificate mServerRootCa; + private PrivateKey mPrivateKey; + + @Before + public void setUp() throws Exception { + mServerRootCa = generateRandomCertAndKeyPair().cert; + + final CertificateAndKey userCertKey = generateRandomCertAndKeyPair(); + mUserCert = userCertKey.cert; + mPrivateKey = userCertKey.key; + } + + private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING); + + builder.setBypassable(true); + builder.setProxy(mProxy); + builder.setMaxMtu(TEST_MTU); + builder.setMetered(true); + + return builder; + } + + @Test + public void testBuildValidProfileWithOptions() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + // Check non-auth parameters correctly stored + assertEquals(SERVER_ADDR_STRING, profile.getServerAddr()); + assertEquals(IDENTITY_STRING, profile.getUserIdentity()); + assertEquals(mProxy, profile.getProxyInfo()); + assertTrue(profile.isBypassable()); + assertTrue(profile.isMetered()); + assertEquals(TEST_MTU, profile.getMaxMtu()); + } + + @Test + public void testBuildUsernamePasswordProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(USERNAME_STRING, profile.getUsername()); + assertEquals(PASSWORD_STRING, profile.getPassword()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildDigitalSignatureProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(profile.getUserCert(), mUserCert); + assertEquals(mPrivateKey, profile.getRsaPrivateKey()); + assertEquals(profile.getServerRootCaCert(), mServerRootCa); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + } + + @Test + public void testBuildPresharedKeyProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertArrayEquals(PSK_BYTES, profile.getPresharedKey()); + + assertNull(profile.getServerRootCaCert()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildNoAuthMethodSet() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.build(); + fail("Expected exception due to lack of auth method"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildInvalidMtu() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setMaxMtu(500); + fail("Expected exception due to too-small MTU"); + } catch (IllegalArgumentException expected) { + } + } + + private void verifyVpnProfileCommon(VpnProfile profile) { + assertEquals(SERVER_ADDR_STRING, profile.server); + assertEquals(IDENTITY_STRING, profile.ipsecIdentifier); + assertEquals(mProxy, profile.proxy); + assertTrue(profile.isBypassable); + assertTrue(profile.isMetered); + assertEquals(TEST_MTU, profile.maxMtu); + } + + @Test + public void testPskConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecCaCert); + } + + @Test + public void testUsernamePasswordConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(USERNAME_STRING, profile.username); + assertEquals(PASSWORD_STRING, profile.password); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecSecret); + } + + @Test + public void testRsaConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); + assertEquals( + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()), + profile.ipsecSecret); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + } + + @Test + public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + assertNull(result.getServerRootCaCert()); + } + + @Test + public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.ipsecSecret = new String(PSK_BYTES); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getPresharedKey()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + } + + @Test + public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getPresharedKey()); + } + + @Test + public void testPskConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testUsernamePasswordConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testRsaConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + private static class CertificateAndKey { + public final X509Certificate cert; + public final PrivateKey key; + + CertificateAndKey(X509Certificate cert, PrivateKey key) { + this.cert = cert; + this.key = key; + } + } + + private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception { + final Date validityBeginDate = + new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L)); + final Date validityEndDate = + new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L)); + + // Generate a keypair + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(512); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + final X500Principal dnName = new X500Principal("CN=test.android.com"); + final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setSubjectDN(dnName); + certGen.setIssuerDN(dnName); + certGen.setNotBefore(validityBeginDate); + certGen.setNotAfter(validityEndDate); + certGen.setPublicKey(keyPair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL"); + return new CertificateAndKey(cert, keyPair.getPrivate()); + } +} diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java new file mode 100644 index 000000000000..655c4d118592 --- /dev/null +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.mockito.Mockito.mock; + +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link VpnManager}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VpnManagerTest { + private static final String VPN_PROFILE_KEY = "KEY"; + + private IConnectivityManager mMockCs; + private VpnManager mVpnManager; + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + + @Before + public void setUp() throws Exception { + mMockCs = mock(IConnectivityManager.class); + mVpnManager = new VpnManager(mMockContext, mMockCs); + } + + @Test + public void testProvisionVpnProfile() throws Exception { + try { + mVpnManager.provisionVpnProfile(mock(PlatformVpnProfile.class)); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testDeleteProvisionedVpnProfile() throws Exception { + try { + mVpnManager.deleteProvisionedVpnProfile(); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testStartProvisionedVpnProfile() throws Exception { + try { + mVpnManager.startProvisionedVpnProfile(); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testStopProvisionedVpnProfile() throws Exception { + try { + mVpnManager.stopProvisionedVpnProfile(); + } catch (UnsupportedOperationException expected) { + } + } +} diff --git a/tests/net/java/com/android/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java new file mode 100644 index 000000000000..8a4b53343c26 --- /dev/null +++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +/** Unit tests for {@link VpnProfile}. */ +@SmallTest +@RunWith(JUnit4.class) +public class VpnProfileTest { + private static final String DUMMY_PROFILE_KEY = "Test"; + + @Test + public void testDefaults() throws Exception { + final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); + + assertEquals(DUMMY_PROFILE_KEY, p.key); + assertEquals("", p.name); + assertEquals(VpnProfile.TYPE_PPTP, p.type); + assertEquals("", p.server); + assertEquals("", p.username); + assertEquals("", p.password); + assertEquals("", p.dnsServers); + assertEquals("", p.searchDomains); + assertEquals("", p.routes); + assertTrue(p.mppe); + assertEquals("", p.l2tpSecret); + assertEquals("", p.ipsecIdentifier); + assertEquals("", p.ipsecSecret); + assertEquals("", p.ipsecUserCert); + assertEquals("", p.ipsecCaCert); + assertEquals("", p.ipsecServerCert); + assertEquals(null, p.proxy); + assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty()); + assertFalse(p.isBypassable); + assertFalse(p.isMetered); + assertEquals(1400, p.maxMtu); + assertFalse(p.areAuthParamsInline); + } + + private VpnProfile getSampleIkev2Profile(String key) { + final VpnProfile p = new VpnProfile(key); + + p.name = "foo"; + p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + p.server = "bar"; + p.username = "baz"; + p.password = "qux"; + p.dnsServers = "8.8.8.8"; + p.searchDomains = ""; + p.routes = "0.0.0.0/0"; + p.mppe = false; + p.l2tpSecret = ""; + p.ipsecIdentifier = "quux"; + p.ipsecSecret = "quuz"; + p.ipsecUserCert = "corge"; + p.ipsecCaCert = "grault"; + p.ipsecServerCert = "garply"; + p.proxy = null; + p.setAllowedAlgorithms( + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.CRYPT_AES_CBC)); + p.isBypassable = true; + p.isMetered = true; + p.maxMtu = 1350; + p.areAuthParamsInline = true; + + // Not saved, but also not compared. + p.saveLogin = true; + + return p; + } + + @Test + public void testEquals() { + assertEquals( + getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY)); + + final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + modified.maxMtu--; + assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified); + } + + @Test + public void testParcelUnparcel() { + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22); + } + + @Test + public void testSetInvalidAlgorithmValueDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.VALUE_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetInvalidAlgorithmListDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.LIST_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEncodeDecode() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertEquals(profile, decoded); + } + + @Test + public void testEncodeDecodeTooManyValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final byte[] tooManyValues = + (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); + } + + @Test + public void testEncodeDecodeInvalidNumberOfValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final String encoded = new String(profile.encode()); + final byte[] tooFewValues = + encoded.substring(0, encoded.lastIndexOf(VpnProfile.VALUE_DELIMITER)).getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues)); + } + + @Test + public void testEncodeDecodeLoginsNotSaved() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + profile.saveLogin = false; + + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertNotEquals(profile, decoded); + + // Add the username/password back, everything else must be equal. + decoded.username = profile.username; + decoded.password = profile.password; + assertEquals(profile, decoded); + } +} diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index f85bb5f8ad59..b9eb3f2b9d04 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -3194,27 +3194,27 @@ public class WifiManager { * soft AP state and number of connected devices immediately after a successful call to this API * via callback. Note that receiving an immediate WIFI_AP_STATE_FAILED value for soft AP state * indicates that the latest attempt to start soft AP has failed. Caller can unregister a - * previously registered callback using {@link unregisterSoftApCallback} + * previously registered callback using {@link #unregisterSoftApCallback} * <p> * Applications should have the * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers * without the permission will trigger a {@link java.lang.SecurityException}. * <p> * - * @param executor The executor to execute the callbacks of the {@code executor} - * object. If null, then the application's main executor will be used. + * @param executor The Executor on whose thread to execute the callbacks of the {@code callback} + * object. * @param callback Callback for soft AP events * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) - public void registerSoftApCallback(@Nullable @CallbackExecutor Executor executor, + public void registerSoftApCallback(@NonNull @CallbackExecutor Executor executor, @NonNull SoftApCallback callback) { + if (executor == null) throw new IllegalArgumentException("executor cannot be null"); if (callback == null) throw new IllegalArgumentException("callback cannot be null"); Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", executor=" + executor); - executor = (executor == null) ? mContext.getMainExecutor() : executor; Binder binder = new Binder(); try { mService.registerSoftApCallback(binder, new SoftApCallbackProxy(executor, callback), diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 4260c20255ac..b130c1737c62 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -693,25 +693,27 @@ public class WifiManagerTest { } /** - * Verify an IllegalArgumentException is thrown if callback is not provided. + * Verify an IllegalArgumentException is thrown if executor is null. */ @Test - public void unregisterSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForCallback() { + public void registerSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForExecutor() { try { - mWifiManager.unregisterSoftApCallback(null); + mWifiManager.registerSoftApCallback(null, mSoftApCallback); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { } } /** - * Verify main looper is used when handler is not provided. + * Verify an IllegalArgumentException is thrown if callback is not provided. */ @Test - public void registerSoftApCallbackUsesMainLooperOnNullArgumentForHandler() { - when(mContext.getMainLooper()).thenReturn(mLooper.getLooper()); - mWifiManager.registerSoftApCallback(null, mSoftApCallback); - verify(mContext).getMainExecutor(); + public void unregisterSoftApCallbackThrowsIllegalArgumentExceptionOnNullArgumentForCallback() { + try { + mWifiManager.unregisterSoftApCallback(null); + fail("expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } } /** |