Merge "Track: ICU TimeZone: publish TIMEZONE_* constants"
diff --git a/api/current.txt b/api/current.txt
index b427d66..c8009f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8188,6 +8188,7 @@
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
+ field public static final java.lang.String IPSEC_SERVICE = "ipsec";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -23726,6 +23727,68 @@
field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
}
+ public final class IpSecAlgorithm implements android.os.Parcelable {
+ ctor public IpSecAlgorithm(java.lang.String, byte[]);
+ ctor public IpSecAlgorithm(java.lang.String, byte[], int);
+ method public int describeContents();
+ method public byte[] getKey();
+ method public java.lang.String getName();
+ method public int getTruncationLengthBits();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
+ field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)";
+ field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+ }
+
+ public final class IpSecManager {
+ method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
+ method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
+ method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+ method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+ method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
+ method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
+ }
+
+ public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+ }
+
+ public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getSpi();
+ }
+
+ public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+ method public int getSpi();
+ }
+
+ public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getPort();
+ method public java.io.FileDescriptor getSocket();
+ }
+
+ public final class IpSecTransform implements java.lang.AutoCloseable {
+ method public void close();
+ field public static final int DIRECTION_IN = 0; // 0x0
+ field public static final int DIRECTION_OUT = 1; // 0x1
+ }
+
+ public static class IpSecTransform.Builder {
+ ctor public IpSecTransform.Builder(android.content.Context);
+ method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
+ method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+ method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
+ method public android.net.IpSecTransform.Builder setSpi(int, int);
+ method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
+ }
+
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -36683,6 +36746,7 @@
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_EXTERNAL_CALL = 64; // 0x40
+ field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -36696,9 +36760,6 @@
field public static final int RTT_MODE_VCO = 3; // 0x3
}
- public static abstract class Call.RttCall.RttAudioMode implements java.lang.annotation.Annotation {
- }
-
public final class CallAudioState implements android.os.Parcelable {
ctor public CallAudioState(boolean, int, int);
method public static java.lang.String audioRouteToString(int);
@@ -36955,9 +37016,9 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
- method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
+ method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
- method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
+ method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -37253,6 +37314,7 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -37289,6 +37351,7 @@
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+ field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -37957,6 +38020,7 @@
method public java.lang.String getDeviceId();
method public java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
+ method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getLine1Number();
diff --git a/api/system-current.txt b/api/system-current.txt
index 07ddb7b..fe33973 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8522,6 +8522,7 @@
field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
+ field public static final java.lang.String IPSEC_SERVICE = "ipsec";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -25563,6 +25564,73 @@
field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
}
+ public final class IpSecAlgorithm implements android.os.Parcelable {
+ ctor public IpSecAlgorithm(java.lang.String, byte[]);
+ ctor public IpSecAlgorithm(java.lang.String, byte[], int);
+ method public int describeContents();
+ method public byte[] getKey();
+ method public java.lang.String getName();
+ method public int getTruncationLengthBits();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
+ field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)";
+ field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+ }
+
+ public final class IpSecManager {
+ method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
+ method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
+ method public void applyTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
+ method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+ method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+ method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
+ method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
+ method public void removeTunnelModeTransform(android.net.Network, android.net.IpSecTransform);
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
+ }
+
+ public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+ }
+
+ public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getSpi();
+ }
+
+ public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+ method public int getSpi();
+ }
+
+ public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getPort();
+ method public java.io.FileDescriptor getSocket();
+ }
+
+ public final class IpSecTransform implements java.lang.AutoCloseable {
+ method public void close();
+ field public static final int DIRECTION_IN = 0; // 0x0
+ field public static final int DIRECTION_OUT = 1; // 0x1
+ }
+
+ public static class IpSecTransform.Builder {
+ ctor public IpSecTransform.Builder(android.content.Context);
+ method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, java.net.InetAddress);
+ method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
+ method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+ method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
+ method public android.net.IpSecTransform.Builder setNattKeepalive(int);
+ method public android.net.IpSecTransform.Builder setSpi(int, int);
+ method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
+ method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network);
+ }
+
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -39652,6 +39720,7 @@
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_EXTERNAL_CALL = 64; // 0x40
+ field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -39669,9 +39738,6 @@
field public static final int RTT_MODE_VCO = 3; // 0x3
}
- public static abstract class Call.RttCall.RttAudioMode implements java.lang.annotation.Annotation {
- }
-
public final class CallAudioState implements android.os.Parcelable {
ctor public CallAudioState(boolean, int, int);
method public static java.lang.String audioRouteToString(int);
@@ -39935,9 +40001,9 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
- method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
+ method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
- method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
+ method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -40414,6 +40480,7 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isRinging();
@@ -40457,6 +40524,7 @@
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+ field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -40958,7 +41026,9 @@
method public void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
method public void sendMultimediaMessage(android.content.Context, android.net.Uri, java.lang.String, android.os.Bundle, android.app.PendingIntent);
method public void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
+ method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
method public void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+ method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
field public static final java.lang.String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
field public static final java.lang.String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
field public static final java.lang.String MMS_CONFIG_ALIAS_ENABLED = "aliasEnabled";
@@ -41166,6 +41236,7 @@
method public java.lang.String getDeviceId();
method public java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
+ method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getImei();
diff --git a/api/test-current.txt b/api/test-current.txt
index c0c5c79..548c14d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8199,6 +8199,7 @@
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
+ field public static final java.lang.String IPSEC_SERVICE = "ipsec";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -23799,6 +23800,68 @@
field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
}
+ public final class IpSecAlgorithm implements android.os.Parcelable {
+ ctor public IpSecAlgorithm(java.lang.String, byte[]);
+ ctor public IpSecAlgorithm(java.lang.String, byte[], int);
+ method public int describeContents();
+ method public byte[] getKey();
+ method public java.lang.String getName();
+ method public int getTruncationLengthBits();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
+ field public static final java.lang.String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
+ field public static final java.lang.String ALGO_CRYPT_AES_CBC = "cbc(aes)";
+ field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+ }
+
+ public final class IpSecManager {
+ method public void applyTransportModeTransform(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException;
+ method public void applyTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException;
+ method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+ method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+ method public void removeTransportModeTransform(java.net.Socket, android.net.IpSecTransform);
+ method public void removeTransportModeTransform(java.net.DatagramSocket, android.net.IpSecTransform);
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
+ }
+
+ public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+ }
+
+ public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getSpi();
+ }
+
+ public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+ method public int getSpi();
+ }
+
+ public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+ method public void close();
+ method public int getPort();
+ method public java.io.FileDescriptor getSocket();
+ }
+
+ public final class IpSecTransform implements java.lang.AutoCloseable {
+ method public void close();
+ field public static final int DIRECTION_IN = 0; // 0x0
+ field public static final int DIRECTION_OUT = 1; // 0x1
+ }
+
+ public static class IpSecTransform.Builder {
+ ctor public IpSecTransform.Builder(android.content.Context);
+ method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+ method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
+ method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+ method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
+ method public android.net.IpSecTransform.Builder setSpi(int, int);
+ method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
+ }
+
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -36765,6 +36828,7 @@
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_EXTERNAL_CALL = 64; // 0x40
+ field public static final int PROPERTY_SELF_MANAGED = 256; // 0x100
field public static final int PROPERTY_WIFI = 8; // 0x8
}
@@ -36778,9 +36842,6 @@
field public static final int RTT_MODE_VCO = 3; // 0x3
}
- public static abstract class Call.RttCall.RttAudioMode implements java.lang.annotation.Annotation {
- }
-
public final class CallAudioState implements android.os.Parcelable {
ctor public CallAudioState(boolean, int, int);
method public static java.lang.String audioRouteToString(int);
@@ -37037,9 +37098,9 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
- method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest);
+ method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
- method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest);
+ method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
@@ -37335,6 +37396,7 @@
method public boolean handleMmi(java.lang.String);
method public boolean handleMmi(java.lang.String, android.telecom.PhoneAccountHandle);
method public boolean isInCall();
+ method public boolean isInManagedCall();
method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle);
method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String);
@@ -37371,6 +37433,7 @@
field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
field public static final java.lang.String METADATA_INCLUDE_EXTERNAL_CALLS = "android.telecom.INCLUDE_EXTERNAL_CALLS";
+ field public static final java.lang.String METADATA_INCLUDE_SELF_MANAGED_CALLS = "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_RINGING = "android.telecom.IN_CALL_SERVICE_RINGING";
field public static final java.lang.String METADATA_IN_CALL_SERVICE_UI = "android.telecom.IN_CALL_SERVICE_UI";
field public static final int PRESENTATION_ALLOWED = 1; // 0x1
@@ -38039,6 +38102,7 @@
method public java.lang.String getDeviceId();
method public java.lang.String getDeviceId(int);
method public java.lang.String getDeviceSoftwareVersion();
+ method public java.lang.String[] getForbiddenPlmns();
method public java.lang.String getGroupIdLevel1();
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getLine1Number();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 02b86fe..46d3835 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2662,6 +2662,7 @@
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
+ IPSEC_SERVICE,
//@hide: UPDATE_LOCK_SERVICE,
//@hide: NETWORKMANAGEMENT_SERVICE,
NETWORK_STATS_SERVICE,
@@ -2763,6 +2764,9 @@
* <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
* <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
* handling management of network connections.
+ * <dt> {@link #IPSEC_SERVICE} ("ipsec")
+ * <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
+ * sockets and networks.
* <dt> {@link #WIFI_SERVICE} ("wifi")
* <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
* connectivity. On releases before NYC, it should only be obtained from an application
@@ -3093,6 +3097,15 @@
public static final String CONNECTIVITY_SERVICE = "connectivity";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
+ * IPSec.
+ *
+ * @see #getSystemService
+ */
+ public static final String IPSEC_SERVICE = "ipsec";
+
+ /**
* Use with {@link #getSystemService} to retrieve a {@link
* android.os.IUpdateLock} for managing runtime sequences that
* must not be interrupted by headless OTA application or similar.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c51945e..46b981e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -482,6 +482,7 @@
*/
boolean performDexOpt(String packageName, boolean checkProfiles,
int compileReason, boolean force);
+
/**
* Ask the package manager to perform a dex-opt with the given compiler filter.
*
@@ -492,6 +493,16 @@
String targetCompilerFilter, boolean force);
/**
+ * Ask the package manager to perform a dex-opt with the given compiler filter on the
+ * secondary dex files belonging to the given package.
+ *
+ * Note: exposed only for the shell command to allow moving packages explicitly to a
+ * definite state.
+ */
+ boolean performDexOptSecondary(String packageName,
+ String targetCompilerFilter, boolean force);
+
+ /**
* Ask the package manager to dump profiles associated with a package.
*/
void dumpProfiles(String packageName);
@@ -499,6 +510,18 @@
void forceDexOpt(String packageName);
/**
+ * Execute the background dexopt job immediately.
+ */
+ boolean runBackgroundDexoptJob();
+
+ /**
+ * Reconcile the information we have about the secondary dex files belonging to
+ * {@code packagName} and the actual dex files. For all dex files that were
+ * deleted, update the internal records and delete the generated oat files.
+ */
+ void reconcileSecondaryDexFiles(String packageName);
+
+ /**
* Update status of external media on the package manager to scan and
* install packages installed on the external media. Like say the
* MountService uses this to call into the package manager to update
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 30afdc2..154efcd 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1052,26 +1052,6 @@
}
/**
- * Request that this callback be invoked at ConnectivityService's earliest
- * convenience with the current satisfying network's LinkProperties.
- * If no such network exists no callback invocation is performed.
- *
- * The callback must have been registered with #requestNetwork() or
- * #registerDefaultNetworkCallback(); callbacks registered with
- * registerNetworkCallback() are not specific to any particular Network so
- * do not cause any updates.
- *
- * @hide
- */
- public void requestLinkProperties(NetworkCallback networkCallback) {
- try {
- mService.requestLinkProperties(networkCallback.networkRequest);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This
* will return {@code null} if the network is unknown.
* <p>This method requires the caller to hold the permission
@@ -1089,26 +1069,6 @@
}
/**
- * Request that this callback be invoked at ConnectivityService's earliest
- * convenience with the current satisfying network's NetworkCapabilities.
- * If no such network exists no callback invocation is performed.
- *
- * The callback must have been registered with #requestNetwork() or
- * #registerDefaultNetworkCallback(); callbacks registered with
- * registerNetworkCallback() are not specific to any particular Network so
- * do not cause any updates.
- *
- * @hide
- */
- public void requestNetworkCapabilities(NetworkCallback networkCallback) {
- try {
- mService.requestNetworkCapabilities(networkCallback.networkRequest);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Gets the URL that should be used for resolving whether a captive portal is present.
* 1. This URL should respond with a 204 response to a GET request to indicate no captive
* portal is present.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 4aabda9..b123c28f 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,8 +156,6 @@
void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation);
- void requestLinkProperties(in NetworkRequest networkRequest);
- void requestNetworkCapabilities(in NetworkRequest networkRequest);
void releaseNetworkRequest(in NetworkRequest networkRequest);
void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
new file mode 100644
index 0000000..da5cb37
--- /dev/null
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.annotation.StringDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
+ * RFC 4301.
+ */
+public final class IpSecAlgorithm implements Parcelable {
+
+ /**
+ * AES-CBC Encryption/Ciphering Algorithm.
+ *
+ * <p>Valid lengths for this key are {128, 192, 256}.
+ */
+ public static final String ALGO_CRYPT_AES_CBC = "cbc(aes)";
+
+ /**
+ * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
+ * applications and is provided for legacy compatibility with 3gpp infrastructure.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
+ */
+ public static final String ALGO_AUTH_HMAC_MD5 = "hmac(md5)";
+
+ /**
+ * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
+ * new applications and is provided for legacy compatibility with 3gpp infrastructure.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
+ */
+ public static final String ALGO_AUTH_HMAC_SHA1 = "hmac(sha1)";
+
+ /**
+ * SHA256 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 256.
+ */
+ public static final String ALGO_AUTH_HMAC_SHA256 = "hmac(sha256)";
+
+ /**
+ * SHA384 HMAC Authentication/Integrity Algorithm.
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
+ */
+ public static final String ALGO_AUTH_HMAC_SHA384 = "hmac(sha384)";
+ /**
+ * SHA512 HMAC Authentication/Integrity Algorithm
+ *
+ * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
+ */
+ public static final String ALGO_AUTH_HMAC_SHA512 = "hmac(sha512)";
+
+ /** @hide */
+ @StringDef({
+ ALGO_CRYPT_AES_CBC,
+ ALGO_AUTH_HMAC_MD5,
+ ALGO_AUTH_HMAC_SHA1,
+ ALGO_AUTH_HMAC_SHA256,
+ ALGO_AUTH_HMAC_SHA512
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AlgorithmName {}
+
+ private final String mName;
+ private final byte[] mKey;
+ private final int mTruncLenBits;
+
+ /**
+ * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
+ * algorithm
+ *
+ * @param algorithm type for IpSec.
+ * @param key non-null Key padded to a multiple of 8 bits.
+ */
+ public IpSecAlgorithm(String algorithm, byte[] key) {
+ this(algorithm, key, key.length * 8);
+ }
+
+ /**
+ * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
+ * algorithm
+ *
+ * @param algoName precise name of the algorithm to be used.
+ * @param key non-null Key padded to a multiple of 8 bits.
+ * @param truncLenBits the number of bits of output hash to use; only meaningful for
+ * Authentication.
+ */
+ public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
+ if (!isTruncationLengthValid(algoName, truncLenBits)) {
+ throw new IllegalArgumentException("Unknown algorithm or invalid length");
+ }
+ mName = algoName;
+ mKey = key.clone();
+ mTruncLenBits = Math.min(truncLenBits, key.length * 8);
+ }
+
+ /** Retrieve the algorithm name */
+ public String getName() {
+ return mName;
+ }
+
+ /** Retrieve the key for this algorithm */
+ public byte[] getKey() {
+ return mKey.clone();
+ }
+
+ /**
+ * Retrieve the truncation length, in bits, for the key in this algo. By default this will be
+ * the length in bits of the key.
+ */
+ public int getTruncationLengthBits() {
+ return mTruncLenBits;
+ }
+
+ /* Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mName);
+ out.writeByteArray(mKey);
+ out.writeInt(mTruncLenBits);
+ }
+
+ /** Parcelable Creator */
+ public static final Parcelable.Creator<IpSecAlgorithm> CREATOR =
+ new Parcelable.Creator<IpSecAlgorithm>() {
+ public IpSecAlgorithm createFromParcel(Parcel in) {
+ return new IpSecAlgorithm(in);
+ }
+
+ public IpSecAlgorithm[] newArray(int size) {
+ return new IpSecAlgorithm[size];
+ }
+ };
+
+ private IpSecAlgorithm(Parcel in) {
+ mName = in.readString();
+ mKey = in.createByteArray();
+ mTruncLenBits = in.readInt();
+ }
+
+ private static boolean isTruncationLengthValid(String algo, int truncLenBits) {
+ switch (algo) {
+ case ALGO_AUTH_HMAC_MD5:
+ return (truncLenBits >= 96 && truncLenBits <= 128);
+ case ALGO_AUTH_HMAC_SHA1:
+ return (truncLenBits >= 96 && truncLenBits <= 160);
+ case ALGO_AUTH_HMAC_SHA256:
+ return (truncLenBits >= 96 && truncLenBits <= 256);
+ case ALGO_AUTH_HMAC_SHA384:
+ return (truncLenBits >= 192 && truncLenBits <= 384);
+ case ALGO_AUTH_HMAC_SHA512:
+ return (truncLenBits >= 256 && truncLenBits <= 512);
+ default:
+ return false;
+ }
+ }
+};
diff --git a/core/java/android/net/IpSecConfig.aidl b/core/java/android/net/IpSecConfig.aidl
new file mode 100644
index 0000000..eaefca7
--- /dev/null
+++ b/core/java/android/net/IpSecConfig.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/** @hide */
+parcelable IpSecConfig;
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
new file mode 100644
index 0000000..b58bf42
--- /dev/null
+++ b/core/java/android/net/IpSecConfig.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/** @hide */
+public final class IpSecConfig implements Parcelable {
+ private static final String TAG = IpSecConfig.class.getSimpleName();
+
+ //MODE_TRANSPORT or MODE_TUNNEL
+ int mode;
+
+ // For tunnel mode
+ InetAddress localAddress;
+
+ InetAddress remoteAddress;
+
+ // Limit selection by network interface
+ Network network;
+
+ public static class Flow {
+ // Minimum requirements for identifying a transform
+ // SPI identifying the IPsec flow in packet processing
+ // and a remote IP address
+ int spi;
+
+ // Encryption Algorithm
+ IpSecAlgorithm encryptionAlgo;
+
+ // Authentication Algorithm
+ IpSecAlgorithm authenticationAlgo;
+ }
+
+ Flow[] flow = new Flow[2];
+
+ // For tunnel mode IPv4 UDP Encapsulation
+ // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
+ int encapType;
+ int encapLocalPort;
+ int encapRemotePort;
+
+ // An optional protocol to match with the selector
+ int selectorProto;
+
+ // A bitmask of FEATURE_* indicating which of the fields
+ // of this class are valid.
+ long features;
+
+ // An interval, in seconds between the NattKeepalive packets
+ int nattKeepaliveInterval;
+
+ public InetAddress getLocalIp() {
+ return localAddress;
+ }
+
+ public int getSpi(int direction) {
+ return flow[direction].spi;
+ }
+
+ public InetAddress getRemoteIp() {
+ return remoteAddress;
+ }
+
+ public IpSecAlgorithm getEncryptionAlgo(int direction) {
+ return flow[direction].encryptionAlgo;
+ }
+
+ public IpSecAlgorithm getAuthenticationAlgo(int direction) {
+ return flow[direction].authenticationAlgo;
+ }
+
+ Network getNetwork() {
+ return network;
+ }
+
+ public int getEncapType() {
+ return encapType;
+ }
+
+ public int getEncapLocalPort() {
+ return encapLocalPort;
+ }
+
+ public int getEncapRemotePort() {
+ return encapRemotePort;
+ }
+
+ public int getSelectorProto() {
+ return selectorProto;
+ }
+
+ int getNattKeepaliveInterval() {
+ return nattKeepaliveInterval;
+ }
+
+ public boolean hasProperty(int featureBits) {
+ return (features & featureBits) == featureBits;
+ }
+
+ // Parcelable Methods
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(features);
+ // TODO: Use a byte array or other better method for storing IPs that can also include scope
+ out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
+ // TODO: Use a byte array or other better method for storing IPs that can also include scope
+ out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
+ out.writeParcelable(network, flags);
+ out.writeInt(flow[IpSecTransform.DIRECTION_IN].spi);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryptionAlgo, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authenticationAlgo, flags);
+ out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spi);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo, flags);
+ out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo, flags);
+ out.writeInt(encapType);
+ out.writeInt(encapLocalPort);
+ out.writeInt(encapRemotePort);
+ out.writeInt(selectorProto);
+ }
+
+ // Package Private: Used by the IpSecTransform.Builder;
+ // there should be no public constructor for this object
+ IpSecConfig() {
+ flow[IpSecTransform.DIRECTION_IN].spi = 0;
+ flow[IpSecTransform.DIRECTION_OUT].spi = 0;
+ nattKeepaliveInterval = 0; //FIXME constant
+ }
+
+ private static InetAddress readInetAddressFromParcel(Parcel in) {
+ String addrString = in.readString();
+ if (addrString == null) {
+ return null;
+ }
+ try {
+ return InetAddress.getByName(addrString);
+ } catch (UnknownHostException e) {
+ Log.wtf(TAG, "Invalid IpAddress " + addrString);
+ return null;
+ }
+ }
+
+ private IpSecConfig(Parcel in) {
+ features = in.readLong();
+ localAddress = readInetAddressFromParcel(in);
+ remoteAddress = readInetAddressFromParcel(in);
+ network = (Network) in.readParcelable(Network.class.getClassLoader());
+ flow[IpSecTransform.DIRECTION_IN].spi = in.readInt();
+ flow[IpSecTransform.DIRECTION_IN].encryptionAlgo =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ flow[IpSecTransform.DIRECTION_IN].authenticationAlgo =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ flow[IpSecTransform.DIRECTION_OUT].spi = in.readInt();
+ flow[IpSecTransform.DIRECTION_OUT].encryptionAlgo =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ flow[IpSecTransform.DIRECTION_OUT].authenticationAlgo =
+ (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+ encapType = in.readInt();
+ encapLocalPort = in.readInt();
+ encapRemotePort = in.readInt();
+ selectorProto = in.readInt();
+ }
+
+ public static final Parcelable.Creator<IpSecConfig> CREATOR =
+ new Parcelable.Creator<IpSecConfig>() {
+ public IpSecConfig createFromParcel(Parcel in) {
+ return new IpSecConfig(in);
+ }
+
+ public IpSecConfig[] newArray(int size) {
+ return new IpSecConfig[size];
+ }
+ };
+}
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
new file mode 100644
index 0000000..2c544e9
--- /dev/null
+++ b/core/java/android/net/IpSecManager.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.INetworkManagementService;
+import android.os.ParcelFileDescriptor;
+import android.util.AndroidException;
+import dalvik.system.CloseGuard;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+
+/**
+ * This class contains methods for managing IPsec sessions, which will perform kernel-space
+ * encryption and decryption of socket or Network traffic.
+ *
+ * <p>An IpSecManager may be obtained by calling {@link
+ * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
+ * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ */
+public final class IpSecManager {
+ private static final String TAG = "IpSecManager";
+
+ /**
+ * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
+ * request. If encountered, selection of a new SPI is required before a transform may be
+ * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
+ * or reserved using reserveSecurityParameterIndex.
+ */
+ public static final class SpiUnavailableException extends AndroidException {
+ private final int mSpi;
+
+ /**
+ * Construct an exception indicating that a transform with the given SPI is already in use
+ * or otherwise unavailable.
+ *
+ * @param msg Description indicating the colliding SPI
+ * @param spi the SPI that could not be used due to a collision
+ */
+ SpiUnavailableException(String msg, int spi) {
+ super(msg + "(spi: " + spi + ")");
+ mSpi = spi;
+ }
+
+ /** Retrieve the SPI that caused a collision */
+ public int getSpi() {
+ return mSpi;
+ }
+ }
+
+ /**
+ * Indicates that the requested system resource for IPsec, such as a socket or other system
+ * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
+ * type requested.
+ */
+ public static final class ResourceUnavailableException extends AndroidException {
+
+ ResourceUnavailableException(String msg) {
+ super(msg);
+ }
+ }
+
+ private final Context mContext;
+ private final INetworkManagementService mService;
+
+ public static final class SecurityParameterIndex implements AutoCloseable {
+ private final Context mContext;
+ private final InetAddress mDestinationAddress;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private int mSpi;
+
+ /** Return the underlying SPI held by this object */
+ public int getSpi() {
+ return mSpi;
+ }
+
+ private SecurityParameterIndex(Context context, InetAddress destinationAddress, int spi)
+ throws ResourceUnavailableException, SpiUnavailableException {
+ mContext = context;
+ mDestinationAddress = destinationAddress;
+ mSpi = spi;
+ mCloseGuard.open("open");
+ }
+
+ /**
+ * Release an SPI that was previously reserved.
+ *
+ * <p>Release an SPI for use by other users in the system. This will fail if the SPI is
+ * currently in use by an IpSecTransform.
+ *
+ * @param destinationAddress SPIs must be unique for each combination of SPI and destination
+ * address. Thus, the destinationAddress to which the SPI will communicate must be
+ * supplied.
+ * @param spi the previously reserved SPI to be freed.
+ */
+ @Override
+ public void close() {
+ mSpi = INVALID_SECURITY_PARAMETER_INDEX; // TODO: Invalid SPI
+ mCloseGuard.close();
+ }
+
+ @Override
+ protected void finalize() {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ }
+ }
+
+ /**
+ * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+ *
+ * <p>No IPsec packet may contain an SPI of 0.
+ */
+ public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
+
+ /**
+ * Reserve an SPI for traffic bound towards the specified destination address.
+ *
+ * <p>If successful, this SPI is guaranteed available until released by a call to {@link
+ * SecurityParameterIndex#close()}.
+ *
+ * @param destinationAddress SPIs must be unique for each combination of SPI and destination
+ * address.
+ * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
+ * @return the reserved SecurityParameterIndex
+ * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
+ * for this user
+ * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved
+ */
+ public SecurityParameterIndex reserveSecurityParameterIndex(
+ InetAddress destinationAddress, int requestedSpi)
+ throws SpiUnavailableException, ResourceUnavailableException {
+ return new SecurityParameterIndex(mContext, destinationAddress, requestedSpi);
+ }
+
+ /**
+ * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
+ * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
+ * transform. For security reasons, attempts to send traffic to any IP address other than the
+ * address associated with that transform will throw an IOException. In addition, if the
+ * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
+ * send() or receive() until the transform is removed from the socket by calling {@link
+ * #removeTransportModeTransform(Socket, IpSecTransform)};
+ *
+ * @param socket a stream socket
+ * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ */
+ public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
+ throws IOException {
+ applyTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+ }
+
+ /**
+ * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
+ * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
+ * transform. For security reasons, attempts to send traffic to any IP address other than the
+ * address associated with that transform will throw an IOException. In addition, if the
+ * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
+ * send() or receive() until the transform is removed from the socket by calling {@link
+ * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
+ *
+ * @param socket a datagram socket
+ * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+ */
+ public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
+ throws IOException {
+ applyTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
+ }
+
+ /* Call down to activate a transform */
+ private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+
+ /**
+ * Apply an active Tunnel Mode IPsec Transform to a network, which will tunnel all traffic to
+ * and from that network's interface with IPsec (applies an outer IP header and IPsec Header to
+ * all traffic, and expects an additional IP header and IPsec Header on all inbound traffic).
+ * Applications should probably not use this API directly. Instead, they should use {@link
+ * VpnService} to provide VPN capability in a more generic fashion.
+ *
+ * @param net a {@link Network} that will be tunneled via IP Sec.
+ * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
+ * @hide
+ */
+ @SystemApi
+ public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
+
+ /**
+ * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
+ * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
+ * communication in the clear in the event socket reuse is desired. This operation will succeed
+ * regardless of the underlying state of a transform. If a transform is removed, communication
+ * on all sockets to which that transform was applied will fail until this method is called.
+ *
+ * @param socket a socket that previously had a transform applied to it.
+ * @param transform the IPsec Transform that was previously applied to the given socket
+ */
+ public void removeTransportModeTransform(Socket socket, IpSecTransform transform) {
+ removeTransportModeTransform(ParcelFileDescriptor.fromSocket(socket), transform);
+ }
+
+ /**
+ * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
+ * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
+ * communication in the clear in the event socket reuse is desired. This operation will succeed
+ * regardless of the underlying state of a transform. If a transform is removed, communication
+ * on all sockets to which that transform was applied will fail until this method is called.
+ *
+ * @param socket a socket that previously had a transform applied to it.
+ * @param transform the IPsec Transform that was previously applied to the given socket
+ */
+ public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) {
+ removeTransportModeTransform(ParcelFileDescriptor.fromDatagramSocket(socket), transform);
+ }
+
+ /* Call down to activate a transform */
+ private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {}
+
+ /**
+ * Remove a Tunnel Mode IPsec Transform from a {@link Network}. This must be used as part of
+ * cleanup if a tunneled Network experiences a change in default route. The Network will drop
+ * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
+ * lost, all traffic will drop.
+ *
+ * @param net a network that currently has transform applied to it.
+ * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
+ * network
+ * @hide
+ */
+ @SystemApi
+ public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
+
+ /**
+ * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
+ * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
+ *
+ * <p>The socket provided by this class cannot be re-bound or closed via the inner
+ * FileDescriptor. Instead, disposing of this socket requires a call to close().
+ */
+ public static final class UdpEncapsulationSocket implements AutoCloseable {
+ private final FileDescriptor mFd;
+ private final Context mContext;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private UdpEncapsulationSocket(Context context, int port)
+ throws ResourceUnavailableException {
+ mContext = context;
+ mCloseGuard.open("constructor");
+ // TODO: go down to the kernel and get a socket on the specified
+ mFd = new FileDescriptor();
+ }
+
+ private UdpEncapsulationSocket(Context context) throws ResourceUnavailableException {
+ mContext = context;
+ mCloseGuard.open("constructor");
+ // TODO: go get a random socket on a random port
+ mFd = new FileDescriptor();
+ }
+
+ /** Access the inner UDP Encapsulation Socket */
+ public FileDescriptor getSocket() {
+ return mFd;
+ }
+
+ /** Retrieve the port number of the inner encapsulation socket */
+ public int getPort() {
+ return 0; // TODO get the port number from the Socket;
+ }
+
+ @Override
+ /**
+ * Release the resources that have been reserved for this Socket.
+ *
+ * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
+ * system. This must be done as part of cleanup following use of a socket. Failure to do so
+ * will cause the socket to count against a total allocation limit for IpSec and eventually
+ * fail due to resource limits.
+ *
+ * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
+ */
+ public void close() {
+ // TODO: Go close the socket
+ mCloseGuard.close();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ close();
+ }
+ };
+
+ /**
+ * Open a socket that is bound to a free UDP port on the system.
+ *
+ * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
+ * the caller. This provides safe access to a socket on a port that can later be used as a UDP
+ * Encapsulation port.
+ *
+ * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
+ * socket port. Explicitly opening this port is only necessary if communication is desired on
+ * that port.
+ *
+ * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
+ * method will bind to the specified port or fail. To retrieve the port number, call {@link
+ * android.system.Os#getsockname(FileDescriptor)}.
+ * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
+ * of the object.
+ */
+ // Returning a socket in this fashion that has been created and bound by the system
+ // is the only safe way to ensure that a socket is both accessible to the user and
+ // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+ // the port, which could potentially impact the traffic of the next user who binds to that
+ // socket.
+ public UdpEncapsulationSocket openUdpEncapsulationSocket(int port)
+ throws IOException, ResourceUnavailableException {
+ // Temporary code
+ return new UdpEncapsulationSocket(mContext, port);
+ }
+
+ /**
+ * Open a socket that is bound to a port selected by the system.
+ *
+ * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
+ * the caller. This provides safe access to a socket on a port that can later be used as a UDP
+ * Encapsulation port.
+ *
+ * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
+ * socket port. Explicitly opening this port is only necessary if communication is desired on
+ * that port.
+ *
+ * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
+ */
+ // Returning a socket in this fashion that has been created and bound by the system
+ // is the only safe way to ensure that a socket is both accessible to the user and
+ // safely usable for Encapsulation without allowing a user to possibly unbind from/close
+ // the port, which could potentially impact the traffic of the next user who binds to that
+ // socket.
+ public UdpEncapsulationSocket openUdpEncapsulationSocket()
+ throws IOException, ResourceUnavailableException {
+ // Temporary code
+ return new UdpEncapsulationSocket(mContext);
+ }
+
+ /**
+ * Retrieve an instance of an IpSecManager within you application context
+ *
+ * @param context the application context for this manager
+ * @hide
+ */
+ public IpSecManager(Context context, INetworkManagementService service) {
+ mContext = checkNotNull(context, "missing context");
+ mService = checkNotNull(service, "missing service");
+ }
+}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
new file mode 100644
index 0000000..d6dd28be
--- /dev/null
+++ b/core/java/android/net/IpSecTransform.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.system.ErrnoException;
+import android.util.Log;
+import dalvik.system.CloseGuard;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetAddress;
+
+/**
+ * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
+ *
+ * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
+ * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
+ * transform may be disabled automatically, with likely undesirable results.
+ *
+ * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
+ * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ */
+public final class IpSecTransform implements AutoCloseable {
+ private static final String TAG = "IpSecTransform";
+
+ /**
+ * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
+ * to traffic towards the host.
+ */
+ public static final int DIRECTION_IN = 0;
+
+ /**
+ * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
+ * to traffic from the host.
+ */
+ public static final int DIRECTION_OUT = 1;
+
+ /** @hide */
+ @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransformDirection {}
+
+ /** @hide */
+ private static final int MODE_TUNNEL = 0;
+
+ /** @hide */
+ private static final int MODE_TRANSPORT = 1;
+
+ /** @hide */
+ public static final int ENCAP_NONE = 0;
+
+ /**
+ * IpSec traffic will be encapsulated within UDP as per <a
+ * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP = 1;
+
+ /**
+ * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
+ * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
+ *
+ * @hide
+ */
+ public static final int ENCAP_ESPINUDP_NONIKE = 2;
+
+ /** @hide */
+ @IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NONIKE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncapType {}
+
+ /**
+ * Sentinel for an invalid transform (means that this transform is inactive).
+ *
+ * @hide
+ */
+ public static final int INVALID_TRANSFORM_ID = -1;
+
+ private IpSecTransform(Context context, IpSecConfig config) {
+ mContext = context;
+ mConfig = config;
+ mTransformId = INVALID_TRANSFORM_ID;
+ }
+
+ private IpSecTransform activate()
+ throws IOException, IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException {
+ int transformId;
+ synchronized (this) {
+ //try {
+ transformId = INVALID_TRANSFORM_ID;
+ //} catch (RemoteException e) {
+ // throw e.rethrowFromSystemServer();
+ //}
+
+ if (transformId < 0) {
+ throw new ErrnoException("addTransform", -transformId).rethrowAsIOException();
+ }
+
+ startKeepalive(mContext); // Will silently fail if not required
+ mTransformId = transformId;
+ Log.d(TAG, "Added Transform with Id " + transformId);
+ }
+ mCloseGuard.open("build");
+
+ return this;
+ }
+
+ /**
+ * Deactivate an IpSecTransform and free all resources for that transform that are managed by
+ * the system for this Transform.
+ *
+ * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
+ * refusing to send or receive data. This method will silently succeed if the specified
+ * transform has already been removed; thus, it is always safe to attempt cleanup when a
+ * transform is no longer needed.
+ */
+ public void close() {
+ Log.d(TAG, "Removing Transform with Id " + mTransformId);
+
+ // Always safe to attempt cleanup
+ if (mTransformId == INVALID_TRANSFORM_ID) {
+ return;
+ }
+ //try {
+ stopKeepalive();
+ //} catch (RemoteException e) {
+ // transform.setTransformId(transformId);
+ // throw e.rethrowFromSystemServer();
+ //} finally {
+ mTransformId = INVALID_TRANSFORM_ID;
+ //}
+ mCloseGuard.close();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /* Package */
+ IpSecConfig getConfig() {
+ return mConfig;
+ }
+
+ private final IpSecConfig mConfig;
+ private int mTransformId;
+ private final Context mContext;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private ConnectivityManager.PacketKeepalive mKeepalive;
+ private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+ private Object mKeepaliveSyncLock = new Object();
+ private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
+ new ConnectivityManager.PacketKeepaliveCallback() {
+
+ @Override
+ public void onStarted() {
+ synchronized (mKeepaliveSyncLock) {
+ mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS;
+ mKeepaliveSyncLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onStopped() {
+ synchronized (mKeepaliveSyncLock) {
+ mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+ mKeepaliveSyncLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onError(int error) {
+ synchronized (mKeepaliveSyncLock) {
+ mKeepaliveStatus = error;
+ mKeepaliveSyncLock.notifyAll();
+ }
+ }
+ };
+
+ /* Package */
+ void startKeepalive(Context c) {
+ if (mConfig.getNattKeepaliveInterval() == 0) {
+ return;
+ }
+
+ ConnectivityManager cm =
+ (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ if (mKeepalive != null) {
+ Log.e(TAG, "Keepalive already started for this IpSecTransform.");
+ return;
+ }
+
+ synchronized (mKeepaliveSyncLock) {
+ mKeepalive =
+ cm.startNattKeepalive(
+ mConfig.getNetwork(),
+ mConfig.getNattKeepaliveInterval(),
+ mKeepaliveCallback,
+ mConfig.getLocalIp(),
+ mConfig.getEncapLocalPort(),
+ mConfig.getRemoteIp());
+ try {
+ mKeepaliveSyncLock.wait(2000);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
+ throw new UnsupportedOperationException("Packet Keepalive cannot be started");
+ }
+ }
+
+ /* Package */
+ void stopKeepalive() {
+ if (mKeepalive == null) {
+ return;
+ }
+ mKeepalive.stop();
+ synchronized (mKeepaliveSyncLock) {
+ if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
+ try {
+ mKeepaliveSyncLock.wait(2000);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ /* Package */
+ void setTransformId(int transformId) {
+ mTransformId = transformId;
+ }
+
+ /* Package */
+ int getTransformId() {
+ return mTransformId;
+ }
+
+ /**
+ * Builder object to facilitate the creation of IpSecTransform objects.
+ *
+ * <p>Apply additional properties to the transform and then call a build() method to return an
+ * IpSecTransform object.
+ *
+ * @see Builder#buildTransportModeTransform(InetAddress)
+ */
+ public static class Builder {
+ private Context mContext;
+ private IpSecConfig mConfig;
+
+ /**
+ * Add an encryption algorithm to the transform for the given direction.
+ *
+ * <p>If encryption is set for a given direction without also providing an SPI for that
+ * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ *
+ * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
+ */
+ public IpSecTransform.Builder setEncryption(
+ @TransformDirection int direction, IpSecAlgorithm algo) {
+ mConfig.flow[direction].encryptionAlgo = algo;
+ return this;
+ }
+
+ /**
+ * Add an authentication/integrity algorithm to the transform.
+ *
+ * <p>If authentication is set for a given direction without also providing an SPI for that
+ * direction, creation of an IpSecTransform will fail upon calling a build() method.
+ *
+ * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
+ */
+ public IpSecTransform.Builder setAuthentication(
+ @TransformDirection int direction, IpSecAlgorithm algo) {
+ mConfig.flow[direction].authenticationAlgo = algo;
+ return this;
+ }
+
+ /**
+ * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
+ * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
+ * given destination address.
+ *
+ * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
+ * possible. Random number generation is a reasonable approach to selecting an SPI. For
+ * outbound SPIs, they must be reserved by calling {@link
+ * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
+ * fail to build.
+ *
+ * <p>Unless an SPI is set for a given direction, traffic in that direction will be
+ * sent/received without any IPsec applied.
+ *
+ * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param spi a unique 32-bit integer to identify transformed traffic
+ */
+ public IpSecTransform.Builder setSpi(@TransformDirection int direction, int spi) {
+ mConfig.flow[direction].spi = spi;
+ return this;
+ }
+
+ /**
+ * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
+ * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
+ * given destination address.
+ *
+ * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
+ * possible. Random number generation is a reasonable approach to selecting an SPI. For
+ * outbound SPIs, they must be reserved by calling {@link
+ * IpSecManager#reserveSecurityParameterIndex(InetAddress, int)}. Otherwise, Transforms will
+ * fail to activate.
+ *
+ * <p>Unless an SPI is set for a given direction, traffic in that direction will be
+ * sent/received without any IPsec applied.
+ *
+ * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+ * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
+ * traffic
+ */
+ public IpSecTransform.Builder setSpi(
+ @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
+ mConfig.flow[direction].spi = spi.getSpi();
+ return this;
+ }
+
+ /**
+ * Specify the network on which this transform will emit its traffic; (otherwise it will
+ * emit on the default network).
+ *
+ * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
+ * tunnel mode.
+ *
+ * @hide
+ */
+ @SystemApi
+ public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
+ mConfig.network = net;
+ return this;
+ }
+
+ /**
+ * Add UDP encapsulation to an IPv4 transform
+ *
+ * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
+ * details on how UDP should be applied to IPsec.
+ *
+ * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
+ * receiving encapsulating traffic.
+ * @param remotePort the UDP port number of the remote that will send and receive
+ * encapsulated traffic. In the case of IKE, this is likely port 4500.
+ */
+ public IpSecTransform.Builder setIpv4Encapsulation(
+ IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
+ // TODO: check encap type is valid.
+ mConfig.encapType = ENCAP_ESPINUDP;
+ mConfig.encapLocalPort = localSocket.getPort(); // TODO: plug in the encap socket
+ mConfig.encapRemotePort = remotePort;
+ return this;
+ }
+
+ // TODO: Decrease the minimum keepalive to maybe 10?
+ // TODO: Probably a better exception to throw for NATTKeepalive failure
+ // TODO: Specify the needed NATT keepalive permission.
+ /**
+ * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
+ * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
+ * be activated, then the transform will fail to activate and throw an IOException.
+ *
+ * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
+ * than 20s and no more than 3600s.
+ * @hide
+ */
+ @SystemApi
+ public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
+ mConfig.nattKeepaliveInterval = intervalSeconds;
+ return this;
+ }
+
+ /**
+ * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
+ * Some parameters have interdependencies that are checked at build time. If a well-formed
+ * transform cannot be created from the supplied parameters, this method will throw an
+ * Exception.
+ *
+ * <p>Upon a successful return from this call, the provided IpSecTransform will be active
+ * and may be applied to sockets. If too many IpSecTransform objects are active for a given
+ * user this operation will fail and throw ResourceUnavailableException. To avoid these
+ * exceptions, unused Transform objects must be cleaned up by calling {@link
+ * IpSecTransform#close()} when they are no longer needed.
+ *
+ * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
+ * socket will cause the transform to be applied.
+ * <p>Note that an active transform will not impact any network traffic until it has
+ * been applied to one or more Sockets. Calling this method is a necessary precondition
+ * for applying it to a socket, but is not sufficient to actually apply IPsec.
+ * @throws IllegalArgumentException indicating that a particular combination of transform
+ * properties is invalid.
+ * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
+ * may be allocated
+ * @throws SpiUnavailableException if the SPI collides with an existing transform
+ * (unlikely).
+ * @throws ResourceUnavailableException if the current user currently has exceeded the
+ * number of allowed active transforms.
+ */
+ public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
+ throws IpSecManager.ResourceUnavailableException,
+ IpSecManager.SpiUnavailableException, IOException {
+ //FIXME: argument validation here
+ //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
+ mConfig.mode = MODE_TRANSPORT;
+ mConfig.remoteAddress = remoteAddress;
+ return new IpSecTransform(mContext, mConfig).activate();
+ }
+
+ /**
+ * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
+ * parameters have interdependencies that are checked at build time.
+ *
+ * @param localAddress the {@link InetAddress} that provides the local endpoint for this
+ * IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
+ * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
+ * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this
+ * IPsec tunnel.
+ * @throws IllegalArgumentException indicating that a particular combination of transform
+ * properties is invalid.
+ * @hide
+ */
+ @SystemApi
+ public IpSecTransform buildTunnelModeTransform(
+ InetAddress localAddress, InetAddress remoteAddress) {
+ //FIXME: argument validation here
+ //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
+ mConfig.localAddress = localAddress;
+ mConfig.remoteAddress = remoteAddress;
+ mConfig.mode = MODE_TUNNEL;
+ return new IpSecTransform(mContext, mConfig);
+ }
+
+ /**
+ * Create a new IpSecTransform.Builder to construct an IpSecTransform
+ *
+ * @param context current Context
+ */
+ public Builder(Context context) {
+ mContext = context;
+ mConfig = new IpSecConfig();
+ }
+ }
+}
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index bd32314..2c9ce3f 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -839,7 +839,7 @@
if (cf && !inChunk) {
// first chunk
- if (typeLength == 0) {
+ if (typeLength == 0 && tnf != NdefRecord.TNF_UNKNOWN) {
throw new FormatException("expected non-zero type length in first chunk");
}
chunks.clear();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index f6edee0..0d269af 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -245,7 +245,7 @@
* Magic version number for a current development build, which has
* not yet turned into an official release.
*/
- public static final int CUR_DEVELOPMENT = 10000;
+ public static final int CUR_DEVELOPMENT = VMRuntime.SDK_VERSION_CUR_DEVELOPMENT;
/**
* October 2008: The original, first, version of Android. Yay!
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index ff69cf6..fa7aebf 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -477,7 +477,7 @@
}
/**
- * Detect unbuffered input/output operations.
+ * Disable detection of unbuffered input/output operations.
*/
public Builder permitUnbufferedIo() {
return disable(DETECT_UNBUFFERED_IO);
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 6a751e8..2bf3c2c 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -35,7 +35,6 @@
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
- public static final int PROP_NAME_MAX = 31;
public static final int PROP_VALUE_MAX = 91;
private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>();
@@ -82,12 +81,8 @@
/**
* Get the value for the given key.
* @return an empty string if the key isn't found
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static String get(String key) {
- if (key.length() > PROP_NAME_MAX) {
- throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key);
}
@@ -95,12 +90,8 @@
/**
* Get the value for the given key.
* @return if the key isn't found, return def if it isn't null, or an empty string otherwise
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static String get(String key, String def) {
- if (key.length() > PROP_NAME_MAX) {
- throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get(key, def);
}
@@ -111,12 +102,8 @@
* @param def a default value to return
* @return the key parsed as an integer, or def if the key isn't found or
* cannot be parsed
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static int getInt(String key, int def) {
- if (key.length() > PROP_NAME_MAX) {
- throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_int(key, def);
}
@@ -127,12 +114,8 @@
* @param def a default value to return
* @return the key parsed as a long, or def if the key isn't found or
* cannot be parsed
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static long getLong(String key, long def) {
- if (key.length() > PROP_NAME_MAX) {
- throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_long(key, def);
}
@@ -148,25 +131,17 @@
* @param def a default value to return
* @return the key parsed as a boolean, or def if the key isn't found or is
* not able to be parsed as a boolean.
- * @throws IllegalArgumentException if the key exceeds 32 characters
*/
public static boolean getBoolean(String key, boolean def) {
- if (key.length() > PROP_NAME_MAX) {
- throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
- }
if (TRACK_KEY_ACCESS) onKeyAccess(key);
return native_get_boolean(key, def);
}
/**
* Set the value for the given key.
- * @throws IllegalArgumentException if the key exceeds 32 characters
* @throws IllegalArgumentException if the value exceeds 92 characters
*/
public static void set(String key, String val) {
- if (key.length() > PROP_NAME_MAX) {
- throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
- }
if (val != null && val.length() > PROP_VALUE_MAX) {
throw new IllegalArgumentException("val.length > " +
PROP_VALUE_MAX);
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index bf03cce..8549cff 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -26,7 +26,18 @@
/**
* UpdateEngine handles calls to the update engine which takes care of A/B OTA
* updates. It wraps up the update engine Binder APIs and exposes them as
- * SystemApis, which will be called by system apps like GmsCore.
+ * SystemApis, which will be called by the system app responsible for OTAs.
+ * On a Google device, this will be GmsCore.
+ *
+ * The minimal flow is:
+ * <ol>
+ * <li>Create a new UpdateEngine instance.
+ * <li>Call {@link #bind}, optionally providing callbacks.
+ * <li>Call {@link #applyPayload}.
+ * </ol>
+ *
+ * In addition, methods are provided to {@link #cancel} or
+ * {@link #suspend}/{@link #resume} application of an update.
*
* The APIs defined in this class and UpdateEngineCallback class must be in
* sync with the ones in
@@ -80,12 +91,20 @@
private IUpdateEngine mUpdateEngine;
+ /**
+ * Creates a new instance.
+ */
@SystemApi
public UpdateEngine() {
mUpdateEngine = IUpdateEngine.Stub.asInterface(
ServiceManager.getService(UPDATE_ENGINE_SERVICE));
}
+ /**
+ * Prepares this instance for use. The callback will be notified on any
+ * status change, and when the update completes. A handler can be supplied
+ * to control which thread runs the callback, or null.
+ */
@SystemApi
public boolean bind(final UpdateEngineCallback callback, final Handler handler) {
IUpdateEngineCallback updateEngineCallback = new IUpdateEngineCallback.Stub() {
@@ -125,11 +144,42 @@
}
}
+ /**
+ * Equivalent to {@code bind(callback, null)}.
+ */
@SystemApi
public boolean bind(final UpdateEngineCallback callback) {
return bind(callback, null);
}
+ /**
+ * Applies the payload found at the given {@code url}. For non-streaming
+ * updates, the URL can be a local file using the {@code file://} scheme.
+ *
+ * <p>The {@code offset} and {@code size} parameters specify the location
+ * of the payload within the file represented by the URL. This is useful
+ * if the downloadable package at the URL contains more than just the
+ * update_engine payload (such as extra metadata). This is true for
+ * Google's OTA system, where the URL points to a zip file in which the
+ * payload is stored uncompressed within the zip file alongside other
+ * data.
+ *
+ * <p>The {@code headerKeyValuePairs} parameter is used to pass metadata
+ * to update_engine. In Google's implementation, this is stored as
+ * {@code payload_properties.txt} in the zip file. It's generated by the
+ * script {@code system/update_engine/scripts/brillo_update_payload}.
+ * The complete list of keys and their documentation is in
+ * {@code system/update_engine/common/constants.cc}, but an example
+ * might be:
+ * <pre>
+ * String[] pairs = {
+ * "FILE_HASH=lURPCIkIAjtMOyB/EjQcl8zDzqtD6Ta3tJef6G/+z2k=",
+ * "FILE_SIZE=871903868",
+ * "METADATA_HASH=tBvj43QOB0Jn++JojcpVdbRLz0qdAuL+uTkSy7hokaw=",
+ * "METADATA_SIZE=70604"
+ * };
+ * </pre>
+ */
@SystemApi
public void applyPayload(String url, long offset, long size, String[] headerKeyValuePairs) {
try {
@@ -139,6 +189,15 @@
}
}
+ /**
+ * Permanently cancels an in-progress update.
+ *
+ * <p>See {@link #resetStatus} to undo a finshed update (only available
+ * before the updated system has been rebooted).
+ *
+ * <p>See {@link #suspend} for a way to temporarily stop an in-progress
+ * update with the ability to resume it later.
+ */
@SystemApi
public void cancel() {
try {
@@ -148,6 +207,10 @@
}
}
+ /**
+ * Suspends an in-progress update. This can be undone by calling
+ * {@link #resume}.
+ */
@SystemApi
public void suspend() {
try {
@@ -157,6 +220,9 @@
}
}
+ /**
+ * Resumes a suspended update.
+ */
@SystemApi
public void resume() {
try {
@@ -166,6 +232,15 @@
}
}
+ /**
+ * Resets the bootable flag on the non-current partition and all internal
+ * update_engine state. This can be used after an unwanted payload has been
+ * successfully applied and the device has not yet been rebooted to signal
+ * that we no longer want to boot into that updated system. After this call
+ * completes, update_engine will no longer report
+ * {@code UPDATED_NEED_REBOOT}, so your callback can remove any outstanding
+ * notification that rebooting into the new system is possible.
+ */
@SystemApi
public void resetStatus() {
try {
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index b3b856f..afff60a 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -19,7 +19,8 @@
import android.annotation.SystemApi;
/**
- * Callback function for UpdateEngine.
+ * Callback function for UpdateEngine. Used to keep the caller up to date
+ * with progress, so the UI (if any) can be updated.
*
* The APIs defined in this class and UpdateEngine class must be in sync with
* the ones in
@@ -31,9 +32,19 @@
@SystemApi
public abstract class UpdateEngineCallback {
+ /**
+ * Invoked when anything changes. The value of {@code status} will
+ * be one of the values from {@link UpdateEngine.UpdateStatusConstants},
+ * and {@code percent} will be valid [TODO: in which cases?].
+ */
@SystemApi
public abstract void onStatusUpdate(int status, float percent);
+ /**
+ * Invoked when the payload has been applied, whether successfully or
+ * unsuccessfully. The value of {@code errorCode} will be one of the
+ * values from {@link UpdateEngine.ErrorCodeConstants}.
+ */
@SystemApi
public abstract void onPayloadApplicationComplete(int errorCode);
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index a9ca12b..6986732 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -218,6 +218,8 @@
LOCAL_STATIC_LIBRARIES := \
libseccomp_policy \
+ libselinux \
+ libcrypto \
LOCAL_SHARED_LIBRARIES := \
libmemtrack \
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index c1d4251..8791e27 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -344,7 +344,7 @@
<< serviceName;
::android::vintf::Transport transport =
- ::android::hardware::getTransport(ifaceName);
+ ::android::hardware::getTransport(ifaceName, serviceName);
if ( transport != ::android::vintf::Transport::EMPTY
&& transport != ::android::vintf::Transport::HWBINDER) {
LOG(ERROR) << "service " << ifaceName << " declares transport method "
diff --git a/core/jni/android_os_seccomp.cpp b/core/jni/android_os_seccomp.cpp
index dd5622d..4502371 100644
--- a/core/jni/android_os_seccomp.cpp
+++ b/core/jni/android_os_seccomp.cpp
@@ -17,9 +17,16 @@
#include "core_jni_helpers.h"
#include "JniConstants.h"
#include "utils/Log.h"
+#include <selinux/selinux.h>
+
#include "seccomp_policy.h"
static void Seccomp_setPolicy(JNIEnv* /*env*/) {
+ if (security_getenforce() == 0) {
+ ALOGI("seccomp disabled by setenforce 0");
+ return;
+ }
+
if (!set_seccomp_filter()) {
ALOGE("Failed to set seccomp policy - killing");
exit(1);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 428159a..afd60f1 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -2161,9 +2161,9 @@
(void*) android_content_AssetManager_readAsset },
{ "seekAsset", "(JJI)J",
(void*) android_content_AssetManager_seekAsset },
- { "getAssetLength", "!(J)J",
+ { "getAssetLength", "(J)J",
(void*) android_content_AssetManager_getAssetLength },
- { "getAssetRemainingLength", "!(J)J",
+ { "getAssetRemainingLength", "(J)J",
(void*) android_content_AssetManager_getAssetRemainingLength },
{ "addAssetPathNative", "(Ljava/lang/String;Z)I",
(void*) android_content_AssetManager_addAssetPath },
@@ -2179,25 +2179,25 @@
(void*) android_content_AssetManager_getNonSystemLocales },
{ "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
(void*) android_content_AssetManager_getSizeConfigurations },
- { "setConfiguration", "!(IILjava/lang/String;IIIIIIIIIIIIII)V",
+ { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIII)V",
(void*) android_content_AssetManager_setConfiguration },
- { "getResourceIdentifier","!(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
(void*) android_content_AssetManager_getResourceIdentifier },
- { "getResourceName","!(I)Ljava/lang/String;",
+ { "getResourceName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceName },
- { "getResourcePackageName","!(I)Ljava/lang/String;",
+ { "getResourcePackageName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourcePackageName },
- { "getResourceTypeName","!(I)Ljava/lang/String;",
+ { "getResourceTypeName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceTypeName },
- { "getResourceEntryName","!(I)Ljava/lang/String;",
+ { "getResourceEntryName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getResourceEntryName },
- { "loadResourceValue","!(ISLandroid/util/TypedValue;Z)I",
+ { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceValue },
- { "loadResourceBagValue","!(IILandroid/util/TypedValue;Z)I",
+ { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadResourceBagValue },
- { "getStringBlockCount","!()I",
+ { "getStringBlockCount","()I",
(void*) android_content_AssetManager_getStringBlockCount },
- { "getNativeStringBlock","!(I)J",
+ { "getNativeStringBlock","(I)J",
(void*) android_content_AssetManager_getNativeStringBlock },
{ "getCookieName","(I)Ljava/lang/String;",
(void*) android_content_AssetManager_getCookieName },
@@ -2215,21 +2215,21 @@
(void*) android_content_AssetManager_copyTheme },
{ "clearTheme", "(J)V",
(void*) android_content_AssetManager_clearTheme },
- { "loadThemeAttributeValue", "!(JILandroid/util/TypedValue;Z)I",
+ { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
(void*) android_content_AssetManager_loadThemeAttributeValue },
- { "getThemeChangingConfigurations", "!(J)I",
+ { "getThemeChangingConfigurations", "(J)I",
(void*) android_content_AssetManager_getThemeChangingConfigurations },
{ "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
(void*) android_content_AssetManager_dumpTheme },
- { "applyStyle","!(JIIJ[I[I[I)Z",
+ { "applyStyle","(JIIJ[I[I[I)Z",
(void*) android_content_AssetManager_applyStyle },
- { "resolveAttrs","!(JII[I[I[I[I)Z",
+ { "resolveAttrs","(JII[I[I[I[I)Z",
(void*) android_content_AssetManager_resolveAttrs },
- { "retrieveAttributes","!(J[I[I[I)Z",
+ { "retrieveAttributes","(J[I[I[I)Z",
(void*) android_content_AssetManager_retrieveAttributes },
- { "getArraySize","!(I)I",
+ { "getArraySize","(I)I",
(void*) android_content_AssetManager_getArraySize },
- { "retrieveArray","!(I[I)I",
+ { "retrieveArray","(I[I)I",
(void*) android_content_AssetManager_retrieveArray },
// XML files.
@@ -2239,11 +2239,11 @@
// Arrays.
{ "getArrayStringResource","(I)[Ljava/lang/String;",
(void*) android_content_AssetManager_getArrayStringResource },
- { "getArrayStringInfo","!(I)[I",
+ { "getArrayStringInfo","(I)[I",
(void*) android_content_AssetManager_getArrayStringInfo },
- { "getArrayIntResource","!(I)[I",
+ { "getArrayIntResource","(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
- { "getStyleAttributes","!(I)[I",
+ { "getStyleAttributes","(I)[I",
(void*) android_content_AssetManager_getStyleAttributes },
// Bookkeeping.
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index c7998a1..1c6ead0 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -111,11 +111,12 @@
}
static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
- NativeInputChannel* nativeInputChannel) {
+ std::unique_ptr<NativeInputChannel> nativeInputChannel) {
jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
gInputChannelClassInfo.ctor);
if (inputChannelObj) {
- android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
+ android_view_InputChannel_setNativeInputChannel(env, inputChannelObj,
+ nativeInputChannel.release());
}
return inputChannelObj;
}
@@ -143,13 +144,13 @@
}
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
- new NativeInputChannel(serverChannel));
+ std::make_unique<NativeInputChannel>(serverChannel));
if (env->ExceptionCheck()) {
return NULL;
}
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
- new NativeInputChannel(clientChannel));
+ std::make_unique<NativeInputChannel>(clientChannel));
if (env->ExceptionCheck()) {
return NULL;
}
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 59a536b..9660de4 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -26,8 +26,8 @@
#include <sys/un.h>
#include <unistd.h>
+#include <android-base/logging.h>
#include <android-base/strings.h>
-#include <cutils/log.h>
// Static whitelist of open paths that the zygote is allowed to keep open.
static const char* kPathWhitelist[] = {
@@ -137,7 +137,7 @@
// This should never happen; the zygote should always have the right set
// of permissions required to stat all its open files.
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
- ALOGE("Unable to stat fd %d : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Unable to stat fd " << fd;
return NULL;
}
@@ -150,7 +150,8 @@
}
if (!whitelist->IsAllowed(socket_name)) {
- ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd);
+ LOG(ERROR) << "Socket name not whitelisted : " << socket_name
+ << " (fd=" << fd << ")";
return NULL;
}
@@ -168,7 +169,7 @@
// with the child process across forks but those should have been closed
// before we got to this point.
if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
- ALOGE("Unsupported st_mode %d", f_stat.st_mode);
+ LOG(ERROR) << "Unsupported st_mode " << f_stat.st_mode;
return NULL;
}
@@ -178,7 +179,7 @@
}
if (!whitelist->IsAllowed(file_path)) {
- ALOGE("Not whitelisted : %s", file_path.c_str());
+ LOG(ERROR) << "Not whitelisted : " << file_path;
return NULL;
}
@@ -187,7 +188,7 @@
// there won't be any races.
const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
if (fd_flags == -1) {
- ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << fd << ", F_GETFD)";
return NULL;
}
@@ -205,7 +206,7 @@
// their presence and pass them in to open().
int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
if (fs_flags == -1) {
- ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << fd << ", F_GETFL)";
return NULL;
}
@@ -224,6 +225,7 @@
bool FileDescriptorInfo::Restat() const {
struct stat f_stat;
if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
+ PLOG(ERROR) << "Unable to restat fd " << fd;
return false;
}
@@ -241,31 +243,31 @@
const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
if (new_fd == -1) {
- ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno));
+ PLOG(ERROR) << "Failed open(" << file_path << ", " << open_flags << ")";
return false;
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
close(new_fd);
- ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << new_fd << ", F_SETFD, " << fd_flags << ")";
return false;
}
if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
close(new_fd);
- ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno));
+ PLOG(ERROR) << "Failed fcntl(" << new_fd << ", F_SETFL, " << fs_flags << ")";
return false;
}
if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
close(new_fd);
- ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno));
+ PLOG(ERROR) << "Failed lseek64(" << new_fd << ", SEEK_SET)";
return false;
}
if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
close(new_fd);
- ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno));
+ PLOG(ERROR) << "Failed dup2(" << fd << ", " << new_fd << ")";
return false;
}
@@ -312,7 +314,10 @@
// ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here.
char buf[4096];
ssize_t len = readlink(path, buf, sizeof(buf));
- if (len == -1) return false;
+ if (len == -1) {
+ PLOG(ERROR) << "Readlink on " << fd << " failed.";
+ return false;
+ }
result->assign(buf, len);
return true;
@@ -325,12 +330,12 @@
socklen_t addr_len = sizeof(ss);
if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
- ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed getsockname(" << fd << ")";
return false;
}
if (addr->sa_family != AF_UNIX) {
- ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family);
+ LOG(ERROR) << "Unsupported socket (fd=" << fd << ") with family " << addr->sa_family;
return false;
}
@@ -339,13 +344,13 @@
size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
// This is an unnamed local socket, we do not accept it.
if (path_len == 0) {
- ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd);
+ LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with empty path.";
return false;
}
// This is a local socket with an abstract address, we do not accept it.
if (unix_addr->sun_path[0] == '\0') {
- ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd);
+ LOG(ERROR) << "Unsupported AF_UNIX socket (fd=" << fd << ") with abstract address.";
return false;
}
@@ -363,17 +368,17 @@
bool FileDescriptorInfo::DetachSocket() const {
const int dev_null_fd = open("/dev/null", O_RDWR);
if (dev_null_fd < 0) {
- ALOGE("Failed to open /dev/null : %s", strerror(errno));
+ PLOG(ERROR) << "Failed to open /dev/null";
return false;
}
if (dup2(dev_null_fd, fd) == -1) {
- ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno));
+ PLOG(ERROR) << "Failed dup2 on socket descriptor " << fd;
return false;
}
if (close(dev_null_fd) == -1) {
- ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno));
+ PLOG(ERROR) << "Failed close(" << dev_null_fd << ")";
return false;
}
@@ -384,7 +389,7 @@
FileDescriptorTable* FileDescriptorTable::Create(const std::vector<int>& fds_to_ignore) {
DIR* d = opendir(kFdPath);
if (d == NULL) {
- ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+ PLOG(ERROR) << "Unable to open directory " << std::string(kFdPath);
return NULL;
}
int dir_fd = dirfd(d);
@@ -397,14 +402,14 @@
continue;
}
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
- ALOGI("Ignoring open file descriptor %d", fd);
+ LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd);
if (info == NULL) {
if (closedir(d) == -1) {
- ALOGE("Unable to close directory : %s", strerror(errno));
+ PLOG(ERROR) << "Unable to close directory";
}
return NULL;
}
@@ -412,7 +417,7 @@
}
if (closedir(d) == -1) {
- ALOGE("Unable to close directory : %s", strerror(errno));
+ PLOG(ERROR) << "Unable to close directory";
return NULL;
}
return new FileDescriptorTable(open_fd_map);
@@ -424,7 +429,7 @@
// First get the list of open descriptors.
DIR* d = opendir(kFdPath);
if (d == NULL) {
- ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+ PLOG(ERROR) << "Unable to open directory " << std::string(kFdPath);
return false;
}
@@ -436,7 +441,7 @@
continue;
}
if (std::find(fds_to_ignore.begin(), fds_to_ignore.end(), fd) != fds_to_ignore.end()) {
- ALOGI("Ignoring open file descriptor %d", fd);
+ LOG(INFO) << "Ignoring open file descriptor " << fd;
continue;
}
@@ -444,7 +449,7 @@
}
if (closedir(d) == -1) {
- ALOGE("Unable to close directory : %s", strerror(errno));
+ PLOG(ERROR) << "Unable to close directory";
return false;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 09bf39c..2b2e18b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -236,6 +236,7 @@
<item>"mobile,0,0,0,-1,true"</item>
<item>"mobile_mms,2,0,2,60000,true"</item>
<item>"mobile_supl,3,0,2,60000,true"</item>
+ <item>"mobile_dun,4,0,2,60000,true"</item>
<item>"mobile_hipri,5,0,3,60000,true"</item>
<item>"mobile_fota,10,0,2,60000,true"</item>
<item>"mobile_ims,11,0,2,60000,true"</item>
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a734401..ce13ebc 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -685,7 +685,9 @@
SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
AutoMutex _lock(mSyncMutex);
mRenderThread.queue(&syncTask);
- mSyncCondition.wait(mSyncMutex);
+ while (!syncTask.hasRun()) {
+ mSyncCondition.wait(mSyncMutex);
+ }
return retval;
}
diff --git a/libs/hwui/renderthread/RenderTask.cpp b/libs/hwui/renderthread/RenderTask.cpp
index b14f580..928a4ef 100644
--- a/libs/hwui/renderthread/RenderTask.cpp
+++ b/libs/hwui/renderthread/RenderTask.cpp
@@ -26,6 +26,7 @@
void SignalingRenderTask::run() {
mTask->run();
mLock->lock();
+ mHasRun = true;
mSignal->signal();
mLock->unlock();
}
diff --git a/libs/hwui/renderthread/RenderTask.h b/libs/hwui/renderthread/RenderTask.h
index 9ea671b..a7acf91 100644
--- a/libs/hwui/renderthread/RenderTask.h
+++ b/libs/hwui/renderthread/RenderTask.h
@@ -60,13 +60,15 @@
public:
// Takes ownership of task, caller owns lock and signal
SignalingRenderTask(RenderTask* task, Mutex* lock, Condition* signal)
- : mTask(task), mLock(lock), mSignal(signal) {}
+ : mTask(task), mLock(lock), mSignal(signal), mHasRun(false) {}
virtual void run() override;
+ bool hasRun() const { return mHasRun; }
private:
RenderTask* mTask;
Mutex* mLock;
Condition* mSignal;
+ bool mHasRun;
};
typedef void* (*RunnableMethod)(void* data);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 9c10c4f..f383adc 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -345,7 +345,9 @@
AutoMutex _lock(mutex);
queue(&syncTask);
- condition.wait(mutex);
+ while (!syncTask.hasRun()) {
+ condition.wait(mutex);
+ }
}
void RenderThread::queueAtFront(RenderTask* task) {
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index 9d2f750..667bf71 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -1071,7 +1071,6 @@
mSize += mElements[ct].mSize * mArraySizes[ct];
}
updateVisibleSubElements();
- guard.open("destroy");
}
Element(long id, RenderScript rs, DataType dt, DataKind dk, boolean norm, int size) {
@@ -1091,7 +1090,6 @@
mKind = dk;
mNormalized = norm;
mVectorSize = size;
- guard.open("destroy");
}
Element(long id, RenderScript rs) {
diff --git a/rs/java/android/renderscript/Type.java b/rs/java/android/renderscript/Type.java
index 9252898..dc23785 100644
--- a/rs/java/android/renderscript/Type.java
+++ b/rs/java/android/renderscript/Type.java
@@ -227,7 +227,6 @@
Type(long id, RenderScript rs) {
super(id, rs);
- guard.open("destroy");
}
@Override
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 3b3ce07..5e9cf74 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -81,6 +81,8 @@
private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID="bluetooth_addr_valid";
private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS="bluetooth_address";
private static final String SECURE_SETTINGS_BLUETOOTH_NAME="bluetooth_name";
+ private static final String REASON_AIRPLANE_MODE = "airplane mode";
+ private static final String REASON_SYSTEM_BOOT = "system boot";
private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save
//Maximum msec to wait for service restart
@@ -166,7 +168,7 @@
}
public String toString() {
- return android.text.format.DateFormat.format("MM-dd hh:mm:ss ", mTimestamp) +
+ return android.text.format.DateFormat.format("MM-dd HH:mm:ss ", mTimestamp) +
(mEnable ? " Enabled " : " Disabled ") + " by " + mPackageName;
}
@@ -194,19 +196,6 @@
private final boolean mPermissionReviewRequired;
- private void registerForAirplaneMode(IntentFilter filter) {
- final ContentResolver resolver = mContext.getContentResolver();
- final String airplaneModeRadios = Settings.Global.getString(resolver,
- Settings.Global.AIRPLANE_MODE_RADIOS);
- final String toggleableRadios = Settings.Global.getString(resolver,
- Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
- boolean mIsAirplaneSensitive = airplaneModeRadios == null ? true :
- airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH);
- if (mIsAirplaneSensitive) {
- filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- }
- }
-
private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
@Override
public void onBluetoothStateChange(int prevState, int newState) throws RemoteException {
@@ -239,6 +228,62 @@
}
};
+ private final ContentObserver mAirplaneModeObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean unused) {
+ synchronized(this) {
+ if (isBluetoothPersistedStateOn()) {
+ if (isAirplaneModeOn()) {
+ persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
+ } else {
+ persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+ }
+ }
+
+ int st = BluetoothAdapter.STATE_OFF;
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ st = mBluetooth.getState();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to call getState", e);
+ return;
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ Slog.d(TAG, "Airplane Mode change - current state: " +
+ BluetoothAdapter.nameForState(st));
+
+ if (isAirplaneModeOn()) {
+ // Clear registered LE apps to force shut-off
+ clearBleApps();
+
+ // If state is BLE_ON make sure we trigger disableBLE
+ if (st == BluetoothAdapter.STATE_BLE_ON) {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth.onBrEdrDown();
+ mEnable = false;
+ mEnableExternal = false;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG,"Unable to call onBrEdrDown", e);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+ } else if (st == BluetoothAdapter.STATE_ON){
+ sendDisableMsg(REASON_AIRPLANE_MODE);
+ }
+ } else if (mEnableExternal) {
+ sendEnableMsg(mQuietEnableExternal, REASON_AIRPLANE_MODE);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -249,57 +294,6 @@
if (newName != null) {
storeNameAndAddress(newName, null);
}
- } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
- synchronized(mReceiver) {
- if (isBluetoothPersistedStateOn()) {
- if (isAirplaneModeOn()) {
- persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
- } else {
- persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
- }
- }
-
- int st = BluetoothAdapter.STATE_OFF;
- try {
- mBluetoothLock.readLock().lock();
- if (mBluetooth != null) {
- st = mBluetooth.getState();
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to call getState", e);
- } finally {
- mBluetoothLock.readLock().unlock();
- }
- Slog.d(TAG, "State " + BluetoothAdapter.nameForState(st));
-
- if (isAirplaneModeOn()) {
- // Clear registered LE apps to force shut-off
- clearBleApps();
- if (st == BluetoothAdapter.STATE_BLE_ON) {
- //if state is BLE_ON make sure you trigger disableBLE part
- try {
- mBluetoothLock.readLock().lock();
- if (mBluetooth != null) {
- mBluetooth.onBrEdrDown();
- mEnable = false;
- mEnableExternal = false;
- }
- } catch (RemoteException e) {
- Slog.e(TAG,"Unable to call onBrEdrDown", e);
- } finally {
- mBluetoothLock.readLock().unlock();
- }
- } else if (st == BluetoothAdapter.STATE_ON){
- // disable without persisting the setting
- Slog.d(TAG, "Calling disable");
- sendDisableMsg("airplane mode");
- }
- } else if (mEnableExternal) {
- // enable without persisting the setting
- Slog.d(TAG, "Calling enable");
- sendEnableMsg(mQuietEnableExternal, "airplane mode");
- }
- }
}
}
};
@@ -332,7 +326,6 @@
mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
- registerForAirplaneMode(filter);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mReceiver, filter);
loadStoredNameAndAddress();
@@ -340,6 +333,15 @@
mEnableExternal = true;
}
+ String airplaneModeRadios = Settings.Global.getString(mContentResolver,
+ Settings.Global.AIRPLANE_MODE_RADIOS);
+ if (airplaneModeRadios == null ||
+ airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) {
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
+ true, mAirplaneModeObserver);
+ }
+
int systemUiUid = -1;
try {
systemUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui",
@@ -636,6 +638,9 @@
if (appCount == 0 && mEnable) {
disableBleScanMode();
}
+ if (appCount == 0 && !mEnableExternal) {
+ sendBrEdrDownCallback();
+ }
return appCount;
}
@@ -691,7 +696,14 @@
return;
}
- if (isBleAppPresent() == false) {
+ if (isBleAppPresent()) {
+ // Need to stay at BLE ON. Disconnect all Gatt connections
+ try {
+ mBluetoothGatt.unregAll();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to disconnect all apps.", e);
+ }
+ } else {
try {
mBluetoothLock.readLock().lock();
if (mBluetooth != null) mBluetooth.onBrEdrDown();
@@ -700,14 +712,8 @@
} finally {
mBluetoothLock.readLock().unlock();
}
- } else {
- // Need to stay at BLE ON. Disconnect all Gatt connections
- try {
- mBluetoothGatt.unregAll();
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to disconnect all apps.", e);
- }
}
+
}
public boolean enableNoAutoConnect(String packageName)
@@ -958,7 +964,7 @@
}
if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
if (DBG) Slog.d(TAG, "Auto-enabling Bluetooth.");
- sendEnableMsg(mQuietEnableExternal, "system boot");
+ sendEnableMsg(mQuietEnableExternal, REASON_SYSTEM_BOOT);
} else if (!isNameAndAddressSet()) {
if (DBG) Slog.d(TAG, "Getting adapter name and address");
Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 954a94e..f58cdba 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -395,16 +395,6 @@
*/
private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
- /**
- * Indicates a caller has requested to have its callback invoked with
- * the latest LinkProperties or NetworkCapabilities.
- *
- * arg1 = UID of caller
- * obj = NetworkRequest
- */
- private static final int EVENT_REQUEST_LINKPROPERTIES = 32;
- private static final int EVENT_REQUEST_NETCAPABILITIES = 33;
-
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -2574,34 +2564,6 @@
return nri;
}
- private void handleRequestCallbackUpdate(NetworkRequest request, int callingUid,
- String description, int callbackType) {
- final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, description);
- if (nri == null) return;
-
- final NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
- // The network that is satisfying this request may have changed since
- // the application requested the update.
- //
- // - If the request is no longer satisfied, don't send any updates.
- // - If the request is satisfied by a different network, it is the
- // caller's responsibility to check that the Network object in the
- // callback matches the network that was returned in the last
- // onAvailable() callback for this request.
- if (nai == null) return;
- callCallbackForRequest(nri, nai, callbackType, 0);
- }
-
- private void handleRequestLinkProperties(NetworkRequest request, int callingUid) {
- handleRequestCallbackUpdate(request, callingUid,
- "request LinkProperties", ConnectivityManager.CALLBACK_IP_CHANGED);
- }
-
- private void handleRequestNetworkCapabilities(NetworkRequest request, int callingUid) {
- handleRequestCallbackUpdate(request, callingUid,
- "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED);
- }
-
private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) {
if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get(
nri.request.requestId) == null) {
@@ -2983,12 +2945,6 @@
handleMobileDataAlwaysOn();
break;
}
- case EVENT_REQUEST_LINKPROPERTIES:
- handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1);
- break;
- case EVENT_REQUEST_NETCAPABILITIES:
- handleRequestNetworkCapabilities((NetworkRequest) msg.obj, msg.arg1);
- break;
// Sent by KeepaliveTracker to process an app request on the state machine thread.
case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
mKeepaliveTracker.handleStartKeepalive(msg);
@@ -4349,22 +4305,6 @@
}
@Override
- public void requestLinkProperties(NetworkRequest networkRequest) {
- ensureNetworkRequestHasType(networkRequest);
- if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_REQUEST_LINKPROPERTIES, getCallingUid(), 0, networkRequest));
- }
-
- @Override
- public void requestNetworkCapabilities(NetworkRequest networkRequest) {
- ensureNetworkRequestHasType(networkRequest);
- if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
- mHandler.sendMessage(mHandler.obtainMessage(
- EVENT_REQUEST_NETCAPABILITIES, getCallingUid(), 0, networkRequest));
- }
-
- @Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
ensureNetworkRequestHasType(networkRequest);
mHandler.sendMessage(mHandler.obtainMessage(
@@ -4856,7 +4796,7 @@
if (!nr.isListen()) continue;
if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) {
nai.addRequest(nr);
- notifyNetworkCallback(nai, nri);
+ notifyNetworkAvailable(nai, nri);
}
}
}
@@ -5038,7 +4978,7 @@
// do this after the default net is switched, but
// before LegacyTypeTracker sends legacy broadcasts
- for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
+ for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri);
// Linger any networks that are no longer needed. This should be done after sending the
// available callback for newNetwork.
@@ -5201,7 +5141,7 @@
}
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
- NetworkInfo.State state = newInfo.getState();
+ final NetworkInfo.State state = newInfo.getState();
NetworkInfo oldInfo = null;
final int oldScore = networkAgent.getCurrentScore();
synchronized (networkAgent) {
@@ -5328,15 +5268,27 @@
sendUpdatedScoreToFactories(nai);
}
- // notify only this one new request of the current state
- protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
- int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+ // Notify only this one new request of the current state. Transfer all the
+ // current state by calling NetworkCapabilities and LinkProperties callbacks
+ // so that callers can be guaranteed to have as close to atomicity in state
+ // transfer as can be supported by this current API.
+ protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
- if (nri.mPendingIntent == null) {
- callCallbackForRequest(nri, nai, notifyType, 0);
- } else {
- sendPendingIntentForRequest(nri, nai, notifyType);
+ if (nri.mPendingIntent != null) {
+ sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
+ // Attempt no subsequent state pushes where intents are involved.
+ return;
}
+
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0);
+ // Whether a network is currently suspended is also an important
+ // element of state to be transferred (it would not otherwise be
+ // delivered by any currently available mechanism).
+ if (nai.networkInfo.getState() == NetworkInfo.State.SUSPENDED) {
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_SUSPENDED, 0);
+ }
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_CAP_CHANGED, 0);
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_IP_CHANGED, 0);
}
private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 63e66a2..dfd647c 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -76,6 +76,7 @@
import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
+import com.android.server.connectivity.tethering.OffloadController;
import com.android.server.connectivity.tethering.TetheringConfiguration;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
@@ -145,6 +146,7 @@
.getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
private final StateMachine mTetherMasterSM;
+ private final OffloadController mOffloadController;
private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private String mCurrentUpstreamIface;
@@ -175,6 +177,7 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
mTetherMasterSM.start();
+ mOffloadController = new OffloadController(mTetherMasterSM.getHandler());
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
@@ -950,6 +953,8 @@
// Events from NetworkCallbacks that we process on the master state
// machine thread on behalf of the UpstreamNetworkMonitor.
static final int EVENT_UPSTREAM_CALLBACK = BASE_MASTER + 5;
+ // we treated the error and want now to clear it
+ static final int CMD_CLEAR_ERROR = BASE_MASTER + 6;
private State mInitialState;
private State mTetherModeAliveState;
@@ -1201,6 +1206,8 @@
protected void handleNewUpstreamNetworkState(NetworkState ns) {
mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
+ mOffloadController.setUpstreamLinkProperties(
+ (ns != null) ? ns.linkProperties : null);
}
}
@@ -1362,12 +1369,14 @@
class TetherModeAliveState extends TetherMasterUtilState {
final SimChangeListener simChange = new SimChangeListener(mContext);
boolean mTryCell = true;
+
@Override
public void enter() {
// TODO: examine if we should check the return value.
turnOnMasterTetherSettings(); // may transition us out
simChange.startListening();
mUpstreamNetworkMonitor.start();
+ mOffloadController.start();
// Better try something first pass or crazy tests cases will fail.
chooseUpstreamType(true);
@@ -1376,6 +1385,7 @@
@Override
public void exit() {
+ mOffloadController.stop();
unrequestUpstreamMobileConnection();
mUpstreamNetworkMonitor.stop();
simChange.stopListening();
@@ -1496,6 +1506,10 @@
TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
who.sendMessage(mErrorNotification);
break;
+ case CMD_CLEAR_ERROR:
+ mErrorNotification = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ transitionTo(mInitialState);
+ break;
default:
retValue = false;
}
@@ -1640,6 +1654,12 @@
// Not really very much we can do here.
}
+ // If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
+ // Thus we give a chance for TetherMasterSM to recover to InitialState
+ // by sending CMD_CLEAR_ERROR
+ if (error == ConnectivityManager.TETHER_ERROR_MASTER_ERROR) {
+ mTetherMasterSM.sendMessage(TetherMasterSM.CMD_CLEAR_ERROR, who);
+ }
switch (state) {
case IControlsTethering.STATE_UNAVAILABLE:
case IControlsTethering.STATE_AVAILABLE:
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
new file mode 100644
index 0000000..220e751
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.net.LinkProperties;
+import android.os.Handler;
+import android.util.Log;
+
+/**
+ * A wrapper around hardware offload interface.
+ *
+ * @hide
+ */
+public class OffloadController {
+ private static final String TAG = OffloadController.class.getSimpleName();
+
+ private final Handler mHandler;
+ private LinkProperties mUpstreamLinkProperties;
+
+ public OffloadController(Handler h) {
+ mHandler = h;
+ }
+
+ public void start() {
+ // TODO: initOffload() and configure callbacks to be handled on our
+ // preferred Handler.
+ Log.d(TAG, "tethering offload not supported");
+ }
+
+ public void stop() {
+ // TODO: stopOffload().
+ mUpstreamLinkProperties = null;
+ }
+
+ public void setUpstreamLinkProperties(LinkProperties lp) {
+ // TODO: setUpstreamParameters().
+ mUpstreamLinkProperties = lp;
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 5e51579..710ab33 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -167,7 +167,8 @@
private void maybeLogMessage(State state, int what) {
if (DBG) {
Log.d(TAG, state.getName() + " got " +
- sMagicDecoderRing.get(what, Integer.toString(what)));
+ sMagicDecoderRing.get(what, Integer.toString(what)) + ", Iface = " +
+ mIfaceName);
}
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 6106093..6209929 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -186,6 +186,7 @@
switch (callbackType) {
case CALLBACK_LISTEN_ALL:
break;
+
case CALLBACK_TRACK_DEFAULT:
if (mDefaultNetworkCallback == null) {
// The callback was unregistered in the interval between
@@ -198,11 +199,9 @@
// These request*() calls can be deleted post oag/339444.
return;
}
-
- cm().requestNetworkCapabilities(mDefaultNetworkCallback);
- cm().requestLinkProperties(mDefaultNetworkCallback);
mCurrentDefault = network;
break;
+
case CALLBACK_MOBILE_REQUEST:
if (mMobileNetworkCallback == null) {
// The callback was unregistered in the interval between
@@ -211,13 +210,8 @@
//
// Clean-up of this network entry is deferred to the
// handling of onLost() by other callbacks.
- //
- // These request*() calls can be deleted post oag/339444.
return;
}
-
- cm().requestNetworkCapabilities(mMobileNetworkCallback);
- cm().requestLinkProperties(mMobileNetworkCallback);
break;
}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 601a219..7aa96cf 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -30,10 +30,13 @@
import android.os.BatteryManager;
import android.os.Environment;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.storage.StorageManager;
import android.util.ArraySet;
import android.util.Log;
+import com.android.server.pm.dex.DexManager;
+
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;
@@ -59,21 +62,33 @@
"android",
BackgroundDexOptService.class.getName());
+ // Possible return codes of individual optimization steps.
+
+ // Optimizations finished. All packages were processed.
+ private static final int OPTIMIZE_PROCESSED = 0;
+ // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
+ private static final int OPTIMIZE_CONTINUE = 1;
+ // Optimizations should be aborted. Job scheduler requested it.
+ private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
+ // Optimizations should be aborted. No space left on device.
+ private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
+
/**
* Set of failed packages remembered across job runs.
*/
- static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
+ static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
+ static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
/**
* Atomics set to true if the JobScheduler requests an abort.
*/
- final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
- final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+ private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
+ private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
/**
* Atomic set to true if one job should exit early because another job was started.
*/
- final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
+ private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
private final File mDataDir = Environment.getDataDirectory();
@@ -104,8 +119,11 @@
// The idle maintanance job skips packages which previously failed to
// compile. The given package has changed and may successfully compile
// now. Remove it from the list of known failing packages.
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.remove(packageName);
+ synchronized (sFailedPackageNamesPrimary) {
+ sFailedPackageNamesPrimary.remove(packageName);
+ }
+ synchronized (sFailedPackageNamesSecondary) {
+ sFailedPackageNamesSecondary.remove(packageName);
}
}
@@ -124,9 +142,9 @@
return (100 * level / scale);
}
- private long getLowStorageThreshold() {
+ private long getLowStorageThreshold(Context context) {
@SuppressWarnings("deprecation")
- final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir);
+ final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
if (lowThreshold == 0) {
Log.e(TAG, "Invalid low storage threshold");
}
@@ -155,7 +173,7 @@
// Load low battery threshold from the system config. This is a 0-100 integer.
final int lowBatteryThreshold = getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
- final long lowThreshold = getLowStorageThreshold();
+ final long lowThreshold = getLowStorageThreshold(this);
mAbortPostBootUpdate.set(false);
@@ -206,61 +224,123 @@
new Thread("BackgroundDexOptService_IdleOptimization") {
@Override
public void run() {
- idleOptimization(jobParams, pm, pkgs);
+ int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
+ if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ Log.w(TAG, "Idle optimizations aborted because of space constraints.");
+ // If we didn't abort we ran to completion (or stopped because of space).
+ // Abandon our timeslice and do not reschedule.
+ jobFinished(jobParams, /* reschedule */ false);
+ }
}
}.start();
return true;
}
- private void idleOptimization(JobParameters jobParams, PackageManagerService pm,
- ArraySet<String> pkgs) {
+ // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
+ private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context) {
Log.i(TAG, "Performing idle optimizations");
// If post-boot update is still running, request that it exits early.
mExitPostBootUpdate.set(true);
-
mAbortIdleOptimization.set(false);
- final long lowThreshold = getLowStorageThreshold();
- for (String pkg : pkgs) {
- if (mAbortIdleOptimization.get()) {
- // JobScheduler requested an early abort.
- return;
+ long lowStorageThreshold = getLowStorageThreshold(context);
+ // Optimize primary apks.
+ int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
+ sFailedPackageNamesPrimary);
+
+ if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ return result;
+ }
+
+ if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
+ result = reconcileSecondaryDexFiles(pm.getDexManager());
+ if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ return result;
}
- synchronized (sFailedPackageNames) {
- if (sFailedPackageNames.contains(pkg)) {
+ result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
+ sFailedPackageNamesSecondary);
+ }
+ return result;
+ }
+
+ private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
+ long lowStorageThreshold, boolean is_for_primary_dex,
+ ArraySet<String> failedPackageNames) {
+ for (String pkg : pkgs) {
+ int abort_code = abortIdleOptimizations(lowStorageThreshold);
+ if (abort_code != OPTIMIZE_CONTINUE) {
+ return abort_code;
+ }
+
+ synchronized (failedPackageNames) {
+ if (failedPackageNames.contains(pkg)) {
// Skip previously failing package
continue;
+ } else {
+ // Conservatively add package to the list of failing ones in case performDexOpt
+ // never returns.
+ failedPackageNames.add(pkg);
}
}
- long usableSpace = mDataDir.getUsableSpace();
- if (usableSpace < lowThreshold) {
- // Rather bail than completely fill up the disk.
- Log.w(TAG, "Aborting background dex opt job due to low storage: " +
- usableSpace);
- break;
- }
-
- // Conservatively add package to the list of failing ones in case performDexOpt
- // never returns.
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.add(pkg);
- }
// Optimize package if needed. Note that there can be no race between
// concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- if (pm.performDexOpt(pkg,
- /* checkProfiles */ true,
- PackageManagerService.REASON_BACKGROUND_DEXOPT,
- /* force */ false)) {
+ boolean success = is_for_primary_dex
+ ? pm.performDexOpt(pkg,
+ /* checkProfiles */ true,
+ PackageManagerService.REASON_BACKGROUND_DEXOPT,
+ /* force */ false)
+ : pm.performDexOptSecondary(pkg,
+ PackageManagerServiceCompilerMapping.getFullCompilerFilter(),
+ /* force */ true);
+ if (success) {
// Dexopt succeeded, remove package from the list of failing ones.
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.remove(pkg);
+ synchronized (failedPackageNames) {
+ failedPackageNames.remove(pkg);
}
}
}
- // Ran to completion, so we abandon our timeslice and do not reschedule.
- jobFinished(jobParams, /* reschedule */ false);
+ return OPTIMIZE_PROCESSED;
+ }
+
+ private int reconcileSecondaryDexFiles(DexManager dm) {
+ // TODO(calin): should we blacklist packages for which we fail to reconcile?
+ for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
+ if (mAbortIdleOptimization.get()) {
+ return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ }
+ dm.reconcileSecondaryDexFiles(p);
+ }
+ return OPTIMIZE_PROCESSED;
+ }
+
+ // Evaluate whether or not idle optimizations should continue.
+ private int abortIdleOptimizations(long lowStorageThreshold) {
+ if (mAbortIdleOptimization.get()) {
+ // JobScheduler requested an early abort.
+ return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ }
+ long usableSpace = mDataDir.getUsableSpace();
+ if (usableSpace < lowStorageThreshold) {
+ // Rather bail than completely fill up the disk.
+ Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
+ return OPTIMIZE_ABORT_NO_SPACE_LEFT;
+ }
+
+ return OPTIMIZE_CONTINUE;
+ }
+
+ /**
+ * Execute the idle optimizations immediately.
+ */
+ public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+ // Create a new object to make sure we don't interfere with the scheduled jobs.
+ // Note that this may still run at the same time with the job scheduled by the
+ // JobScheduler but the scheduler will not be able to cancel it.
+ BackgroundDexOptService bdos = new BackgroundDexOptService();
+ int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+ return result == OPTIMIZE_PROCESSED;
}
@Override
@@ -281,7 +361,7 @@
}
final ArraySet<String> pkgs = pm.getOptimizablePackages();
- if (pkgs == null || pkgs.isEmpty()) {
+ if (pkgs.isEmpty()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "No packages to optimize");
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 04569c2..42a0bad 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -50,6 +50,14 @@
public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
/** Hint that the dexopt type is profile-guided. */
public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
+ /** The compilation is for a secondary dex file. */
+ public static final int DEXOPT_SECONDARY_DEX = 1 << 6;
+ /** Ignore the result of dexoptNeeded and force compilation. */
+ public static final int DEXOPT_FORCE = 1 << 7;
+ /** Indicates that the dex file passed to dexopt in on CE storage. */
+ public static final int DEXOPT_STORAGE_CE = 1 << 8;
+ /** Indicates that the dex file passed to dexopt in on DE storage. */
+ public static final int DEXOPT_STORAGE_DE = 1 << 9;
// NOTE: keep in sync with installd
public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
@@ -425,6 +433,20 @@
}
}
+ public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
+ String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
+ for (int i = 0; i < isas.length; i++) {
+ assertValidInstructionSet(isas[i]);
+ }
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
+ volumeUuid, flags);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
private static void assertValidInstructionSet(String instructionSet)
throws InstallerException {
for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 60c83b4..9418e74 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -313,7 +313,8 @@
optimizer.performDexOpt(pkg, libraryDependencies,
null /* ISAs */, false /* checkProfiles */,
getCompilerFilterForReason(compilationReason),
- null /* CompilerStats.PackageStats */);
+ null /* CompilerStats.PackageStats */,
+ mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName));
return commands;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 8e201ac..d9ea728 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.os.Environment;
import android.os.PowerManager;
@@ -35,6 +36,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import dalvik.system.DexFile;
@@ -43,6 +45,10 @@
import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
+import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
+import static com.android.server.pm.Installer.DEXOPT_FORCE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
@@ -52,13 +58,16 @@
/**
* Helper class for running dexopt command on packages.
*/
-class PackageDexOptimizer {
+public class PackageDexOptimizer {
private static final String TAG = "PackageManager.DexOptimizer";
static final String OAT_DIR_NAME = "oat";
// TODO b/19550105 Remove error codes and use exceptions
- static final int DEX_OPT_SKIPPED = 0;
- static final int DEX_OPT_PERFORMED = 1;
- static final int DEX_OPT_FAILED = -1;
+ public static final int DEX_OPT_SKIPPED = 0;
+ public static final int DEX_OPT_PERFORMED = 1;
+ public static final int DEX_OPT_FAILED = -1;
+
+ /** Special library name that skips shared libraries check during compilation. */
+ public static final String SKIP_SHARED_LIBRARY_CHECK = "&";
private final Installer mInstaller;
private final Object mInstallLock;
@@ -95,11 +104,14 @@
*/
int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
- CompilerStats.PackageStats packageStats) {
+ CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps) {
if (!canOptimizePackage(pkg)) {
return DEX_OPT_SKIPPED;
}
synchronized (mInstallLock) {
+ // During boot the system doesn't need to instantiate and obtain a wake lock.
+ // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+ // dexopt.
final boolean useLock = mSystemReady;
if (useLock) {
mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
@@ -107,7 +119,7 @@
}
try {
return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
- targetCompilationFilter, packageStats);
+ targetCompilationFilter, packageStats, isUsedByOtherApps);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -123,16 +135,19 @@
@GuardedBy("mInstallLock")
private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
String[] targetInstructionSets, boolean checkForProfileUpdates,
- String targetCompilerFilter, CompilerStats.PackageStats packageStats) {
+ String targetCompilerFilter, CompilerStats.PackageStats packageStats,
+ boolean isUsedByOtherApps) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final String compilerFilter = getRealCompilerFilter(pkg, targetCompilerFilter);
+ final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
+ targetCompilerFilter, isUsedByOtherApps);
final boolean profileUpdated = checkForProfileUpdates &&
isProfileUpdated(pkg, sharedGid, compilerFilter);
+
// TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
// paths (b/34169257).
final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
@@ -201,6 +216,79 @@
}
/**
+ * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}.
+ *
+ * @return
+ * DEX_OPT_FAILED if there was any exception during dexopt
+ * DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
+ * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file
+ * didn't need an update. That's because at the moment we don't get more than success/failure
+ * from installd.
+ *
+ * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than
+ * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
+ * that seems wasteful.
+ */
+ public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
+ String compilerFilter, boolean isUsedByOtherApps) {
+ synchronized (mInstallLock) {
+ // During boot the system doesn't need to instantiate and obtain a wake lock.
+ // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+ // dexopt.
+ final boolean useLock = mSystemReady;
+ if (useLock) {
+ mDexoptWakeLock.setWorkSource(new WorkSource(info.uid));
+ mDexoptWakeLock.acquire();
+ }
+ try {
+ return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
+ isUsedByOtherApps);
+ } finally {
+ if (useLock) {
+ mDexoptWakeLock.release();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mInstallLock")
+ private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
+ String compilerFilter, boolean isUsedByOtherApps) {
+ int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
+ // Check the app storage and add the appropriate flags.
+ if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+ dexoptFlags |= DEXOPT_STORAGE_DE;
+ } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+ dexoptFlags |= DEXOPT_STORAGE_CE;
+ } else {
+ Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+ return DEX_OPT_FAILED;
+ }
+ compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
+ Log.d(TAG, "Running dexopt on: " + path
+ + " pkg=" + info.packageName + " isa=" + isas
+ + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+ + " target-filter=" + compilerFilter);
+
+ try {
+ for (String isa : isas) {
+ // Reuse the same dexopt path as for the primary apks. We don't need all the
+ // arguments as some (dexopNeeded and oatDir) will be computed by installd because
+ // system server cannot read untrusted app content.
+ // TODO(calin): maybe add a separate call.
+ mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
+ /*oatDir*/ null, dexoptFlags,
+ compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK);
+ }
+
+ return DEX_OPT_PERFORMED;
+ } catch (InstallerException e) {
+ Slog.w(TAG, "Failed to dexopt", e);
+ return DEX_OPT_FAILED;
+ }
+ }
+
+ /**
* Adjust the given dexopt-needed value. Can be overridden to influence the decision to
* optimize or not (and in what way).
*/
@@ -246,8 +334,9 @@
* The target filter will be updated if the package code is used by other apps
* or if it has the safe mode flag set.
*/
- private String getRealCompilerFilter(PackageParser.Package pkg, String targetCompilerFilter) {
- int flags = pkg.applicationInfo.flags;
+ private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
+ boolean isUsedByOtherApps) {
+ int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
if (vmSafeMode) {
// For the compilation, it doesn't really matter what we return here because installd
@@ -259,7 +348,7 @@
return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
}
- if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps(pkg)) {
+ if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
// If the dex files is used by other apps, we cannot use profile-guided compilation.
return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
}
@@ -272,12 +361,16 @@
* filter.
*/
private int getDexFlags(PackageParser.Package pkg, String compilerFilter) {
- int flags = pkg.applicationInfo.flags;
+ return getDexFlags(pkg.applicationInfo, compilerFilter);
+ }
+
+ private int getDexFlags(ApplicationInfo info, String compilerFilter) {
+ int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// Profile guide compiled oat files should not be public.
boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
- boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
+ boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
int dexFlags =
(isPublic ? DEXOPT_PUBLIC : 0)
@@ -385,40 +478,6 @@
mSystemReady = true;
}
- /**
- * Returns true if the profiling data collected for the given app indicate
- * that the apps's APK has been loaded by another app.
- * Note that this returns false for all forward-locked apps and apps without
- * any collected profiling data.
- */
- public static boolean isUsedByOtherApps(PackageParser.Package pkg) {
- if (pkg.isForwardLocked()) {
- // Skip the check for forward locked packages since they don't share their code.
- return false;
- }
-
- for (String apkPath : pkg.getAllCodePathsExcludingResourceOnly()) {
- try {
- apkPath = PackageManagerServiceUtils.realpath(new File(apkPath));
- } catch (IOException e) {
- // Log an error but continue without it.
- Slog.w(TAG, "Failed to get canonical path", e);
- continue;
- }
- String useMarker = apkPath.replace('/', '@');
- final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
- for (int i = 0; i < currentUserIds.length; i++) {
- File profileDir =
- Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
- File foreignUseMark = new File(profileDir, useMarker);
- if (foreignUseMark.exists()) {
- return true;
- }
- }
- }
- return false;
- }
-
private String printDexoptFlags(int flags) {
ArrayList<String> flagsList = new ArrayList<>();
@@ -437,6 +496,19 @@
if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) {
flagsList.add("safemode");
}
+ if ((flags & DEXOPT_SECONDARY_DEX) == DEXOPT_SECONDARY_DEX) {
+ flagsList.add("secondary");
+ }
+ if ((flags & DEXOPT_FORCE) == DEXOPT_FORCE) {
+ flagsList.add("force");
+ }
+ if ((flags & DEXOPT_STORAGE_CE) == DEXOPT_STORAGE_CE) {
+ flagsList.add("storage_ce");
+ }
+ if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
+ flagsList.add("storage_de");
+ }
+
return String.join(",", flagsList);
}
@@ -461,5 +533,12 @@
// TODO: The return value is wrong when patchoat is needed.
return DexFile.DEX2OAT_FROM_SCRATCH;
}
+
+ @Override
+ protected int adjustDexoptFlags(int flags) {
+ // Add DEXOPT_FORCE flag to signal installd that it should force compilation
+ // and discard dexoptanalyzer result.
+ return flags | DEXOPT_FORCE;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ca0f85d..76f5a23 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -524,9 +524,6 @@
public static final int REASON_LAST = REASON_CORE_APP;
- /** Special library name that skips shared libraries check during compilation. */
- private static final String SKIP_SHARED_LIBRARY_CHECK = "&";
-
final ServiceThread mHandlerThread;
final PackageHandler mHandler;
@@ -1787,6 +1784,18 @@
res.removedInfo.args.doPostDeleteLI(true);
}
}
+
+ if (!isEphemeral(res.pkg)) {
+ // Notify DexManager that the package was installed for new users.
+ // The updated users should already be indexed and the package code paths
+ // should not change.
+ // Don't notify the manager for ephemeral apps as they are not expected to
+ // survive long enough to benefit of background optimizations.
+ for (int userId : firstUsers) {
+ PackageInfo info = getPackageInfo(packageName, /*flags*/ 0, userId);
+ mDexManager.notifyPackageInstalled(info, userId);
+ }
+ }
}
// If someone is watching installs - notify them
@@ -2121,7 +2130,7 @@
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
- mDexManager = new DexManager();
+ mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2248,7 +2257,7 @@
DEXOPT_PUBLIC,
getCompilerFilterForReason(REASON_SHARED_APK),
StorageManager.UUID_PRIVATE_INTERNAL,
- SKIP_SHARED_LIBRARY_CHECK);
+ PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + lib);
@@ -7489,11 +7498,46 @@
pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets,
false /* checkProfiles */,
getCompilerFilterForReason(REASON_NON_SYSTEM_LIBRARY),
- getOrCreateCompilerPackageStats(depPackage));
+ getOrCreateCompilerPackageStats(depPackage),
+ mDexManager.isUsedByOtherApps(p.packageName));
}
}
return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles,
- targetCompilerFilter, getOrCreateCompilerPackageStats(p));
+ targetCompilerFilter, getOrCreateCompilerPackageStats(p),
+ mDexManager.isUsedByOtherApps(p.packageName));
+ }
+
+ // Performs dexopt on the used secondary dex files belonging to the given package.
+ // Returns true if all dex files were process successfully (which could mean either dexopt or
+ // skip). Returns false if any of the files caused errors.
+ @Override
+ public boolean performDexOptSecondary(String packageName, String compilerFilter,
+ boolean force) {
+ return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
+ }
+
+ /**
+ * Reconcile the information we have about the secondary dex files belonging to
+ * {@code packagName} and the actual dex files. For all dex files that were
+ * deleted, update the internal records and delete the generated oat files.
+ */
+ @Override
+ public void reconcileSecondaryDexFiles(String packageName) {
+ mDexManager.reconcileSecondaryDexFiles(packageName);
+ }
+
+ // TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject
+ // a reference there.
+ /*package*/ DexManager getDexManager() {
+ return mDexManager;
+ }
+
+ /**
+ * Execute the background dexopt job immediately.
+ */
+ @Override
+ public boolean runBackgroundDexoptJob() {
+ return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
}
Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -15279,7 +15323,8 @@
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null /* instructionSets */, false /* checkProfiles */,
getCompilerFilterForReason(REASON_INSTALL),
- getOrCreateCompilerPackageStats(pkg));
+ getOrCreateCompilerPackageStats(pkg),
+ mDexManager.isUsedByOtherApps(pkg.packageName));
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
// Notify BackgroundDexOptService that the package has been changed.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 8a3f48e..0634dac 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -23,7 +23,7 @@
/**
* Manage (retrieve) mappings from compilation reason to compilation filter.
*/
-class PackageManagerServiceCompilerMapping {
+public class PackageManagerServiceCompilerMapping {
// Names for compilation reasons.
static final String REASON_STRINGS[] = {
"first-boot", "boot", "install", "bg-dexopt", "ab-ota", "nsys-library", "shared-apk",
@@ -80,9 +80,7 @@
try {
// Check that the system property name is legal.
String sysPropName = getSystemPropertyName(reason);
- if (sysPropName == null ||
- sysPropName.isEmpty() ||
- sysPropName.length() > SystemProperties.PROP_NAME_MAX) {
+ if (sysPropName == null || sysPropName.isEmpty()) {
throw new IllegalStateException("Reason system property name \"" +
sysPropName +"\" for reason " + REASON_STRINGS[reason]);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 45887e1..9feee8c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -133,7 +133,8 @@
sortTemp, packageManagerService);
// Give priority to apps used by other apps.
- applyPackageFilter((pkg) -> PackageDexOptimizer.isUsedByOtherApps(pkg), result,
+ applyPackageFilter((pkg) ->
+ packageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), result,
remainingPkgs, sortTemp, packageManagerService);
// Filter out packages that aren't recently used, add all remaining apps.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e18d4e0..44b372b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -110,6 +110,10 @@
return runInstallWrite();
case "compile":
return runCompile();
+ case "reconcile-secondary-dex-files":
+ return runreconcileSecondaryDexFiles();
+ case "bg-dexopt-job":
+ return runDexoptJob();
case "dump-profiles":
return runDumpProfiles();
case "list":
@@ -281,6 +285,7 @@
String compilerFilter = null;
String compilationReason = null;
String checkProfilesRaw = null;
+ boolean secondaryDex = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -308,6 +313,9 @@
clearProfileData = true;
compilationReason = "install";
break;
+ case "--secondary-dex":
+ secondaryDex = true;
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -380,8 +388,11 @@
mInterface.clearApplicationProfileData(packageName);
}
- boolean result = mInterface.performDexOptMode(packageName,
- checkProfiles, targetCompilerFilter, forceCompilation);
+ boolean result = secondaryDex
+ ? mInterface.performDexOptSecondary(packageName,
+ targetCompilerFilter, forceCompilation)
+ : mInterface.performDexOptMode(packageName,
+ checkProfiles, targetCompilerFilter, forceCompilation);
if (!result) {
failedPackages.add(packageName);
}
@@ -409,6 +420,17 @@
}
}
+ private int runreconcileSecondaryDexFiles() throws RemoteException {
+ String packageName = getNextArg();
+ mInterface.reconcileSecondaryDexFiles(packageName);
+ return 0;
+ }
+
+ private int runDexoptJob() throws RemoteException {
+ boolean result = mInterface.runBackgroundDexoptJob();
+ return result ? 0 : -1;
+ }
+
private int runDumpProfiles() throws RemoteException {
String packageName = getNextArg();
mInterface.dumpProfiles(packageName);
@@ -1441,6 +1463,13 @@
}
pw.println(" --reset: restore package to its post-install state");
pw.println(" --check-prof (true | false): look at profiles when doing dexopt?");
+ pw.println(" --secondary-dex: compile app secondary dex files");
+ pw.println(" bg-dexopt-job");
+ pw.println(" Execute the background optimizations immediately.");
+ pw.println(" Note that the command only runs the background optimizer logic. It may");
+ pw.println(" overlap with the actual job but the job scheduler will not be able to");
+ pw.println(" cancel it. It will also run even if the device is not in the idle");
+ pw.println(" maintenance mode.");
pw.println(" list features");
pw.println(" Prints all features of the system.");
pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");
@@ -1460,6 +1489,8 @@
pw.println(" -3: filter to only show third party packages");
pw.println(" -i: see the installer for the packages");
pw.println(" -u: also include uninstalled packages");
+ pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE");
+ pw.println(" Reconciles the package secondary dex files with the generated oat files.");
pw.println(" list permission-groups");
pw.println(" Prints all known permission groups.");
pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]");
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 6d06838..01124e2 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,13 +16,21 @@
package com.android.server.pm.dex;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
-import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+import android.os.storage.StorageManager;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.PackageDexOptimizer;
import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageManagerServiceCompilerMapping;
import java.io.File;
import java.io.IOException;
@@ -32,6 +40,9 @@
import java.util.Map;
import java.util.Set;
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
/**
* This class keeps track of how dex files are used.
* Every time it gets a notification about a dex file being loaded it tracks
@@ -54,15 +65,26 @@
// encode and save the dex usage data.
private final PackageDexUsage mPackageDexUsage;
+ private final IPackageManager mPackageManager;
+ private final PackageDexOptimizer mPackageDexOptimizer;
+ private final Object mInstallLock;
+ @GuardedBy("mInstallLock")
+ private final Installer mInstaller;
+
// Possible outcomes of a dex search.
private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk
private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk
private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex
- public DexManager() {
+ public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
+ Installer installer, Object installLock) {
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
+ mPackageManager = pms;
+ mPackageDexOptimizer = pdo;
+ mInstaller = installer;
+ mInstallLock = installLock;
}
/**
@@ -132,8 +154,8 @@
// - new installed splits
// If we can't find the owner of the dex we simply do not track it. The impact is
// that the dex file will not be considered for offline optimizations.
- // TODO(calin): add hooks for install/uninstall notifications to
- // capture new or obsolete packages.
+ // TODO(calin): add hooks for move/uninstall notifications to
+ // capture package moves or obsolete packages.
if (DEBUG) {
Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
}
@@ -157,6 +179,20 @@
}
}
+ public void notifyPackageInstalled(PackageInfo info, int userId) {
+ cachePackageCodeLocation(info, userId);
+ }
+
+ private void cachePackageCodeLocation(PackageInfo info, int userId) {
+ PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName);
+ if (pcl != null) {
+ pcl.mergeAppDataDirs(info.applicationInfo, userId);
+ } else {
+ mPackageCodeLocationsCache.put(info.packageName,
+ new PackageCodeLocations(info.applicationInfo, userId));
+ }
+ }
+
private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) {
Map<String, Set<Integer>> packageToUsersMap = new HashMap<>();
// Cache the code locations for the installed packages. This allows for
@@ -166,13 +202,8 @@
int userId = entry.getKey();
for (PackageInfo pi : packageInfoList) {
// Cache the code locations.
- PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName);
- if (pcl != null) {
- pcl.mergeAppDataDirs(pi.applicationInfo, userId);
- } else {
- mPackageCodeLocationsCache.put(pi.packageName,
- new PackageCodeLocations(pi.applicationInfo, userId));
- }
+ cachePackageCodeLocation(pi, userId);
+
// Cache a map from package name to the set of user ids who installed the package.
// We will use it to sync the data and remove obsolete entries from
// mPackageDexUsage.
@@ -190,11 +221,161 @@
* Get the package dex usage for the given package name.
* @return the package data or null if there is no data available for this package.
*/
- public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
+ public PackageUseInfo getPackageUseInfo(String packageName) {
return mPackageDexUsage.getPackageUseInfo(packageName);
}
/**
+ * Perform dexopt on the package {@code packageName} secondary dex files.
+ * @return true if all secondary dex files were processed successfully (compiled or skipped
+ * because they don't need to be compiled)..
+ */
+ public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
+ // Select the dex optimizer based on the force parameter.
+ // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
+ // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
+ // passing the force flag through the multitude of layers.
+ // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+ // allocate an object here.
+ PackageDexOptimizer pdo = force
+ ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+ : mPackageDexOptimizer;
+ PackageUseInfo useInfo = getPackageUseInfo(packageName);
+ if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "No secondary dex use for package:" + packageName);
+ }
+ // Nothing to compile, return true.
+ return true;
+ }
+ boolean success = true;
+ for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+ String dexPath = entry.getKey();
+ DexUseInfo dexUseInfo = entry.getValue();
+ PackageInfo pkg = null;
+ try {
+ pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+ dexUseInfo.getOwnerUserId());
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ // It may be that the package gets uninstalled while we try to compile its
+ // secondary dex files. If that's the case, just ignore.
+ // Note that we don't break the entire loop because the package might still be
+ // installed for other users.
+ if (pkg == null) {
+ Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+ + " for user " + dexUseInfo.getOwnerUserId());
+ mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
+ continue;
+ }
+ int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
+ dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
+ success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
+ }
+ return success;
+ }
+
+ /**
+ * Reconcile the information we have about the secondary dex files belonging to
+ * {@code packagName} and the actual dex files. For all dex files that were
+ * deleted, update the internal records and delete any generated oat files.
+ */
+ public void reconcileSecondaryDexFiles(String packageName) {
+ PackageUseInfo useInfo = getPackageUseInfo(packageName);
+ if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "No secondary dex use for package:" + packageName);
+ }
+ // Nothing to reconcile.
+ return;
+ }
+ Set<String> dexFilesToRemove = new HashSet<>();
+ boolean updated = false;
+ for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+ String dexPath = entry.getKey();
+ DexUseInfo dexUseInfo = entry.getValue();
+ PackageInfo pkg = null;
+ try {
+ // Note that we look for the package in the PackageManager just to be able
+ // to get back the real app uid and its storage kind. These are only used
+ // to perform extra validation in installd.
+ // TODO(calin): maybe a bit overkill.
+ pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+ dexUseInfo.getOwnerUserId());
+ } catch (RemoteException ignore) {
+ // Can't happen, DexManager is local.
+ }
+ if (pkg == null) {
+ // It may be that the package was uninstalled while we process the secondary
+ // dex files.
+ Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+ + " for user " + dexUseInfo.getOwnerUserId());
+ // Update the usage and continue, another user might still have the package.
+ updated = mPackageDexUsage.removeUserPackage(
+ packageName, dexUseInfo.getOwnerUserId()) || updated;
+ continue;
+ }
+ ApplicationInfo info = pkg.applicationInfo;
+ int flags = 0;
+ if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+ flags |= StorageManager.FLAG_STORAGE_DE;
+ } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+ flags |= StorageManager.FLAG_STORAGE_CE;
+ } else {
+ Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+ updated = mPackageDexUsage.removeUserPackage(
+ packageName, dexUseInfo.getOwnerUserId()) || updated;
+ continue;
+ }
+
+ boolean dexStillExists = true;
+ synchronized(mInstallLock) {
+ try {
+ String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
+ dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
+ pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
+ " : " + e.getMessage());
+ }
+ }
+ if (!dexStillExists) {
+ updated = mPackageDexUsage.removeDexFile(
+ packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
+ }
+
+ }
+ if (updated) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+ }
+
+ /**
+ * Return all packages that contain records of secondary dex files.
+ */
+ public Set<String> getAllPackagesWithSecondaryDexFiles() {
+ return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
+ }
+
+ /**
+ * Return true if the profiling data collected for the given app indicate
+ * that the apps's APK has been loaded by another app.
+ * Note that this returns false for all apps without any collected profiling data.
+ */
+ public boolean isUsedByOtherApps(String packageName) {
+ PackageUseInfo useInfo = getPackageUseInfo(packageName);
+ if (useInfo == null) {
+ // No use info, means the package was not used or it was used but not by other apps.
+ // Note that right now we might prune packages which are not used by other apps.
+ // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
+ // to access the package use.
+ return false;
+ }
+ return useInfo.isUsedByOtherApps();
+ }
+
+ /**
* Retrieves the package which owns the given dexPath.
*/
private DexSearchResult getDexPackage(
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 10384a2..3693bce0 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -376,12 +376,86 @@
}
}
+ /**
+ * Remove all the records about package {@code packageName} belonging to user {@code userId}.
+ * @return true if the record was found and actually deleted,
+ * false if the record doesn't exist
+ */
+ public boolean removeUserPackage(String packageName, int userId) {
+ synchronized (mPackageUseInfoMap) {
+ PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+ if (packageUseInfo == null) {
+ return false;
+ }
+ boolean updated = false;
+ Iterator<Map.Entry<String, DexUseInfo>> dIt =
+ packageUseInfo.mDexUseInfoMap.entrySet().iterator();
+ while (dIt.hasNext()) {
+ DexUseInfo dexUseInfo = dIt.next().getValue();
+ if (dexUseInfo.mOwnerUserId == userId) {
+ dIt.remove();
+ updated = true;
+ }
+ }
+ return updated;
+ }
+ }
+
+ /**
+ * Remove the secondary dex file record belonging to the package {@code packageName}
+ * and user {@code userId}.
+ * @return true if the record was found and actually deleted,
+ * false if the record doesn't exist
+ */
+ public boolean removeDexFile(String packageName, String dexFile, int userId) {
+ synchronized (mPackageUseInfoMap) {
+ PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+ if (packageUseInfo == null) {
+ return false;
+ }
+ return removeDexFile(packageUseInfo, dexFile, userId);
+ }
+ }
+
+ private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) {
+ DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile);
+ if (dexUseInfo == null) {
+ return false;
+ }
+ if (dexUseInfo.mOwnerUserId == userId) {
+ packageUseInfo.mDexUseInfoMap.remove(dexFile);
+ return true;
+ }
+ return false;
+ }
+
public PackageUseInfo getPackageUseInfo(String packageName) {
synchronized (mPackageUseInfoMap) {
- return mPackageUseInfoMap.get(packageName);
+ PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName);
+ // The useInfo contains a map for secondary dex files which could be modified
+ // concurrently after this method returns and thus outside the locking we do here.
+ // (i.e. the map is updated when new class loaders are created, which can happen anytime
+ // after this method returns)
+ // Make a defensive copy to be sure we don't get concurrent modifications.
+ return useInfo == null ? null : new PackageUseInfo(useInfo);
}
}
+ /**
+ * Return all packages that contain records of secondary dex files.
+ */
+ public Set<String> getAllPackagesWithSecondaryDexFiles() {
+ Set<String> packages = new HashSet<>();
+ synchronized (mPackageUseInfoMap) {
+ for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) {
+ if (!entry.getValue().mDexUseInfoMap.isEmpty()) {
+ packages.add(entry.getKey());
+ }
+ }
+ }
+ return packages;
+ }
+
public void clear() {
synchronized (mPackageUseInfoMap) {
mPackageUseInfoMap.clear();
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index b655f3a..90a2ec0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -73,8 +73,7 @@
mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0);
mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1);
-
- mDexManager = new DexManager();
+ mDexManager = new DexManager(null, null, null, null);
// Foo and Bar are available to user0.
// Only Bar is available to user1;
@@ -204,6 +203,46 @@
assertNull(getPackageUseInfo(mBarUser1));
}
+ @Test
+ public void testNotifyPackageInstallUsedByOther() {
+ TestData newPackage = new TestData("newPackage",
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0);
+
+ List<String> newSecondaries = newPackage.getSecondaryDexPaths();
+ // Before we notify about the installation of the newPackage if mFoo
+ // is trying to load something from it we should not find it.
+ notifyDexLoad(mFooUser0, newSecondaries, mUser0);
+ assertNull(getPackageUseInfo(newPackage));
+
+ // Notify about newPackage install and let mFoo load its dexes.
+ mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0);
+ notifyDexLoad(mFooUser0, newSecondaries, mUser0);
+
+ // We should get back the right info.
+ PackageUseInfo pui = getPackageUseInfo(newPackage);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
+ }
+
+ @Test
+ public void testNotifyPackageInstallSelfUse() {
+ TestData newPackage = new TestData("newPackage",
+ VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0);
+
+ List<String> newSecondaries = newPackage.getSecondaryDexPaths();
+ // Packages should be able to find their own dex files even if the notification about
+ // their installation is delayed.
+ notifyDexLoad(newPackage, newSecondaries, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(newPackage);
+ assertNotNull(pui);
+ assertFalse(pui.isUsedByOtherApps());
+ assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
+ assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) {
for (String dex : secondaries) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
index 5a42841..19e0bcf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
@@ -256,6 +256,32 @@
assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
}
+ @Test
+ public void testRemoveUserPackage() {
+ // Record Bar secondaries for two different users.
+ assertTrue(record(mBarSecondary1User0));
+ assertTrue(record(mBarSecondary2User1));
+
+ // Remove user 0 files.
+ assertTrue(mPackageDexUsage.removeUserPackage(mBarSecondary1User0.mPackageName,
+ mBarSecondary1User0.mOwnerUserId));
+ // Assert that only user 1 files are there.
+ assertPackageDexUsage(null, mBarSecondary2User1);
+ }
+
+ @Test
+ public void testRemoveDexFile() {
+ // Record Bar secondaries for two different users.
+ assertTrue(record(mBarSecondary1User0));
+ assertTrue(record(mBarSecondary2User1));
+
+ // Remove mBarSecondary1User0 file.
+ assertTrue(mPackageDexUsage.removeDexFile(mBarSecondary1User0.mPackageName,
+ mBarSecondary1User0.mDexFile, mBarSecondary1User0.mOwnerUserId));
+ // Assert that only user 1 files are there.
+ assertPackageDexUsage(null, mBarSecondary2User1);
+ }
+
private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d67a0d6..98f7ccb 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -321,8 +321,15 @@
*/
public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 0x00000080;
+ /**
+ * Indicates that the call is from a self-managed {@link ConnectionService}.
+ * <p>
+ * See also {@link Connection#PROPERTY_SELF_MANAGED}
+ */
+ public static final int PROPERTY_SELF_MANAGED = 0x00000100;
+
//******************************************************************************************
- // Next PROPERTY value: 0x00000100
+ // Next PROPERTY value: 0x00000200
//******************************************************************************************
private final String mTelecomCallId;
@@ -871,6 +878,7 @@
* party, if it is active.
*/
public static final class RttCall {
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({RTT_MODE_INVALID, RTT_MODE_FULL, RTT_MODE_HCO, RTT_MODE_VCO})
public @interface RttAudioMode {}
@@ -999,6 +1007,7 @@
private int mState;
private List<String> mCannedTextResponses = null;
private String mCallingPackage;
+ private int mTargetSdkVersion;
private String mRemainingPostDialSequence;
private VideoCallImpl mVideoCallImpl;
private RttCall mRttCall;
@@ -1532,22 +1541,25 @@
}
/** {@hide} */
- Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage) {
+ Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage,
+ int targetSdkVersion) {
mPhone = phone;
mTelecomCallId = telecomCallId;
mInCallAdapter = inCallAdapter;
mState = STATE_NEW;
mCallingPackage = callingPackage;
+ mTargetSdkVersion = targetSdkVersion;
}
/** {@hide} */
Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state,
- String callingPackage) {
+ String callingPackage, int targetSdkVersion) {
mPhone = phone;
mTelecomCallId = telecomCallId;
mInCallAdapter = inCallAdapter;
mState = state;
mCallingPackage = callingPackage;
+ mTargetSdkVersion = targetSdkVersion;
}
/** {@hide} */
@@ -1573,7 +1585,8 @@
cannedTextResponsesChanged = true;
}
- VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage);
+ VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage,
+ mTargetSdkVersion);
boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() &&
!Objects.equals(mVideoCallImpl, newVideoCallImpl);
if (videoCallChanged) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 3e690b9..417fa3bf 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -23,6 +23,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.Notification;
+import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.os.Binder;
@@ -1005,7 +1007,7 @@
try {
onSetCamera((String) args.arg1);
onSetCamera((String) args.arg1, (String) args.arg2, args.argi1,
- args.argi2);
+ args.argi2, args.argi3);
} finally {
args.recycle();
}
@@ -1065,7 +1067,9 @@
MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
}
- public void setCamera(String cameraId, String callingPackageName) {
+ public void setCamera(String cameraId, String callingPackageName,
+ int targetSdkVersion) {
+
SomeArgs args = SomeArgs.obtain();
args.arg1 = cameraId;
// Propagate the calling package; originally determined in
@@ -1077,6 +1081,9 @@
// check to see if the calling app is able to use the camera.
args.argi1 = Binder.getCallingUid();
args.argi2 = Binder.getCallingPid();
+ // Pass along the target SDK version of the calling InCallService. This is used to
+ // maintain backwards compatibility of the API for older callers.
+ args.argi3 = targetSdkVersion;
mMessageHandler.obtainMessage(MSG_SET_CAMERA, args).sendToTarget();
}
@@ -1179,10 +1186,11 @@
* @param callingPackageName The AppOpps package name of the caller.
* @param callingUid The UID of the caller.
* @param callingPid The PID of the caller.
+ * @param targetSdkVersion The target SDK version of the caller.
* @hide
*/
public void onSetCamera(String cameraId, String callingPackageName, int callingUid,
- int callingPid) {}
+ int callingPid, int targetSdkVersion) {}
/**
* Sets the surface to be used for displaying a preview of what the user's camera is
@@ -2592,6 +2600,41 @@
* regular {@link ConnectionService}, the Telecom framework will display its own incoming call
* user interface to allow the user to choose whether to answer the new incoming call and
* disconnect other ongoing calls, or to reject the new incoming call.
+ * <p>
+ * You should trigger the display of the incoming call user interface for your application by
+ * showing a {@link Notification} with a full-screen {@link Intent} specified.
+ * For example:
+ * <pre><code>
+ * // Create an intent which triggers your fullscreen incoming call user interface.
+ * Intent intent = new Intent(Intent.ACTION_MAIN, null);
+ * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+ * intent.setClass(context, YourIncomingCallActivity.class);
+ * PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, 0);
+ *
+ * // Build the notification as an ongoing high priority item; this ensures it will show as
+ * // a heads up notification which slides down over top of the current content.
+ * final Notification.Builder builder = new Notification.Builder(context);
+ * builder.setOngoing(true);
+ * builder.setPriority(Notification.PRIORITY_HIGH);
+ *
+ * // Set notification content intent to take user to fullscreen UI if user taps on the
+ * // notification body.
+ * builder.setContentIntent(pendingIntent);
+ * // Set full screen intent to trigger display of the fullscreen UI when the notification
+ * // manager deems it appropriate.
+ * builder.setFullScreenIntent(pendingIntent, true);
+ *
+ * // Setup notification content.
+ * builder.setSmallIcon( yourIconResourceId );
+ * builder.setContentTitle("Your notification title");
+ * builder.setContentText("Your notification content.");
+ *
+ * // Use builder.addAction(..) to add buttons to answer or reject the call.
+ *
+ * NotificationManager notificationManager = mContext.getSystemService(
+ * NotificationManager.class);
+ * notificationManager.notify(YOUR_TAG, YOUR_ID, builder.build());
+ * </code></pre>
*/
public void onShowIncomingCallUi() {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6e10029..79f085c 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -214,6 +214,7 @@
@Override
public void createConnectionFailed(
+ PhoneAccountHandle connectionManagerPhoneAccount,
String callId,
ConnectionRequest request,
boolean isIncoming,
@@ -224,6 +225,7 @@
args.arg1 = callId;
args.arg2 = request;
args.arg3 = Log.createSubsession();
+ args.arg4 = connectionManagerPhoneAccount;
args.argi1 = isIncoming ? 1 : 0;
mHandler.obtainMessage(MSG_CREATE_CONNECTION_FAILED, args).sendToTarget();
} finally {
@@ -581,6 +583,8 @@
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 request %s", id);
mPreInitializationConnectionRequests.add(
@@ -589,12 +593,14 @@
null /*lock*/) {
@Override
public void loggedRun() {
- createConnectionFailed(id, request, isIncoming);
+ createConnectionFailed(connectionMgrPhoneAccount, id,
+ request, isIncoming);
}
}.prepare());
} else {
Log.i(this, "createConnectionFailed %s", id);
- createConnectionFailed(id, request, isIncoming);
+ createConnectionFailed(connectionMgrPhoneAccount, id, request,
+ isIncoming);
}
} finally {
args.recycle();
@@ -1225,14 +1231,15 @@
}
}
- private void createConnectionFailed(final String callId, final ConnectionRequest request,
- boolean isIncoming) {
+ private void createConnectionFailed(final PhoneAccountHandle callManagerAccount,
+ final String callId, final ConnectionRequest request,
+ boolean isIncoming) {
Log.i(this, "createConnectionFailed %s", callId);
if (isIncoming) {
- onCreateIncomingConnectionFailed(request);
+ onCreateIncomingConnectionFailed(callManagerAccount, request);
} else {
- onCreateOutgoingConnectionFailed(request);
+ onCreateOutgoingConnectionFailed(callManagerAccount, request);
}
}
@@ -1682,9 +1689,12 @@
* <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 onCreateIncomingConnectionFailed(ConnectionRequest request) {
+ public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
}
/**
@@ -1698,9 +1708,12 @@
* <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 onCreateOutgoingConnectionFailed(ConnectionRequest request) {
+ public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
+ ConnectionRequest request) {
}
/**
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 4bc64c0..c4f5ffc 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -89,7 +89,8 @@
switch (msg.what) {
case MSG_SET_IN_CALL_ADAPTER:
String callingPackage = getApplicationContext().getOpPackageName();
- mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage);
+ mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,
+ getApplicationContext().getApplicationInfo().targetSdkVersion);
mPhone.addListener(mPhoneListener);
onPhoneCreated(mPhone);
break;
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 975aa5a..85a92d1 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -193,13 +193,16 @@
/**
* Returns an object for remotely communicating through the video call provider's binder.
-
+ *
+ * @param callingPackageName the package name of the calling InCallService.
+ * @param targetSdkVersion the target SDK version of the calling InCallService.
* @return The video call.
*/
- public VideoCallImpl getVideoCallImpl(String callingPackageName) {
+ public VideoCallImpl getVideoCallImpl(String callingPackageName, int targetSdkVersion) {
if (mVideoCall == null && mVideoCallProvider != null) {
try {
- mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName);
+ mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName,
+ targetSdkVersion);
} catch (RemoteException ignored) {
// Ignore RemoteException.
}
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index ebd04c7..524dc70 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -127,14 +127,20 @@
private final String mCallingPackage;
- Phone(InCallAdapter adapter, String callingPackage) {
+ /**
+ * The Target SDK version of the InCallService implementation.
+ */
+ private final int mTargetSdkVersion;
+
+ Phone(InCallAdapter adapter, String callingPackage, int targetSdkVersion) {
mInCallAdapter = adapter;
mCallingPackage = callingPackage;
+ mTargetSdkVersion = targetSdkVersion;
}
final void internalAddCall(ParcelableCall parcelableCall) {
Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
- parcelableCall.getState(), mCallingPackage);
+ parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
mCallByTelecomCallId.put(parcelableCall.getId(), call);
mCalls.add(call);
checkCallTree(parcelableCall);
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 77e0e54..e121238 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -410,6 +410,8 @@
private final String mCallingPackage;
+ private final int mTargetSdkVersion;
+
/**
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
* load factor before resizing, 1 means we only expect a single thread to
@@ -418,9 +420,12 @@
private final Set<Callback> mCallbacks = Collections.newSetFromMap(
new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
- VideoProvider(IVideoProvider videoProviderBinder, String callingPackage) {
+ VideoProvider(IVideoProvider videoProviderBinder, String callingPackage,
+ int targetSdkVersion) {
+
mVideoProviderBinder = videoProviderBinder;
mCallingPackage = callingPackage;
+ mTargetSdkVersion = targetSdkVersion;
try {
mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
} catch (RemoteException e) {
@@ -455,7 +460,7 @@
*/
public void setCamera(String cameraId) {
try {
- mVideoProviderBinder.setCamera(cameraId, mCallingPackage);
+ mVideoProviderBinder.setCamera(cameraId, mCallingPackage, mTargetSdkVersion);
} catch (RemoteException e) {
}
}
@@ -631,7 +636,7 @@
* @hide
*/
RemoteConnection(String callId, IConnectionService connectionService,
- ParcelableConnection connection, String callingPackage) {
+ ParcelableConnection connection, String callingPackage, int targetSdkVersion) {
mConnectionId = callId;
mConnectionService = connectionService;
mConnected = true;
@@ -643,7 +648,8 @@
mVideoState = connection.getVideoState();
IVideoProvider videoProvider = connection.getVideoProvider();
if (videoProvider != null) {
- mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage);
+ mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage,
+ targetSdkVersion);
} else {
mVideoProvider = null;
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 60a40f5..b00c0aa 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -286,10 +286,11 @@
String callingPackage = mOurConnectionServiceImpl.getApplicationContext()
.getOpPackageName();
+ int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion;
RemoteConnection.VideoProvider remoteVideoProvider = null;
if (videoProvider != null) {
remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider,
- callingPackage);
+ callingPackage, targetSdkVersion);
}
findConnectionForAction(callId, "setVideoProvider")
.setVideoProvider(remoteVideoProvider);
@@ -357,8 +358,11 @@
Session.Info sessionInfo) {
String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
getOpPackageName();
+ int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
+ .targetSdkVersion;
RemoteConnection remoteConnection = new RemoteConnection(callId,
- mOutgoingConnectionServiceRpc, connection, callingPackage);
+ mOutgoingConnectionServiceRpc, connection, callingPackage,
+ callingTargetSdkVersion);
mConnectionById.put(callId, remoteConnection);
remoteConnection.registerCallback(new RemoteConnection.Callback() {
@Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6807ef4..e21b4db 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -373,6 +373,24 @@
"android.telecom.INCLUDE_EXTERNAL_CALLS";
/**
+ * A boolean meta-data value indicating whether an {@link InCallService} wants to be informed of
+ * calls which have the {@link Call.Details#PROPERTY_SELF_MANAGED} property. A self-managed
+ * call is one which originates from a self-managed {@link ConnectionService} which has chosen
+ * to implement its own call user interface. An {@link InCallService} implementation which
+ * would like to be informed of external calls should set this meta-data to {@code true} in the
+ * manifest registration of their {@link InCallService}. By default, the {@link InCallService}
+ * will NOT be informed about self-managed calls.
+ * <p>
+ * An {@link InCallService} which receives self-managed calls is free to view and control the
+ * state of calls in the self-managed {@link ConnectionService}. An example use-case is
+ * exposing these calls to a wearable or automotive device via its companion app.
+ * <p>
+ * See also {@link Connection#PROPERTY_SELF_MANAGED}.
+ */
+ public static final String METADATA_INCLUDE_SELF_MANAGED_CALLS =
+ "android.telecom.INCLUDE_SELF_MANAGED_CALLS";
+
+ /**
* The dual tone multi-frequency signaling character sent to indicate the dialing system should
* pause for a predefined period.
*/
@@ -1051,10 +1069,12 @@
/**
* Returns whether there is an ongoing phone call (can be in dialing, ringing, active or holding
- * states).
+ * states) originating from either a manager or self-managed {@link ConnectionService}.
* <p>
* Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
- * </p>
+ *
+ * @return {@code true} if there is an ongoing call in either a managed or self-managed
+ * {@link ConnectionService}, {@code false} otherwise.
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public boolean isInCall() {
@@ -1069,6 +1089,31 @@
}
/**
+ * Returns whether there is an ongoing call originating from a managed
+ * {@link ConnectionService}. An ongoing call can be in dialing, ringing, active or holding
+ * states.
+ * <p>
+ * If you also need to know if there are ongoing self-managed calls, use {@link #isInCall()}
+ * instead.
+ * <p>
+ * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
+ *
+ * @return {@code true} if there is an ongoing call in a managed {@link ConnectionService},
+ * {@code false} otherwise.
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public boolean isInManagedCall() {
+ try {
+ if (isServiceConnected()) {
+ return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException calling isInManagedCall().", e);
+ }
+ return false;
+ }
+
+ /**
* Returns one of the following constants that represents the current state of Telecom:
*
* {@link TelephonyManager#CALL_STATE_RINGING}
@@ -1079,6 +1124,9 @@
* {@link android.Manifest.permission#READ_PHONE_STATE} permission. This is intentional, to
* preserve the behavior of {@link TelephonyManager#getCallState()}, which also did not require
* the permission.
+ *
+ * Takes into consideration both managed and self-managed calls.
+ *
* @hide
*/
@SystemApi
@@ -1096,6 +1144,7 @@
/**
* Returns whether there currently exists is a ringing incoming-call.
*
+ * @return {@code true} if there is a managed or self-managed ringing call.
* @hide
*/
@SystemApi
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index d8ede5c..429a434 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -44,6 +44,7 @@
private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN;
private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
private final String mCallingPackageName;
+ private final int mTargetSdkVersion;
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
@@ -198,13 +199,15 @@
private Handler mHandler;
- VideoCallImpl(IVideoProvider videoProvider, String callingPackageName) throws RemoteException {
+ VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion)
+ throws RemoteException {
mVideoProvider = videoProvider;
mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
mBinder = new VideoCallListenerBinder();
mVideoProvider.addVideoCallback(mBinder);
mCallingPackageName = callingPackageName;
+ mTargetSdkVersion = targetSdkVersion;
}
public void destroy() {
@@ -243,7 +246,7 @@
public void setCamera(String cameraId) {
try {
Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName);
- mVideoProvider.setCamera(cameraId, mCallingPackageName);
+ mVideoProvider.setCamera(cameraId, mCallingPackageName, mTargetSdkVersion);
} catch (RemoteException e) {
}
}
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 20feba7..6df4067 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -46,8 +46,8 @@
boolean isUnknown,
in Session.Info sessionInfo);
- void createConnectionFailed(String callId, in ConnectionRequest request, boolean isIncoming,
- in Session.Info sessionInfo);
+ void createConnectionFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId,
+ in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo);
void abort(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 d9465dc..c044742 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -165,6 +165,11 @@
boolean isInCall(String callingPackage);
/**
+ * @see TelecomServiceImpl#isInManagedCall
+ */
+ boolean isInManagedCall(String callingPackage);
+
+ /**
* @see TelecomServiceImpl#isRinging
*/
boolean isRinging(String callingPackage);
diff --git a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
index a109e90..272b884 100644
--- a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
+++ b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl
@@ -30,7 +30,7 @@
void removeVideoCallback(IBinder videoCallbackBinder);
- void setCamera(String cameraId, in String mCallingPackageName);
+ void setCamera(String cameraId, in String mCallingPackageName, int targetSdkVersion);
void setPreviewSurface(in Surface surface);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 02774b3..1076afc 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1127,6 +1127,14 @@
public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL =
"support_3gpp_call_forwarding_while_roaming_bool";
+ /**
+ * When {@code true}, the user will be notified when they attempt to place an international call
+ * when the call is placed using wifi calling.
+ * @hide
+ */
+ public static final String KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL =
+ "notify_international_call_on_wfc_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1332,6 +1340,7 @@
sDefaults.putStringArray(KEY_FILTERED_CNAP_NAMES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+ sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
}
/**
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 38cffae..2eba402 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1439,6 +1439,30 @@
}
/**
+ * Determines if a {@param phoneNumber} is international if dialed from
+ * {@param defaultCountryIso}.
+ *
+ * @param phoneNumber The phone number.
+ * @param defaultCountryIso The current country ISO.
+ * @return {@code true} if the number is international, {@code false} otherwise.
+ * @hide
+ */
+ public static boolean isInternationalNumber(String phoneNumber, String defaultCountryIso) {
+ // If it starts with # or * its not international.
+ if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
+ return false;
+ }
+
+ PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+ try {
+ PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
+ return pn.getCountryCode() != util.getCountryCodeForRegion(defaultCountryIso);
+ } catch (NumberParseException e) {
+ return false;
+ }
+ }
+
+ /**
* Format a phone number.
* <p>
* If the given number doesn't have the country code, the phone will be
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8479f88..c691879 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -780,6 +780,21 @@
"android.telephony.event.EVENT_DOWNGRADE_DATA_DISABLED";
/**
+ * {@link android.telecom.Connection} event used to indicate that the InCall UI should notify
+ * the user when an international call is placed while on WFC only.
+ * <p>
+ * Used when the carrier config value
+ * {@link CarrierConfigManager#KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL} is true, the device
+ * is on WFC (VoLTE not available) and an international number is dialed.
+ * <p>
+ * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}.
+ * The {@link Bundle} parameter is expected to be null when this connection event is used.
+ * @hide
+ */
+ public static final String EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC =
+ "android.telephony.event.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC";
+
+ /**
* Response codes for sim activation. Activation completed successfully.
* @hide
*/
@@ -3915,9 +3930,8 @@
}
}
- if (property.length() > SystemProperties.PROP_NAME_MAX
- || propVal.length() > SystemProperties.PROP_VALUE_MAX) {
- Rlog.d(TAG, "setTelephonyProperty: property to long phoneId=" + phoneId +
+ if (propVal.length() > SystemProperties.PROP_VALUE_MAX) {
+ Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId +
" property=" + property + " value: " + value + " propVal=" + propVal);
return;
}
@@ -4174,6 +4188,45 @@
}
/**
+ * Returns an array of Forbidden PLMNs from the USIM App
+ * Returns null if the query fails.
+ *
+ *
+ * <p>Requires that the caller has READ_PRIVILEGED_PHONE_STATE
+ *
+ * @return an array of forbidden PLMNs or null if not available
+ */
+ public String[] getForbiddenPlmns() {
+ return getForbiddenPlmns(getSubId(), APPTYPE_USIM);
+ }
+
+ /**
+ * Returns an array of Forbidden PLMNs from the specified SIM App
+ * Returns null if the query fails.
+ *
+ *
+ * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+ *
+ * @param subId subscription ID used for authentication
+ * @param appType the icc application type, like {@link #APPTYPE_USIM}
+ * @return fplmns an array of forbidden PLMNs
+ * @hide
+ */
+ public String[] getForbiddenPlmns(int subId, int appType) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null)
+ return null;
+ return telephony.getForbiddenPlmns(subId, appType);
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone starts
+ return null;
+ }
+ }
+
+ /**
* Get P-CSCF address from PCO after data connection is established or modified.
* @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN
* @return array of P-CSCF address
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 406f01e..f1f683c 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
@@ -43,6 +44,7 @@
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
/**
* Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
@@ -137,7 +139,7 @@
@Override
public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
throws RemoteException {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, "isConnected");
+ enforceReadPhoneStatePermission("isConnected");
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
@@ -149,7 +151,7 @@
@Override
public boolean isOpened(int slotId, int featureType) throws RemoteException {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, "isOpened");
+ enforceReadPhoneStatePermission("isOpened");
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
@@ -161,7 +163,7 @@
@Override
public int getFeatureStatus(int slotId, int featureType) throws RemoteException {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, "getFeatureStatus");
+ enforceReadPhoneStatePermission("getFeatureStatus");
int status = ImsFeature.STATE_NOT_AVAILABLE;
synchronized (mFeatures) {
SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
@@ -178,7 +180,7 @@
@Override
public void addRegistrationListener(int slotId, int featureType,
IImsRegistrationListener listener) throws RemoteException {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, "addRegistrationListener");
+ enforceReadPhoneStatePermission("addRegistrationListener");
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
@@ -190,7 +192,7 @@
@Override
public void removeRegistrationListener(int slotId, int featureType,
IImsRegistrationListener listener) throws RemoteException {
- enforceCallingOrSelfPermission(READ_PHONE_STATE, "removeRegistrationListener");
+ enforceReadPhoneStatePermission("removeRegistrationListener");
synchronized (mFeatures) {
MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
if (feature != null) {
@@ -351,6 +353,8 @@
}
ImsFeature f = makeImsFeature(slotId, featureType);
if (f != null) {
+ f.setContext(this);
+ f.setSlotId(slotId);
f.setImsFeatureStatusCallback(c);
featureMap.put(featureType, f);
}
@@ -434,6 +438,17 @@
}
/**
+ * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a
+ * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps.
+ */
+ private void enforceReadPhoneStatePermission(String fn) {
+ if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ enforceCallingOrSelfPermission(READ_PHONE_STATE, fn);
+ }
+ }
+
+ /**
* @return An implementation of MMTelFeature that will be used by the system for MMTel
* functionality. Must be able to handle emergency calls at any time as well.
*/
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 8d7d260..988dd58 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -17,7 +17,10 @@
package android.telephony.ims.feature;
import android.annotation.IntDef;
+import android.content.Context;
+import android.content.Intent;
import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
import android.util.Log;
import com.android.ims.internal.IImsFeatureStatusCallback;
@@ -35,6 +38,32 @@
private static final String LOG_TAG = "ImsFeature";
+ /**
+ * Action to broadcast when ImsService is up.
+ * Internal use only.
+ * Only defined here separately compatibility purposes with the old ImsService.
+ * @hide
+ */
+ public static final String ACTION_IMS_SERVICE_UP =
+ "com.android.ims.IMS_SERVICE_UP";
+
+ /**
+ * Action to broadcast when ImsService is down.
+ * Internal use only.
+ * Only defined here separately for compatibility purposes with the old ImsService.
+ * @hide
+ */
+ public static final String ACTION_IMS_SERVICE_DOWN =
+ "com.android.ims.IMS_SERVICE_DOWN";
+
+ /**
+ * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents.
+ * A long value; the phone ID corresponding to the IMS service coming up or down.
+ * Only defined here separately for compatibility purposes with the old ImsService.
+ * @hide
+ */
+ public static final String EXTRA_PHONE_ID = "android:phone_id";
+
// Invalid feature value
public static final int INVALID = -1;
// ImsFeatures that are defined in the Manifests. Ensure that these values match the previously
@@ -61,11 +90,21 @@
private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
private IImsFeatureStatusCallback mStatusCallback;
private @ImsState int mState = STATE_NOT_AVAILABLE;
+ private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+ private Context mContext;
public interface INotifyFeatureRemoved {
void onFeatureRemoved(int slotId);
}
+ public void setContext(Context context) {
+ mContext = context;
+ }
+
+ public void setSlotId(int slotId) {
+ mSlotId = slotId;
+ }
+
public void addFeatureRemovedListener(INotifyFeatureRemoved listener) {
synchronized (mRemovedListeners) {
mRemovedListeners.add(listener);
@@ -118,6 +157,30 @@
Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
}
}
+ sendImsServiceIntent(state);
+ }
+
+ /**
+ * Provide backwards compatibility using deprecated service UP/DOWN intents.
+ */
+ private void sendImsServiceIntent(@ImsState int state) {
+ if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+ return;
+ }
+ Intent intent;
+ switch (state) {
+ case ImsFeature.STATE_NOT_AVAILABLE:
+ case ImsFeature.STATE_INITIALIZING:
+ intent = new Intent(ACTION_IMS_SERVICE_DOWN);
+ break;
+ case ImsFeature.STATE_READY:
+ intent = new Intent(ACTION_IMS_SERVICE_UP);
+ break;
+ default:
+ intent = new Intent(ACTION_IMS_SERVICE_DOWN);
+ }
+ intent.putExtra(EXTRA_PHONE_ID, mSlotId);
+ mContext.sendBroadcast(intent);
}
/**
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 42a80b7..d21efc6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1218,7 +1218,6 @@
*/
void setPolicyDataEnabled(boolean enabled, int subId);
-
/**
* Get Client request stats which will contain statistical information
* on each request made by client.
@@ -1235,4 +1234,16 @@
* @hide
* */
void setSimPowerStateForSlot(int slotId, boolean powerUp);
+
+ /**
+ * Returns a list of Forbidden PLMNs from the specified SIM App
+ * Returns null if the query fails.
+ *
+ *
+ * <p>Requires that the calling app has READ_PRIVILEGED_PHONE_STATE
+ *
+ * @param subId subscription ID used for authentication
+ * @param appType the icc application type, like {@link #APPTYPE_USIM}
+ */
+ String[] getForbiddenPlmns(int subId, int appType);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 81ecdc9..1e1d7a6 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -411,6 +411,8 @@
int RIL_REQUEST_GET_ACTIVITY_INFO = 135;
int RIL_REQUEST_SET_ALLOWED_CARRIERS = 136;
int RIL_REQUEST_GET_ALLOWED_CARRIERS = 137;
+ int RIL_REQUEST_SEND_DEVICE_STATE = 138;
+ int RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER = 139;
int RIL_REQUEST_SET_SIM_CARD_POWER = 140;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 39406a11..b033382 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -369,6 +369,11 @@
connect(false);
}
+ public void suspend() {
+ mNetworkInfo.setDetailedState(DetailedState.SUSPENDED, null, null);
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ }
+
public void disconnect() {
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
@@ -1048,6 +1053,7 @@
AVAILABLE,
NETWORK_CAPABILITIES,
LINK_PROPERTIES,
+ SUSPENDED,
LOSING,
LOST,
UNAVAILABLE
@@ -1061,7 +1067,7 @@
state = s; network = n; arg = o;
}
public String toString() {
- return String.format("%s (%s)", state, network);
+ return String.format("%s (%s) (%s)", state, network, arg);
}
@Override
public boolean equals(Object o) {
@@ -1099,11 +1105,26 @@
}
@Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
+ setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
+ setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
+ }
+
+ @Override
public void onUnavailable() {
setLastCallback(CallbackState.UNAVAILABLE, null, null);
}
@Override
+ public void onNetworkSuspended(Network network) {
+ setLastCallback(CallbackState.SUSPENDED, network, null);
+ }
+
+ @Override
public void onLosing(Network network, int maxMsToLive) {
setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
}
@@ -1126,11 +1147,12 @@
return cb;
}
- void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) {
- CallbackInfo expected = new CallbackInfo(
- state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0);
+ CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent, int timeoutMs) {
+ final Network expectedNetwork = (agent != null) ? agent.getNetwork() : null;
+ CallbackInfo expected = new CallbackInfo(state, expectedNetwork, 0);
CallbackInfo actual = nextCallback(timeoutMs);
assertEquals("Unexpected callback:", expected, actual);
+
if (state == CallbackState.LOSING) {
String msg = String.format(
"Invalid linger time value %d, must be between %d and %d",
@@ -1138,10 +1160,46 @@
int maxMsToLive = (Integer) actual.arg;
assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= TEST_LINGER_DELAY_MS);
}
+
+ return actual;
}
- void expectCallback(CallbackState state, MockNetworkAgent mockAgent) {
- expectCallback(state, mockAgent, TIMEOUT_MS);
+ CallbackInfo expectCallback(CallbackState state, MockNetworkAgent agent) {
+ return expectCallback(state, agent, TIMEOUT_MS);
+ }
+
+ void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+ expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
+ if (expectSuspended) {
+ expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
+ }
+ expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
+ expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
+ }
+
+ void expectAvailableCallbacks(MockNetworkAgent agent) {
+ expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+ }
+
+ void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) {
+ expectAvailableCallbacks(agent, true, TIMEOUT_MS);
+ }
+
+ void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) {
+ expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+ expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+ }
+
+ void expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
+ CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+ NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
+ assertTrue(nc.hasCapability(capability));
+ }
+
+ void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
+ CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
+ NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
+ assertFalse(nc.hasCapability(capability));
}
void assertNoCallback() {
@@ -1178,8 +1236,8 @@
ConditionVariable cv = waitForConnectivityBroadcasts(1);
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false);
- genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
waitFor(cv);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1193,8 +1251,8 @@
cv = waitForConnectivityBroadcasts(2);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
waitFor(cv);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1217,8 +1275,8 @@
// Test validated networks
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1230,9 +1288,10 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- genericNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
- wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1268,28 +1327,32 @@
mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
mCellNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.connect(true);
// We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
// We then get LOSING when wifi validates and cell is outscored.
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ // TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+ callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+ // TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mEthernetNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
for (int i = 0; i < 4; i++) {
MockNetworkAgent oldNetwork, newNetwork;
@@ -1306,7 +1369,7 @@
callback.expectCallback(CallbackState.LOSING, oldNetwork);
// TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
// longer lingering?
- defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork);
+ defaultCallback.expectAvailableCallbacks(newNetwork);
assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
}
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1314,17 +1377,19 @@
// Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even
// if the network is still up.
mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
+ // We expect a notification about the capabilities change, and nothing else.
+ defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent);
+ defaultCallback.assertNoCallback();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Wifi no longer satisfies our listen, which is for an unmetered network.
// But because its score is 55, it's still up (and the default network).
- defaultCallback.assertNoCallback();
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Disconnect our test networks.
mWiFiNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1340,22 +1405,22 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false); // Score: 10
- callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ callback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi with a score of 20.
// Cell stays up because it would satisfy the default request if it validated.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false); // Score: 20
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi with a score of 70.
@@ -1363,31 +1428,33 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.adjustScore(50);
mWiFiNetworkAgent.connect(false); // Score: 70
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Tear down wifi.
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
// it's arguably correct to linger it, since it was the default network before it validated.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ // TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1395,13 +1462,15 @@
// If a network is lingering, and we add and remove a request from it, resume lingering.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ // TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
@@ -1417,7 +1486,7 @@
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
// Cell is now the default network. Pin it with a cell-specific request.
noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1426,8 +1495,8 @@
// Now connect wifi, and expect it to become the default network.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
// The default request is lingering on cell, but nothing happens to cell, and we send no
// callbacks for it, because it's kept up by cellRequest.
callback.assertNoCallback();
@@ -1613,7 +1682,7 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mCellNetworkAgent.connectWithoutInternet();
- networkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
verifyActiveNetwork(TRANSPORT_WIFI);
// Test releasing NetworkRequest disconnects cellular with MMS
cv = mCellNetworkAgent.getDisconnectedCV();
@@ -1639,7 +1708,7 @@
MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mmsNetworkAgent.connectWithoutInternet();
- networkCallback.expectCallback(CallbackState.AVAILABLE, mmsNetworkAgent);
+ networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
verifyActiveNetwork(TRANSPORT_CELLULAR);
// Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
cv = mmsNetworkAgent.getDisconnectedCV();
@@ -1665,7 +1734,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
- captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
// Take down network.
@@ -1678,7 +1747,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String secondRedirectUrl = "http://example.com/secondPath";
mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
- captivePortalCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
// Make captive portal disappear then revalidate.
@@ -1688,7 +1757,9 @@
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ // TODO: Investigate only sending available callbacks.
+ validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -1733,7 +1804,7 @@
mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
// Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
// But there should be no CaptivePortal callback.
captivePortalCallback.assertNoCallback();
}
@@ -1786,14 +1857,14 @@
// Bring up cell and expect CALLBACK_AVAILABLE.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
// Bring up wifi and expect CALLBACK_AVAILABLE.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
cellNetworkCallback.assertNoCallback();
- defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
// Bring down cell. Expect no default network callback, since it wasn't the default.
mCellNetworkAgent.disconnect();
@@ -1803,7 +1874,7 @@
// Bring up cell. Expect no default network callback, since it won't be the default.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
// Bring down wifi. Expect the default network callback to notified of LOST wifi
@@ -1811,28 +1882,16 @@
mWiFiNetworkAgent.disconnect();
cellNetworkCallback.assertNoCallback();
defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
}
- private class TestRequestUpdateCallback extends TestNetworkCallback {
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
- setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
- }
-
- @Override
- public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
- setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
- }
- }
-
@SmallTest
- public void testRequestCallbackUpdates() throws Exception {
+ public void testAdditionalStateCallbacks() throws Exception {
// File a network request for mobile.
- final TestNetworkCallback cellNetworkCallback = new TestRequestUpdateCallback();
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
final NetworkRequest cellRequest = new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build();
mCm.requestNetwork(cellRequest, cellNetworkCallback);
@@ -1841,10 +1900,10 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- // We should get onAvailable().
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- // We should get onCapabilitiesChanged(), when the mobile network successfully validates.
- cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
+ // We should get onAvailable(), onCapabilitiesChanged(), and
+ // onLinkPropertiesChanged() in rapid succession. Additionally, we
+ // should get onCapabilitiesChanged() when the mobile network validates.
+ cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
// Update LinkProperties.
@@ -1855,23 +1914,17 @@
cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
+ // Suspend the network.
+ mCellNetworkAgent.suspend();
+ cellNetworkCallback.expectCallback(CallbackState.SUSPENDED, mCellNetworkAgent);
+ cellNetworkCallback.assertNoCallback();
+
// Register a garden variety default network request.
- final TestNetworkCallback dfltNetworkCallback = new TestRequestUpdateCallback();
+ final TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
- // Only onAvailable() is called; no other information is delivered.
- dfltNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- dfltNetworkCallback.assertNoCallback();
-
- // Request a NetworkCapabilities update; only the requesting callback is notified.
- mCm.requestNetworkCapabilities(dfltNetworkCallback);
- dfltNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
- cellNetworkCallback.assertNoCallback();
- dfltNetworkCallback.assertNoCallback();
-
- // Request a LinkProperties update; only the requesting callback is notified.
- mCm.requestLinkProperties(dfltNetworkCallback);
- dfltNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
- cellNetworkCallback.assertNoCallback();
+ // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
+ // as well as onNetworkSuspended() in rapid succession.
+ dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent);
dfltNetworkCallback.assertNoCallback();
mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -1911,18 +1964,20 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
// When wifi connects, cell lingers.
- callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- fgCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ callback.expectAvailableCallbacks(mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
+ fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -1930,7 +1985,8 @@
mService.waitForIdle();
int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4;
fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent, timeoutMs);
- callback.assertNoCallback();
+ // Expect a network capabilities update sans FOREGROUND.
+ callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
assertFalse(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -1939,9 +1995,15 @@
.addTransportType(TRANSPORT_CELLULAR).build();
final TestNetworkCallback cellCallback = new TestNetworkCallback();
mCm.requestNetwork(cellRequest, cellCallback);
- cellCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- callback.assertNoCallback(); // Because the network is already up.
+ // NOTE: This request causes the network's capabilities to change. This
+ // is currently delivered before the onAvailable() callbacks.
+ // TODO: Fix this.
+ cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
+ cellCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ // Expect a network capabilities update with FOREGROUND, because the most recent
+ // request causes its state to change.
+ callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -1949,7 +2011,8 @@
// lingering.
mCm.unregisterNetworkCallback(cellCallback);
fgCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- callback.assertNoCallback();
+ // Expect a network capabilities update sans FOREGROUND.
+ callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
assertFalse(isForegroundNetwork(mCellNetworkAgent));
assertTrue(isForegroundNetwork(mWiFiNetworkAgent));
@@ -1957,7 +2020,7 @@
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- fgCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
mCm.unregisterNetworkCallback(callback);
@@ -2098,7 +2161,7 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
testFactory.expectAddRequests(2); // Because the cell request changes score twice.
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
testFactory.waitForNetworkRequests(2);
assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us.
@@ -2189,20 +2252,22 @@
// Bring up validated cell.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
Network cellNetwork = mCellNetworkAgent.getNetwork();
// Bring up validated wifi.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
mCm.reportNetworkConnectivity(wifiNetwork, false);
+ defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Because avoid bad wifi is off, we don't switch to cellular.
@@ -2217,18 +2282,18 @@
// that we switch back to cell.
tracker.configRestrictsAvoidBadWifi = false;
tracker.reevaluate();
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Switch back to a restrictive carrier.
tracker.configRestrictsAvoidBadWifi = true;
tracker.reevaluate();
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
// Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
mCm.setAvoidUnvalidated(wifiNetwork);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2239,13 +2304,15 @@
mWiFiNetworkAgent.disconnect();
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
- validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
mCm.reportNetworkConnectivity(wifiNetwork, false);
+ defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Simulate the user selecting "switch" and checking the don't ask again checkbox.
@@ -2253,7 +2320,7 @@
tracker.reevaluate();
// We now switch to cell.
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2264,17 +2331,17 @@
// We switch to wifi and then to cell.
Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
tracker.reevaluate();
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
tracker.reevaluate();
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// If cell goes down, we switch to wifi.
mCellNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
validatedWifiCallback.assertNoCallback();
mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2296,7 +2363,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, timeoutMs);
+ networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs);
// pass timeout and validate that UNAVAILABLE is not called
networkCallback.assertNoCallback();
@@ -2317,7 +2384,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
final int assertTimeoutMs = 150;
- networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent, assertTimeoutMs);
+ networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
sleepFor(20);
mWiFiNetworkAgent.disconnect();
networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 2ba573c..6095c86 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -376,7 +376,9 @@
/**
* Flag indicating if this network is provided by a home Passpoint provider or a roaming
- * Passpoint provider.
+ * Passpoint provider. This flag will be {@code true} if this network is provided by
+ * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
+ * or is a non-Passpoint network.
*/
public boolean isHomeProviderNetwork;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index f790332..4268f24 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -236,11 +236,11 @@
public static final int TTLS = 2;
/** EAP-Password */
public static final int PWD = 3;
- /** EAP-Subscriber Identity Module */
+ /** EAP-Subscriber Identity Module [RFC-4186] */
public static final int SIM = 4;
- /** EAP-Authentication and Key Agreement */
+ /** EAP-Authentication and Key Agreement [RFC-4187] */
public static final int AKA = 5;
- /** EAP-Authentication and Key Agreement Prime */
+ /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
public static final int AKA_PRIME = 6;
/** Hotspot 2.0 r2 OSEN */
public static final int UNAUTH_TLS = 7;
@@ -263,11 +263,11 @@
public static final int MSCHAPV2 = 3;
/** Generic Token Card */
public static final int GTC = 4;
- /** EAP-Subscriber Identity Module */
+ /** EAP-Subscriber Identity Module [RFC-4186] */
public static final int SIM = 5;
- /** EAP-Authentication and Key Agreement */
+ /** EAP-Authentication and Key Agreement [RFC-4187] */
public static final int AKA = 6;
- /** EAP-Authentication and Key Agreement Prime */
+ /** EAP-Authentication and Key Agreement Prime [RFC-5448] */
public static final int AKA_PRIME = 7;
private static final String AUTH_PREFIX = "auth=";
private static final String AUTHEAP_PREFIX = "autheap=";
@@ -756,8 +756,8 @@
* key entry when the config is saved and removing the key entry when
* the config is removed.
- * @param privateKey
- * @param clientCertificate
+ * @param privateKey a PrivateKey instance for the end certificate.
+ * @param clientCertificate an X509Certificate representing the end certificate.
* @throws IllegalArgumentException for an invalid key or certificate.
*/
public void setClientKeyEntry(PrivateKey privateKey, X509Certificate clientCertificate) {
@@ -775,9 +775,11 @@
* with this configuration. The framework takes care of installing the
* key entry when the config is saved and removing the key entry when
* the config is removed.
-
- * @param privateKey
- * @param clientCertificateChain
+ *
+ * @param privateKey a PrivateKey instance for the end certificate.
+ * @param clientCertificateChain an array of X509Certificate instances which starts with
+ * end certificate and continues with additional CA certificates necessary to
+ * link the end certificate with some root certificate known by the authenticator.
* @throws IllegalArgumentException for an invalid key or certificate.
*/
public void setClientKeyEntryWithCertificateChain(PrivateKey privateKey,
@@ -835,7 +837,15 @@
}
/**
- * Get the complete client certificate chain
+ * Get the complete client certificate chain in the same order as it was last supplied.
+ *
+ * <p>If the chain was last supplied by a call to
+ * {@link #setClientKeyEntry(java.security.PrivateKey, java.security.cert.X509Certificate)}
+ * with a non-null * certificate instance, a single-element array containing the certificate
+ * will be * returned. If {@link #setClientKeyEntryWithCertificateChain(
+ * java.security.PrivateKey, java.security.cert.X509Certificate[])} was last called with a
+ * non-empty array, this array will be returned in the same order as it was supplied.
+ * Otherwise, {@code null} will be returned.
*
* @return X.509 client certificates
*/
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 57b98e9..59fe1ee 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -31,7 +31,7 @@
* {@link PublishDiscoverySession} and {@link SubscribeDiscoverySession}. This
* class provides functionality common to both publish and subscribe discovery sessions:
* <ul>
- * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])}.
+ * <li>Sending messages: {@link #sendMessage(PeerHandle, int, byte[])} method.
* <li>Creating a network-specifier when requesting a Aware connection:
* {@link #createNetworkSpecifier(PeerHandle, byte[])}.
* </ul>
@@ -247,8 +247,8 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for a
- * WiFi Aware connection to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
+ * unencrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
@@ -256,7 +256,58 @@
* discovery or communication (in such scenarios the MAC address of the peer is shielded by
* an opaque peer ID handle). If a Aware connection is needed to a peer discovered using other
* OOB (out-of-band) mechanism then use the alternative
- * {@link WifiAwareSession#createNetworkSpecifier(int, byte[], byte[])} method - which uses the
+ * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])} method - which uses the
+ * peer's MAC address.
+ * <p>
+ * Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
+ * and a Publisher is a RESPONDER.
+ *
+ * @param peerHandle The peer's handle obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], java.util.List)}
+ * or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
+ * On a RESPONDER this value is used to gate the acceptance of a connection
+ * request from only that peer. A RESPONDER may specify a null - indicating
+ * that it will accept connection requests from any device.
+ *
+ * @return A string to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ *
+ * @hide
+ */
+ public String createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
+ if (mTerminated) {
+ Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
+ return null;
+ } else {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.w(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
+ return null;
+ }
+
+ int role = this instanceof SubscribeDiscoverySession
+ ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
+ : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+
+ return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, null);
+ }
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
+ * encrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This method should be used when setting up a connection with a peer discovered through Aware
+ * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+ * an opaque peer ID handle). If a Aware connection is needed to a peer discovered using other
+ * OOB (out-of-band) mechanism then use the alternative
+ * {@link WifiAwareSession#createNetworkSpecifierPmk(int, byte[], byte[])} method - which uses the
* peer's MAC address.
* <p>
* Note: per the Wi-Fi Aware specification the roles are fixed - a Subscriber is an INITIATOR
@@ -267,29 +318,34 @@
* byte[], java.util.List)} or
* {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
* byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
- * from only that peer. A RESPONDER may specified a null - indicating that
+ * from only that peer. A RESPONDER may specify a null - indicating that
* it will accept connection requests from any device.
- * @param token An arbitrary token (message) to be used to match connection initiation request
- * to a responder setup. A RESPONDER is set up with a {@code token} which must
- * be matched by the token provided by the INITIATOR. A null token is permitted
- * on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
- * not the same as a null token and requires the peer token to be empty as well.
+ * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+ * encrypting the data-path. Use the
+ * {@link #createNetworkSpecifierOpen(PeerHandle)} to specify an open (unencrypted)
+ * link.
*
* @return A string to be used to construct
* {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
+ *
+ * @hide
*/
- public String createNetworkSpecifier(@Nullable PeerHandle peerHandle,
- @Nullable byte[] token) {
+ public String createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
+ @NonNull byte[] pmk) {
+ if (pmk == null || pmk.length == 0) {
+ throw new IllegalArgumentException("PMK must not be null or empty");
+ }
+
if (mTerminated) {
- Log.w(TAG, "createNetworkSpecifier: called on terminated session");
+ Log.w(TAG, "createNetworkSpecifierPmk: called on terminated session");
return null;
} else {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
- Log.w(TAG, "createNetworkSpecifier: called post GC on WifiAwareManager");
+ Log.w(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
return null;
}
@@ -297,7 +353,30 @@
? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
: WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
- return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, token);
+ return mgr.createNetworkSpecifier(mClientId, role, mSessionId, peerHandle, pmk);
}
}
+
+ /**
+ * Place-holder for {@code createNetworkSpecifierOpen(PeerHandle)}. Present to enable
+ * development of replacements CL without causing an API change. Will be removed when new
+ * APIs are exposed.
+ *
+ * @param peerHandle The peer's handle obtained through
+ * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
+ * byte[], java.util.List)} or
+ * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
+ * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
+ * from only that peer. A RESPONDER may specify a null - indicating that
+ * it will accept connection requests from any device.
+ * @param token Deprecated and ignored.
+ * @return A string to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ */
+ public String createNetworkSpecifier(@Nullable PeerHandle peerHandle, @Nullable byte[] token) {
+ return createNetworkSpecifierOpen(peerHandle);
+ }
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 0eb6a3d..3d784ba 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -130,55 +130,34 @@
*/
/**
- * TYPE_1A: role, client_id, session_id, peer_id, token
+ * TYPE: in band, specific peer: role, client_id, session_id, peer_id, pmk optional
* @hide
*/
- public static final int NETWORK_SPECIFIER_TYPE_1A = 0;
+ public static final int NETWORK_SPECIFIER_TYPE_IB = 0;
/**
- * TYPE_1B: role, client_id, session_id, peer_id [only permitted for RESPONDER]
+ * TYPE: in band, any peer: role, client_id, session_id, pmk optional
+ * [only permitted for RESPONDER]
* @hide
*/
- public static final int NETWORK_SPECIFIER_TYPE_1B = 1;
+ public static final int NETWORK_SPECIFIER_TYPE_IB_ANY_PEER = 1;
/**
- * TYPE_1C: role, client_id, session_id, token [only permitted for RESPONDER]
+ * TYPE: out-of-band: role, client_id, peer_mac, pmk optional
* @hide
*/
- public static final int NETWORK_SPECIFIER_TYPE_1C = 2;
+ public static final int NETWORK_SPECIFIER_TYPE_OOB = 2;
/**
- * TYPE_1C: role, client_id, session_id [only permitted for RESPONDER]
+ * TYPE: out-of-band, any peer: role, client_id, pmk optional
+ * [only permitted for RESPONDER]
* @hide
*/
- public static final int NETWORK_SPECIFIER_TYPE_1D = 3;
+ public static final int NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER = 3;
- /**
- * TYPE_2A: role, client_id, peer_mac, token
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_2A = 4;
-
- /**
- * TYPE_2B: role, client_id, peer_mac [only permitted for RESPONDER]
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_2B = 5;
-
- /**
- * TYPE_2C: role, client_id, token [only permitted for RESPONDER]
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_2C = 6;
-
- /**
- * TYPE_2D: role, client_id [only permitted for RESPONDER]
- * @hide
- */
- public static final int NETWORK_SPECIFIER_TYPE_2D = 7;
/** @hide */
- public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_2D;
+ public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER;
/** @hide */
public static final String NETWORK_SPECIFIER_KEY_TYPE = "type";
@@ -199,7 +178,7 @@
public static final String NETWORK_SPECIFIER_KEY_PEER_MAC = "peer_mac";
/** @hide */
- public static final String NETWORK_SPECIFIER_KEY_TOKEN = "token";
+ public static final String NETWORK_SPECIFIER_KEY_PMK = "pmk";
/**
* Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
@@ -494,23 +473,15 @@
/** @hide */
public String createNetworkSpecifier(int clientId, int role, int sessionId,
- PeerHandle peerHandle, byte[] token) {
+ PeerHandle peerHandle, @Nullable byte[] pmk) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
+ ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
- + ", token=" + token);
+ + ", pmk=" + ((pmk == null) ? "null" : "non-null"));
}
- int type;
- if (token != null && peerHandle != null) {
- type = NETWORK_SPECIFIER_TYPE_1A;
- } else if (token == null && peerHandle != null) {
- type = NETWORK_SPECIFIER_TYPE_1B;
- } else if (token != null && peerHandle == null) {
- type = NETWORK_SPECIFIER_TYPE_1C;
- } else {
- type = NETWORK_SPECIFIER_TYPE_1D;
- }
+ int type = (peerHandle == null) ? NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
+ : NETWORK_SPECIFIER_TYPE_IB;
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
@@ -519,10 +490,6 @@
+ "specifier");
}
if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
- if (token == null) {
- throw new IllegalArgumentException(
- "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
- }
if (peerHandle == null) {
throw new IllegalArgumentException(
"createNetworkSpecifier: Invalid peer handle (value of null) - not "
@@ -540,10 +507,11 @@
if (peerHandle != null) {
json.put(NETWORK_SPECIFIER_KEY_PEER_ID, peerHandle.peerId);
}
- if (token != null) {
- json.put(NETWORK_SPECIFIER_KEY_TOKEN,
- Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+ if (pmk == null) {
+ pmk = new byte[0];
}
+ json.put(NETWORK_SPECIFIER_KEY_PMK,
+ Base64.encodeToString(pmk, 0, pmk.length, Base64.DEFAULT));
} catch (JSONException e) {
return "";
}
@@ -553,21 +521,14 @@
/** @hide */
public String createNetworkSpecifier(int clientId, @DataPathRole int role,
- @Nullable byte[] peer, @Nullable byte[] token) {
+ @Nullable byte[] peer, @Nullable byte[] pmk) {
if (VDBG) {
- Log.v(TAG, "createNetworkSpecifier: role=" + role + ", token=" + token);
+ Log.v(TAG, "createNetworkSpecifier: role=" + role
+ + ", pmk=" + ((pmk == null) ? "null" : "non-null"));
}
- int type;
- if (token != null && peer != null) {
- type = NETWORK_SPECIFIER_TYPE_2A;
- } else if (token == null && peer != null) {
- type = NETWORK_SPECIFIER_TYPE_2B;
- } else if (token != null && peer == null) {
- type = NETWORK_SPECIFIER_TYPE_2C;
- } else { // both are null
- type = NETWORK_SPECIFIER_TYPE_2D;
- }
+ int type = (peer == null) ?
+ NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER : NETWORK_SPECIFIER_TYPE_OOB;
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
@@ -576,19 +537,13 @@
+ "specifier");
}
if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
- if (peer == null || peer.length != 6) {
- throw new IllegalArgumentException(
- "createNetworkSpecifier: Invalid peer MAC address");
+ if (peer == null) {
+ throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC "
+ + "address - null not permitted on INITIATOR");
}
- if (token == null) {
- throw new IllegalArgumentException(
- "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
- }
- } else {
- if (peer != null && peer.length != 6) {
- throw new IllegalArgumentException(
- "createNetworkSpecifier: Invalid peer MAC address");
- }
+ }
+ if (peer != null && peer.length != 6) {
+ throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
}
JSONObject json;
@@ -600,10 +555,11 @@
if (peer != null) {
json.put(NETWORK_SPECIFIER_KEY_PEER_MAC, new String(HexEncoding.encode(peer)));
}
- if (token != null) {
- json.put(NETWORK_SPECIFIER_KEY_TOKEN,
- Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+ if (pmk == null) {
+ pmk = new byte[0];
}
+ json.put(NETWORK_SPECIFIER_KEY_PMK,
+ Base64.encodeToString(pmk, 0, pmk.length, Base64.DEFAULT));
} catch (JSONException e) {
return "";
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index 8696920..856066e 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -183,47 +183,114 @@
}
/**
- * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for a
- * WiFi Aware connection to the specified peer. The
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
+ * unencrypted WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <p>
* This API is targeted for applications which can obtain the peer MAC address using OOB
* (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
* when using Aware discovery use the alternative network specifier method -
- * {@link DiscoverySession#createNetworkSpecifier(PeerHandle,
- * byte[])}.
+ * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}.
*
* @param role The role of this device:
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
* @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
* value is used to gate the acceptance of a connection request from only that
- * peer. A RESPONDER may specified a null - indicating that it will accept
+ * peer. A RESPONDER may specify a null - indicating that it will accept
* connection requests from any device.
- * @param token An arbitrary token (message) to be used to match connection initiation request
- * to a responder setup. A RESPONDER is set up with a {@code token} which must
- * be matched by the token provided by the INITIATOR. A null token is permitted
- * on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
- * not the same as a null token and requires the peer token to be empty as well.
*
* @return A string to be used to construct
* {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
* {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
+ *
+ * @hide
+ */
+ public String createNetworkSpecifierOpen(@WifiAwareManager.DataPathRole int role,
+ @Nullable byte[] peer) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
+ return "";
+ }
+ if (mTerminated) {
+ Log.e(TAG, "createNetworkSpecifierOpen: called after termination");
+ return "";
+ }
+ return mgr.createNetworkSpecifier(mClientId, role, peer, null);
+ }
+
+ /**
+ * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for an
+ * encrypted WiFi Aware connection (link) to the specified peer. The
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+ * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
+ * <p>
+ * This API is targeted for applications which can obtain the peer MAC address using OOB
+ * (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
+ * when using Aware discovery use the alternative network specifier method -
+ * {@link DiscoverySession#createNetworkSpecifierPmk(PeerHandle, byte[])}}.
+ *
+ * @param role The role of this device:
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+ * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer. A RESPONDER may specify a null - indicating that it will accept
+ * connection requests from any device.
+ * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
+ * encrypting the data-path. Use the {@link #createNetworkSpecifierOpen(int, byte[])}
+ * to specify an open (unencrypted) link.
+ *
+ * @return A string to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
+ *
+ * @hide
+ */
+ public String createNetworkSpecifierPmk(@WifiAwareManager.DataPathRole int role,
+ @Nullable byte[] peer, @NonNull byte[] pmk) {
+ WifiAwareManager mgr = mMgr.get();
+ if (mgr == null) {
+ Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
+ return "";
+ }
+ if (mTerminated) {
+ Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
+ return "";
+ }
+ if (pmk == null || pmk.length == 0) {
+ throw new IllegalArgumentException("PMK must not be null or empty");
+ }
+ return mgr.createNetworkSpecifier(mClientId, role, peer, pmk);
+ }
+
+ /**
+ * Place-holder for {@code #createNetworkSpecifierOpen(int, byte[])}. Present to enable
+ * development of replacements CL without causing an API change. Will be removed when new
+ * APIs are exposed.
+ *
+ * @param role The role of this device:
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
+ * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
+ * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
+ * value is used to gate the acceptance of a connection request from only that
+ * peer. A RESPONDER may specify a null - indicating that it will accept
+ * connection requests from any device.
+ * @param token Deprecated and ignored.
+ * @return A string to be used to construct
+ * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+ * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
+ * android.net.ConnectivityManager.NetworkCallback)}
+ * [or other varieties of that API].
*/
public String createNetworkSpecifier(@WifiAwareManager.DataPathRole int role,
@Nullable byte[] peer, @Nullable byte[] token) {
- WifiAwareManager mgr = mMgr.get();
- if (mgr == null) {
- Log.e(TAG, "createNetworkSpecifier: called post GC on WifiAwareManager");
- return "";
- }
- if (mTerminated) {
- Log.e(TAG, "createNetworkSpecifier: called after termination");
- return "";
- }
- return mgr.createNetworkSpecifier(mClientId, role, peer, token);
+ return createNetworkSpecifierOpen(role, peer);
}
}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 620759d..2388841 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -122,12 +122,22 @@
private static final int MAX_PASSWORD_BYTES = 255;
/**
+ * Supported authentication methods.
+ * @hide
+ */
+ public static final String AUTH_METHOD_PAP = "PAP";
+ /** @hide */
+ public static final String AUTH_METHOD_MSCHAP = "MS-CHAP";
+ /** @hide */
+ public static final String AUTH_METHOD_MSCHAPV2 = "MS-CHAP-V2";
+
+ /**
* Supported Non-EAP inner methods. Refer to
* Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical
* Specification Section 9.1 for more info.
*/
- private static final Set<String> SUPPORTED_AUTH =
- new HashSet<String>(Arrays.asList("PAP", "CHAP", "MS-CHAP", "MS-CHAP-V2"));
+ private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(
+ Arrays.asList(AUTH_METHOD_PAP, AUTH_METHOD_MSCHAP, AUTH_METHOD_MSCHAPV2));
/**
* Username of the credential.
@@ -348,8 +358,9 @@
public static final class CertificateCredential implements Parcelable {
/**
* Supported certificate types.
+ * @hide
*/
- private static final String CERT_TYPE_X509V3 = "x509v3";
+ public static final String CERT_TYPE_X509V3 = "x509v3";
/**
* Certificate SHA-256 fingerprint length.
diff --git a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index c4d2d32..d0aedba 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -89,11 +89,29 @@
@Test
public void testSetClientKeyEntryWithNull() {
mEnterpriseConfig.setClientKeyEntry(null, null);
- assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
- assertEquals(null, mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
+ assertNull(mEnterpriseConfig.getClientCertificate());
mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
- assertEquals(null, mEnterpriseConfig.getClientCertificateChain());
- assertEquals(null, mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
+ assertNull(mEnterpriseConfig.getClientCertificate());
+
+ // Setting the client certificate to null should clear the existing chain.
+ PrivateKey clientKey = FakeKeys.RSA_KEY1;
+ X509Certificate clientCert0 = FakeKeys.CLIENT_CERT;
+ X509Certificate clientCert1 = FakeKeys.CA_CERT1;
+ mEnterpriseConfig.setClientKeyEntry(clientKey, clientCert0);
+ assertNotNull(mEnterpriseConfig.getClientCertificate());
+ mEnterpriseConfig.setClientKeyEntry(null, null);
+ assertNull(mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
+
+ // Setting the chain to null should clear the existing chain.
+ X509Certificate[] clientChain = new X509Certificate[] {clientCert0, clientCert1};
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
+ assertNotNull(mEnterpriseConfig.getClientCertificateChain());
+ mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
+ assertNull(mEnterpriseConfig.getClientCertificate());
+ assertNull(mEnterpriseConfig.getClientCertificateChain());
}
@Test
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 7f68f6f..992958b 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -973,11 +973,11 @@
final int sessionId = 123;
final PeerHandle peerHandle = new PeerHandle(123412);
final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
- final String token = "Some arbitrary token string - can really be anything";
+ final byte[] pmk = "Some arbitrary byte array".getBytes();
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final PublishConfig publishConfig = new PublishConfig.Builder().build();
- String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+ String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
WifiAwareSession.class);
@@ -1008,9 +1008,8 @@
mMockLooper.dispatchAll();
inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
- // (3) request a network specifier from the session
- String networkSpecifier = publishSession.getValue().createNetworkSpecifier(peerHandle,
- token.getBytes());
+ // (3) request an open (unencrypted) network specifier from the session
+ String networkSpecifier = publishSession.getValue().createNetworkSpecifierOpen(peerHandle);
// validate format
JSONObject jsonObject = new JSONObject(networkSpecifier);
@@ -1022,8 +1021,22 @@
equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
collector.checkThat("peer_id", peerHandle.peerId,
equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
- collector.checkThat("token", tokenB64,
- equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+ // (4) request an encrypted (PMK) network specifier from the session
+ networkSpecifier = publishSession.getValue().createNetworkSpecifierPmk(peerHandle, pmk);
+
+ // validate format
+ jsonObject = new JSONObject(networkSpecifier);
+ collector.checkThat("role", role,
+ equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
+ collector.checkThat("client_id", clientId,
+ equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
+ collector.checkThat("session_id", sessionId,
+ equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
+ collector.checkThat("peer_id", peerHandle.peerId,
+ equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
+ collector.checkThat("pmk", pmkB64 ,
+ equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK)));
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);
@@ -1039,9 +1052,9 @@
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
- final String token = "Some arbitrary token string - can really be anything";
+ final byte[] pmk = "Some arbitrary pmk data".getBytes();
- String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+ String pmkB64 = Base64.encodeToString(pmk, Base64.DEFAULT);
ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
WifiAwareSession.class);
@@ -1060,10 +1073,10 @@
inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
WifiAwareSession session = sessionCaptor.getValue();
- /* (2) request a direct network specifier*/
- String networkSpecifier = session.createNetworkSpecifier(role, someMac, token.getBytes());
+ // (2) request an open (unencrypted) direct network specifier
+ String networkSpecifier = session.createNetworkSpecifierOpen(role, someMac);
- /* validate format*/
+ // validate format
JSONObject jsonObject = new JSONObject(networkSpecifier);
collector.checkThat("role", role,
equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
@@ -1072,8 +1085,21 @@
collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
false)));
- collector.checkThat("token", tokenB64,
- equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+ // (3) request an encrypted (PMK) direct network specifier
+ networkSpecifier = session.createNetworkSpecifierPmk(role, someMac, pmk);
+
+ // validate format
+ jsonObject = new JSONObject(networkSpecifier);
+ collector.checkThat("role", role,
+ equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
+ collector.checkThat("client_id", clientId,
+ equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
+ collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
+ jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
+ false)));
+ collector.checkThat("pmk", pmkB64,
+ equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PMK)));
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);