Merge "Modify Bluetooth Class of Device from Android stack"
diff --git a/Android.mk b/Android.mk
index bd32b55..b92f31e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -486,8 +486,8 @@
telecomm/java/com/android/internal/telecom/IInCallService.aidl \
telecomm/java/com/android/internal/telecom/ITelecomService.aidl \
telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl \
- telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \
- telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \
+ telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl \
+ telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl \
telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl \
telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6e4890c..c1f05e5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8935,6 +8935,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";
@@ -25550,6 +25551,67 @@
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 AUTH_HMAC_MD5 = "hmac(md5)";
+ field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
+ field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
+ field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
+ field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
+ field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+ field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
+ }
+
+ public final class IpSecManager {
+ method public void applyTransportModeTransform(java.io.FileDescriptor, 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.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, 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 protected void finalize();
+ 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() throws java.io.IOException;
+ 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, android.net.IpSecManager.SecurityParameterIndex);
+ }
+
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -26002,6 +26064,7 @@
method public boolean protect(java.net.DatagramSocket);
method public boolean setUnderlyingNetworks(android.net.Network[]);
field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
+ field public static final java.lang.String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = "android.net.VpnService.SUPPORTS_ALWAYS_ON";
}
public class VpnService.Builder {
@@ -39750,22 +39813,23 @@
field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
}
- public class MbmsDownloadManager {
- method public void cancelDownload(android.telephony.mbms.DownloadRequest) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, int) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public void dispose();
- method public void download(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.telephony.mbms.MbmsException;
- method public void getFileServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+ public class MbmsDownloadSession implements java.lang.AutoCloseable {
+ method public void cancelDownload(android.telephony.mbms.DownloadRequest);
+ method public void close();
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
+ method public void download(android.telephony.mbms.DownloadRequest);
+ method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
method public java.io.File getTempFileRootDirectory();
- method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads() throws android.telephony.mbms.MbmsException;
- method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.telephony.mbms.MbmsException;
- method public void setTempFileRootDirectory(java.io.File) throws android.telephony.mbms.MbmsException;
+ method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
+ method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+ method public void requestUpdateFileServices(java.util.List<java.lang.String>);
+ method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
+ method public void setTempFileRootDirectory(java.io.File);
+ method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
+ field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
field public static final int RESULT_CANCELLED = 2; // 0x2
@@ -39779,13 +39843,12 @@
field public static final int STATUS_UNKNOWN = 0; // 0x0
}
- public class MbmsStreamingManager {
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
- method public void dispose();
- method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
- method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
+ public class MbmsStreamingSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+ method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
+ method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
}
public class NeighboringCellInfo implements android.os.Parcelable {
@@ -39818,8 +39881,10 @@
public class PhoneNumberUtils {
ctor public PhoneNumberUtils();
method public static void addTtsSpan(android.text.Spannable, int, int);
- method public static java.lang.String calledPartyBCDFragmentToString(byte[], int, int);
- method public static java.lang.String calledPartyBCDToString(byte[], int, int);
+ method public static deprecated java.lang.String calledPartyBCDFragmentToString(byte[], int, int);
+ method public static java.lang.String calledPartyBCDFragmentToString(byte[], int, int, int);
+ method public static deprecated java.lang.String calledPartyBCDToString(byte[], int, int);
+ method public static java.lang.String calledPartyBCDToString(byte[], int, int, int);
method public static boolean compare(java.lang.String, java.lang.String);
method public static boolean compare(android.content.Context, java.lang.String, java.lang.String);
method public static java.lang.String convertKeypadLettersToDigits(java.lang.String);
@@ -39852,12 +39917,15 @@
method public static byte[] networkPortionToCalledPartyBCD(java.lang.String);
method public static byte[] networkPortionToCalledPartyBCDWithLength(java.lang.String);
method public static java.lang.String normalizeNumber(java.lang.String);
- method public static byte[] numberToCalledPartyBCD(java.lang.String);
+ method public static deprecated byte[] numberToCalledPartyBCD(java.lang.String);
+ method public static byte[] numberToCalledPartyBCD(java.lang.String, int);
method public static java.lang.String replaceUnicodeDigits(java.lang.String);
method public static java.lang.String stringFromStringAndTOA(java.lang.String, int);
method public static java.lang.String stripSeparators(java.lang.String);
method public static java.lang.String toCallerIDMinMatch(java.lang.String);
method public static int toaFromString(java.lang.String);
+ field public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2; // 0x2
+ field public static final int BCD_EXTENDED_TYPE_EF_ADN = 1; // 0x1
field public static final int FORMAT_JAPAN = 2; // 0x2
field public static final int FORMAT_NANP = 1; // 0x1
field public static final int FORMAT_UNKNOWN = 0; // 0x0
@@ -40422,7 +40490,6 @@
public final class DownloadRequest implements android.os.Parcelable {
method public static android.telephony.mbms.DownloadRequest copy(android.telephony.mbms.DownloadRequest);
method public int describeContents();
- method public android.net.Uri getDestinationUri();
method public java.lang.String getFileServiceId();
method public static int getMaxAppIntentSize();
method public static int getMaxDestinationUriSize();
@@ -40436,7 +40503,6 @@
ctor public DownloadRequest.Builder();
method public android.telephony.mbms.DownloadRequest build();
method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
- method public android.telephony.mbms.DownloadRequest.Builder setDest(android.net.Uri);
method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
method public android.telephony.mbms.DownloadRequest.Builder setSource(android.net.Uri);
method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int);
@@ -40445,7 +40511,7 @@
public class DownloadStateCallback {
ctor public DownloadStateCallback();
method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
- method public void onStateChanged(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+ method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
}
public final class FileInfo implements android.os.Parcelable {
@@ -40463,32 +40529,31 @@
field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR;
}
- public class MbmsDownloadManagerCallback {
- ctor public MbmsDownloadManagerCallback();
- method public void onError(int, java.lang.String);
- method public void onFileServicesUpdated(java.util.List<android.telephony.mbms.FileServiceInfo>);
- method public void onMiddlewareReady();
- }
-
public class MbmsDownloadReceiver extends android.content.BroadcastReceiver {
ctor public MbmsDownloadReceiver();
method public void onReceive(android.content.Context, android.content.Intent);
}
- public class MbmsException extends java.lang.Exception {
- method public int getErrorCode();
+ public class MbmsDownloadSessionCallback {
+ ctor public MbmsDownloadSessionCallback();
+ method public void onError(int, java.lang.String);
+ method public void onFileServicesUpdated(java.util.List<android.telephony.mbms.FileServiceInfo>);
+ method public void onMiddlewareReady();
+ }
+
+ public class MbmsErrors {
field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
field public static final int SUCCESS = 0; // 0x0
}
- public static class MbmsException.DownloadErrors {
+ public static class MbmsErrors.DownloadErrors {
field public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 401; // 0x191
field public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402; // 0x192
}
- public static class MbmsException.GeneralErrors {
+ public static class MbmsErrors.GeneralErrors {
field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
field public static final int ERROR_IN_E911 = 204; // 0xcc
field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
@@ -40498,39 +40563,38 @@
field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
}
- public static class MbmsException.InitializationErrors {
+ public static class MbmsErrors.InitializationErrors {
field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
}
- public static class MbmsException.StreamingErrors {
+ public static class MbmsErrors.StreamingErrors {
field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
}
- public class MbmsStreamingManagerCallback {
- ctor public MbmsStreamingManagerCallback();
+ public class MbmsStreamingSessionCallback {
+ ctor public MbmsStreamingSessionCallback();
method public void onError(int, java.lang.String);
method public void onMiddlewareReady();
method public void onStreamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>);
}
public class ServiceInfo {
- method public java.lang.String getClassName();
method public java.util.List<java.util.Locale> getLocales();
- method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+ method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+ method public java.lang.String getServiceClassName();
method public java.lang.String getServiceId();
method public java.util.Date getSessionEndTime();
method public java.util.Date getSessionStartTime();
}
public class StreamingService {
- method public void dispose() throws android.telephony.mbms.MbmsException;
method public android.telephony.mbms.StreamingServiceInfo getInfo();
- method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
- method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+ method public android.net.Uri getPlaybackUri();
+ method public void stopStreaming();
field public static final int BROADCAST_METHOD = 1; // 0x1
field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
field public static final int REASON_END_OF_SESSION = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 5a57171..0ce7908 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -9446,6 +9446,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";
@@ -27746,6 +27747,69 @@
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 AUTH_HMAC_MD5 = "hmac(md5)";
+ field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
+ field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
+ field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
+ field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
+ field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+ field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
+ }
+
+ public final class IpSecManager {
+ method public void applyTransportModeTransform(java.io.FileDescriptor, 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.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, 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 protected void finalize();
+ 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() throws java.io.IOException;
+ 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 setNattKeepalive(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();
@@ -28266,6 +28330,7 @@
method public boolean protect(java.net.DatagramSocket);
method public boolean setUnderlyingNetworks(android.net.Network[]);
field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
+ field public static final java.lang.String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = "android.net.VpnService.SUPPORTS_ALWAYS_ON";
}
public class VpnService.Builder {
@@ -43179,22 +43244,23 @@
field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
}
- public class MbmsDownloadManager {
- method public void cancelDownload(android.telephony.mbms.DownloadRequest) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, int) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public void dispose();
- method public void download(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.telephony.mbms.MbmsException;
- method public void getFileServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+ public class MbmsDownloadSession implements java.lang.AutoCloseable {
+ method public void cancelDownload(android.telephony.mbms.DownloadRequest);
+ method public void close();
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
+ method public void download(android.telephony.mbms.DownloadRequest);
+ method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
method public java.io.File getTempFileRootDirectory();
- method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads() throws android.telephony.mbms.MbmsException;
- method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.telephony.mbms.MbmsException;
- method public void setTempFileRootDirectory(java.io.File) throws android.telephony.mbms.MbmsException;
+ method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
+ method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+ method public void requestUpdateFileServices(java.util.List<java.lang.String>);
+ method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
+ method public void setTempFileRootDirectory(java.io.File);
+ method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
+ field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload";
@@ -43209,13 +43275,12 @@
field public static final int STATUS_UNKNOWN = 0; // 0x0
}
- public class MbmsStreamingManager {
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
- method public void dispose();
- method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
- method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
+ public class MbmsStreamingSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+ method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
+ method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
}
@@ -43249,8 +43314,10 @@
public class PhoneNumberUtils {
ctor public PhoneNumberUtils();
method public static void addTtsSpan(android.text.Spannable, int, int);
- method public static java.lang.String calledPartyBCDFragmentToString(byte[], int, int);
- method public static java.lang.String calledPartyBCDToString(byte[], int, int);
+ method public static deprecated java.lang.String calledPartyBCDFragmentToString(byte[], int, int);
+ method public static java.lang.String calledPartyBCDFragmentToString(byte[], int, int, int);
+ method public static deprecated java.lang.String calledPartyBCDToString(byte[], int, int);
+ method public static java.lang.String calledPartyBCDToString(byte[], int, int, int);
method public static boolean compare(java.lang.String, java.lang.String);
method public static boolean compare(android.content.Context, java.lang.String, java.lang.String);
method public static java.lang.String convertKeypadLettersToDigits(java.lang.String);
@@ -43283,12 +43350,15 @@
method public static byte[] networkPortionToCalledPartyBCD(java.lang.String);
method public static byte[] networkPortionToCalledPartyBCDWithLength(java.lang.String);
method public static java.lang.String normalizeNumber(java.lang.String);
- method public static byte[] numberToCalledPartyBCD(java.lang.String);
+ method public static deprecated byte[] numberToCalledPartyBCD(java.lang.String);
+ method public static byte[] numberToCalledPartyBCD(java.lang.String, int);
method public static java.lang.String replaceUnicodeDigits(java.lang.String);
method public static java.lang.String stringFromStringAndTOA(java.lang.String, int);
method public static java.lang.String stripSeparators(java.lang.String);
method public static java.lang.String toCallerIDMinMatch(java.lang.String);
method public static int toaFromString(java.lang.String);
+ field public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2; // 0x2
+ field public static final int BCD_EXTENDED_TYPE_EF_ADN = 1; // 0x1
field public static final int FORMAT_JAPAN = 2; // 0x2
field public static final int FORMAT_NANP = 1; // 0x1
field public static final int FORMAT_UNKNOWN = 0; // 0x0
@@ -43936,7 +44006,6 @@
public final class DownloadRequest implements android.os.Parcelable {
method public static android.telephony.mbms.DownloadRequest copy(android.telephony.mbms.DownloadRequest);
method public int describeContents();
- method public android.net.Uri getDestinationUri();
method public java.lang.String getFileServiceId();
method public static int getMaxAppIntentSize();
method public static int getMaxDestinationUriSize();
@@ -43951,7 +44020,6 @@
ctor public DownloadRequest.Builder();
method public android.telephony.mbms.DownloadRequest build();
method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
- method public android.telephony.mbms.DownloadRequest.Builder setDest(android.net.Uri);
method public android.telephony.mbms.DownloadRequest.Builder setOpaqueData(byte[]);
method public android.telephony.mbms.DownloadRequest.Builder setServiceId(java.lang.String);
method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
@@ -43962,7 +44030,7 @@
public class DownloadStateCallback {
ctor public DownloadStateCallback();
method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
- method public void onStateChanged(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+ method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
}
public final class FileInfo implements android.os.Parcelable {
@@ -43982,16 +44050,10 @@
field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR;
}
- public class MbmsDownloadManagerCallback {
- ctor public MbmsDownloadManagerCallback();
- method public void onError(int, java.lang.String);
- method public void onFileServicesUpdated(java.util.List<android.telephony.mbms.FileServiceInfo>);
- method public void onMiddlewareReady();
- }
-
public class MbmsDownloadReceiver extends android.content.BroadcastReceiver {
ctor public MbmsDownloadReceiver();
method public void onReceive(android.content.Context, android.content.Intent);
+ field public static final int RESULT_APP_NOTIFICATION_ERROR = 6; // 0x6
field public static final int RESULT_BAD_TEMP_FILE_ROOT = 3; // 0x3
field public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4; // 0x4
field public static final int RESULT_INVALID_ACTION = 1; // 0x1
@@ -44000,20 +44062,26 @@
field public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5; // 0x5
}
- public class MbmsException extends java.lang.Exception {
- method public int getErrorCode();
+ public class MbmsDownloadSessionCallback {
+ ctor public MbmsDownloadSessionCallback();
+ method public void onError(int, java.lang.String);
+ method public void onFileServicesUpdated(java.util.List<android.telephony.mbms.FileServiceInfo>);
+ method public void onMiddlewareReady();
+ }
+
+ public class MbmsErrors {
field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
field public static final int SUCCESS = 0; // 0x0
}
- public static class MbmsException.DownloadErrors {
+ public static class MbmsErrors.DownloadErrors {
field public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 401; // 0x191
field public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402; // 0x192
}
- public static class MbmsException.GeneralErrors {
+ public static class MbmsErrors.GeneralErrors {
field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
field public static final int ERROR_IN_E911 = 204; // 0xcc
field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
@@ -44023,39 +44091,38 @@
field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
}
- public static class MbmsException.InitializationErrors {
+ public static class MbmsErrors.InitializationErrors {
field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
}
- public static class MbmsException.StreamingErrors {
+ public static class MbmsErrors.StreamingErrors {
field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
}
- public class MbmsStreamingManagerCallback {
- ctor public MbmsStreamingManagerCallback();
+ public class MbmsStreamingSessionCallback {
+ ctor public MbmsStreamingSessionCallback();
method public void onError(int, java.lang.String);
method public void onMiddlewareReady();
method public void onStreamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>);
}
public class ServiceInfo {
- method public java.lang.String getClassName();
method public java.util.List<java.util.Locale> getLocales();
- method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+ method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+ method public java.lang.String getServiceClassName();
method public java.lang.String getServiceId();
method public java.util.Date getSessionEndTime();
method public java.util.Date getSessionStartTime();
}
public class StreamingService {
- method public void dispose() throws android.telephony.mbms.MbmsException;
method public android.telephony.mbms.StreamingServiceInfo getInfo();
- method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
- method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+ method public android.net.Uri getPlaybackUri();
+ method public void stopStreaming();
field public static final int BROADCAST_METHOD = 1; // 0x1
field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
field public static final int REASON_END_OF_SESSION = 2; // 0x2
@@ -44103,24 +44170,25 @@
ctor public MbmsDownloadServiceBase();
method public int cancelDownload(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
method public void dispose(int) throws android.os.RemoteException;
- method public int download(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
+ method public int download(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.os.RemoteException;
- method public int getFileServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
- method public int initialize(int, android.telephony.mbms.MbmsDownloadManagerCallback) throws android.os.RemoteException;
+ method public int initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback) throws android.os.RemoteException;
method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads(int) throws android.os.RemoteException;
method public void onAppCallbackDied(int, int);
+ method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException;
+ method public int requestUpdateFileServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
method public int resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException;
method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException;
+ method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException;
}
public class MbmsStreamingServiceBase extends android.os.Binder {
ctor public MbmsStreamingServiceBase();
method public void dispose(int) throws android.os.RemoteException;
- method public void disposeStream(int, java.lang.String) throws android.os.RemoteException;
method public android.net.Uri getPlaybackUri(int, java.lang.String) throws android.os.RemoteException;
- method public int getStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
- method public int initialize(android.telephony.mbms.MbmsStreamingManagerCallback, int) throws android.os.RemoteException;
+ method public int initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) throws android.os.RemoteException;
method public void onAppCallbackDied(int, int);
+ method public int requestUpdateStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
method public int startStreaming(int, java.lang.String, android.telephony.mbms.StreamingServiceCallback) throws android.os.RemoteException;
method public void stopStreaming(int, java.lang.String) throws android.os.RemoteException;
}
@@ -44136,7 +44204,6 @@
field public static final java.lang.String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
field public static final java.lang.String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
field public static final java.lang.String EXTRA_PAUSED_URI_LIST = "android.telephony.mbms.extra.PAUSED_URI_LIST";
- field public static final java.lang.String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
field public static final java.lang.String EXTRA_SERVICE_ID = "android.telephony.mbms.extra.SERVICE_ID";
field public static final java.lang.String EXTRA_TEMP_FILES_IN_USE = "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
field public static final java.lang.String EXTRA_TEMP_FILE_ROOT = "android.telephony.mbms.extra.TEMP_FILE_ROOT";
diff --git a/api/test-current.txt b/api/test-current.txt
index b51a2b7..685e06a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8968,6 +8968,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";
@@ -25659,6 +25660,67 @@
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 AUTH_HMAC_MD5 = "hmac(md5)";
+ field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
+ field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
+ field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
+ field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
+ field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+ field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
+ }
+
+ public final class IpSecManager {
+ method public void applyTransportModeTransform(java.io.FileDescriptor, 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.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+ method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, 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 protected void finalize();
+ 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() throws java.io.IOException;
+ 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, android.net.IpSecManager.SecurityParameterIndex);
+ }
+
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -26111,6 +26173,7 @@
method public boolean protect(java.net.DatagramSocket);
method public boolean setUnderlyingNetworks(android.net.Network[]);
field public static final java.lang.String SERVICE_INTERFACE = "android.net.VpnService";
+ field public static final java.lang.String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON = "android.net.VpnService.SUPPORTS_ALWAYS_ON";
}
public class VpnService.Builder {
@@ -39972,22 +40035,23 @@
field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4
}
- public class MbmsDownloadManager {
- method public void cancelDownload(android.telephony.mbms.DownloadRequest) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, int) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsDownloadManager create(android.content.Context, android.telephony.mbms.MbmsDownloadManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public void dispose();
- method public void download(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.telephony.mbms.MbmsException;
- method public void getFileServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
+ public class MbmsDownloadSession implements java.lang.AutoCloseable {
+ method public void cancelDownload(android.telephony.mbms.DownloadRequest);
+ method public void close();
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, android.os.Handler);
+ method public static android.telephony.MbmsDownloadSession create(android.content.Context, android.telephony.mbms.MbmsDownloadSessionCallback, int, android.os.Handler);
+ method public void download(android.telephony.mbms.DownloadRequest);
+ method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo);
method public java.io.File getTempFileRootDirectory();
- method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads() throws android.telephony.mbms.MbmsException;
- method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.telephony.mbms.MbmsException;
- method public void setTempFileRootDirectory(java.io.File) throws android.telephony.mbms.MbmsException;
+ method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads();
+ method public void registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback, android.os.Handler);
+ method public void requestUpdateFileServices(java.util.List<java.lang.String>);
+ method public void resetDownloadKnowledge(android.telephony.mbms.DownloadRequest);
+ method public void setTempFileRootDirectory(java.io.File);
+ method public void unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback);
field public static final java.lang.String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
field public static final java.lang.String EXTRA_MBMS_COMPLETED_FILE_URI = "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
+ field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
field public static final int RESULT_CANCELLED = 2; // 0x2
@@ -40001,13 +40065,12 @@
field public static final int STATUS_UNKNOWN = 0; // 0x0
}
- public class MbmsStreamingManager {
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, int, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
- method public static android.telephony.MbmsStreamingManager create(android.content.Context, android.telephony.mbms.MbmsStreamingManagerCallback) throws android.telephony.mbms.MbmsException;
- method public void dispose();
- method public void getStreamingServices(java.util.List<java.lang.String>) throws android.telephony.mbms.MbmsException;
- method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler) throws android.telephony.mbms.MbmsException;
+ public class MbmsStreamingSession implements java.lang.AutoCloseable {
+ method public void close();
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, int, android.os.Handler);
+ method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
+ method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
+ method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
}
public class NeighboringCellInfo implements android.os.Parcelable {
@@ -40040,8 +40103,10 @@
public class PhoneNumberUtils {
ctor public PhoneNumberUtils();
method public static void addTtsSpan(android.text.Spannable, int, int);
- method public static java.lang.String calledPartyBCDFragmentToString(byte[], int, int);
- method public static java.lang.String calledPartyBCDToString(byte[], int, int);
+ method public static deprecated java.lang.String calledPartyBCDFragmentToString(byte[], int, int);
+ method public static java.lang.String calledPartyBCDFragmentToString(byte[], int, int, int);
+ method public static deprecated java.lang.String calledPartyBCDToString(byte[], int, int);
+ method public static java.lang.String calledPartyBCDToString(byte[], int, int, int);
method public static boolean compare(java.lang.String, java.lang.String);
method public static boolean compare(android.content.Context, java.lang.String, java.lang.String);
method public static java.lang.String convertKeypadLettersToDigits(java.lang.String);
@@ -40074,12 +40139,15 @@
method public static byte[] networkPortionToCalledPartyBCD(java.lang.String);
method public static byte[] networkPortionToCalledPartyBCDWithLength(java.lang.String);
method public static java.lang.String normalizeNumber(java.lang.String);
- method public static byte[] numberToCalledPartyBCD(java.lang.String);
+ method public static deprecated byte[] numberToCalledPartyBCD(java.lang.String);
+ method public static byte[] numberToCalledPartyBCD(java.lang.String, int);
method public static java.lang.String replaceUnicodeDigits(java.lang.String);
method public static java.lang.String stringFromStringAndTOA(java.lang.String, int);
method public static java.lang.String stripSeparators(java.lang.String);
method public static java.lang.String toCallerIDMinMatch(java.lang.String);
method public static int toaFromString(java.lang.String);
+ field public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2; // 0x2
+ field public static final int BCD_EXTENDED_TYPE_EF_ADN = 1; // 0x1
field public static final int FORMAT_JAPAN = 2; // 0x2
field public static final int FORMAT_NANP = 1; // 0x1
field public static final int FORMAT_UNKNOWN = 0; // 0x0
@@ -40644,7 +40712,6 @@
public final class DownloadRequest implements android.os.Parcelable {
method public static android.telephony.mbms.DownloadRequest copy(android.telephony.mbms.DownloadRequest);
method public int describeContents();
- method public android.net.Uri getDestinationUri();
method public java.lang.String getFileServiceId();
method public static int getMaxAppIntentSize();
method public static int getMaxDestinationUriSize();
@@ -40658,7 +40725,6 @@
ctor public DownloadRequest.Builder();
method public android.telephony.mbms.DownloadRequest build();
method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
- method public android.telephony.mbms.DownloadRequest.Builder setDest(android.net.Uri);
method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
method public android.telephony.mbms.DownloadRequest.Builder setSource(android.net.Uri);
method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int);
@@ -40667,7 +40733,7 @@
public class DownloadStateCallback {
ctor public DownloadStateCallback();
method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
- method public void onStateChanged(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+ method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
}
public final class FileInfo implements android.os.Parcelable {
@@ -40685,32 +40751,31 @@
field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR;
}
- public class MbmsDownloadManagerCallback {
- ctor public MbmsDownloadManagerCallback();
- method public void onError(int, java.lang.String);
- method public void onFileServicesUpdated(java.util.List<android.telephony.mbms.FileServiceInfo>);
- method public void onMiddlewareReady();
- }
-
public class MbmsDownloadReceiver extends android.content.BroadcastReceiver {
ctor public MbmsDownloadReceiver();
method public void onReceive(android.content.Context, android.content.Intent);
}
- public class MbmsException extends java.lang.Exception {
- method public int getErrorCode();
+ public class MbmsDownloadSessionCallback {
+ ctor public MbmsDownloadSessionCallback();
+ method public void onError(int, java.lang.String);
+ method public void onFileServicesUpdated(java.util.List<android.telephony.mbms.FileServiceInfo>);
+ method public void onMiddlewareReady();
+ }
+
+ public class MbmsErrors {
field public static final int ERROR_MIDDLEWARE_LOST = 3; // 0x3
field public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; // 0x2
field public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; // 0x1
field public static final int SUCCESS = 0; // 0x0
}
- public static class MbmsException.DownloadErrors {
+ public static class MbmsErrors.DownloadErrors {
field public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 401; // 0x191
field public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402; // 0x192
}
- public static class MbmsException.GeneralErrors {
+ public static class MbmsErrors.GeneralErrors {
field public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; // 0xcf
field public static final int ERROR_IN_E911 = 204; // 0xcc
field public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; // 0xc9
@@ -40720,39 +40785,38 @@
field public static final int ERROR_UNABLE_TO_READ_SIM = 206; // 0xce
}
- public static class MbmsException.InitializationErrors {
+ public static class MbmsErrors.InitializationErrors {
field public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; // 0x66
field public static final int ERROR_DUPLICATE_INITIALIZE = 101; // 0x65
field public static final int ERROR_UNABLE_TO_INITIALIZE = 103; // 0x67
}
- public static class MbmsException.StreamingErrors {
+ public static class MbmsErrors.StreamingErrors {
field public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; // 0x12d
field public static final int ERROR_DUPLICATE_START_STREAM = 303; // 0x12f
field public static final int ERROR_UNABLE_TO_START_SERVICE = 302; // 0x12e
}
- public class MbmsStreamingManagerCallback {
- ctor public MbmsStreamingManagerCallback();
+ public class MbmsStreamingSessionCallback {
+ ctor public MbmsStreamingSessionCallback();
method public void onError(int, java.lang.String);
method public void onMiddlewareReady();
method public void onStreamingServicesUpdated(java.util.List<android.telephony.mbms.StreamingServiceInfo>);
}
public class ServiceInfo {
- method public java.lang.String getClassName();
method public java.util.List<java.util.Locale> getLocales();
- method public java.util.Map<java.util.Locale, java.lang.String> getNames();
+ method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+ method public java.lang.String getServiceClassName();
method public java.lang.String getServiceId();
method public java.util.Date getSessionEndTime();
method public java.util.Date getSessionStartTime();
}
public class StreamingService {
- method public void dispose() throws android.telephony.mbms.MbmsException;
method public android.telephony.mbms.StreamingServiceInfo getInfo();
- method public android.net.Uri getPlaybackUri() throws android.telephony.mbms.MbmsException;
- method public void stopStreaming() throws android.telephony.mbms.MbmsException;
+ method public android.net.Uri getPlaybackUri();
+ method public void stopStreaming();
field public static final int BROADCAST_METHOD = 1; // 0x1
field public static final int REASON_BY_USER_REQUEST = 1; // 0x1
field public static final int REASON_END_OF_SESSION = 2; // 0x2
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index e5c2466..445665e 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -69,9 +69,6 @@
static status_t notifyMediaScanner(const char* fileName) {
String8 cmd("am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://");
- String8 fileUrl("\"");
- fileUrl.append(fileName);
- fileUrl.append("\"");
cmd.append(fileName);
cmd.append(" > /dev/null");
int result = system(cmd.string());
diff --git a/compiled-classes-phone b/compiled-classes-phone
index cf4b28b..71fc5c1 100644
--- a/compiled-classes-phone
+++ b/compiled-classes-phone
@@ -3824,7 +3824,6 @@
android.system.StructAddrinfo
android.system.StructFlock
android.system.StructGroupReq
-android.system.StructGroupSourceReq
android.system.StructIcmpHdr
android.system.StructIfaddrs
android.system.StructLinger
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 87e512c..7ca65a4 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -175,6 +175,9 @@
}
if (result != null) {
response.onResult(result);
+ } else {
+ response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle returned");
}
} catch (Exception e) {
handleException(response, "addAccount", accountType, e);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index a446296..d22e268 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -2318,6 +2318,10 @@
private class Response extends IAccountManagerResponse.Stub {
@Override
public void onResult(Bundle bundle) {
+ if (bundle == null) {
+ onError(ERROR_CODE_INVALID_RESPONSE, "null bundle returned");
+ return;
+ }
Intent intent = bundle.getParcelable(KEY_INTENT);
if (intent != null && mActivity != null) {
// since the user provided an Activity we will silently start intents
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 01c4656..2931467 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -55,7 +55,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract.Directory;
-import android.provider.Settings;
import android.security.Credentials;
import android.service.restrictions.RestrictionsReceiver;
import android.telephony.TelephonyManager;
@@ -3904,26 +3903,18 @@
/**
* Called by a device or profile owner to configure an always-on VPN connection through a
- * specific application for the current user.
- *
- * @deprecated this version only exists for compability with previous developer preview builds.
- * TODO: delete once there are no longer any live references.
- * @hide
- */
- @Deprecated
- public void setAlwaysOnVpnPackage(@NonNull ComponentName admin, @Nullable String vpnPackage)
- throws NameNotFoundException, UnsupportedOperationException {
- setAlwaysOnVpnPackage(admin, vpnPackage, /* lockdownEnabled */ true);
- }
-
- /**
- * Called by a device or profile owner to configure an always-on VPN connection through a
* specific application for the current user. This connection is automatically granted and
* persisted after a reboot.
* <p>
- * The designated package should declare a {@link android.net.VpnService} in its manifest
- * guarded by {@link android.Manifest.permission#BIND_VPN_SERVICE}, otherwise the call will
- * fail.
+ * To support the always-on feature, an app must
+ * <ul>
+ * <li>declare a {@link android.net.VpnService} in its manifest, guarded by
+ * {@link android.Manifest.permission#BIND_VPN_SERVICE};</li>
+ * <li>target {@link android.os.Build.VERSION_CODES#N API 24} or above; and</li>
+ * <li><i>not</i> explicitly opt out of the feature through
+ * {@link android.net.VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}.</li>
+ * </ul>
+ * The call will fail if called with the package name of an unsupported VPN app.
*
* @param vpnPackage The package name for an installed VPN app on the device, or {@code null} to
* remove an existing always-on VPN configuration.
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 83a82f7..a2af342 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -311,8 +311,7 @@
for (BluetoothGattService brokenRef : includedServices) {
BluetoothGattService includedService = getService(mDevice,
- brokenRef.getUuid(), brokenRef.getInstanceId(),
- brokenRef.getType());
+ brokenRef.getUuid(), brokenRef.getInstanceId());
if (includedService != null) {
fixedService.addIncludedService(includedService);
} else {
@@ -714,10 +713,9 @@
* @hide
*/
/*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
- int instanceId, int type) {
+ int instanceId) {
for (BluetoothGattService svc : mServices) {
if (svc.getDevice().equals(device)
- && svc.getType() == type
&& svc.getInstanceId() == instanceId
&& svc.getUuid().equals(uuid)) {
return svc;
@@ -913,7 +911,7 @@
/**
* Set the preferred connection PHY for this app. Please note that this is just a
- * recommendation, whether the PHY change will happen depends on other applications peferences,
+ * recommendation, whether the PHY change will happen depends on other applications preferences,
* local and remote controller capabilities. Controller can override these settings.
* <p>
* {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index 22eba35..2c8114b 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -184,7 +184,7 @@
/**
* Callback indicating the connection parameters were updated.
*
- * @param gatt The remote device involved
+ * @param device The remote device involved
* @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
* 6 (7.5ms) to 3200 (4000ms).
* @param latency Slave latency for the connection in number of connection events. Valid range
@@ -195,7 +195,7 @@
* successfully
* @hide
*/
- public void onConnectionUpdated(BluetoothDevice gatt, int interval, int latency, int timeout,
+ public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout,
int status) {
}
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 5bfc54d..76cb3f5 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -232,7 +232,7 @@
*/
public static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
UUID uuid = parcelUuid.getUuid();
- long value = (uuid.getMostSignificantBits() & 0x0000FFFF00000000L) >>> 32;
+ long value = (uuid.getMostSignificantBits() & 0xFFFFFFFF00000000L) >>> 32;
return (int) value;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6c62f5e..32e7d48 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2995,6 +2995,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
@@ -3339,7 +3342,6 @@
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
- * @hide
* @see #getSystemService
*/
public static final String IPSEC_SERVICE = "ipsec";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 1d879e9..970b362 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -9329,7 +9329,7 @@
for (int i=0; i<N; i++) {
char c = data.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
- || c == '.' || c == '-') {
+ || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+') {
continue;
}
if (c == ':' && i > 0) {
diff --git a/core/java/android/hardware/LegacySensorManager.java b/core/java/android/hardware/LegacySensorManager.java
index f5cf3f7..098121d 100644
--- a/core/java/android/hardware/LegacySensorManager.java
+++ b/core/java/android/hardware/LegacySensorManager.java
@@ -204,7 +204,7 @@
}
private static final class LegacyListener implements SensorEventListener {
- private float mValues[] = new float[6];
+ private float[] mValues = new float[6];
private SensorListener mTarget;
private int mSensors;
private final LmsFilter mYawfilter = new LmsFilter();
@@ -256,7 +256,7 @@
}
public void onSensorChanged(SensorEvent event) {
- final float v[] = mValues;
+ final float[] v = mValues;
v[0] = event.values[0];
v[1] = event.values[1];
v[2] = event.values[2];
@@ -264,10 +264,10 @@
int legacyType = getLegacySensorType(type);
mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
if (type == Sensor.TYPE_ORIENTATION) {
- if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW)!=0) {
+ if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW) != 0) {
mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
}
- if ((mSensors & SensorManager.SENSOR_ORIENTATION)!=0) {
+ if ((mSensors & SensorManager.SENSOR_ORIENTATION) != 0) {
v[0] = mYawfilter.filter(event.timestamp, v[0]);
mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
}
@@ -317,7 +317,7 @@
switch (sensor) {
case SensorManager.SENSOR_ACCELEROMETER:
case SensorManager.SENSOR_MAGNETIC_FIELD:
- values[0] =-y;
+ values[0] = -y;
values[1] = x;
values[2] = z;
break;
@@ -337,15 +337,15 @@
switch (sensor) {
case SensorManager.SENSOR_ACCELEROMETER:
case SensorManager.SENSOR_MAGNETIC_FIELD:
- values[0] =-x;
- values[1] =-y;
+ values[0] = -x;
+ values[1] = -y;
values[2] = z;
break;
case SensorManager.SENSOR_ORIENTATION:
case SensorManager.SENSOR_ORIENTATION_RAW:
values[0] = (x >= 180) ? (x - 180) : (x + 180);
- values[1] =-y;
- values[2] =-z;
+ values[1] = -y;
+ values[2] = -z;
break;
}
}
@@ -369,10 +369,11 @@
private static final class LmsFilter {
private static final int SENSORS_RATE_MS = 20;
private static final int COUNT = 12;
- private static final float PREDICTION_RATIO = 1.0f/3.0f;
- private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
- private float mV[] = new float[COUNT*2];
- private long mT[] = new long[COUNT*2];
+ private static final float PREDICTION_RATIO = 1.0f / 3.0f;
+ private static final float PREDICTION_TIME =
+ (SENSORS_RATE_MS * COUNT / 1000.0f) * PREDICTION_RATIO;
+ private float[] mV = new float[COUNT * 2];
+ private long[] mT = new long[COUNT * 2];
private int mIndex;
public LmsFilter() {
@@ -383,9 +384,9 @@
float v = in;
final float ns = 1.0f / 1000000000.0f;
float v1 = mV[mIndex];
- if ((v-v1) > 180) {
+ if ((v - v1) > 180) {
v -= 360;
- } else if ((v1-v) > 180) {
+ } else if ((v1 - v) > 180) {
v += 360;
}
/* Manage the circular buffer, we write the data twice spaced
@@ -393,40 +394,43 @@
* when it's full
*/
mIndex++;
- if (mIndex >= COUNT*2)
+ if (mIndex >= COUNT * 2) {
mIndex = COUNT;
+ }
mV[mIndex] = v;
mT[mIndex] = time;
- mV[mIndex-COUNT] = v;
- mT[mIndex-COUNT] = time;
+ mV[mIndex - COUNT] = v;
+ mT[mIndex - COUNT] = time;
float A, B, C, D, E;
float a, b;
int i;
A = B = C = D = E = 0;
- for (i=0 ; i<COUNT-1 ; i++) {
+ for (i = 0; i < COUNT - 1; i++) {
final int j = mIndex - 1 - i;
final float Z = mV[j];
- final float T = (mT[j]/2 + mT[j+1]/2 - time)*ns;
- float dT = (mT[j] - mT[j+1])*ns;
+ final float T = (mT[j] / 2 + mT[j + 1] / 2 - time) * ns;
+ float dT = (mT[j] - mT[j + 1]) * ns;
dT *= dT;
- A += Z*dT;
- B += T*(T*dT);
- C += (T*dT);
- D += Z*(T*dT);
+ A += Z * dT;
+ B += T * (T * dT);
+ C += (T * dT);
+ D += Z * (T * dT);
E += dT;
}
- b = (A*B + C*D) / (E*B + C*C);
- a = (E*b - A) / C;
- float f = b + PREDICTION_TIME*a;
+ b = (A * B + C * D) / (E * B + C * C);
+ a = (E * b - A) / C;
+ float f = b + PREDICTION_TIME * a;
// Normalize
f *= (1.0f / 360.0f);
- if (((f>=0)?f:-f) >= 0.5f)
- f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
- if (f < 0)
+ if (((f >= 0) ? f : -f) >= 0.5f) {
+ f = f - (float) Math.ceil(f + 0.5f) + 1.0f;
+ }
+ if (f < 0) {
f += 1.0f;
+ }
f *= 360.0f;
return f;
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index f02e484..7fb0c89 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -794,12 +794,12 @@
1, // SENSOR_TYPE_PICK_UP_GESTURE
1, // SENSOR_TYPE_WRIST_TILT_GESTURE
1, // SENSOR_TYPE_DEVICE_ORIENTATION
- 16,// SENSOR_TYPE_POSE_6DOF
+ 16, // SENSOR_TYPE_POSE_6DOF
1, // SENSOR_TYPE_STATIONARY_DETECT
1, // SENSOR_TYPE_MOTION_DETECT
1, // SENSOR_TYPE_HEART_BEAT
2, // SENSOR_TYPE_DYNAMIC_SENSOR_META
- 16,// skip over additional sensor info type
+ 16, // skip over additional sensor info type
1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT
6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED
};
@@ -857,8 +857,8 @@
static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
// RotationVector length has changed to 3 to 5 for API level 18
// Set it to 3 for backward compatibility.
- if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR &&
- sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR
+ && sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return 3;
}
int offset = sensor.mType;
@@ -1033,9 +1033,9 @@
* Returns true if the sensor is a wake-up sensor.
* <p>
* <b>Application Processor Power modes</b> <p>
- * Application Processor(AP), is the processor on which applications run. When no wake lock is held
- * and the user is not interacting with the device, this processor can enter a “Suspend” mode,
- * reducing the power consumption by 10 times or more.
+ * Application Processor(AP), is the processor on which applications run. When no wake lock is
+ * held and the user is not interacting with the device, this processor can enter a “Suspend”
+ * mode, reducing the power consumption by 10 times or more.
* </p>
* <p>
* <b>Non-wake-up sensors</b> <p>
@@ -1232,6 +1232,6 @@
*/
private void setUuid(long msb, long lsb) {
// TODO(b/29547335): Rename this method to setId.
- mId = (int)msb;
+ mId = (int) msb;
}
}
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index ea1d01b..7c876cf 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -189,8 +189,18 @@
*/
public static final int TYPE_MAGNETIC_FIELD_CALIBRATION = 0x30004;
+ /**
+ * Custom sensor info: array of float values interpreted by sensor based on the type
+ * Any type between TYPE_CUSTOM_INFO <= info_type < TYPE_DEBUG_INFO may be
+ * used to send custom sensor info.
+ * @hide
+ */
+ public static final int TYPE_CUSTOM_INFO = 0x10000000;
+ /** @hide */
+ public static final int TYPE_DEBUG_INFO = 0x40000000;
+
SensorAdditionalInfo(
- Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
+ Sensor aSensor, int aType, int aSerial, int[] aIntValues, float[] aFloatValues) {
sensor = aSensor;
type = aType;
serial = aSerial;
@@ -211,4 +221,13 @@
null, TYPE_LOCAL_GEOMAGNETIC_FIELD, 0,
null, new float[] { strength, declination, inclination});
}
+ /** @hide */
+ public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float[] data) {
+ if (type < TYPE_CUSTOM_INFO || type >= TYPE_DEBUG_INFO || aSensor == null) {
+ throw new IllegalArgumentException(
+ "invalid parameter(s): type: " + type + "; sensor: " + aSensor);
+ }
+
+ return new SensorAdditionalInfo(aSensor, type, 0, null, data);
+ }
}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index c0bca97..bbd04a3 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -207,8 +207,8 @@
* timestamp = event.timestamp;
* float[] deltaRotationMatrix = new float[9];
* SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
- * // User code should concatenate the delta rotation we computed with the current rotation
- * // in order to get the updated rotation.
+ * // User code should concatenate the delta rotation we computed with the current
+ * // rotation in order to get the updated rotation.
* // rotationCurrent = rotationCurrent * deltaRotationMatrix;
* }
* </pre>
@@ -244,21 +244,22 @@
* <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4>
* <p>A three dimensional vector indicating the direction and magnitude of gravity. Units
* are m/s^2. The coordinate system is the same as is used by the acceleration sensor.</p>
- * <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be identical
- * to that of the accelerometer.</p>
+ * <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be
+ * identical to that of the accelerometer.</p>
*
- * <h4>{@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:</h4>
- * A three dimensional vector indicating acceleration along each device axis, not including
- * gravity. All values have units of m/s^2. The coordinate system is the same as is used by the
- * acceleration sensor.
+ * <h4>
+ * {@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:
+ * </h4> A three dimensional vector indicating acceleration along each device axis, not
+ * including gravity. All values have units of m/s^2. The coordinate system is the same as is
+ * used by the acceleration sensor.
* <p>The output of the accelerometer, gravity and linear-acceleration sensors must obey the
* following relation:</p>
- * <p><ul>acceleration = gravity + linear-acceleration</ul></p>
+ * <p><ul>acceleration = gravity + linear-acceleration</ul></p>
*
* <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4>
- * <p>The rotation vector represents the orientation of the device as a combination of an <i>angle</i>
- * and an <i>axis</i>, in which the device has rotated through an angle θ around an axis
- * <x, y, z>.</p>
+ * <p>The rotation vector represents the orientation of the device as a combination of an
+ * <i>angle</i> and an <i>axis</i>, in which the device has rotated through an angle θ
+ * around an axis <x, y, z>.</p>
* <p>The three elements of the rotation vector are
* <x*sin(θ/2), y*sin(θ/2), z*sin(θ/2)>, such that the magnitude of the rotation
* vector is equal to sin(θ/2), and the direction of the rotation vector is equal to the
diff --git a/core/java/android/hardware/SensorListener.java b/core/java/android/hardware/SensorListener.java
index c71e968..e2033b6 100644
--- a/core/java/android/hardware/SensorListener.java
+++ b/core/java/android/hardware/SensorListener.java
@@ -19,8 +19,8 @@
/**
* Used for receiving notifications from the SensorManager when
* sensor values have changed.
- *
- * @deprecated Use
+ *
+ * @deprecated Use
* {@link android.hardware.SensorEventListener SensorEventListener} instead.
*/
@Deprecated
@@ -36,7 +36,7 @@
* <p><u>Definition of the coordinate system used below.</u><p>
* <p>The X axis refers to the screen's horizontal axis
* (the small edge in portrait mode, the long edge in landscape mode) and
- * points to the right.
+ * points to the right.
* <p>The Y axis refers to the screen's vertical axis and points towards
* the top of the screen (the origin is in the lower-left corner).
* <p>The Z axis points toward the sky when the device is lying on its back
@@ -44,18 +44,18 @@
* <p> <b>IMPORTANT NOTE:</b> The axis <b><u>are swapped</u></b> when the
* device's screen orientation changes. To access the unswapped values,
* use indices 3, 4 and 5 in values[].
- *
+ *
* <p>{@link android.hardware.SensorManager#SENSOR_ORIENTATION SENSOR_ORIENTATION},
* {@link android.hardware.SensorManager#SENSOR_ORIENTATION_RAW SENSOR_ORIENTATION_RAW}:<p>
* All values are angles in degrees.
- *
+ *
* <p>values[0]: Azimuth, rotation around the Z axis (0<=azimuth<360).
* 0 = North, 90 = East, 180 = South, 270 = West
- *
+ *
* <p>values[1]: Pitch, rotation around X axis (-180<=pitch<=180), with positive
* values when the z-axis moves toward the y-axis.
*
- * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
+ * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
* when the z-axis moves toward the x-axis.
*
* <p>Note that this definition of yaw, pitch and roll is different from the
@@ -64,17 +64,17 @@
*
* <p>{@link android.hardware.SensorManager#SENSOR_ACCELEROMETER SENSOR_ACCELEROMETER}:<p>
* All values are in SI units (m/s^2) and measure contact forces.
- *
- * <p>values[0]: force applied by the device on the x-axis
- * <p>values[1]: force applied by the device on the y-axis
+ *
+ * <p>values[0]: force applied by the device on the x-axis
+ * <p>values[1]: force applied by the device on the y-axis
* <p>values[2]: force applied by the device on the z-axis
- *
+ *
* <p><u>Examples</u>:
* <li>When the device is pushed on its left side toward the right, the
* x acceleration value is negative (the device applies a reaction force
* to the push toward the left)</li>
- *
- * <li>When the device lies flat on a table, the acceleration value is
+ *
+ * <li>When the device lies flat on a table, the acceleration value is
* {@link android.hardware.SensorManager#STANDARD_GRAVITY -STANDARD_GRAVITY},
* which correspond to the force the device applies on the table in reaction
* to gravity.</li>
@@ -83,7 +83,7 @@
* All values are in micro-Tesla (uT) and measure the ambient magnetic
* field in the X, Y and -Z axis.
* <p><b><u>Note:</u></b> the magnetic field's Z axis is inverted.
- *
+ *
* @param sensor The ID of the sensor being monitored
* @param values The new values for the sensor.
*/
@@ -97,5 +97,5 @@
* @param sensor The ID of the sensor being monitored
* @param accuracy The new accuracy of this sensor.
*/
- public void onAccuracyChanged(int sensor, int accuracy);
+ public void onAccuracyChanged(int sensor, int accuracy);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 4bc62b1..35aaf78 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -83,7 +83,7 @@
/** @hide */
protected static final String TAG = "SensorManager";
- private static final float[] mTempMatrix = new float[16];
+ private static final float[] sTempMatrix = new float[16];
// Cached lists of sensors by type. Guarded by mSensorListByType.
private final SparseArray<List<Sensor>> mSensorListByType =
@@ -188,7 +188,7 @@
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
- public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
+ public static final int SENSOR_MAX = ((SENSOR_ALL + 1) >> 1);
/**
@@ -425,8 +425,9 @@
} else {
list = new ArrayList<Sensor>();
for (Sensor i : fullList) {
- if (i.getType() == type)
+ if (i.getType() == type) {
list.add(i);
+ }
}
}
list = Collections.unmodifiableList(list);
@@ -461,8 +462,9 @@
} else {
List<Sensor> list = new ArrayList();
for (Sensor i : fullList) {
- if (i.getType() == type)
+ if (i.getType() == type) {
list.add(i);
+ }
}
return Collections.unmodifiableList(list);
}
@@ -490,10 +492,11 @@
// For the following sensor types, return a wake-up sensor. These types are by default
// defined as wake-up sensors. For the rest of the SDK defined sensor types return a
// non_wake-up version.
- if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
- type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
- type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
- type == Sensor.TYPE_WRIST_TILT_GESTURE || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
+ if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION
+ || type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE
+ || type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE
+ || type == Sensor.TYPE_WRIST_TILT_GESTURE
+ || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
wakeUpSensor = true;
}
@@ -509,12 +512,12 @@
* <p>
* For example,
* <ul>
- * <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up accelerometer
- * sensor if it exists. </li>
- * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up proximity
- * sensor if it exists. </li>
- * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity sensor
- * which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
+ * <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up
+ * accelerometer sensor if it exists. </li>
+ * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up
+ * proximity sensor if it exists. </li>
+ * <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity
+ * sensor which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
* </ul>
* </p>
* <p class="note">
@@ -532,8 +535,9 @@
public Sensor getDefaultSensor(int type, boolean wakeUp) {
List<Sensor> l = getSensorList(type);
for (Sensor sensor : l) {
- if (sensor.isWakeUpSensor() == wakeUp)
+ if (sensor.isWakeUpSensor() == wakeUp) {
return sensor;
+ }
}
return null;
}
@@ -842,8 +846,8 @@
* @return <code>true</code> if the sensor is supported and successfully enabled.
* @see #registerListener(SensorEventListener, Sensor, int, int)
*/
- public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs,
- int maxReportLatencyUs, Handler handler) {
+ public boolean registerListener(SensorEventListener listener, Sensor sensor,
+ int samplingPeriodUs, int maxReportLatencyUs, Handler handler) {
int delayUs = getDelay(samplingPeriodUs);
return registerListenerImpl(listener, sensor, delayUs, handler, maxReportLatencyUs, 0);
}
@@ -894,8 +898,9 @@
* to free up resource in sensor system associated with the direct channel.
*
* @param mem A {@link android.os.MemoryFile} shared memory object.
- * @return A {@link android.hardware.SensorDirectChannel} object if successful, null otherwise.
+ * @return A {@link android.hardware.SensorDirectChannel} object.
* @throws NullPointerException when mem is null.
+ * @throws UncheckedIOException if not able to create channel.
* @see SensorDirectChannel#close()
* @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
*/
@@ -916,9 +921,9 @@
* to free up resource in sensor system associated with the direct channel.
*
* @param mem A {@link android.hardware.HardwareBuffer} shared memory object.
- * @return A {@link android.hardware.SensorDirectChannel} object if successful,
- * null otherwise.
+ * @return A {@link android.hardware.SensorDirectChannel} object.
* @throws NullPointerException when mem is null.
+ * @throws UncheckedIOException if not able to create channel.
* @see SensorDirectChannel#close()
* @see #configureDirectChannel(SensorDirectChannel, Sensor, int)
*/
@@ -952,7 +957,7 @@
* Used for receiving notifications from the SensorManager when dynamic sensors are connected or
* disconnected.
*/
- public static abstract class DynamicSensorCallback {
+ public abstract static class DynamicSensorCallback {
/**
* Called when there is a dynamic sensor being connected to the system.
*
@@ -1179,7 +1184,7 @@
float Ay = gravity[1];
float Az = gravity[2];
- final float normsqA = (Ax*Ax + Ay*Ay + Az*Az);
+ final float normsqA = (Ax * Ax + Ay * Ay + Az * Az);
final float g = 9.81f;
final float freeFallGravitySquared = 0.01f * g * g;
if (normsqA < freeFallGravitySquared) {
@@ -1190,10 +1195,10 @@
final float Ex = geomagnetic[0];
final float Ey = geomagnetic[1];
final float Ez = geomagnetic[2];
- float Hx = Ey*Az - Ez*Ay;
- float Hy = Ez*Ax - Ex*Az;
- float Hz = Ex*Ay - Ey*Ax;
- final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
+ float Hx = Ey * Az - Ez * Ay;
+ float Hy = Ez * Ax - Ex * Az;
+ float Hz = Ex * Ay - Ey * Ax;
+ final float normH = (float) Math.sqrt(Hx * Hx + Hy * Hy + Hz * Hz);
if (normH < 0.1f) {
// device is close to free fall (or in space?), or close to
@@ -1204,13 +1209,13 @@
Hx *= invH;
Hy *= invH;
Hz *= invH;
- final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
+ final float invA = 1.0f / (float) Math.sqrt(Ax * Ax + Ay * Ay + Az * Az);
Ax *= invA;
Ay *= invA;
Az *= invA;
- final float Mx = Ay*Hz - Az*Hy;
- final float My = Az*Hx - Ax*Hz;
- final float Mz = Ax*Hy - Ay*Hx;
+ final float Mx = Ay * Hz - Az * Hy;
+ final float My = Az * Hx - Ax * Hz;
+ final float Mz = Ax * Hy - Ay * Hx;
if (R != null) {
if (R.length == 9) {
R[0] = Hx; R[1] = Hy; R[2] = Hz;
@@ -1227,17 +1232,17 @@
// compute the inclination matrix by projecting the geomagnetic
// vector onto the Z (gravity) and X (horizontal component
// of geomagnetic vector) axes.
- final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
- final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
- final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
+ final float invE = 1.0f / (float) Math.sqrt(Ex * Ex + Ey * Ey + Ez * Ez);
+ final float c = (Ex * Mx + Ey * My + Ez * Mz) * invE;
+ final float s = (Ex * Ax + Ey * Ay + Ez * Az) * invE;
if (I.length == 9) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[3] = 0; I[4] = c; I[5] = s;
- I[6] = 0; I[7] =-s; I[8] = c;
+ I[6] = 0; I[7] = -s; I[8] = c;
} else if (I.length == 16) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[4] = 0; I[5] = c; I[6] = s;
- I[8] = 0; I[9] =-s; I[10]= c;
+ I[8] = 0; I[9] = -s; I[10] = c;
I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
I[15] = 1;
}
@@ -1261,9 +1266,9 @@
*/
public static float getInclination(float[] I) {
if (I.length == 9) {
- return (float)Math.atan2(I[5], I[4]);
+ return (float) Math.atan2(I[5], I[4]);
} else {
- return (float)Math.atan2(I[6], I[5]);
+ return (float) Math.atan2(I[6], I[5]);
}
}
@@ -1342,17 +1347,16 @@
* @see #getRotationMatrix(float[], float[], float[], float[])
*/
- public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
- float[] outR)
- {
+ public static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) {
if (inR == outR) {
- final float[] temp = mTempMatrix;
- synchronized(temp) {
+ final float[] temp = sTempMatrix;
+ synchronized (temp) {
// we don't expect to have a lot of contention
if (remapCoordinateSystemImpl(inR, X, Y, temp)) {
final int size = outR.length;
- for (int i=0 ; i<size ; i++)
+ for (int i = 0; i < size; i++) {
outR[i] = temp[i];
+ }
return true;
}
}
@@ -1360,9 +1364,7 @@
return remapCoordinateSystemImpl(inR, X, Y, outR);
}
- private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y,
- float[] outR)
- {
+ private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y, float[] outR) {
/*
* X and Y define a rotation matrix 'r':
*
@@ -1375,14 +1377,18 @@
*/
final int length = outR.length;
- if (inR.length != length)
+ if (inR.length != length) {
return false; // invalid parameter
- if ((X & 0x7C)!=0 || (Y & 0x7C)!=0)
+ }
+ if ((X & 0x7C) != 0 || (Y & 0x7C) != 0) {
return false; // invalid parameter
- if (((X & 0x3)==0) || ((Y & 0x3)==0))
+ }
+ if (((X & 0x3) == 0) || ((Y & 0x3) == 0)) {
return false; // no axis specified
- if ((X & 0x3) == (Y & 0x3))
+ }
+ if ((X & 0x3) == (Y & 0x3)) {
return false; // same axis specified
+ }
// Z is "the other" axis, its sign is either +/- sign(X)*sign(Y)
// this can be calculated by exclusive-or'ing X and Y; except for
@@ -1390,28 +1396,29 @@
int Z = X ^ Y;
// extract the axis (remove the sign), offset in the range 0 to 2.
- final int x = (X & 0x3)-1;
- final int y = (Y & 0x3)-1;
- final int z = (Z & 0x3)-1;
+ final int x = (X & 0x3) - 1;
+ final int y = (Y & 0x3) - 1;
+ final int z = (Z & 0x3) - 1;
// compute the sign of Z (whether it needs to be inverted)
- final int axis_y = (z+1)%3;
- final int axis_z = (z+2)%3;
- if (((x^axis_y)|(y^axis_z)) != 0)
+ final int axis_y = (z + 1) % 3;
+ final int axis_z = (z + 2) % 3;
+ if (((x ^ axis_y) | (y ^ axis_z)) != 0) {
Z ^= 0x80;
+ }
- final boolean sx = (X>=0x80);
- final boolean sy = (Y>=0x80);
- final boolean sz = (Z>=0x80);
+ final boolean sx = (X >= 0x80);
+ final boolean sy = (Y >= 0x80);
+ final boolean sz = (Z >= 0x80);
// Perform R * r, in avoiding actual muls and adds.
- final int rowLength = ((length==16)?4:3);
- for (int j=0 ; j<3 ; j++) {
- final int offset = j*rowLength;
- for (int i=0 ; i<3 ; i++) {
- if (x==i) outR[offset+i] = sx ? -inR[offset+0] : inR[offset+0];
- if (y==i) outR[offset+i] = sy ? -inR[offset+1] : inR[offset+1];
- if (z==i) outR[offset+i] = sz ? -inR[offset+2] : inR[offset+2];
+ final int rowLength = ((length == 16) ? 4 : 3);
+ for (int j = 0; j < 3; j++) {
+ final int offset = j * rowLength;
+ for (int i = 0; i < 3; i++) {
+ if (x == i) outR[offset + i] = sx ? -inR[offset + 0] : inR[offset + 0];
+ if (y == i) outR[offset + i] = sy ? -inR[offset + 1] : inR[offset + 1];
+ if (z == i) outR[offset + i] = sz ? -inR[offset + 2] : inR[offset + 2];
}
}
if (length == 16) {
@@ -1465,7 +1472,7 @@
* @see #getRotationMatrix(float[], float[], float[], float[])
* @see GeomagneticField
*/
- public static float[] getOrientation(float[] R, float values[]) {
+ public static float[] getOrientation(float[] R, float[] values) {
/*
* 4x4 (length=16) case:
* / R[ 0] R[ 1] R[ 2] 0 \
@@ -1480,13 +1487,13 @@
*
*/
if (R.length == 9) {
- values[0] = (float)Math.atan2(R[1], R[4]);
- values[1] = (float)Math.asin(-R[7]);
- values[2] = (float)Math.atan2(-R[6], R[8]);
+ values[0] = (float) Math.atan2(R[1], R[4]);
+ values[1] = (float) Math.asin(-R[7]);
+ values[2] = (float) Math.atan2(-R[6], R[8]);
} else {
- values[0] = (float)Math.atan2(R[1], R[5]);
- values[1] = (float)Math.asin(-R[9]);
- values[2] = (float)Math.atan2(-R[8], R[10]);
+ values[0] = (float) Math.atan2(R[1], R[5]);
+ values[1] = (float) Math.asin(-R[9]);
+ values[2] = (float) Math.atan2(-R[8], R[10]);
}
return values;
@@ -1523,7 +1530,7 @@
*/
public static float getAltitude(float p0, float p) {
final float coef = 1.0f / 5.255f;
- return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef));
+ return 44330.0f * (1.0f - (float) Math.pow(p / p0, coef));
}
/** Helper function to compute the angle change between two rotation matrices.
@@ -1556,12 +1563,13 @@
* (in radians) is stored
*/
- public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) {
- float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0;
- float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0;
- float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0;
+ public static void getAngleChange(float[] angleChange, float[] R, float[] prevR) {
+ float rd1 = 0, rd4 = 0, rd6 = 0, rd7 = 0, rd8 = 0;
+ float ri0 = 0, ri1 = 0, ri2 = 0, ri3 = 0, ri4 = 0, ri5 = 0, ri6 = 0, ri7 = 0, ri8 = 0;
+ float pri0 = 0, pri1 = 0, pri2 = 0, pri3 = 0, pri4 = 0;
+ float pri5 = 0, pri6 = 0, pri7 = 0, pri8 = 0;
- if(R.length == 9) {
+ if (R.length == 9) {
ri0 = R[0];
ri1 = R[1];
ri2 = R[2];
@@ -1571,7 +1579,7 @@
ri6 = R[6];
ri7 = R[7];
ri8 = R[8];
- } else if(R.length == 16) {
+ } else if (R.length == 16) {
ri0 = R[0];
ri1 = R[1];
ri2 = R[2];
@@ -1583,7 +1591,7 @@
ri8 = R[10];
}
- if(prevR.length == 9) {
+ if (prevR.length == 9) {
pri0 = prevR[0];
pri1 = prevR[1];
pri2 = prevR[2];
@@ -1593,7 +1601,7 @@
pri6 = prevR[6];
pri7 = prevR[7];
pri8 = prevR[8];
- } else if(prevR.length == 16) {
+ } else if (prevR.length == 16) {
pri0 = prevR[0];
pri1 = prevR[1];
pri2 = prevR[2];
@@ -1614,9 +1622,9 @@
rd7 = pri2 * ri1 + pri5 * ri4 + pri8 * ri7; //rd[2][1]
rd8 = pri2 * ri2 + pri5 * ri5 + pri8 * ri8; //rd[2][2]
- angleChange[0] = (float)Math.atan2(rd1, rd4);
- angleChange[1] = (float)Math.asin(-rd7);
- angleChange[2] = (float)Math.atan2(-rd6, rd8);
+ angleChange[0] = (float) Math.atan2(rd1, rd4);
+ angleChange[1] = (float) Math.asin(-rd7);
+ angleChange[2] = (float) Math.atan2(-rd6, rd8);
}
@@ -1649,8 +1657,8 @@
if (rotationVector.length >= 4) {
q0 = rotationVector[3];
} else {
- q0 = 1 - q1*q1 - q2*q2 - q3*q3;
- q0 = (q0 > 0) ? (float)Math.sqrt(q0) : 0;
+ q0 = 1 - q1 * q1 - q2 * q2 - q3 * q3;
+ q0 = (q0 > 0) ? (float) Math.sqrt(q0) : 0;
}
float sq_q1 = 2 * q1 * q1;
@@ -1663,7 +1671,7 @@
float q2_q3 = 2 * q2 * q3;
float q1_q0 = 2 * q1 * q0;
- if(R.length == 9) {
+ if (R.length == 9) {
R[0] = 1 - sq_q2 - sq_q3;
R[1] = q1_q2 - q3_q0;
R[2] = q1_q3 + q2_q0;
@@ -1706,8 +1714,8 @@
if (rv.length >= 4) {
Q[0] = rv[3];
} else {
- Q[0] = 1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2];
- Q[0] = (Q[0] > 0) ? (float)Math.sqrt(Q[0]) : 0;
+ Q[0] = 1 - rv[0] * rv[0] - rv[1] * rv[1] - rv[2] * rv[2];
+ Q[0] = (Q[0] > 0) ? (float) Math.sqrt(Q[0]) : 0;
}
Q[1] = rv[0];
Q[2] = rv[1];
@@ -1799,7 +1807,7 @@
*/
@SystemApi
public boolean initDataInjection(boolean enable) {
- return initDataInjectionImpl(enable);
+ return initDataInjectionImpl(enable);
}
/**
@@ -1845,9 +1853,9 @@
}
int expectedNumValues = Sensor.getMaxLengthValuesArray(sensor, Build.VERSION_CODES.M);
if (values.length != expectedNumValues) {
- throw new IllegalArgumentException ("Wrong number of values for sensor " +
- sensor.getName() + " actual=" + values.length + " expected=" +
- expectedNumValues);
+ throw new IllegalArgumentException("Wrong number of values for sensor "
+ + sensor.getName() + " actual=" + values.length + " expected="
+ + expectedNumValues);
}
if (accuracy < SENSOR_STATUS_NO_CONTACT || accuracy > SENSOR_STATUS_ACCURACY_HIGH) {
throw new IllegalArgumentException("Invalid sensor accuracy");
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 0dab5d7..1174cb6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -28,10 +28,11 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import dalvik.system.CloseGuard;
import com.android.internal.annotations.GuardedBy;
+import dalvik.system.CloseGuard;
+
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.ref.WeakReference;
@@ -40,7 +41,6 @@
import java.util.List;
import java.util.Map;
-
/**
* Sensor manager implementation that communicates with the built-in
* system sensors.
@@ -68,7 +68,7 @@
long nativeInstance, int channelHandle, int sensorHandle, int rate);
private static native int nativeSetOperationParameter(
- long nativeInstance, int type, float[] floatValues, int[] intValues);
+ long nativeInstance, int handle, int type, float[] floatValues, int[] intValues);
private static final Object sLock = new Object();
@GuardedBy("sLock")
@@ -101,7 +101,7 @@
/** {@hide} */
public SystemSensorManager(Context context, Looper mainLooper) {
- synchronized(sLock) {
+ synchronized (sLock) {
if (!sNativeClassInited) {
sNativeClassInited = true;
nativeClassInit();
@@ -114,7 +114,7 @@
mNativeInstance = nativeCreate(context.getOpPackageName());
// initialize the sensor list
- for (int index = 0;;++index) {
+ for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
mFullSensorsList.add(sensor);
@@ -157,9 +157,9 @@
return false;
}
if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
- throw new IllegalStateException("register failed, " +
- "the sensor listeners size has exceeded the maximum limit " +
- MAX_LISTENER_COUNT);
+ throw new IllegalStateException("register failed, "
+ + "the sensor listeners size has exceeded the maximum limit "
+ + MAX_LISTENER_COUNT);
}
// Invariants to preserve:
@@ -170,9 +170,10 @@
SensorEventQueue queue = mSensorListeners.get(listener);
if (queue == null) {
Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
- final String fullClassName = listener.getClass().getEnclosingClass() != null ?
- listener.getClass().getEnclosingClass().getName() :
- listener.getClass().getName();
+ final String fullClassName =
+ listener.getClass().getEnclosingClass() != null
+ ? listener.getClass().getEnclosingClass().getName()
+ : listener.getClass().getName();
queue = new SensorEventQueue(listener, looper, this, fullClassName);
if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
queue.dispose();
@@ -221,17 +222,18 @@
if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false;
if (mTriggerListeners.size() >= MAX_LISTENER_COUNT) {
- throw new IllegalStateException("request failed, " +
- "the trigger listeners size has exceeded the maximum limit " +
- MAX_LISTENER_COUNT);
+ throw new IllegalStateException("request failed, "
+ + "the trigger listeners size has exceeded the maximum limit "
+ + MAX_LISTENER_COUNT);
}
synchronized (mTriggerListeners) {
TriggerEventQueue queue = mTriggerListeners.get(listener);
if (queue == null) {
- final String fullClassName = listener.getClass().getEnclosingClass() != null ?
- listener.getClass().getEnclosingClass().getName() :
- listener.getClass().getName();
+ final String fullClassName =
+ listener.getClass().getEnclosingClass() != null
+ ? listener.getClass().getEnclosingClass().getName()
+ : listener.getClass().getName();
queue = new TriggerEventQueue(listener, mMainLooper, this, fullClassName);
if (!queue.addSensor(sensor, 0, 0)) {
queue.dispose();
@@ -336,27 +338,27 @@
mHandleToSensor.remove(sensor.getHandle());
if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
- synchronized(mTriggerListeners) {
+ synchronized (mTriggerListeners) {
HashMap<TriggerEventListener, TriggerEventQueue> triggerListeners =
- new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
+ new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
- for (TriggerEventListener l: triggerListeners.keySet()) {
- if (DEBUG_DYNAMIC_SENSOR){
- Log.i(TAG, "removed trigger listener" + l.toString() +
- " due to sensor disconnection");
+ for (TriggerEventListener l : triggerListeners.keySet()) {
+ if (DEBUG_DYNAMIC_SENSOR) {
+ Log.i(TAG, "removed trigger listener" + l.toString()
+ + " due to sensor disconnection");
}
cancelTriggerSensorImpl(l, sensor, true);
}
}
} else {
- synchronized(mSensorListeners) {
+ synchronized (mSensorListeners) {
HashMap<SensorEventListener, SensorEventQueue> sensorListeners =
- new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
+ new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
for (SensorEventListener l: sensorListeners.keySet()) {
- if (DEBUG_DYNAMIC_SENSOR){
- Log.i(TAG, "removed event listener" + l.toString() +
- " due to sensor disconnection");
+ if (DEBUG_DYNAMIC_SENSOR) {
+ Log.i(TAG, "removed event listener" + l.toString()
+ + " due to sensor disconnection");
}
unregisterListenerImpl(l, sensor);
}
@@ -365,7 +367,7 @@
}
private void updateDynamicSensorList() {
- synchronized(mFullDynamicSensorsList) {
+ synchronized (mFullDynamicSensorsList) {
if (mDynamicSensorListDirty) {
List<Sensor> list = new ArrayList<>();
nativeGetDynamicSensors(mNativeInstance, list);
@@ -488,15 +490,15 @@
int i = 0, j = 0;
while (true) {
- if (j < oldList.size() && ( i >= newList.size() ||
- newList.get(i).getHandle() > oldList.get(j).getHandle()) ) {
+ if (j < oldList.size() && (i >= newList.size()
+ || newList.get(i).getHandle() > oldList.get(j).getHandle())) {
changed = true;
if (removed != null) {
removed.add(oldList.get(j));
}
++j;
- } else if (i < newList.size() && ( j >= oldList.size() ||
- newList.get(i).getHandle() < oldList.get(j).getHandle())) {
+ } else if (i < newList.size() && (j >= oldList.size()
+ || newList.get(i).getHandle() < oldList.get(j).getHandle())) {
changed = true;
if (added != null) {
added.add(newList.get(i));
@@ -505,8 +507,8 @@
updated.add(newList.get(i));
}
++i;
- } else if (i < newList.size() && j < oldList.size() &&
- newList.get(i).getHandle() == oldList.get(j).getHandle()) {
+ } else if (i < newList.size() && j < oldList.size()
+ && newList.get(i).getHandle() == oldList.get(j).getHandle()) {
if (updated != null) {
updated.add(oldList.get(j));
}
@@ -623,7 +625,7 @@
* associated with any listener and there is one InjectEventQueue associated with a
* SensorManager instance.
*/
- private static abstract class BaseEventQueue {
+ private abstract static class BaseEventQueue {
private static native long nativeInitBaseEventQueue(long nativeManager,
WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ,
String packageName, int mode, String opPackageName);
@@ -633,9 +635,9 @@
private static native void nativeDestroySensorEventQueue(long eventQ);
private static native int nativeFlushSensor(long eventQ);
private static native int nativeInjectSensorData(long eventQ, int handle,
- float[] values,int accuracy, long timestamp);
+ float[] values, int accuracy, long timestamp);
- private long nSensorEventQueue;
+ private long mNativeSensorEventQueue;
private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -646,7 +648,7 @@
BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
if (packageName == null) packageName = "";
- nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
+ mNativeSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
new WeakReference<>(this), looper.getQueue(),
packageName, mode, manager.mContext.getOpPackageName());
mCloseGuard.open("dispose");
@@ -668,17 +670,17 @@
addSensorEvent(sensor);
if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
// Try continuous mode if batching fails.
- if (maxBatchReportLatencyUs == 0 ||
- maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
- removeSensor(sensor, false);
- return false;
+ if (maxBatchReportLatencyUs == 0
+ || maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
+ removeSensor(sensor, false);
+ return false;
}
}
return true;
}
public boolean removeAllSensors() {
- for (int i=0 ; i<mActiveSensors.size(); i++) {
+ for (int i = 0; i < mActiveSensors.size(); i++) {
if (mActiveSensors.valueAt(i) == true) {
int handle = mActiveSensors.keyAt(i);
Sensor sensor = mManager.mHandleToSensor.get(handle);
@@ -706,8 +708,8 @@
}
public int flush() {
- if (nSensorEventQueue == 0) throw new NullPointerException();
- return nativeFlushSensor(nSensorEventQueue);
+ if (mNativeSensorEventQueue == 0) throw new NullPointerException();
+ return nativeFlushSensor(mNativeSensorEventQueue);
}
public boolean hasSensors() {
@@ -731,29 +733,30 @@
}
mCloseGuard.close();
}
- if (nSensorEventQueue != 0) {
- nativeDestroySensorEventQueue(nSensorEventQueue);
- nSensorEventQueue = 0;
+ if (mNativeSensorEventQueue != 0) {
+ nativeDestroySensorEventQueue(mNativeSensorEventQueue);
+ mNativeSensorEventQueue = 0;
}
}
private int enableSensor(
Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
- if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
- return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs,
+ return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
maxBatchReportLatencyUs);
}
protected int injectSensorDataBase(int handle, float[] values, int accuracy,
long timestamp) {
- return nativeInjectSensorData(nSensorEventQueue, handle, values, accuracy, timestamp);
+ return nativeInjectSensorData(
+ mNativeSensorEventQueue, handle, values, accuracy, timestamp);
}
private int disableSensor(Sensor sensor) {
- if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
- return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
+ return nativeDisableSensor(mNativeSensorEventQueue, sensor.getHandle());
}
protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
long timestamp);
@@ -840,7 +843,7 @@
// sensor disconnected
return;
}
- ((SensorEventListener2)mListener).onFlushCompleted(sensor);
+ ((SensorEventListener2) mListener).onFlushCompleted(sensor);
}
return;
}
@@ -858,7 +861,7 @@
}
SensorAdditionalInfo info =
new SensorAdditionalInfo(sensor, type, serial, intValues, floatValues);
- ((SensorEventCallback)mListener).onSensorAdditionalInfo(info);
+ ((SensorEventCallback) mListener).onSensorAdditionalInfo(info);
}
}
}
@@ -930,8 +933,8 @@
super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
}
- int injectSensorData(int handle, float[] values,int accuracy, long timestamp) {
- return injectSensorDataBase(handle, values, accuracy, timestamp);
+ int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
+ return injectSensorDataBase(handle, values, accuracy, timestamp);
}
@SuppressWarnings("unused")
@@ -956,7 +959,10 @@
}
protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+ int handle = -1;
+ if (parameter.sensor != null) handle = parameter.sensor.getHandle();
return nativeSetOperationParameter(
- mNativeInstance, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+ mNativeInstance, handle,
+ parameter.type, parameter.floatValues, parameter.intValues) == 0;
}
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d914823..583cf32 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -835,6 +835,29 @@
}
/**
+ * Checks if a VPN app supports always-on mode.
+ *
+ * In order to support the always-on feature, an app has to
+ * <ul>
+ * <li>target {@link VERSION_CODES#N API 24} or above, and
+ * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+ * meta-data field.
+ * </ul>
+ *
+ * @param userId The identifier of the user for whom the VPN app is installed.
+ * @param vpnPackage The canonical package name of the VPN app.
+ * @return {@code true} if and only if the VPN app exists and supports always-on mode.
+ * @hide
+ */
+ public boolean isAlwaysOnVpnPackageSupportedForUser(int userId, @Nullable String vpnPackage) {
+ try {
+ return mService.isAlwaysOnVpnPackageSupported(userId, vpnPackage);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Configures an always-on VPN connection through a specific application.
* This connection is automatically granted and persisted after a reboot.
*
@@ -1102,6 +1125,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS)
public String getCaptivePortalServerUrl() {
try {
return mService.getCaptivePortalServerUrl();
@@ -2065,10 +2089,11 @@
* {@hide}
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public boolean isTetheringSupported() {
try {
- return mService.isTetheringSupported();
+ String pkgName = mContext.getOpPackageName();
+ return mService.isTetheringSupported(pkgName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2098,6 +2123,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void startTethering(int type, boolean showProvisioningUi,
final OnStartTetheringCallback callback) {
startTethering(type, showProvisioningUi, callback, null);
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 27729dc..a6fe738 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -75,7 +75,7 @@
int getLastTetherError(String iface);
- boolean isTetheringSupported();
+ boolean isTetheringSupported(String callerPkg);
void startTethering(int type, in ResultReceiver receiver, boolean showProvisioningUi,
String callerPkg);
@@ -123,6 +123,7 @@
VpnInfo[] getAllVpnInfo();
boolean updateLockdownVpn();
+ boolean isAlwaysOnVpnPackageSupported(int userId, String packageName);
boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown);
String getAlwaysOnVpnPackage(int userId);
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 5ae3400..ead406c 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -19,15 +19,15 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+
import com.android.internal.util.HexDump;
+
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.
- *
- * @hide
*/
public final class IpSecAlgorithm implements Parcelable {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 2f791e1..d7908c8 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -25,7 +25,9 @@
import android.os.RemoteException;
import android.util.AndroidException;
import android.util.Log;
+
import dalvik.system.CloseGuard;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramSocket;
@@ -36,7 +38,9 @@
* This class contains methods for managing IPsec sessions, which will perform kernel-space
* encryption and decryption of socket or Network traffic.
*
- * @hide
+ * <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}
*/
@SystemService(Context.IPSEC_SERVICE)
public final class IpSecManager {
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index cfbac58b..62fd65b 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -26,9 +26,12 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+
import dalvik.system.CloseGuard;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -43,8 +46,6 @@
*
* <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.
- *
- * @hide
*/
public final class IpSecTransform implements AutoCloseable {
private static final String TAG = "IpSecTransform";
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 2d9860c..3cc52a6 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -28,8 +28,6 @@
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.net.Network;
-import android.net.NetworkUtils;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
@@ -124,6 +122,36 @@
public static final String SERVICE_INTERFACE = VpnConfig.SERVICE_INTERFACE;
/**
+ * Key for boolean meta-data field indicating whether this VpnService supports always-on mode.
+ *
+ * <p>For a VPN app targeting {@link android.os.Build.VERSION_CODES#N API 24} or above, Android
+ * provides users with the ability to set it as always-on, so that VPN connection is
+ * persisted after device reboot and app upgrade. Always-on VPN can also be enabled by device
+ * owner and profile owner apps through
+ * {@link android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage}.
+ *
+ * <p>VPN apps not supporting this feature should opt out by adding this meta-data field to the
+ * {@code VpnService} component of {@code AndroidManifest.xml}. In case there is more than one
+ * {@code VpnService} component defined in {@code AndroidManifest.xml}, opting out any one of
+ * them will opt out the entire app. For example,
+ * <pre> {@code
+ * <service android:name=".ExampleVpnService"
+ * android:permission="android.permission.BIND_VPN_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.net.VpnService"/>
+ * </intent-filter>
+ * <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
+ * android:value=false/>
+ * </service>
+ * } </pre>
+ *
+ * <p>This meta-data field defaults to {@code true} if absent. It will only have effect on
+ * {@link android.os.Build.VERSION_CODES#O_MR1} or higher.
+ */
+ public static final String SERVICE_META_DATA_SUPPORTS_ALWAYS_ON =
+ "android.net.VpnService.SUPPORTS_ALWAYS_ON";
+
+ /**
* Use IConnectivityManager since those methods are hidden and not
* available in ConnectivityManager.
*/
diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java
new file mode 100644
index 0000000..cbf3fc8
--- /dev/null
+++ b/core/java/android/net/metrics/WakeupEvent.java
@@ -0,0 +1,34 @@
+/*
+ * 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.metrics;
+
+/**
+ * An event logged when NFLOG notifies userspace of a wakeup packet for
+ * watched interfaces.
+ * {@hide}
+ */
+public class WakeupEvent {
+ public String iface;
+ public long timestampMs;
+ public int uid;
+
+ @Override
+ public String toString() {
+ return String.format("WakeupEvent(%tT.%tL, %s, uid: %d)",
+ timestampMs, timestampMs, iface, uid);
+ }
+}
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
new file mode 100644
index 0000000..97e83f9
--- /dev/null
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -0,0 +1,87 @@
+/*
+ * 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.metrics;
+
+import android.os.Process;
+import android.os.SystemClock;
+
+/**
+ * An event logged per interface and that aggregates WakeupEvents for that interface.
+ * {@hide}
+ */
+public class WakeupStats {
+
+ private static final int NO_UID = -1;
+
+ public final long creationTimeMs = SystemClock.elapsedRealtime();
+ public final String iface;
+
+ public long totalWakeups = 0;
+ public long rootWakeups = 0;
+ public long systemWakeups = 0;
+ public long nonApplicationWakeups = 0;
+ public long applicationWakeups = 0;
+ public long noUidWakeups = 0;
+ public long durationSec = 0;
+
+ public WakeupStats(String iface) {
+ this.iface = iface;
+ }
+
+ /** Update durationSec with current time. */
+ public void updateDuration() {
+ durationSec = (SystemClock.elapsedRealtime() - creationTimeMs) / 1000;
+ }
+
+ /** Update wakeup counters for the given WakeupEvent. */
+ public void countEvent(WakeupEvent ev) {
+ totalWakeups++;
+ switch (ev.uid) {
+ case Process.ROOT_UID:
+ rootWakeups++;
+ break;
+ case Process.SYSTEM_UID:
+ systemWakeups++;
+ break;
+ case NO_UID:
+ noUidWakeups++;
+ break;
+ default:
+ if (ev.uid >= Process.FIRST_APPLICATION_UID) {
+ applicationWakeups++;
+ } else {
+ nonApplicationWakeups++;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public String toString() {
+ updateDuration();
+ return new StringBuilder()
+ .append("WakeupStats(").append(iface)
+ .append(", total: ").append(totalWakeups)
+ .append(", root: ").append(rootWakeups)
+ .append(", system: ").append(systemWakeups)
+ .append(", apps: ").append(applicationWakeups)
+ .append(", non-apps: ").append(nonApplicationWakeups)
+ .append(", no uid: ").append(noUidWakeups)
+ .append(", ").append(durationSec).append("s)")
+ .toString();
+ }
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 44e6f1b..a83c7b3 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -21,7 +21,6 @@
import android.util.Slog;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
@@ -200,7 +199,7 @@
* then its own pid is returned.
*/
public static final native int getCallingPid();
-
+
/**
* Return the Linux uid assigned to the process that sent you the
* current transaction that is being processed. This uid can be used with
@@ -333,7 +332,7 @@
* it needs to.
*/
public static final native void flushPendingCommands();
-
+
/**
* Add the calling thread to the IPC thread pool. This function does
* not return until the current process is exiting.
@@ -370,7 +369,7 @@
}
}
}
-
+
/**
* Convenience method for associating a specific interface with the Binder.
* After calling, queryLocalInterface() will be implemented for you
@@ -381,7 +380,7 @@
mOwner = owner;
mDescriptor = descriptor;
}
-
+
/**
* Default implementation returns an empty interface name.
*/
@@ -406,7 +405,7 @@
public boolean isBinderAlive() {
return true;
}
-
+
/**
* Use information supplied to attachInterface() to return the
* associated IInterface if it matches the requested
@@ -607,7 +606,7 @@
}
return r;
}
-
+
/**
* Local implementation is a no-op.
*/
@@ -620,7 +619,7 @@
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return true;
}
-
+
protected void finalize() throws Throwable {
try {
destroyBinder();
@@ -714,7 +713,15 @@
}
}
+/**
+ * Java proxy for a native IBinder object.
+ * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
+ * directly from Java code.
+ */
final class BinderProxy implements IBinder {
+ // See android_util_Binder.cpp for the native half of this.
+ // TODO: Consider using NativeAllocationRegistry instead of finalization.
+
// Assume the process-wide default value when created
volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
@@ -773,7 +780,7 @@
reply.recycle();
}
}
-
+
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -810,7 +817,7 @@
BinderProxy() {
mSelf = new WeakReference(this);
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -819,9 +826,9 @@
super.finalize();
}
}
-
+
private native final void destroy();
-
+
private static final void sendDeathNotice(DeathRecipient recipient) {
if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
try {
@@ -832,8 +839,20 @@
exc);
}
}
-
+
+ // This WeakReference to "this" is used only by native code to "attach" to the
+ // native IBinder object.
+ // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a
+ // non-null value after the BinderProxy is enqueued for finalization.
+ // Used only once immediately after construction.
+ // TODO: Consider making the extra native-to-java call to compute this on the fly.
final private WeakReference mSelf;
+
+ // Native pointer to the wrapped native IBinder object. Counted as strong reference.
private long mObject;
+
+ // Native pointer to native DeathRecipientList. Counted as strong reference.
+ // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients
+ // hold a weak reference that can be temporarily promoted.
private long mOrgue;
}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index b63e302..66e16a6 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -104,11 +104,6 @@
void setIPv6AddrGenMode(String iface, int mode);
/**
- * Enables or enables IPv6 ND offload.
- */
- void setInterfaceIpv6NdOffload(String iface, boolean enable);
-
- /**
* Add the specified route to the interface.
*/
void addRoute(int netId, in RouteInfo route);
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 7045200..93826d80 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -463,8 +463,8 @@
* Instructs the zygote to pre-load the classes and native libraries at the given paths
* for the specified abi. Not all zygotes support this function.
*/
- public void preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
- String abi) throws ZygoteStartFailedEx, IOException {
+ public boolean preloadPackageForAbi(String packagePath, String libsPath, String cacheKey,
+ String abi) throws ZygoteStartFailedEx, IOException {
synchronized(mLock) {
ZygoteState state = openZygoteSocketIfNeeded(abi);
state.writer.write("4");
@@ -483,6 +483,8 @@
state.writer.newLine();
state.writer.flush();
+
+ return (state.inputStream.readInt() == 0);
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index f4be128..66475e4 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -31,6 +31,7 @@
import com.android.internal.logging.AndroidConfig;
import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.VMRuntime;
+import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.TimeZone;
@@ -228,8 +229,8 @@
* @param argv Argument vector for main()
* @param classLoader the classLoader to load {@className} with
*/
- private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
- throws Zygote.MethodAndArgsCaller {
+ private static Runnable findStaticMain(String className, String[] argv,
+ ClassLoader classLoader) {
Class<?> cl;
try {
@@ -263,7 +264,7 @@
* clears up all the stack frames that were required in setting
* up the process.
*/
- throw new Zygote.MethodAndArgsCaller(m, argv);
+ return new MethodAndArgsCaller(m, argv);
}
public static final void main(String[] argv) {
@@ -286,8 +287,8 @@
if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
}
- protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
- throws Zygote.MethodAndArgsCaller {
+ protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
+ ClassLoader classLoader) {
// If the application calls System.exit(), terminate the process
// immediately without running any shutdown hooks. It is not possible to
// shutdown an Android application gracefully. Among other things, the
@@ -300,20 +301,13 @@
VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
- final Arguments args;
- try {
- args = new Arguments(argv);
- } catch (IllegalArgumentException ex) {
- Slog.e(TAG, ex.getMessage());
- // let the process exit
- return;
- }
+ final Arguments args = new Arguments(argv);
// The end of of the RuntimeInit event (see #zygoteInit).
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Remaining arguments are passed to the start class's static main
- invokeStaticMain(args.startClass, args.startArgs, classLoader);
+ return findStaticMain(args.startClass, args.startArgs, classLoader);
}
/**
@@ -422,4 +416,37 @@
System.arraycopy(args, curArg, startArgs, 0, startArgs.length);
}
}
+
+ /**
+ * Helper class which holds a method and arguments and can call them. This is used as part of
+ * a trampoline to get rid of the initial process setup stack frames.
+ */
+ static class MethodAndArgsCaller implements Runnable {
+ /** method to call */
+ private final Method mMethod;
+
+ /** argument array */
+ private final String[] mArgs;
+
+ public MethodAndArgsCaller(Method method, String[] args) {
+ mMethod = method;
+ mArgs = args;
+ }
+
+ public void run() {
+ try {
+ mMethod.invoke(null, new Object[] { mArgs });
+ } catch (IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ } catch (InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else if (cause instanceof Error) {
+ throw (Error) cause;
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index e28079f..7f46a0c 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -26,6 +26,7 @@
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
+import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -68,8 +69,7 @@
}
@Override
- protected boolean handlePreloadPackage(String packagePath, String libsPath,
- String cacheKey) {
+ protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
Log.i(TAG, "Beginning package preload");
// Ask ApplicationLoaders to create and cache a classloader for the WebView APK so that
// our children will reuse the same classloader instead of creating their own.
@@ -87,19 +87,28 @@
// Once we have the classloader, look up the WebViewFactoryProvider implementation and
// call preloadInZygote() on it to give it the opportunity to preload the native library
// and perform any other initialisation work that should be shared among the children.
+ boolean preloadSucceeded = false;
try {
Class<WebViewFactoryProvider> providerClass =
WebViewFactory.getWebViewProviderClass(loader);
Object result = providerClass.getMethod("preloadInZygote").invoke(null);
- if (!((Boolean)result).booleanValue()) {
+ preloadSucceeded = ((Boolean) result).booleanValue();
+ if (!preloadSucceeded) {
Log.e(TAG, "preloadInZygote returned false");
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
IllegalAccessException | InvocationTargetException e) {
Log.e(TAG, "Exception while preloading package", e);
}
+
+ try {
+ DataOutputStream socketOut = getSocketOutputStream();
+ socketOut.writeInt(preloadSucceeded ? 1 : 0);
+ } catch (IOException ioe) {
+ throw new IllegalStateException("Error writing to command socket", ioe);
+ }
+
Log.i(TAG, "Package preload done");
- return false;
}
}
@@ -113,16 +122,23 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
+ final Runnable caller;
try {
sServer.registerServerSocket("webview_zygote");
- sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
- sServer.closeServerSocket();
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
} catch (RuntimeException e) {
Log.e(TAG, "Fatal exception:", e);
+ throw e;
+ } finally {
+ sServer.closeServerSocket();
}
- System.exit(0);
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
+ }
}
}
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 608bc9f..89328b2 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -25,7 +25,6 @@
import android.system.StructCapUserHeader;
import android.util.BootTimingsTraceLog;
import android.util.Slog;
-import com.android.internal.os.Zygote.MethodAndArgsCaller;
import dalvik.system.VMRuntime;
import java.io.DataOutputStream;
import java.io.FileDescriptor;
@@ -61,37 +60,35 @@
* @param args The command-line arguments.
*/
public static void main(String[] args) {
- try {
- // Parse our mandatory arguments.
- int fdNum = Integer.parseInt(args[0], 10);
- int targetSdkVersion = Integer.parseInt(args[1], 10);
+ // Parse our mandatory arguments.
+ int fdNum = Integer.parseInt(args[0], 10);
+ int targetSdkVersion = Integer.parseInt(args[1], 10);
- // Tell the Zygote what our actual PID is (since it only knows about the
- // wrapper that it directly forked).
- if (fdNum != 0) {
- try {
- FileDescriptor fd = new FileDescriptor();
- fd.setInt$(fdNum);
- DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
- os.writeInt(Process.myPid());
- os.close();
- IoUtils.closeQuietly(fd);
- } catch (IOException ex) {
- Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
- }
+ // Tell the Zygote what our actual PID is (since it only knows about the
+ // wrapper that it directly forked).
+ if (fdNum != 0) {
+ try {
+ FileDescriptor fd = new FileDescriptor();
+ fd.setInt$(fdNum);
+ DataOutputStream os = new DataOutputStream(new FileOutputStream(fd));
+ os.writeInt(Process.myPid());
+ os.close();
+ IoUtils.closeQuietly(fd);
+ } catch (IOException ex) {
+ Slog.d(TAG, "Could not write pid of wrapped process to Zygote pipe.", ex);
}
-
- // Mimic system Zygote preloading.
- ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
- Trace.TRACE_TAG_DALVIK));
-
- // Launch the application.
- String[] runtimeArgs = new String[args.length - 2];
- System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
- WrapperInit.wrapperInit(targetSdkVersion, runtimeArgs);
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
}
+
+ // Mimic system Zygote preloading.
+ ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+ Trace.TRACE_TAG_DALVIK));
+
+ // Launch the application.
+ String[] runtimeArgs = new String[args.length - 2];
+ System.arraycopy(args, 2, runtimeArgs, 0, runtimeArgs.length);
+ Runnable r = wrapperInit(targetSdkVersion, runtimeArgs);
+
+ r.run();
}
/**
@@ -142,8 +139,7 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- private static void wrapperInit(int targetSdkVersion, String[] argv)
- throws Zygote.MethodAndArgsCaller {
+ private static Runnable wrapperInit(int targetSdkVersion, String[] argv) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper");
}
@@ -165,7 +161,7 @@
// Perform the same initialization that would happen after the Zygote forks.
Zygote.nativePreApplicationInit();
- RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
/**
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index e159495..1d286fc 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -51,6 +51,11 @@
/** Make the code Java debuggable by turning off some optimizations. */
public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
+ /** Turn off the verifier. */
+ public static final int DISABLE_VERIFIER = 1 << 9;
+ /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
+ public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = 0;
/** Default external storage should be mounted. */
@@ -221,39 +226,4 @@
command.append(" '").append(arg.replace("'", "'\\''")).append("'");
}
}
-
- /**
- * Helper exception class which holds a method and arguments and
- * can call them. This is used as part of a trampoline to get rid of
- * the initial process setup stack frames.
- */
- public static class MethodAndArgsCaller extends Exception
- implements Runnable {
- /** method to call */
- private final Method mMethod;
-
- /** argument array */
- private final String[] mArgs;
-
- public MethodAndArgsCaller(Method method, String[] args) {
- mMethod = method;
- mArgs = args;
- }
-
- public void run() {
- try {
- mMethod.invoke(null, new Object[] { mArgs });
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(ex);
- } catch (InvocationTargetException ex) {
- Throwable cause = ex.getCause();
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- } else if (cause instanceof Error) {
- throw (Error) cause;
- }
- throw new RuntimeException(ex);
- }
- }
- }
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 45cb840..0bb7326 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -30,7 +30,6 @@
import android.net.LocalSocket;
import android.os.FactoryTest;
import android.os.Process;
-import android.os.SELinux;
import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
@@ -42,14 +41,13 @@
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.io.EOFException;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
import libcore.io.IoUtils;
/**
@@ -73,6 +71,7 @@
private final BufferedReader mSocketReader;
private final Credentials peer;
private final String abiList;
+ private boolean isEof;
/**
* Constructs instance from connected socket.
@@ -99,6 +98,8 @@
Log.e(TAG, "Cannot read peer credentials", ex);
throw ex;
}
+
+ isEof = false;
}
/**
@@ -111,21 +112,14 @@
}
/**
- * Reads one start command from the command socket. If successful,
- * a child is forked and a {@link Zygote.MethodAndArgsCaller}
- * exception is thrown in that child while in the parent process,
- * the method returns normally. On failure, the child is not
- * spawned and messages are printed to the log and stderr. Returns
- * a boolean status value indicating whether an end-of-file on the command
- * socket has been encountered.
+ * Reads one start command from the command socket. If successful, a child is forked and a
+ * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
+ * process. {@code null} is always returned in the parent process (the zygote).
*
- * @return false if command socket should continue to be read from, or
- * true if an end-of-file has been encountered.
- * @throws Zygote.MethodAndArgsCaller trampoline to invoke main()
- * method in child process
+ * If the client closes the socket, an {@code EOF} condition is set, which callers can test
+ * for by calling {@code ZygoteConnection.isClosedByPeer}.
*/
- boolean runOnce(ZygoteServer zygoteServer) throws Zygote.MethodAndArgsCaller {
-
+ Runnable processOneCommand(ZygoteServer zygoteServer) {
String args[];
Arguments parsedArgs = null;
FileDescriptor[] descriptors;
@@ -134,130 +128,120 @@
args = readArgumentList();
descriptors = mSocket.getAncillaryFileDescriptors();
} catch (IOException ex) {
- Log.w(TAG, "IOException on command socket " + ex.getMessage());
- closeSocket();
- return true;
+ throw new IllegalStateException("IOException on command socket", ex);
}
+ // readArgumentList returns null only when it has reached EOF with no available
+ // data to read. This will only happen when the remote socket has disconnected.
if (args == null) {
- // EOF reached.
- closeSocket();
- return true;
- }
-
- /** the stderr of the most recent request, if avail */
- PrintStream newStderr = null;
-
- if (descriptors != null && descriptors.length >= 3) {
- newStderr = new PrintStream(
- new FileOutputStream(descriptors[2]));
+ isEof = true;
+ return null;
}
int pid = -1;
FileDescriptor childPipeFd = null;
FileDescriptor serverPipeFd = null;
- try {
- parsedArgs = new Arguments(args);
+ parsedArgs = new Arguments(args);
- if (parsedArgs.abiListQuery) {
- return handleAbiListQuery();
- }
+ if (parsedArgs.abiListQuery) {
+ handleAbiListQuery();
+ return null;
+ }
- if (parsedArgs.preloadDefault) {
- return handlePreload();
- }
+ if (parsedArgs.preloadDefault) {
+ handlePreload();
+ return null;
+ }
- if (parsedArgs.preloadPackage != null) {
- return handlePreloadPackage(parsedArgs.preloadPackage,
- parsedArgs.preloadPackageLibs, parsedArgs.preloadPackageCacheKey);
- }
+ if (parsedArgs.preloadPackage != null) {
+ handlePreloadPackage(parsedArgs.preloadPackage, parsedArgs.preloadPackageLibs,
+ parsedArgs.preloadPackageCacheKey);
+ return null;
+ }
- if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
- throw new ZygoteSecurityException("Client may not specify capabilities: " +
- "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
- ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
- }
+ if (parsedArgs.permittedCapabilities != 0 || parsedArgs.effectiveCapabilities != 0) {
+ throw new ZygoteSecurityException("Client may not specify capabilities: " +
+ "permitted=0x" + Long.toHexString(parsedArgs.permittedCapabilities) +
+ ", effective=0x" + Long.toHexString(parsedArgs.effectiveCapabilities));
+ }
- applyUidSecurityPolicy(parsedArgs, peer);
- applyInvokeWithSecurityPolicy(parsedArgs, peer);
+ applyUidSecurityPolicy(parsedArgs, peer);
+ applyInvokeWithSecurityPolicy(parsedArgs, peer);
- applyDebuggerSystemProperty(parsedArgs);
- applyInvokeWithSystemProperty(parsedArgs);
+ applyDebuggerSystemProperty(parsedArgs);
+ applyInvokeWithSystemProperty(parsedArgs);
- int[][] rlimits = null;
+ int[][] rlimits = null;
- if (parsedArgs.rlimits != null) {
- rlimits = parsedArgs.rlimits.toArray(intArray2d);
- }
+ if (parsedArgs.rlimits != null) {
+ rlimits = parsedArgs.rlimits.toArray(intArray2d);
+ }
- int[] fdsToIgnore = null;
+ int[] fdsToIgnore = null;
- if (parsedArgs.invokeWith != null) {
+ if (parsedArgs.invokeWith != null) {
+ try {
FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC);
childPipeFd = pipeFds[1];
serverPipeFd = pipeFds[0];
Os.fcntlInt(childPipeFd, F_SETFD, 0);
- fdsToIgnore = new int[] { childPipeFd.getInt$(), serverPipeFd.getInt$() };
+ fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()};
+ } catch (ErrnoException errnoEx) {
+ throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx);
}
-
- /**
- * In order to avoid leaking descriptors to the Zygote child,
- * the native code must close the two Zygote socket descriptors
- * in the child process before it switches from Zygote-root to
- * the UID and privileges of the application being launched.
- *
- * In order to avoid "bad file descriptor" errors when the
- * two LocalSocket objects are closed, the Posix file
- * descriptors are released via a dup2() call which closes
- * the socket and substitutes an open descriptor to /dev/null.
- */
-
- int [] fdsToClose = { -1, -1 };
-
- FileDescriptor fd = mSocket.getFileDescriptor();
-
- if (fd != null) {
- fdsToClose[0] = fd.getInt$();
- }
-
- fd = zygoteServer.getServerSocketFileDescriptor();
-
- if (fd != null) {
- fdsToClose[1] = fd.getInt$();
- }
-
- fd = null;
-
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
- parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
- parsedArgs.appDataDir);
- } catch (ErrnoException ex) {
- logAndPrintError(newStderr, "Exception creating pipe", ex);
- } catch (IllegalArgumentException ex) {
- logAndPrintError(newStderr, "Invalid zygote arguments", ex);
- } catch (ZygoteSecurityException ex) {
- logAndPrintError(newStderr,
- "Zygote security policy prevents request: ", ex);
}
+ /**
+ * In order to avoid leaking descriptors to the Zygote child,
+ * the native code must close the two Zygote socket descriptors
+ * in the child process before it switches from Zygote-root to
+ * the UID and privileges of the application being launched.
+ *
+ * In order to avoid "bad file descriptor" errors when the
+ * two LocalSocket objects are closed, the Posix file
+ * descriptors are released via a dup2() call which closes
+ * the socket and substitutes an open descriptor to /dev/null.
+ */
+
+ int [] fdsToClose = { -1, -1 };
+
+ FileDescriptor fd = mSocket.getFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[0] = fd.getInt$();
+ }
+
+ fd = zygoteServer.getServerSocketFileDescriptor();
+
+ if (fd != null) {
+ fdsToClose[1] = fd.getInt$();
+ }
+
+ fd = null;
+
+ pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+ parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+ parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.instructionSet,
+ parsedArgs.appDataDir);
+
try {
if (pid == 0) {
// in child
+ zygoteServer.setForkChild();
+
zygoteServer.closeServerSocket();
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
- handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
- // should never get here, the child is expected to either
- // throw Zygote.MethodAndArgsCaller or exec().
- return true;
+ return handleChildProc(parsedArgs, descriptors, childPipeFd);
} else {
- // in parent...pid of < 0 means failure
+ // In the parent. A pid < 0 indicates a failure and will be handled in
+ // handleParentProc.
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
- return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
+ handleParentProc(pid, descriptors, serverPipeFd);
+ return null;
}
} finally {
IoUtils.closeQuietly(childPipeFd);
@@ -265,15 +249,13 @@
}
}
- private boolean handleAbiListQuery() {
+ private void handleAbiListQuery() {
try {
final byte[] abiListBytes = abiList.getBytes(StandardCharsets.US_ASCII);
mSocketOutStream.writeInt(abiListBytes.length);
mSocketOutStream.write(abiListBytes);
- return false;
} catch (IOException ioe) {
- Log.e(TAG, "Error writing to command socket", ioe);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ioe);
}
}
@@ -283,7 +265,7 @@
* if no preload was initiated. The latter implies that the zygote is not configured to load
* resources lazy or that the zygote has already handled a previous request to handlePreload.
*/
- private boolean handlePreload() {
+ private void handlePreload() {
try {
if (isPreloadComplete()) {
mSocketOutStream.writeInt(1);
@@ -291,11 +273,8 @@
preload();
mSocketOutStream.writeInt(0);
}
-
- return false;
} catch (IOException ioe) {
- Log.e(TAG, "Error writing to command socket", ioe);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ioe);
}
}
@@ -307,7 +286,11 @@
return ZygoteInit.isPreloadComplete();
}
- protected boolean handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
+ protected DataOutputStream getSocketOutputStream() {
+ return mSocketOutStream;
+ }
+
+ protected void handlePreloadPackage(String packagePath, String libsPath, String cacheKey) {
throw new RuntimeException("Zyogte does not support package preloading");
}
@@ -323,6 +306,10 @@
}
}
+ boolean isClosedByPeer() {
+ return isEof;
+ }
+
/**
* Handles argument parsing for args related to the zygote spawner.
*
@@ -753,15 +740,9 @@
* @param parsedArgs non-null; zygote args
* @param descriptors null-ok; new file descriptors for stdio if available.
* @param pipeFd null-ok; pipe for communication back to Zygote.
- * @param newStderr null-ok; stream to use for stderr until stdio
- * is reopened.
- *
- * @throws Zygote.MethodAndArgsCaller on success to
- * trampoline to code that invokes static main.
*/
- private void handleChildProc(Arguments parsedArgs,
- FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
- throws Zygote.MethodAndArgsCaller {
+ private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
+ FileDescriptor pipeFd) {
/**
* By the time we get here, the native code has closed the two actual Zygote
* socket connections, and substituted /dev/null in their place. The LocalSocket
@@ -778,7 +759,6 @@
for (FileDescriptor fd: descriptors) {
IoUtils.closeQuietly(fd);
}
- newStderr = System.err;
} catch (ErrnoException ex) {
Log.e(TAG, "Error reopening stdio", ex);
}
@@ -795,9 +775,12 @@
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs);
+
+ // Should not get here.
+ throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
- ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion,
- parsedArgs.remainingArgs, null /* classLoader */);
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
+ null /* classLoader */);
}
}
@@ -809,13 +792,8 @@
* @param descriptors null-ok; file descriptors for child's new stdio if
* specified.
* @param pipeFd null-ok; pipe for communication with child.
- * @param parsedArgs non-null; zygote args
- * @return true for "exit command loop" and false for "continue command
- * loop"
*/
- private boolean handleParentProc(int pid,
- FileDescriptor[] descriptors, FileDescriptor pipeFd, Arguments parsedArgs) {
-
+ private void handleParentProc(int pid, FileDescriptor[] descriptors, FileDescriptor pipeFd) {
if (pid > 0) {
setChildPgid(pid);
}
@@ -907,11 +885,8 @@
mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper);
} catch (IOException ex) {
- Log.e(TAG, "Error writing to command socket", ex);
- return true;
+ throw new IllegalStateException("Error writing to command socket", ex);
}
-
- return false;
}
private void setChildPgid(int pid) {
@@ -927,20 +902,4 @@
+ "normal if peer is not in our session");
}
}
-
- /**
- * Logs an error message and prints it to the specified stream, if
- * provided
- *
- * @param newStderr null-ok; a standard error stream
- * @param message non-null; error message
- * @param ex null-ok an exception
- */
- private static void logAndPrintError (PrintStream newStderr,
- String message, Throwable ex) {
- Log.e(TAG, message, ex);
- if (newStderr != null) {
- newStderr.println(message + (ex == null ? "" : ex));
- }
- }
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 03459c0..ee19163 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -24,7 +24,6 @@
import android.icu.impl.CacheValue;
import android.icu.text.DecimalFormatSymbols;
import android.icu.util.ULocale;
-import android.net.LocalServerSocket;
import android.opengl.EGL14;
import android.os.Build;
import android.os.IInstalld;
@@ -447,10 +446,7 @@
/**
* Finish remaining work for the newly forked system server process.
*/
- private static void handleSystemServerProcess(
- ZygoteConnection.Arguments parsedArgs)
- throws Zygote.MethodAndArgsCaller {
-
+ private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
// set umask to 0077 so new files and directories will default to owner-only permissions.
Os.umask(S_IRWXG | S_IRWXO);
@@ -496,6 +492,8 @@
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(), null, args);
+
+ throw new IllegalStateException("Unexpected return from WrapperInit.execApplication");
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
@@ -507,7 +505,7 @@
/*
* Pass the remaining arguments to SystemServer.
*/
- ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
+ return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
/* should never reach here */
@@ -589,10 +587,13 @@
}
/**
- * Prepare the arguments and fork for the system server process.
+ * Prepare the arguments and forks for the system server process.
+ *
+ * Returns an {@code Runnable} that provides an entrypoint into system_server code in the
+ * child process, and {@code null} in the parent.
*/
- private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
- throws Zygote.MethodAndArgsCaller, RuntimeException {
+ private static Runnable forkSystemServer(String abiList, String socketName,
+ ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
@@ -657,10 +658,10 @@
}
zygoteServer.closeServerSocket();
- handleSystemServerProcess(parsedArgs);
+ return handleSystemServerProcess(parsedArgs);
}
- return true;
+ return null;
}
/**
@@ -691,6 +692,7 @@
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
+ final Runnable caller;
try {
// Report Zygote start time to tron unless it is a runtime restart
if (!"1".equals(SystemProperties.get("sys.boot_completed"))) {
@@ -760,19 +762,32 @@
ZygoteHooks.stopZygoteNoThreadCreation();
if (startSystemServer) {
- startSystemServer(abiList, socketName, zygoteServer);
+ Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
+
+ // {@code r == null} in the parent (zygote) process, and {@code r != null} in the
+ // child (system_server) process.
+ if (r != null) {
+ r.run();
+ return;
+ }
}
Log.i(TAG, "Accepting command socket connections");
- zygoteServer.runSelectLoop(abiList);
- zygoteServer.closeServerSocket();
- } catch (Zygote.MethodAndArgsCaller caller) {
- caller.run();
+ // The select loop returns early in the child process after a fork and
+ // loops forever in the zygote.
+ caller = zygoteServer.runSelectLoop(abiList);
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
- zygoteServer.closeServerSocket();
throw ex;
+ } finally {
+ zygoteServer.closeServerSocket();
+ }
+
+ // We're in the child process and have exited the select loop. Proceed to execute the
+ // command.
+ if (caller != null) {
+ caller.run();
}
}
@@ -830,8 +845,7 @@
* @param targetSdkVersion target SDK version
* @param argv arg strings
*/
- public static final void zygoteInit(int targetSdkVersion, String[] argv,
- ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
+ public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
@@ -841,7 +855,7 @@
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
- RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
+ return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
private static final native void nativeZygoteInit();
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 126d9e7..8baa15a 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -25,6 +25,7 @@
import android.system.StructPollfd;
import android.util.Log;
+import android.util.Slog;
import java.io.IOException;
import java.io.FileDescriptor;
import java.util.ArrayList;
@@ -45,9 +46,18 @@
private LocalServerSocket mServerSocket;
+ /**
+ * Set by the child process, immediately after a call to {@code Zygote.forkAndSpecialize}.
+ */
+ private boolean mIsForkChild;
+
ZygoteServer() {
}
+ void setForkChild() {
+ mIsForkChild = true;
+ }
+
/**
* Registers a server socket for zygote command connections
*
@@ -129,11 +139,8 @@
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
- *
- * @throws Zygote.MethodAndArgsCaller in a child process when a main()
- * should be executed.
*/
- void runSelectLoop(String abiList) throws Zygote.MethodAndArgsCaller {
+ Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
@@ -156,15 +163,62 @@
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
+
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
- boolean done = peers.get(i).runOnce(this);
- if (done) {
- peers.remove(i);
- fds.remove(i);
+ try {
+ ZygoteConnection connection = peers.get(i);
+ final Runnable command = connection.processOneCommand(this);
+
+ if (mIsForkChild) {
+ // We're in the child. We should always have a command to run at this
+ // stage if processOneCommand hasn't called "exec".
+ if (command == null) {
+ throw new IllegalStateException("command == null");
+ }
+
+ return command;
+ } else {
+ // We're in the server - we should never have any commands to run.
+ if (command != null) {
+ throw new IllegalStateException("command != null");
+ }
+
+ // We don't know whether the remote side of the socket was closed or
+ // not until we attempt to read from it from processOneCommand. This shows up as
+ // a regular POLLIN event in our regular processing loop.
+ if (connection.isClosedByPeer()) {
+ connection.closeSocket();
+ peers.remove(i);
+ fds.remove(i);
+ }
+ }
+ } catch (Exception e) {
+ if (!mIsForkChild) {
+ // We're in the server so any exception here is one that has taken place
+ // pre-fork while processing commands or reading / writing from the
+ // control socket. Make a loud noise about any such exceptions so that
+ // we know exactly what failed and why.
+
+ Slog.e(TAG, "Exception executing zygote command: ", e);
+
+ // Make sure the socket is closed so that the other end knows immediately
+ // that something has gone wrong and doesn't time out waiting for a
+ // response.
+ ZygoteConnection conn = peers.remove(i);
+ conn.closeSocket();
+
+ fds.remove(i);
+ } else {
+ // We're in the child so any exception caught here has happened post
+ // fork and before we execute ActivityThread.main (or any other main()
+ // method). Log the details of the exception and bring down the process.
+ Log.e(TAG, "Caught post-fork exception in child process.", e);
+ throw e;
+ }
}
}
}
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
new file mode 100644
index 0000000..ad84353
--- /dev/null
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.util;
+
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * A simple ring buffer structure with bounded capacity backed by an array.
+ * Events can always be added at the logical end of the buffer. If the buffer is
+ * full, oldest events are dropped when new events are added.
+ * {@hide}
+ */
+public class RingBuffer<T> {
+
+ // Array for storing events.
+ private final T[] mBuffer;
+ // Cursor keeping track of the logical end of the array. This cursor never
+ // wraps and instead keeps track of the total number of append() operations.
+ private long mCursor = 0;
+
+ public RingBuffer(Class<T> c, int capacity) {
+ checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
+ // Java cannot create generic arrays without a runtime hint.
+ mBuffer = (T[]) Array.newInstance(c, capacity);
+ }
+
+ public int size() {
+ return (int) Math.min(mBuffer.length, (long) mCursor);
+ }
+
+ public void append(T t) {
+ mBuffer[indexOf(mCursor++)] = t;
+ }
+
+ public T[] toArray() {
+ // Only generic way to create a T[] from another T[]
+ T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
+ // Reverse iteration from youngest event to oldest event.
+ long inCursor = mCursor - 1;
+ int outIdx = out.length - 1;
+ while (outIdx >= 0) {
+ out[outIdx--] = (T) mBuffer[indexOf(inCursor--)];
+ }
+ return out;
+ }
+
+ private int indexOf(long cursor) {
+ return (int) Math.abs(cursor % mBuffer.length);
+ }
+}
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index 60ac74c..5b327d4 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -270,7 +270,7 @@
}
static jint nativeSetOperationParameter(JNIEnv *_env, jclass _this, jlong sensorManager,
- jint type, jfloatArray floats, jintArray ints) {
+ jint handle, jint type, jfloatArray floats, jintArray ints) {
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
Vector<float> floatVector;
Vector<int32_t> int32Vector;
@@ -285,7 +285,7 @@
_env->GetIntArrayRegion(ints, 0, _env->GetArrayLength(ints), int32Vector.editArray());
}
- return mgr->setOperationParameter(type, floatVector, int32Vector);
+ return mgr->setOperationParameter(handle, type, floatVector, int32Vector);
}
//----------------------------------------------------------------------------
@@ -512,7 +512,7 @@
(void*)nativeConfigDirectChannel },
{"nativeSetOperationParameter",
- "(JI[F[I)I",
+ "(JII[F[I)I",
(void*)nativeSetOperationParameter },
};
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 5b0f776..768f53f 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -488,8 +488,8 @@
private:
JavaVM* const mVM;
- jobject mObject;
- jweak mObjectWeak; // will be a weak ref to the same VM-side DeathRecipient after binderDied()
+ jobject mObject; // Initial strong ref to Java-side DeathRecipient. Cleared on binderDied().
+ jweak mObjectWeak; // weak ref to the same Java-side DeathRecipient after binderDied().
wp<DeathRecipientList> mList;
};
@@ -561,7 +561,7 @@
env->DeleteGlobalRef((jobject)obj);
}
-static Mutex mProxyLock;
+static Mutex gProxyLock;
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
@@ -576,7 +576,7 @@
// For the rest of the function we will hold this lock, to serialize
// looking/creation/destruction of Java proxies for native Binder proxies.
- AutoMutex _l(mProxyLock);
+ AutoMutex _l(gProxyLock);
// Someone else's... do we know about it?
jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
@@ -1252,7 +1252,7 @@
static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj)
{
// Don't race with construction/initialization
- AutoMutex _l(mProxyLock);
+ AutoMutex _l(gProxyLock);
IBinder* b = (IBinder*)
env->GetLongField(obj, gBinderProxyOffsets.mObject);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9a4d63c..0668b8b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3764,6 +3764,10 @@
<service android:name="com.android.server.PreloadsFileCacheExpirationJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+
+ <service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
</application>
</manifest>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 70c39af..7e47a2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2167,10 +2167,14 @@
<string name="config_customAdbPublicKeyConfirmationSecondaryUserComponent"
>com.android.systemui/com.android.systemui.usb.UsbDebuggingSecondaryUserActivity</string>
- <!-- Name of the CustomDialog that is used for VPN -->
- <string name="config_customVpnConfirmDialogComponent"
+ <!-- Name of the dialog that is used to request the user's consent to VPN connection -->
+ <string name="config_customVpnConfirmDialogComponent" translatable="false"
>com.android.vpndialogs/com.android.vpndialogs.ConfirmDialog</string>
+ <!-- Name of the dialog that is used to inform the user that always-on VPN is disconnected -->
+ <string name="config_customVpnAlwaysOnDisconnectedDialogComponent" translatable="false"
+ >com.android.vpndialogs/com.android.vpndialogs.AlwaysOnDisconnectedDialog</string>
+
<!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
<string name="config_appsAuthorizedForSharedAccounts" translatable="false">;com.android.settings;</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4797dd9..07dbc5d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3445,16 +3445,21 @@
<!-- The text of the notification when VPN is active with a session name. -->
<string name="vpn_text_long">Connected to <xliff:g id="session" example="office">%s</xliff:g>. Tap to manage the network.</string>
- <!-- Notification title when connecting to lockdown VPN. -->
+ <!-- Notification title when connecting to always-on VPN, a VPN that's set to always stay
+ connected. -->
<string name="vpn_lockdown_connecting">Always-on VPN connecting\u2026</string>
- <!-- Notification title when connected to lockdown VPN. -->
+ <!-- Notification title when connected to always-on VPN, a VPN that's set to always stay
+ connected. -->
<string name="vpn_lockdown_connected">Always-on VPN connected</string>
- <!-- Notification title when not connected to lockdown VPN. -->
- <string name="vpn_lockdown_disconnected">Always-on VPN disconnected</string>
- <!-- Notification title when error connecting to lockdown VPN. -->
+ <!-- Notification title when not connected to always-on VPN, a VPN that's set to always stay
+ connected. -->
+ <string name="vpn_lockdown_disconnected">Disconnected from always-on VPN</string>
+ <!-- Notification title when error connecting to always-on VPN, a VPN that's set to always stay
+ connected. -->
<string name="vpn_lockdown_error">Always-on VPN error</string>
- <!-- Notification body that indicates user can touch to configure lockdown VPN connection. -->
- <string name="vpn_lockdown_config">Tap to set up</string>
+ <!-- Notification body that indicates user can touch to configure always-on VPN, a VPN that's
+ set to always stay connected. -->
+ <string name="vpn_lockdown_config">Change network or VPN settings</string>
<!-- Localized strings for WebView -->
<!-- Label for button in a WebView that will open a chooser to choose a file to upload -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 600c82f..786080f9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2003,6 +2003,7 @@
<java-symbol type="string" name="config_customAdbPublicKeyConfirmationComponent" />
<java-symbol type="string" name="config_customAdbPublicKeyConfirmationSecondaryUserComponent" />
<java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
+ <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
<java-symbol type="string" name="config_persistentDataPackageName" />
<java-symbol type="string" name="reset_retail_demo_mode_title" />
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index bc2b9a6..1cb0ecd 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -753,7 +753,7 @@
@SmallTest
public void testCompareResult() {
// Either adding or removing items
- testCompareResult(Arrays.asList(1), Arrays.asList(1, 2, 3, 4),
+ testCompareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
Arrays.asList(2, 3, 4), new ArrayList<>());
testCompareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4),
new ArrayList<>(), Arrays.asList(3, 4));
@@ -780,9 +780,9 @@
assertEquals(expectedSet, actualSet);
}
- private <T> void testCompareResult(List<T> oldItems, List<T> newItems, List<T> expectAdded,
- List<T> expectRemoved) {
- CompareResult<T> result = new CompareResult<>(newItems, oldItems);
+ private <T> void testCompareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved,
+ List<T> expectAdded) {
+ CompareResult<T> result = new CompareResult<>(oldItems, newItems);
assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added));
assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
}
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index 14485d3..e6aea99 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -220,6 +220,33 @@
}
@Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o instanceof MediaDescription)){
+ return false;
+ }
+
+ final MediaDescription d = (MediaDescription) o;
+
+ if (!String.valueOf(mTitle).equals(String.valueOf(d.mTitle))) {
+ return false;
+ }
+
+ if (!String.valueOf(mSubtitle).equals(String.valueOf(d.mSubtitle))) {
+ return false;
+ }
+
+ if (!String.valueOf(mDescription).equals(String.valueOf(d.mDescription))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
public String toString() {
return mTitle + ", " + mSubtitle + ", " + mDescription;
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index dfd2bb3..9536d3d 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -48,6 +48,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.Objects;
/**
* Allows interaction with media controllers, volume keys, media buttons, and
@@ -1256,6 +1257,28 @@
"Description=" + mDescription +
", Id=" + mId + " }";
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+
+ if (!(o instanceof QueueItem)) {
+ return false;
+ }
+
+ final QueueItem item = (QueueItem) o;
+ if (mId != item.mId) {
+ return false;
+ }
+
+ if (!Objects.equals(mDescription, item.mDescription)) {
+ return false;
+ }
+
+ return true;
+ }
}
private static final class Command {
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index db3274a..d90665a 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -105,6 +105,7 @@
<!-- Titles for Bluetooth AVRCP Versions -->
<string-array name="bluetooth_avrcp_versions">
<item>AVRCP 1.4 (Default)</item>
+ <item>AVRCP 1.3</item>
<item>AVRCP 1.5</item>
<item>AVRCP 1.6</item>
</string-array>
@@ -112,6 +113,7 @@
<!-- Values for Bluetooth AVRCP Versions -->
<string-array name="bluetooth_avrcp_version_values">
<item>avrcp14</item>
+ <item>avrcp13</item>
<item>avrcp15</item>
<item>avrcp16</item>
</string-array>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 55498af..50e4f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3842,7 +3842,9 @@
}
public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
- mNavigationBar.onKeyguardOccludedChanged(keyguardOccluded);
+ if (mNavigationBar != null) {
+ mNavigationBar.onKeyguardOccludedChanged(keyguardOccluded);
+ }
}
// State logging
@@ -4633,7 +4635,8 @@
// TODO: Figure out way to remove this.
public NavigationBarView getNavigationBarView() {
- return (NavigationBarView) mNavigationBar.getView();
+ return (mNavigationBar != null) ?
+ (NavigationBarView) mNavigationBar.getView() : null;
}
// ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index a3d27ce..8172e71 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -23,9 +23,10 @@
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<application android:label="VpnDialogs"
- android:allowBackup="false" >
+ android:allowBackup="false">
+
<activity android:name=".ConfirmDialog"
- android:theme="@android:style/Theme.Material.Light.Dialog.Alert">
+ android:theme="@android:style/Theme.Material.Light.Dialog.Alert">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -33,12 +34,21 @@
</activity>
<activity android:name=".ManageDialog"
- android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
- android:noHistory="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
+ android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:permission="android.permission.NETWORK_SETTINGS"
+ android:exported="true">
</activity>
+
+ <activity android:name=".AlwaysOnDisconnectedDialog"
+ android:label="@string/always_on_disconnected_title"
+ android:theme="@android:style/Theme.Material.Light.Dialog.Alert"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:permission="android.permission.NETWORK_SETTINGS"
+ android:exported="true">
+ </activity>
+
</application>
</manifest>
diff --git a/packages/VpnDialogs/res/layout/always_on_disconnected.xml b/packages/VpnDialogs/res/layout/always_on_disconnected.xml
new file mode 100644
index 0000000..0f4a46d
--- /dev/null
+++ b/packages/VpnDialogs/res/layout/always_on_disconnected.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp">
+ <TextView android:id="@+id/message"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+</ScrollView>
diff --git a/packages/VpnDialogs/res/values/strings.xml b/packages/VpnDialogs/res/values/strings.xml
index 406bcc3..443a9bc 100644
--- a/packages/VpnDialogs/res/values/strings.xml
+++ b/packages/VpnDialogs/res/values/strings.xml
@@ -18,7 +18,6 @@
<!-- Dialog title to identify the request from a VPN application. [CHAR LIMIT=60] -->
<string name="prompt">Connection request</string>
-
<!-- Dialog message to warn about the risk of using a VPN application. [CHAR LIMIT=NONE] -->
<string name="warning"><xliff:g id="app">%s</xliff:g> wants to set up a VPN connection
that allows it to monitor network traffic. Only accept if you trust the source.
@@ -31,11 +30,6 @@
<!-- Dialog title for built-in VPN. [CHAR LIMIT=40] -->
<string name="legacy_title">VPN is connected</string>
- <!-- Button label to configure the current VPN session. [CHAR LIMIT=20] -->
- <string name="configure">Configure</string>
- <!-- Button label to disconnect the current VPN session. [CHAR LIMIT=20] -->
- <string name="disconnect">Disconnect</string>
-
<!-- Label for the name of the current VPN session. [CHAR LIMIT=20] -->
<string name="session">Session:</string>
<!-- Label for the duration of the current VPN session. [CHAR LIMIT=20] -->
@@ -44,10 +38,55 @@
<string name="data_transmitted">Sent:</string>
<!-- Label for the network usage of data received over VPN. [CHAR LIMIT=20] -->
<string name="data_received">Received:</string>
-
<!-- Formatted string for the network usage over VPN. [CHAR LIMIT=40] -->
<string name="data_value_format">
<xliff:g id="number">%1$s</xliff:g> bytes /
<xliff:g id="number">%2$s</xliff:g> packets
</string>
+
+ <!-- This string is the title of a dialog. The dialog shows up for Android users when always-on
+ VPN, a VPN that's set to always stay connected, loses its connection. [CHAR LIMIT=60] -->
+ <string name="always_on_disconnected_title">Can\'t connect to always-on VPN</string>
+ <!-- This message is part of a dialog. The dialog shows up for users when always-on VPN, a VPN
+ that's set to always stay connected, loses its connection. Until the phone can reconnect to
+ the VPN, it'll automatically connect to a public network if possible. This text is followed
+ by a clickable link that leads to VPN settings. [CHAR LIMIT=NONE] -->
+ <string name="always_on_disconnected_message">
+ <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> is set up to stay connected all
+ the time, but it can\'t connect right now. Your phone will use a public network until it can
+ reconnect to <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g>.
+ </string>
+ <!-- This message is part of a dialog. The dialog shows up for users when always-on VPN, a VPN
+ that's set to always stay connected, loses its connection while in the lockdown mode.
+ Until the phone can reconnect to the VPN, it won't be able to connect to the Internet. This
+ text is followed by a clickable link that leads to VPN settings. [CHAR LIMIT=NONE] -->
+ <string name="always_on_disconnected_message_lockdown">
+ <xliff:g id="vpn_app" example="Foo VPN App">%1$s</xliff:g> is set up to stay connected all
+ the time, but it can\'t connect right now. You won\'t have a connection until the VPN can
+ reconnect.
+ </string>
+ <!-- This is a space separating the body text and the "Change VPN settings" link that follows
+ it. [CHAR LIMIT=5] -->
+ <string name="always_on_disconnected_message_separator">" "</string>
+ <!-- This is a clickable link appended at the end of the body text of a dialog. The dialog shows
+ up for users when always-on VPN, a VPN that's set to always stay connected, loses its
+ connection. This link takes the user to the VPN page in Settings. -->
+ <string name="always_on_disconnected_message_settings_link">Change VPN settings</string>
+
+ <!-- This is the label of a button in a dialog. The button takes the user to the VPN settings
+ screen. [CHAR LIMIT=20] -->
+ <string name="configure">Configure</string>
+ <!-- This is the label of a button in a dialog. The button lets the user disconnect from the
+ current VPN connection. [CHAR LIMIT=20] -->
+ <string name="disconnect">Disconnect</string>
+ <!-- This button is part of a dialog, and it opens the user's VPN app. The dialog shows up for
+ users when always-on VPN, a VPN that's set to always stay connected, loses its connection.
+ Until the phone can reconnect to the VPN, it may automatically connect to a public network.
+ If it doesn't, the user won't have a connection until the VPN reconnects. [CHAR LIMIT=20]
+ -->
+ <string name="open_app">Open app</string>
+ <!-- This is the label of a button in a dialog. The button lets the user dismiss the dialog
+ without any consequences. [CHAR LIMIT=20] -->
+ <string name="dismiss">Dismiss</string>
+
</resources>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
new file mode 100644
index 0000000..846fcf8
--- /dev/null
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/AlwaysOnDisconnectedDialog.java
@@ -0,0 +1,139 @@
+/*
+ * 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.vpndialogs;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.IConnectivityManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.SpannableStringBuilder;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.net.VpnConfig;
+
+public class AlwaysOnDisconnectedDialog extends AlertActivity
+ implements DialogInterface.OnClickListener{
+
+ private static final String TAG = "VpnDisconnected";
+
+ private IConnectivityManager mService;
+ private int mUserId;
+ private String mVpnPackage;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mService = IConnectivityManager.Stub.asInterface(
+ ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
+ mUserId = UserHandle.myUserId();
+ mVpnPackage = getAlwaysOnVpnPackage();
+ if (mVpnPackage == null) {
+ finish();
+ return;
+ }
+
+ View view = View.inflate(this, R.layout.always_on_disconnected, null);
+ TextView messageView = view.findViewById(R.id.message);
+ messageView.setText(getMessage(getIntent().getBooleanExtra("lockdown", false)));
+ messageView.setMovementMethod(LinkMovementMethod.getInstance());
+
+ mAlertParams.mTitle = getString(R.string.always_on_disconnected_title);
+ mAlertParams.mPositiveButtonText = getString(R.string.open_app);
+ mAlertParams.mPositiveButtonListener = this;
+ mAlertParams.mNegativeButtonText = getString(R.string.dismiss);
+ mAlertParams.mNegativeButtonListener = this;
+ mAlertParams.mCancelable = false;
+ mAlertParams.mView = view;
+ setupAlert();
+
+ getWindow().setCloseOnTouchOutside(false);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case BUTTON_POSITIVE:
+ PackageManager pm = getPackageManager();
+ final Intent intent = pm.getLaunchIntentForPackage(mVpnPackage);
+ if (intent != null) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ }
+ finish();
+ break;
+ case BUTTON_NEGATIVE:
+ finish();
+ break;
+ default:
+ break;
+ }
+ }
+
+ private String getAlwaysOnVpnPackage() {
+ try {
+ return mService.getAlwaysOnVpnPackage(mUserId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Can't getAlwaysOnVpnPackage()", e);
+ return null;
+ }
+ }
+
+ private CharSequence getVpnLabel() {
+ try {
+ return VpnConfig.getVpnLabel(this, mVpnPackage);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Can't getVpnLabel() for " + mVpnPackage, e);
+ return mVpnPackage;
+ }
+ }
+
+ private CharSequence getMessage(boolean isLockdown) {
+ final SpannableStringBuilder message = new SpannableStringBuilder();
+ final int baseMessageResId = isLockdown
+ ? R.string.always_on_disconnected_message_lockdown
+ : R.string.always_on_disconnected_message;
+ message.append(getString(baseMessageResId, getVpnLabel()));
+ message.append(getString(R.string.always_on_disconnected_message_separator));
+ message.append(getString(R.string.always_on_disconnected_message_settings_link),
+ new VpnSpan(), 0 /*flags*/);
+ return message;
+ }
+
+ private class VpnSpan extends ClickableSpan {
+ @Override
+ public void onClick(View unused) {
+ final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index 2fe6648..01dca7e 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -54,12 +54,6 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (getCallingPackage() != null) {
- Log.e(TAG, getCallingPackage() + " cannot start this activity");
- finish();
- return;
- }
-
try {
mService = IConnectivityManager.Stub.asInterface(
diff --git a/preloaded-classes b/preloaded-classes
index 9612231..c1896aa 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -2103,7 +2103,6 @@
android.system.StructAddrinfo
android.system.StructFlock
android.system.StructGroupReq
-android.system.StructGroupSourceReq
android.system.StructIfaddrs
android.system.StructLinger
android.system.StructPasswd
diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto
index 01fb860..8dd35af 100644
--- a/proto/src/ipconnectivity.proto
+++ b/proto/src/ipconnectivity.proto
@@ -473,6 +473,38 @@
repeated Pair validation_states = 8;
}
+// Represents statistics from NFLOG wakeup events due to ingress packets.
+// Since oc-mr1.
+// Next tag: 8.
+message WakeupStats {
+ // The time duration in seconds covered by these stats, for deriving
+ // exact wakeup rates.
+ optional int64 duration_sec = 1;
+
+ // The total number of ingress packets waking up the device.
+ optional int64 total_wakeups = 2;
+
+ // The total number of wakeup packets routed to a socket belonging to
+ // the root uid (uid 0).
+ optional int64 root_wakeups = 3;
+
+ // The total number of wakeup packets routed to a socket belonging to
+ // the system server (uid 1000).
+ optional int64 system_wakeups = 4;
+
+ // The total number of wakeup packets routed to a socket belonging to
+ // an application (uid > 9999).
+ optional int64 application_wakeups = 5;
+
+ // The total number of wakeup packets routed to a socket belonging to another
+ // uid than the root uid, system uid, or an application uid (any uid in
+ // between [1001, 9999]. See android.os.Process for possible uids.
+ optional int64 non_application_wakeups = 6;
+
+ // The total number of wakeup packets with no associated socket or uid.
+ optional int64 no_uid_wakeups = 7;
+}
+
// Represents one of the IP connectivity event defined in this file.
// Next tag: 20
message IpConnectivityEvent {
@@ -547,6 +579,9 @@
// Network statistics.
NetworkStats network_stats = 19;
+
+ // Ingress packet wakeup statistics.
+ WakeupStats wakeup_stats = 20;
};
};
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index aad4431..16a927c 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2426,14 +2426,14 @@
out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
}
if (widget.options != null) {
- out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
- out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
- out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
- out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
- AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+ int minWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+ int minHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+ int maxWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
+ int maxHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+ out.attribute(null, "min_width", Integer.toHexString((minWidth > 0) ? minWidth : 0));
+ out.attribute(null, "min_height", Integer.toHexString((minHeight > 0) ? minHeight : 0));
+ out.attribute(null, "max_width", Integer.toHexString((maxWidth > 0) ? maxWidth : 0));
+ out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 31fc874..5c11100 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -91,6 +92,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -128,9 +130,8 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
import com.android.server.connectivity.KeepaliveTracker;
-import com.android.server.connectivity.MockableSystemProperties;
-import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.LingerMonitor;
+import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkMonitor;
@@ -783,6 +784,13 @@
mNetworksDefined++; // used only in the log() statement below.
}
+ // Do the same for Ethernet, since it's often not specified in the configs, although many
+ // devices can use it via USB host adapters.
+ if (mNetConfigs[TYPE_ETHERNET] == null && hasService(Context.ETHERNET_SERVICE)) {
+ mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
+ mNetworksDefined++;
+ }
+
if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
mProtectedNetworks = new ArrayList<Integer>();
@@ -1515,6 +1523,12 @@
ConnectivityManager.enforceChangePermission(mContext);
}
+ private void enforceSettingsPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.NETWORK_SETTINGS,
+ "ConnectivityService");
+ }
+
private void enforceTetherAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -2217,7 +2231,7 @@
// A network factory has connected. Send it all current NetworkRequests.
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (nri.request.isListen()) continue;
- NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
(nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
}
@@ -2294,9 +2308,9 @@
// Remove all previously satisfied requests.
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest request = nai.requestAt(i);
- NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
+ NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
- mNetworkForRequestId.remove(request.requestId);
+ clearNetworkForRequest(request.requestId);
sendUpdatedScoreToFactories(request, 0);
}
}
@@ -2372,7 +2386,7 @@
}
}
rematchAllNetworksAndRequests(null, 0);
- if (nri.request.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
+ if (nri.request.isRequest() && getNetworkForRequest(nri.request.requestId) == null) {
sendUpdatedScoreToFactories(nri.request, 0);
}
}
@@ -2427,7 +2441,7 @@
// 2. Unvalidated WiFi will not be reaped when validated cellular
// is currently satisfying the request. This is desirable when
// WiFi ends up validating and out scoring cellular.
- mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+ getNetworkForRequest(nri.request.requestId).getCurrentScore() <
nai.getCurrentScoreAsValidated())) {
return false;
}
@@ -2454,7 +2468,7 @@
if (mNetworkRequests.get(nri.request) == null) {
return;
}
- if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+ if (getNetworkForRequest(nri.request.requestId) != null) {
return;
}
if (VDBG || (DBG && nri.request.isRequest())) {
@@ -2494,7 +2508,7 @@
mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (nri.request.isRequest()) {
boolean wasKept = false;
- NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
if (nai != null) {
boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
nai.removeRequest(nri.request.requestId);
@@ -2511,7 +2525,7 @@
} else {
wasKept = true;
}
- mNetworkForRequestId.remove(nri.request.requestId);
+ clearNetworkForRequest(nri.request.requestId);
if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
// Went from foreground to background.
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2974,12 +2988,16 @@
return mTethering.getTetheredDhcpRanges();
}
+ @Override
+ public boolean isTetheringSupported(String callerPkg) {
+ ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg);
+ return isTetheringSupported();
+ }
+
// if ro.tether.denied = true we default to no tethering
// gservices could set the secure setting to 1 though to enable it on a build where it
// had previously been turned off.
- @Override
- public boolean isTetheringSupported() {
- enforceTetherAccessPermission();
+ private boolean isTetheringSupported() {
int defaultVal = encodeBool(!mSystemProperties.get("ro.tether.denied").equals("true"));
boolean tetherSupported = toBool(Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.TETHER_SUPPORTED, defaultVal));
@@ -3646,6 +3664,21 @@
}
@Override
+ public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) {
+ enforceSettingsPermission();
+ enforceCrossUserPermission(userId);
+
+ synchronized (mVpns) {
+ Vpn vpn = mVpns.get(userId);
+ if (vpn == null) {
+ Slog.w(TAG, "User " + userId + " has no Vpn configuration");
+ return false;
+ }
+ return vpn.isAlwaysOnPackageSupported(packageName);
+ }
+ }
+
+ @Override
public boolean setAlwaysOnVpnPackage(int userId, String packageName, boolean lockdown) {
enforceConnectivityInternalPermission();
enforceCrossUserPermission(userId);
@@ -4288,7 +4321,8 @@
* and the are the highest scored network available.
* the are keyed off the Requests requestId.
*/
- // TODO: Yikes, this is accessed on multiple threads: add synchronization.
+ // NOTE: Accessed on multiple threads, must be synchronized on itself.
+ @GuardedBy("mNetworkForRequestId")
private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
new SparseArray<NetworkAgentInfo>();
@@ -4318,8 +4352,26 @@
// priority networks like Wi-Fi are active.
private final NetworkRequest mDefaultMobileDataRequest;
+ private NetworkAgentInfo getNetworkForRequest(int requestId) {
+ synchronized (mNetworkForRequestId) {
+ return mNetworkForRequestId.get(requestId);
+ }
+ }
+
+ private void clearNetworkForRequest(int requestId) {
+ synchronized (mNetworkForRequestId) {
+ mNetworkForRequestId.remove(requestId);
+ }
+ }
+
+ private void setNetworkForRequest(int requestId, NetworkAgentInfo nai) {
+ synchronized (mNetworkForRequestId) {
+ mNetworkForRequestId.put(requestId, nai);
+ }
+ }
+
private NetworkAgentInfo getDefaultNetwork() {
- return mNetworkForRequestId.get(mDefaultRequest.requestId);
+ return getNetworkForRequest(mDefaultRequest.requestId);
}
private boolean isDefaultNetwork(NetworkAgentInfo nai) {
@@ -4873,7 +4925,7 @@
// requests or not, and doesn't affect the network's score.
if (nri.request.isListen()) continue;
- final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ final NetworkAgentInfo currentNetwork = getNetworkForRequest(nri.request.requestId);
final boolean satisfies = newNetwork.satisfies(nri.request);
if (newNetwork == currentNetwork && satisfies) {
if (VDBG) {
@@ -4905,7 +4957,7 @@
if (VDBG) log(" accepting network in place of null");
}
newNetwork.unlingerRequest(nri.request);
- mNetworkForRequestId.put(nri.request.requestId, newNetwork);
+ setNetworkForRequest(nri.request.requestId, newNetwork);
if (!newNetwork.addRequest(nri.request)) {
Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
}
@@ -4939,7 +4991,7 @@
}
newNetwork.removeRequest(nri.request.requestId);
if (currentNetwork == newNetwork) {
- mNetworkForRequestId.remove(nri.request.requestId);
+ clearNetworkForRequest(nri.request.requestId);
sendUpdatedScoreToFactories(nri.request, 0);
} else {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
@@ -5416,6 +5468,7 @@
@Override
public String getCaptivePortalServerUrl() {
+ enforceConnectivityInternalPermission();
return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
}
@@ -5513,6 +5566,11 @@
return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
}
+ @VisibleForTesting
+ public boolean hasService(String name) {
+ return ServiceManager.checkService(name) != null;
+ }
+
private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
int newNetid = NETID_UNSET;
int prevNetid = NETID_UNSET;
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index caa2d51..f0528bc 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1140,17 +1140,6 @@
}
@Override
- public void setInterfaceIpv6NdOffload(String iface, boolean enable) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- mConnector.execute(
- "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable"));
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
public void addRoute(int netId, RouteInfo route) {
modifyRoute("add", "" + netId, route);
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 93e29c1..6a81d32 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -91,8 +91,9 @@
"android.hardware.bluetooth@1.0::IBluetoothHci",
"android.hardware.camera.provider@2.4::ICameraProvider",
"android.hardware.graphics.composer@2.1::IComposer",
- "android.hardware.vr@1.0::IVr",
- "android.hardware.media.omx@1.0::IOmx"
+ "android.hardware.media.omx@1.0::IOmx",
+ "android.hardware.sensors@1.0::ISensors",
+ "android.hardware.vr@1.0::IVr"
);
static Watchdog sWatchdog;
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f0b1b3b..d6a010f 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2954,9 +2954,13 @@
* have users launching arbitrary activities by tricking users to
* interact with malicious notifications.
*/
- checkKeyIntent(
+ if (!checkKeyIntent(
Binder.getCallingUid(),
- intent);
+ intent)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "invalid intent in bundle returned");
+ return;
+ }
doNotification(
mAccounts,
account,
@@ -3351,9 +3355,13 @@
Intent intent = null;
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
- checkKeyIntent(
+ if (!checkKeyIntent(
Binder.getCallingUid(),
- intent);
+ intent)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "invalid intent in bundle returned");
+ return;
+ }
}
IAccountManagerResponse response;
if (mExpectActivityLaunch && result != null
@@ -4700,13 +4708,14 @@
* into launching arbitrary intents on the device via by tricking to click authenticator
* supplied entries in the system Settings app.
*/
- protected void checkKeyIntent(
- int authUid,
- Intent intent) throws SecurityException {
+ protected boolean checkKeyIntent(int authUid, Intent intent) {
long bid = Binder.clearCallingIdentity();
try {
PackageManager pm = mContext.getPackageManager();
ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+ if (resolveInfo == null) {
+ return false;
+ }
ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
int targetUid = targetActivityInfo.applicationInfo.uid;
if (!isExportedSystemActivity(targetActivityInfo)
@@ -4716,9 +4725,10 @@
String activityName = targetActivityInfo.name;
String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
+ "does not share a signature with the supplying authenticator (%s).";
- throw new SecurityException(
- String.format(tmpl, activityName, pkgName, mAccountType));
+ Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
+ return false;
}
+ return true;
} finally {
Binder.restoreCallingIdentity(bid);
}
@@ -4868,9 +4878,13 @@
}
if (result != null
&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
- checkKeyIntent(
+ if (!checkKeyIntent(
Binder.getCallingUid(),
- intent);
+ intent)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "invalid intent in bundle returned");
+ return;
+ }
}
if (result != null
&& !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c699a56..b7144d4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3871,6 +3871,12 @@
mNativeDebuggingApp = null;
}
+ if (app.info.isPrivilegedApp() &&
+ !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ runtimeFlags |= Zygote.DISABLE_VERIFIER;
+ runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
+ }
+
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index ee38219..67e7216 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -38,6 +38,7 @@
import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.net.metrics.WakeupStats;
import android.os.Parcelable;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -115,6 +116,22 @@
return out;
}
+ public static IpConnectivityEvent toProto(WakeupStats in) {
+ IpConnectivityLogClass.WakeupStats wakeupStats =
+ new IpConnectivityLogClass.WakeupStats();
+ in.updateDuration();
+ wakeupStats.durationSec = in.durationSec;
+ wakeupStats.totalWakeups = in.totalWakeups;
+ wakeupStats.rootWakeups = in.rootWakeups;
+ wakeupStats.systemWakeups = in.systemWakeups;
+ wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
+ wakeupStats.applicationWakeups = in.applicationWakeups;
+ wakeupStats.noUidWakeups = in.noUidWakeups;
+ final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
+ out.setWakeupStats(wakeupStats);
+ return out;
+ }
+
private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
final IpConnectivityEvent ev = new IpConnectivityEvent();
ev.networkId = netId;
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 475d786..f2445fa 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -34,6 +34,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
import com.android.server.SystemService;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -44,7 +45,11 @@
import java.util.List;
import java.util.function.ToIntFunction;
-/** {@hide} */
+/**
+ * Event buffering service for core networking and connectivity metrics.
+ *
+ * {@hide}
+ */
final public class IpConnectivityMetrics extends SystemService {
private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
private static final boolean DBG = false;
@@ -58,7 +63,10 @@
private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
- // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
+ // Default size of the event rolling log for bug report dumps.
+ private static final int DEFAULT_LOG_SIZE = 500;
+ // Default size of the event buffer for metrics reporting.
+ // Once the buffer is full, incoming events are dropped.
private static final int DEFAULT_BUFFER_SIZE = 2000;
// Maximum size of the event buffer.
private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
@@ -67,24 +75,38 @@
private static final int ERROR_RATE_LIMITED = -1;
- // Lock ensuring that concurrent manipulations of the event buffer are correct.
+ // Lock ensuring that concurrent manipulations of the event buffers are correct.
// There are three concurrent operations to synchronize:
// - appending events to the buffer.
// - iterating throught the buffer.
// - flushing the buffer content and replacing it by a new buffer.
private final Object mLock = new Object();
+ // Implementation instance of IIpConnectivityMetrics.aidl.
@VisibleForTesting
public final Impl impl = new Impl();
+ // Subservice listening to Netd events via INetdEventListener.aidl.
@VisibleForTesting
NetdEventListenerService mNetdListener;
+ // Rolling log of the most recent events. This log is used for dumping
+ // connectivity events in bug reports.
+ @GuardedBy("mLock")
+ private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
+ new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
+ // Buffer of connectivity events used for metrics reporting. This buffer
+ // does not rotate automatically and instead saturates when it becomes full.
+ // It is flushed at metrics reporting.
@GuardedBy("mLock")
private ArrayList<ConnectivityMetricsEvent> mBuffer;
+ // Total number of events dropped from mBuffer since last metrics reporting.
@GuardedBy("mLock")
private int mDropped;
+ // Capacity of mBuffer
@GuardedBy("mLock")
private int mCapacity;
+ // A list of rate limiting counters keyed by connectivity event types for
+ // metrics reporting mBuffer.
@GuardedBy("mLock")
private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
@@ -132,6 +154,7 @@
private int append(ConnectivityMetricsEvent event) {
if (DBG) Log.d(TAG, "logEvent: " + event);
synchronized (mLock) {
+ mEventLog.append(event);
final int left = mCapacity - mBuffer.size();
if (event == null) {
return left;
@@ -216,6 +239,23 @@
}
}
+ /**
+ * Prints for bug reports the content of the rolling event log and the
+ * content of Netd event listener.
+ */
+ private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final ConnectivityMetricsEvent[] events;
+ synchronized (mLock) {
+ events = mEventLog.toArray();
+ }
+ for (ConnectivityMetricsEvent ev : events) {
+ pw.println(ev.toString());
+ }
+ if (mNetdListener != null) {
+ mNetdListener.list(pw);
+ }
+ }
+
private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
pw.println("Buffered events: " + mBuffer.size());
@@ -258,7 +298,8 @@
cmdFlush(fd, pw, args);
return;
case CMD_DUMPSYS:
- // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
+ cmdDumpsys(fd, pw, args);
+ return;
case CMD_LIST:
cmdList(fd, pw, args);
return;
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index e6585ad..fceacba 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -20,6 +20,7 @@
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkInfo;
import android.net.RouteInfo;
import android.os.INetworkManagementService;
import android.os.RemoteException;
@@ -44,12 +45,18 @@
// This must match the interface prefix in clatd.c.
private static final String CLAT_PREFIX = "v4-";
- // The network types we will start clatd on,
+ // The network types on which we will start clatd,
// allowing clat only on networks for which we can support IPv6-only.
private static final int[] NETWORK_TYPES = {
- ConnectivityManager.TYPE_MOBILE,
- ConnectivityManager.TYPE_WIFI,
- ConnectivityManager.TYPE_ETHERNET,
+ ConnectivityManager.TYPE_MOBILE,
+ ConnectivityManager.TYPE_WIFI,
+ ConnectivityManager.TYPE_ETHERNET,
+ };
+
+ // The network states in which running clatd is supported.
+ private static final NetworkInfo.State[] NETWORK_STATES = {
+ NetworkInfo.State.CONNECTED,
+ NetworkInfo.State.SUSPENDED,
};
private final INetworkManagementService mNMService;
@@ -81,11 +88,8 @@
*/
public static boolean requiresClat(NetworkAgentInfo nai) {
// TODO: migrate to NetworkCapabilities.TRANSPORT_*.
- final int netType = nai.networkInfo.getType();
final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
- // TODO: this should also consider if the network is in SUSPENDED state to avoid stopping
- // clatd in SUSPENDED state.
- final boolean connected = nai.networkInfo.isConnected();
+ final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
// We only run clat on networks that don't have a native IPv4 address.
final boolean hasIPv4Address =
(nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
@@ -148,7 +152,6 @@
* turn ND offload off if on WiFi.
*/
private void enterRunningState() {
- maybeSetIpv6NdOffload(mBaseIface, false);
mState = State.RUNNING;
}
@@ -156,10 +159,6 @@
* Stop clatd, and turn ND offload on if it had been turned off.
*/
private void enterStoppingState() {
- if (isRunning()) {
- maybeSetIpv6NdOffload(mBaseIface, true);
- }
-
try {
mNMService.stopClatd(mBaseIface);
} catch(RemoteException|IllegalStateException e) {
@@ -275,19 +274,6 @@
}
}
- private void maybeSetIpv6NdOffload(String iface, boolean on) {
- // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
- if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
- return;
- }
- try {
- Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
- mNMService.setInterfaceIpv6NdOffload(iface, on);
- } catch(RemoteException|IllegalStateException e) {
- Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
- }
- }
-
/**
* Adds stacked link on base link and transitions to RUNNING state.
*/
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 4094083..6206dfc 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity;
+import static android.util.TimeUtils.NANOS_PER_MS;
+
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetdEventCallback;
@@ -25,14 +27,18 @@
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.WakeupEvent;
+import android.net.metrics.WakeupStats;
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log;
+import android.util.ArrayMap;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.BitUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.PrintWriter;
@@ -59,12 +65,27 @@
private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS;
private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+ @VisibleForTesting
+ static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024;
+ // TODO: dedup this String constant with the one used in
+ // ConnectivityService#wakeupModifyInterface().
+ @VisibleForTesting
+ static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:";
+
// Sparse arrays of DNS and connect events, grouped by net id.
@GuardedBy("this")
private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
@GuardedBy("this")
private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
+ // Array of aggregated wakeup event stats, grouped by interface name.
+ @GuardedBy("this")
+ private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
+ // Ring buffer array for storing packet wake up events sent by Netd.
+ @GuardedBy("this")
+ private final RingBuffer<WakeupEvent> mWakeupEvents =
+ new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
+
private final ConnectivityManager mCm;
@GuardedBy("this")
@@ -137,11 +158,43 @@
@Override
public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) {
+ maybeVerboseLog("onWakeupEvent(%s, %d, %d, %sns)", prefix, uid, gid, timestampNs);
+
+ // TODO: add ip protocol and port
+
+ String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
+ final long timestampMs;
+ if (timestampNs > 0) {
+ timestampMs = timestampNs / NANOS_PER_MS;
+ } else {
+ timestampMs = System.currentTimeMillis();
+ }
+
+ addWakeupEvent(iface, timestampMs, uid);
+ }
+
+ @GuardedBy("this")
+ private void addWakeupEvent(String iface, long timestampMs, int uid) {
+ WakeupEvent event = new WakeupEvent();
+ event.iface = iface;
+ event.timestampMs = timestampMs;
+ event.uid = uid;
+ mWakeupEvents.append(event);
+ WakeupStats stats = mWakeupStats.get(iface);
+ if (stats == null) {
+ stats = new WakeupStats(iface);
+ mWakeupStats.put(iface, stats);
+ }
+ stats.countEvent(event);
}
public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
+ for (int i = 0; i < mWakeupStats.size(); i++) {
+ events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
+ }
+ mWakeupStats.clear();
}
public synchronized void dump(PrintWriter writer) {
@@ -153,13 +206,22 @@
}
public synchronized void list(PrintWriter pw) {
- listEvents(pw, mConnectEvents, (x) -> x);
- listEvents(pw, mDnsEvents, (x) -> x);
+ listEvents(pw, mConnectEvents, (x) -> x, "\n");
+ listEvents(pw, mDnsEvents, (x) -> x, "\n");
+ for (int i = 0; i < mWakeupStats.size(); i++) {
+ pw.println(mWakeupStats.valueAt(i));
+ }
+ for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
+ pw.println(wakeup);
+ }
}
public synchronized void listAsProtos(PrintWriter pw) {
- listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto);
- listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto);
+ listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, "");
+ listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, "");
+ for (int i = 0; i < mWakeupStats.size(); i++) {
+ pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
+ }
}
private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
@@ -170,10 +232,13 @@
in.clear();
}
- public static <T> void listEvents(
- PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper) {
+ private static <T> void listEvents(
+ PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) {
+ // Proto derived Classes have toString method that adds a \n at the end.
+ // Let the caller control that by passing in the line separator explicitly.
for (int i = 0; i < events.size(); i++) {
- pw.println(mapper.apply(events.valueAt(i)).toString());
+ pw.print(mapper.apply(events.valueAt(i)));
+ pw.print(separator);
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index e96f4b0..a4d7242 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -278,6 +278,10 @@
return mHandler;
}
+ public Network network() {
+ return network;
+ }
+
// Functions for manipulating the requests satisfied by this network.
//
// These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index d3a9354..8b886d6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -20,7 +20,6 @@
import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
-import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -229,6 +228,8 @@
// Delay between reevaluations once a captive portal has been found.
private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10*60*1000;
+ private static final int NUM_VALIDATION_LOG_LINES = 20;
+
private final Context mContext;
private final Handler mConnectivityServiceHandler;
private final NetworkAgentInfo mNetworkAgentInfo;
@@ -236,9 +237,15 @@
private final int mNetId;
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
- private final AlarmManager mAlarmManager;
private final NetworkRequest mDefaultRequest;
private final IpConnectivityLog mMetricsLog;
+ private final NetworkMonitorSettings mSettings;
+
+ // Configuration values for captive portal detection probes.
+ private final String mCaptivePortalUserAgent;
+ private final URL mCaptivePortalHttpsUrl;
+ private final URL mCaptivePortalHttpUrl;
+ private final URL[] mCaptivePortalFallbackUrls;
@VisibleForTesting
protected boolean mIsCaptivePortalCheckEnabled;
@@ -262,40 +269,37 @@
private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
- private final LocalLog validationLogs = new LocalLog(20); // 20 lines
+ private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
private final Stopwatch mEvaluationTimer = new Stopwatch();
// This variable is set before transitioning to the mCaptivePortalState.
private CaptivePortalProbeResult mLastPortalProbeResult = CaptivePortalProbeResult.FAILED;
- // Configuration values for captive portal detection probes.
- private final String mCaptivePortalUserAgent;
- private final URL mCaptivePortalHttpsUrl;
- private final URL mCaptivePortalHttpUrl;
- private final URL[] mCaptivePortalFallbackUrls;
private int mNextFallbackUrlIndex = 0;
public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
NetworkRequest defaultRequest) {
- this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog());
+ this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
+ NetworkMonitorSettings.DEFAULT);
}
@VisibleForTesting
protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
- NetworkRequest defaultRequest, IpConnectivityLog logger) {
+ NetworkRequest defaultRequest, IpConnectivityLog logger,
+ NetworkMonitorSettings settings) {
// Add suffix indicating which NetworkMonitor we're talking about.
super(TAG + networkAgentInfo.name());
mContext = context;
mMetricsLog = logger;
mConnectivityServiceHandler = handler;
+ mSettings = settings;
mNetworkAgentInfo = networkAgentInfo;
- mNetwork = new OneAddressPerFamilyNetwork(networkAgentInfo.network);
+ mNetwork = new OneAddressPerFamilyNetwork(networkAgentInfo.network());
mNetId = mNetwork.netId;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mDefaultRequest = defaultRequest;
addState(mDefaultState);
@@ -305,16 +309,12 @@
addState(mCaptivePortalState, mMaybeNotifyState);
setInitialState(mDefaultState);
- mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_MODE, Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT)
- != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
- mUseHttps = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
-
- mCaptivePortalUserAgent = getCaptivePortalUserAgent(context);
- mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl(context));
- mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(context));
- mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls(context);
+ mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
+ mUseHttps = getUseHttpsValidation();
+ mCaptivePortalUserAgent = getCaptivePortalUserAgent();
+ mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
+ mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(settings, context));
+ mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
start();
}
@@ -705,19 +705,42 @@
}
}
- private static String getCaptivePortalServerHttpsUrl(Context context) {
- return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
+ public boolean getIsCaptivePortalCheckEnabled() {
+ String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
+ int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
+ int mode = mSettings.getSetting(mContext, symbol, defaultValue);
+ return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
}
+ public boolean getUseHttpsValidation() {
+ return mSettings.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
+ }
+
+ public boolean getWifiScansAlwaysAvailableDisabled() {
+ return mSettings.getSetting(mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
+ }
+
+ private String getCaptivePortalServerHttpsUrl() {
+ return mSettings.getSetting(mContext,
+ Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, DEFAULT_HTTPS_URL);
+ }
+
+ // Static for direct access by ConnectivityService
public static String getCaptivePortalServerHttpUrl(Context context) {
- return getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
+ return getCaptivePortalServerHttpUrl(NetworkMonitorSettings.DEFAULT, context);
}
- private URL[] makeCaptivePortalFallbackUrls(Context context) {
+ public static String getCaptivePortalServerHttpUrl(
+ NetworkMonitorSettings settings, Context context) {
+ return settings.getSetting(
+ context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
+ }
+
+ private URL[] makeCaptivePortalFallbackUrls() {
String separator = ",";
- String firstUrl = getSetting(context,
+ String firstUrl = mSettings.getSetting(mContext,
Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, DEFAULT_FALLBACK_URL);
- String joinedUrls = firstUrl + separator + getSetting(context,
+ String joinedUrls = firstUrl + separator + mSettings.getSetting(mContext,
Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS, DEFAULT_OTHER_FALLBACK_URLS);
List<URL> urls = new ArrayList<>();
for (String s : joinedUrls.split(separator)) {
@@ -733,13 +756,9 @@
return urls.toArray(new URL[urls.size()]);
}
- private static String getCaptivePortalUserAgent(Context context) {
- return getSetting(context, Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
- }
-
- private static String getSetting(Context context, String symbol, String defaultValue) {
- final String value = Settings.Global.getString(context.getContentResolver(), symbol);
- return value != null ? value : defaultValue;
+ private String getCaptivePortalUserAgent() {
+ return mSettings.getSetting(mContext,
+ Settings.Global.CAPTIVE_PORTAL_USER_AGENT, DEFAULT_USER_AGENT);
}
private URL nextFallbackUrl() {
@@ -1035,12 +1054,13 @@
*/
private void sendNetworkConditionsBroadcast(boolean responseReceived, boolean isCaptivePortal,
long requestTimestampMs, long responseTimestampMs) {
- if (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0) {
+ if (getWifiScansAlwaysAvailableDisabled()) {
return;
}
- if (systemReady == false) return;
+ if (!systemReady) {
+ return;
+ }
Intent latencyBroadcast = new Intent(ACTION_NETWORK_CONDITIONS_MEASURED);
switch (mNetworkAgentInfo.networkInfo.getType()) {
@@ -1144,4 +1164,24 @@
ev.durationMs = durationMs;
mMetricsLog.log(mNetId, transports, ev);
}
+
+ @VisibleForTesting
+ public interface NetworkMonitorSettings {
+ int getSetting(Context context, String symbol, int defaultValue);
+ String getSetting(Context context, String symbol, String defaultValue);
+
+ static NetworkMonitorSettings DEFAULT = new DefaultNetworkMonitorSettings();
+ }
+
+ @VisibleForTesting
+ public static class DefaultNetworkMonitorSettings implements NetworkMonitorSettings {
+ public int getSetting(Context context, String symbol, int defaultValue) {
+ return Settings.Global.getInt(context.getContentResolver(), symbol, defaultValue);
+ }
+
+ public String getSetting(Context context, String symbol, String defaultValue) {
+ final String value = Settings.Global.getString(context.getContentResolver(), symbol);
+ return value != null ? value : defaultValue;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 27968a9..a44b18d 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -36,6 +36,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -56,7 +57,10 @@
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.Uri;
+import android.net.VpnService;
import android.os.Binder;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
import android.os.FileUtils;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -296,6 +300,56 @@
}
/**
+ * Checks if a VPN app supports always-on mode.
+ *
+ * In order to support the always-on feature, an app has to
+ * <ul>
+ * <li>target {@link VERSION_CODES#N API 24} or above, and
+ * <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+ * meta-data field.
+ * </ul>
+ *
+ * @param packageName the canonical package name of the VPN app
+ * @return {@code true} if and only if the VPN app exists and supports always-on mode
+ */
+ public boolean isAlwaysOnPackageSupported(String packageName) {
+ enforceSettingsPermission();
+
+ if (packageName == null) {
+ return false;
+ }
+
+ PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo appInfo = null;
+ try {
+ appInfo = pm.getApplicationInfoAsUser(packageName, 0 /*flags*/, mUserHandle);
+ } catch (NameNotFoundException unused) {
+ Log.w(TAG, "Can't find \"" + packageName + "\" when checking always-on support");
+ }
+ if (appInfo == null || appInfo.targetSdkVersion < VERSION_CODES.N) {
+ return false;
+ }
+
+ final Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
+ intent.setPackage(packageName);
+ List<ResolveInfo> services =
+ pm.queryIntentServicesAsUser(intent, PackageManager.GET_META_DATA, mUserHandle);
+ if (services == null || services.size() == 0) {
+ return false;
+ }
+
+ for (ResolveInfo rInfo : services) {
+ final Bundle metaData = rInfo.serviceInfo.metaData;
+ if (metaData != null &&
+ !metaData.getBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, true)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Configures an always-on VPN connection through a specific application.
* This connection is automatically granted and persisted after a reboot.
*
@@ -303,6 +357,10 @@
* manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
* otherwise the call will fail.
*
+ * <p>Note that this method does not check if the VPN app supports always-on mode. The check is
+ * delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
+ * method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
+ *
* @param packageName the package to designate as always-on VPN supplier.
* @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
* @return {@code true} if the package has been set as always-on, {@code false} otherwise.
@@ -443,6 +501,11 @@
if (alwaysOnPackage == null) {
return true;
}
+ // Remove always-on VPN if it's not supported.
+ if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
+ setAlwaysOnPackage(null, false);
+ return false;
+ }
// Skip if the service is already established. This isn't bulletproof: it's not bound
// until after establish(), so if it's mid-setup onStartCommand will be sent twice,
// which may restart the connection.
@@ -1219,6 +1282,11 @@
"Unauthorized Caller");
}
+ private void enforceSettingsPermission() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_SETTINGS,
+ "Unauthorized Caller");
+ }
+
private class Connection implements ServiceConnection {
private IBinder mService;
@@ -1348,7 +1416,11 @@
notificationManager.cancelAsUser(TAG, SystemMessage.NOTE_VPN_DISCONNECTED, user);
return;
}
- final Intent intent = new Intent(Settings.ACTION_VPN_SETTINGS);
+ final Intent intent = new Intent();
+ intent.setComponent(ComponentName.unflattenFromString(mContext.getString(
+ R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)));
+ intent.putExtra("lockdown", mLockdown);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent configIntent = mSystemServices.pendingIntentGetActivityAsUser(
intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, user);
final Notification.Builder builder =
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 5eafe5f..057704a 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -52,6 +52,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -73,6 +74,8 @@
private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
+ private static enum UpdateType { IF_NEEDED, FORCE };
+
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
@@ -185,8 +188,8 @@
updateStatsForAllUpstreams();
forceTetherStatsPoll();
// [2] (Re)Push all state.
- // TODO: computeAndPushLocalPrefixes()
- // TODO: push all downstream state.
+ computeAndPushLocalPrefixes(UpdateType.FORCE);
+ pushAllDownstreamState();
pushUpstreamParameters(null);
}
@@ -319,7 +322,7 @@
}
private boolean maybeUpdateDataLimit(String iface) {
- // setDataLimit may only be called while offload is occuring on this upstream.
+ // setDataLimit may only be called while offload is occurring on this upstream.
if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
return true;
}
@@ -368,15 +371,15 @@
// upstream parameters fails (probably just wait for a subsequent
// onOffloadEvent() callback to tell us offload is available again and
// then reapply all state).
- computeAndPushLocalPrefixes();
+ computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
pushUpstreamParameters(prevUpstream);
}
public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
- if (!started()) return;
-
mExemptPrefixes = localPrefixes;
- computeAndPushLocalPrefixes();
+
+ if (!started()) return;
+ computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
}
public void notifyDownstreamLinkProperties(LinkProperties lp) {
@@ -385,27 +388,38 @@
if (Objects.equals(oldLp, lp)) return;
if (!started()) return;
+ pushDownstreamState(oldLp, lp);
+ }
- final List<RouteInfo> oldRoutes = (oldLp != null) ? oldLp.getRoutes() : new ArrayList<>();
- final List<RouteInfo> newRoutes = lp.getRoutes();
+ private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) {
+ final String ifname = newLp.getInterfaceName();
+ final List<RouteInfo> oldRoutes =
+ (oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST;
+ final List<RouteInfo> newRoutes = newLp.getRoutes();
// For each old route, if not in new routes: remove.
- for (RouteInfo oldRoute : oldRoutes) {
- if (shouldIgnoreDownstreamRoute(oldRoute)) continue;
- if (!newRoutes.contains(oldRoute)) {
- mHwInterface.removeDownstreamPrefix(ifname, oldRoute.getDestination().toString());
+ for (RouteInfo ri : oldRoutes) {
+ if (shouldIgnoreDownstreamRoute(ri)) continue;
+ if (!newRoutes.contains(ri)) {
+ mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
}
}
// For each new route, if not in old routes: add.
- for (RouteInfo newRoute : newRoutes) {
- if (shouldIgnoreDownstreamRoute(newRoute)) continue;
- if (!oldRoutes.contains(newRoute)) {
- mHwInterface.addDownstreamPrefix(ifname, newRoute.getDestination().toString());
+ for (RouteInfo ri : newRoutes) {
+ if (shouldIgnoreDownstreamRoute(ri)) continue;
+ if (!oldRoutes.contains(ri)) {
+ mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
}
}
}
+ private void pushAllDownstreamState() {
+ for (LinkProperties lp : mDownstreams.values()) {
+ pushDownstreamState(null, lp);
+ }
+ }
+
public void removeDownstreamInterface(String ifname) {
final LinkProperties lp = mDownstreams.remove(ifname);
if (lp == null) return;
@@ -484,10 +498,11 @@
return success;
}
- private boolean computeAndPushLocalPrefixes() {
+ private boolean computeAndPushLocalPrefixes(UpdateType how) {
+ final boolean force = (how == UpdateType.FORCE);
final Set<String> localPrefixStrs = computeLocalPrefixStrings(
mExemptPrefixes, mUpstreamLinkProperties);
- if (mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
+ if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
mLastLocalPrefixStrs = localPrefixStrs;
return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 54ca6b9..a43f8af 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -152,7 +152,7 @@
Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
+ PackageManagerServiceUtils.packagesToString(others));
for (PackageParser.Package pkg : others) {
- deleteOatArtifactsOfPackage(pkg);
+ mPackageManagerService.deleteOatArtifactsOfPackage(pkg.packageName);
}
}
long spaceAvailableNow = getAvailableSpace();
@@ -242,30 +242,6 @@
return usableSpace - lowThreshold;
}
- private static String getOatDir(PackageParser.Package pkg) {
- if (!pkg.canHaveOatDir()) {
- return null;
- }
- File codePath = new File(pkg.codePath);
- if (codePath.isDirectory()) {
- return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath();
- }
- return null;
- }
-
- private void deleteOatArtifactsOfPackage(PackageParser.Package pkg) {
- String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
- for (String codePath : pkg.getAllCodePaths()) {
- for (String isa : instructionSets) {
- try {
- mPackageManagerService.mInstaller.deleteOdex(codePath, isa, getOatDir(pkg));
- } catch (InstallerException e) {
- Log.e(TAG, "Failed deleting oat files for " + codePath, e);
- }
- }
- }
- }
-
/**
* Generate all dexopt commands for the given package.
*/
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5021f2f..7e9596a 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -25,6 +25,7 @@
import android.os.FileUtils;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Log;
@@ -101,7 +102,17 @@
}
static boolean canOptimizePackage(PackageParser.Package pkg) {
- return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+ // We do not dexopt a package with no code.
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+ return false;
+ }
+
+ // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+ if (pkg.isPrivilegedApp()) {
+ return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+ }
+
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9f518c3..2b3bfc8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10137,6 +10137,12 @@
assertPackageIsValid(pkg, policyFlags, scanFlags);
+ if (Build.IS_DEBUGGABLE &&
+ pkg.isPrivilegedApp() &&
+ !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
+ }
+
// Initialize package source and resource directories
final File scanFile = new File(pkg.codePath);
final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -24872,6 +24878,50 @@
return mInstantAppRegistry.getInstantAppAndroidIdLPw(packageName, userId);
}
}
+
+ boolean canHaveOatDir(String packageName) {
+ synchronized (mPackages) {
+ PackageParser.Package p = mPackages.get(packageName);
+ if (p == null) {
+ return false;
+ }
+ return p.canHaveOatDir();
+ }
+ }
+
+ private String getOatDir(PackageParser.Package pkg) {
+ if (!pkg.canHaveOatDir()) {
+ return null;
+ }
+ File codePath = new File(pkg.codePath);
+ if (codePath.isDirectory()) {
+ return PackageDexOptimizer.getOatDir(codePath).getAbsolutePath();
+ }
+ return null;
+ }
+
+ void deleteOatArtifactsOfPackage(String packageName) {
+ final String[] instructionSets;
+ final List<String> codePaths;
+ final String oatDir;
+ final PackageParser.Package pkg;
+ synchronized (mPackages) {
+ pkg = mPackages.get(packageName);
+ }
+ instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
+ codePaths = pkg.getAllCodePaths();
+ oatDir = getOatDir(pkg);
+
+ for (String codePath : codePaths) {
+ for (String isa : instructionSets) {
+ try {
+ mInstaller.deleteOdex(codePath, isa, oatDir);
+ } catch (InstallerException e) {
+ Log.e(TAG, "Failed deleting oat files for " + codePath, e);
+ }
+ }
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 9feee8c..ec9aa63 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -19,6 +19,8 @@
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.TAG;
+import com.android.internal.util.ArrayUtils;
+
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.Intent;
@@ -30,6 +32,8 @@
import android.system.ErrnoException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
+import android.util.jar.StrictJarFile;
import dalvik.system.VMRuntime;
import libcore.io.Libcore;
@@ -38,9 +42,11 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
/**
* Class containing helper methods for the PackageManagerService.
@@ -213,4 +219,59 @@
}
return false;
}
+
+ /**
+ * Checks that the archive located at {@code fileName} has uncompressed dex file and so
+ * files that can be direclty mapped.
+ */
+ public static void logApkHasUncompressedCode(String fileName) {
+ StrictJarFile jarFile = null;
+ try {
+ jarFile = new StrictJarFile(fileName,
+ false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+ Iterator<ZipEntry> it = jarFile.iterator();
+ while (it.hasNext()) {
+ ZipEntry entry = it.next();
+ if (entry.getName().endsWith(".dex")) {
+ if (entry.getMethod() != ZipEntry.STORED) {
+ Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
+ entry.getName());
+ } else if ((entry.getDataOffset() & 0x3) != 0) {
+ Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
+ entry.getName());
+ }
+ } else if (entry.getName().endsWith(".so")) {
+ if (entry.getMethod() != ZipEntry.STORED) {
+ Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
+ entry.getName());
+ } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
+ Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
+ entry.getName());
+ }
+ }
+ }
+ } catch (IOException ignore) {
+ Slog.wtf(TAG, "Error when parsing APK " + fileName);
+ } finally {
+ try {
+ if (jarFile != null) {
+ jarFile.close();
+ }
+ } catch (IOException ignore) {}
+ }
+ return;
+ }
+
+ /**
+ * Checks that the APKs in the given package have uncompressed dex file and so
+ * files that can be direclty mapped.
+ */
+ public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
+ logApkHasUncompressedCode(pkg.baseCodePath);
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ logApkHasUncompressedCode(pkg.splitCodePaths[i]);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/timezone/IntentHelper.java b/services/core/java/com/android/server/timezone/IntentHelper.java
index 0cb9065..5de5432 100644
--- a/services/core/java/com/android/server/timezone/IntentHelper.java
+++ b/services/core/java/com/android/server/timezone/IntentHelper.java
@@ -23,15 +23,22 @@
*/
interface IntentHelper {
- void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+ void initialize(String updateAppPackageName, String dataAppPackageName,
+ PackageTracker packageTracker);
void sendTriggerUpdateCheck(CheckToken checkToken);
- void enableReliabilityTriggering();
+ /**
+ * Schedule a "reliability trigger" after at least minimumDelayMillis, replacing any existing
+ * scheduled one. A reliability trigger ensures that the {@link PackageTracker} can pick up
+ * reliably if a previous update check did not complete for some reason. It can happen when
+ * the device is idle. The trigger is expected to call
+ * {@link PackageTracker#triggerUpdateIfNeeded(boolean)} with a {@code false} value.
+ */
+ void scheduleReliabilityTrigger(long minimumDelayMillis);
- void disableReliabilityTriggering();
-
- interface Listener {
- void triggerUpdateIfNeeded(boolean packageUpdated);
- }
+ /**
+ * Make sure there is no reliability trigger scheduled. No-op if there wasn't one.
+ */
+ void unscheduleReliabilityTrigger();
}
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
index 11928b9..bc0f6e4 100644
--- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -36,16 +36,13 @@
private final Context mContext;
private String mUpdaterAppPackageName;
- private boolean mReliabilityReceiverEnabled;
- private Receiver mReliabilityReceiver;
-
IntentHelperImpl(Context context) {
mContext = context;
}
@Override
- public void initialize(
- String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+ public void initialize(String updaterAppPackageName, String dataAppPackageName,
+ PackageTracker packageTracker) {
mUpdaterAppPackageName = updaterAppPackageName;
// Register for events of interest.
@@ -53,21 +50,33 @@
// The intent filter that triggers when package update events happen that indicate there may
// be work to do.
IntentFilter packageIntentFilter = new IntentFilter();
- // Either of these mean a downgrade?
- packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+
packageIntentFilter.addDataScheme("package");
packageIntentFilter.addDataSchemeSpecificPart(
updaterAppPackageName, PatternMatcher.PATTERN_LITERAL);
packageIntentFilter.addDataSchemeSpecificPart(
dataAppPackageName, PatternMatcher.PATTERN_LITERAL);
- Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
- mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
- // TODO(nfuller): Add more exotic intents as needed. e.g.
- // packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- // Also, disabled...?
- mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+ // ACTION_PACKAGE_ADDED is fired when a package is upgraded or downgraded (in addition to
+ // ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED). A system/priv-app can never be
+ // removed entirely so we do not need to trigger on ACTION_PACKAGE_REMOVED or
+ // ACTION_PACKAGE_FULLY_REMOVED.
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+
+ // ACTION_PACKAGE_CHANGED is used when a package is disabled / re-enabled. It is not
+ // strictly necessary to trigger on this but it won't hurt anything and may catch some cases
+ // where a package has changed while disabled.
+ // Note: ACTION_PACKAGE_CHANGED is not fired when updating a suspended app, but
+ // ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and ACTION_PACKAGE_REPLACED are (and the app
+ // is left in an unsuspended state after this).
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+
+ // We do not register for ACTION_PACKAGE_RESTARTED because it doesn't imply an update.
+ // We do not register for ACTION_PACKAGE_DATA_CLEARED because the updater / data apps are
+ // not expected to need local data.
+
+ Receiver packageUpdateReceiver = new Receiver(packageTracker);
+ mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
}
/** Sends an intent to trigger an update check. */
@@ -79,39 +88,26 @@
}
@Override
- public synchronized void enableReliabilityTriggering() {
- if (!mReliabilityReceiverEnabled) {
- // The intent filter that exists to make updates reliable in the event of failures /
- // reboots.
- IntentFilter reliabilityIntentFilter = new IntentFilter();
- reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
- mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
- mReliabilityReceiverEnabled = true;
- }
+ public synchronized void scheduleReliabilityTrigger(long minimumDelayMillis) {
+ TimeZoneUpdateIdler.schedule(mContext, minimumDelayMillis);
}
@Override
- public synchronized void disableReliabilityTriggering() {
- if (mReliabilityReceiverEnabled) {
- mContext.unregisterReceiver(mReliabilityReceiver);
- mReliabilityReceiverEnabled = false;
- }
+ public synchronized void unscheduleReliabilityTrigger() {
+ TimeZoneUpdateIdler.unschedule(mContext);
}
private static class Receiver extends BroadcastReceiver {
- private final Listener mListener;
- private final boolean mPackageUpdated;
+ private final PackageTracker mPackageTracker;
- private Receiver(Listener listener, boolean packageUpdated) {
- mListener = listener;
- mPackageUpdated = packageUpdated;
+ private Receiver(PackageTracker packageTracker) {
+ mPackageTracker = packageTracker;
}
@Override
public void onReceive(Context context, Intent intent) {
Slog.d(TAG, "Received intent: " + intent.toString());
- mListener.triggerUpdateIfNeeded(mPackageUpdated);
+ mPackageTracker.triggerUpdateIfNeeded(true /* packageChanged */);
}
}
-
}
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
index 24e0fe4..f0306b9 100644
--- a/services/core/java/com/android/server/timezone/PackageTracker.java
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -51,7 +51,7 @@
*/
// Also made non-final so it can be mocked.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class PackageTracker implements IntentHelper.Listener {
+public class PackageTracker {
private static final String TAG = "timezone.PackageTracker";
private final PackageManagerHelper mPackageManagerHelper;
@@ -72,6 +72,13 @@
// The number of failed checks in a row before reliability checks should stop happening.
private long mFailedCheckRetryCount;
+ /*
+ * The minimum delay between a successive reliability triggers / other operations. Should to be
+ * larger than mCheckTimeAllowedMillis to avoid reliability triggers happening during package
+ * update checks.
+ */
+ private int mDelayBeforeReliabilityCheckMillis;
+
// Reliability check state: If a check was triggered but not acknowledged within
// mCheckTimeAllowedMillis then another one can be triggered.
private Long mLastTriggerTimestamp = null;
@@ -122,6 +129,7 @@
mDataAppPackageName = mConfigHelper.getDataAppPackageName();
mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+ mDelayBeforeReliabilityCheckMillis = mCheckTimeAllowedMillis + (60 * 1000);
// Validate the device configuration including the application packages.
// The manifest entries in the apps themselves are not validated until use as they can
@@ -135,9 +143,10 @@
// Initialize the intent helper.
mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
- // Enable the reliability triggering so we will have at least one reliability trigger if
- // a package isn't updated.
- mIntentHelper.enableReliabilityTriggering();
+ // Schedule a reliability trigger so we will have at least one after boot. This will allow
+ // us to catch if a package updated wasn't handled to completion. There's no hurry: it's ok
+ // to delay for a while before doing this even if idle.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
Slog.i(TAG, "Time zone updater / data package tracking enabled");
}
@@ -195,7 +204,6 @@
* @param packageChanged true if this method was called because a known packaged definitely
* changed, false if the cause is a reliability trigger
*/
- @Override
public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
if (!mTrackingEnabled) {
throw new IllegalStateException("Unexpected call. Tracking is disabled.");
@@ -212,8 +220,8 @@
+ " updaterApp=" + updaterAppManifestValid
+ ", dataApp=" + dataAppManifestValid);
- // There's no point in doing reliability checks if the current packages are bad.
- mIntentHelper.disableReliabilityTriggering();
+ // There's no point in doing any reliability triggers if the current packages are bad.
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
@@ -238,7 +246,8 @@
Slog.d(TAG,
"triggerUpdateIfNeeded: checkComplete call is not yet overdue."
+ " Not triggering.");
- // Not doing any work, but also not disabling future reliability triggers.
+ // Don't do any work now but we do schedule a future reliability trigger.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
return;
}
} else if (mCheckFailureCount > mFailedCheckRetryCount) {
@@ -247,13 +256,13 @@
Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
+ " exceeded. Stopping reliability triggers until next reboot or package"
+ " update.");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
} else if (mCheckFailureCount == 0) {
// Case 4.
Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
+ " successful.");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
}
@@ -263,7 +272,7 @@
if (currentInstalledVersions == null) {
// This should not happen if the device is configured in a valid way.
Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
@@ -288,7 +297,7 @@
// The last check succeeded and nothing has changed. Do nothing and disable
// reliability checks.
Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
return;
}
}
@@ -299,6 +308,8 @@
if (checkToken == null) {
Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
+ " Not sending check request.");
+ // Trigger again later: perhaps we'll have better luck.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
return;
}
@@ -309,9 +320,9 @@
// Update the reliability check state in case the update fails.
setCheckInProgress();
- // Enable reliability triggering in case the check doesn't succeed and there is no
- // response at all. Enabling reliability triggering is idempotent.
- mIntentHelper.enableReliabilityTriggering();
+ // Schedule a reliability trigger in case the update check doesn't succeed and there is no
+ // response at all. It will be cancelled if the check is successful in recordCheckResult.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
}
/**
@@ -370,9 +381,9 @@
+ " storage state.");
mPackageStatusStorage.resetCheckState();
- // Enable reliability triggering and reset the failure count so we know that the
+ // Schedule a reliability trigger and reset the failure count so we know that the
// next reliability trigger will do something.
- mIntentHelper.enableReliabilityTriggering();
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount = 0;
} else {
// This is the expected case when tracking is enabled: a check was triggered and it has
@@ -385,13 +396,13 @@
setCheckComplete();
if (success) {
- // Since the check was successful, no more reliability checks are required until
+ // Since the check was successful, no reliability trigger is required until
// there is a package change.
- mIntentHelper.disableReliabilityTriggering();
+ mIntentHelper.unscheduleReliabilityTrigger();
mCheckFailureCount = 0;
} else {
- // Enable reliability triggering to potentially check again in future.
- mIntentHelper.enableReliabilityTriggering();
+ // Enable schedule a reliability trigger to check again in future.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount++;
}
} else {
@@ -400,8 +411,8 @@
Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
+ " with success=" + success + ". Optimistic lock failure");
- // Enable reliability triggering to potentially try again in future.
- mIntentHelper.enableReliabilityTriggering();
+ // Schedule a reliability trigger to potentially try again in future.
+ mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
mCheckFailureCount++;
}
}
@@ -515,6 +526,7 @@
", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
", mDataAppPackageName='" + mDataAppPackageName + '\'' +
", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
+ ", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis +
", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
", mCheckTriggered=" + mCheckTriggered +
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 50f27ed..6824a59 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -69,18 +69,22 @@
DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
public static class Lifecycle extends SystemService {
- private RulesManagerService mService;
-
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
- mService = RulesManagerService.create(getContext());
- mService.start();
+ RulesManagerService service = RulesManagerService.create(getContext());
+ service.start();
- publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+ // Publish the binder service so it can be accessed from other (appropriately
+ // permissioned) processes.
+ publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service);
+
+ // Publish the service instance locally so we can use it directly from within the system
+ // server from TimeZoneUpdateIdler.
+ publishLocalService(RulesManagerService.class, service);
}
}
@@ -343,16 +347,20 @@
@Override
public void run() {
EventLogTags.writeTimezoneUninstallStarted(toStringOrNull(mCheckToken));
- boolean success = false;
+ boolean packageTrackerStatus = false;
try {
- success = mInstaller.stageUninstall();
- // Right now we just have success (0) / failure (1). All clients should be checking
- // against SUCCESS. More granular failures may be added in future.
- int resultCode = success ? Callback.SUCCESS
- : Callback.ERROR_UNKNOWN_FAILURE;
+ int uninstallResult = mInstaller.stageUninstall();
+ packageTrackerStatus = (uninstallResult == TimeZoneDistroInstaller.UNINSTALL_SUCCESS
+ || uninstallResult == TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED);
+
+ // Right now we just have Callback.SUCCESS / Callback.ERROR_UNKNOWN_FAILURE for
+ // uninstall. All clients should be checking against SUCCESS. More granular failures
+ // may be added in future.
+ int callbackResultCode =
+ packageTrackerStatus ? Callback.SUCCESS : Callback.ERROR_UNKNOWN_FAILURE;
EventLogTags.writeTimezoneUninstallComplete(
- toStringOrNull(mCheckToken), resultCode);
- sendFinishedStatus(mCallback, resultCode);
+ toStringOrNull(mCheckToken), callbackResultCode);
+ sendFinishedStatus(mCallback, callbackResultCode);
} catch (Exception e) {
EventLogTags.writeTimezoneUninstallComplete(
toStringOrNull(mCheckToken), Callback.ERROR_UNKNOWN_FAILURE);
@@ -360,7 +368,7 @@
sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
} finally {
// Notify the package tracker that the operation is now complete.
- mPackageTracker.recordCheckResult(mCheckToken, success);
+ mPackageTracker.recordCheckResult(mCheckToken, packageTrackerStatus);
mOperationInProgress.set(false);
}
@@ -492,6 +500,16 @@
mPackageTracker.dump(pw);
}
+ /**
+ * Called when the device is considered idle.
+ */
+ void notifyIdle() {
+ // No package has changed: we are just triggering because the device is idle and there
+ // *might* be work to do.
+ final boolean packageChanged = false;
+ mPackageTracker.triggerUpdateIfNeeded(packageChanged);
+ }
+
@Override
public String toString() {
return "RulesManagerService{" +
diff --git a/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
new file mode 100644
index 0000000..a7767a4
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -0,0 +1,100 @@
+/*
+ * 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.timezone;
+
+import com.android.server.LocalServices;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * A JobService used to trigger time zone rules update work when a device falls idle.
+ */
+public final class TimeZoneUpdateIdler extends JobService {
+
+ private static final String TAG = "timezone.TimeZoneUpdateIdler";
+
+ /** The static job ID used to handle on-idle work. */
+ // Must be unique within UID (system service)
+ private static final int TIME_ZONE_UPDATE_IDLE_JOB_ID = 27042305;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ RulesManagerService rulesManagerService =
+ LocalServices.getService(RulesManagerService.class);
+
+ Slog.d(TAG, "onStartJob() called");
+
+ // Note: notifyIdle() explicitly handles canceling / re-scheduling so no need to reschedule
+ // here.
+ rulesManagerService.notifyIdle();
+
+ // Everything is handled synchronously. We are done.
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // Reschedule if stopped unless it was cancelled due to unschedule().
+ boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+ Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
+ return reschedule;
+ }
+
+ /**
+ * Schedules the TimeZoneUpdateIdler job service to run once.
+ *
+ * @param context Context to use to get a job scheduler.
+ */
+ public static void schedule(Context context, long minimumDelayMillis) {
+ // Request that the JobScheduler tell us when the device falls idle.
+ JobScheduler jobScheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+ // The TimeZoneUpdateIdler will send an intent that will trigger the Receiver.
+ ComponentName idlerJobServiceName =
+ new ComponentName(context, TimeZoneUpdateIdler.class);
+
+ // We require the device is idle, but also that it is charging to be as non-invasive as
+ // we can.
+ JobInfo.Builder jobInfoBuilder =
+ new JobInfo.Builder(TIME_ZONE_UPDATE_IDLE_JOB_ID, idlerJobServiceName)
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setMinimumLatency(minimumDelayMillis);
+
+ Slog.d(TAG, "schedule() called: minimumDelayMillis=" + minimumDelayMillis);
+ jobScheduler.schedule(jobInfoBuilder.build());
+ }
+
+ /**
+ * Unschedules the TimeZoneUpdateIdler job service.
+ *
+ * @param context Context to use to get a job scheduler.
+ */
+ public static void unschedule(Context context) {
+ JobScheduler jobScheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ Slog.d(TAG, "unschedule() called");
+ jobScheduler.cancel(TIME_ZONE_UPDATE_IDLE_JOB_ID);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f92f83a..be85fc9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -660,6 +660,7 @@
VibratorService vibrator = null;
IStorageManager storageManager = null;
NetworkManagementService networkManagement = null;
+ IpSecService ipSecService = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
ConnectivityService connectivity = null;
@@ -1010,6 +1011,15 @@
reportWtf("starting NetworkManagement Service", e);
}
traceEnd();
+
+ traceBeginAndSlog("StartIpSecService");
+ try {
+ ipSecService = IpSecService.create(context);
+ ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+ } catch (Throwable e) {
+ reportWtf("starting IpSec Service", e);
+ }
+ traceEnd();
}
if (!disableNonCoreServices && !disableTextServices) {
@@ -1182,18 +1192,6 @@
traceEnd();
}
- // timezone.RulesManagerService will prevent a device starting up if the chain of trust
- // required for safe time zone updates might be broken. RuleManagerService cannot do
- // this check when mOnlyCore == true, so we don't enable the service in this case.
- final boolean startRulesManagerService =
- !mOnlyCore && context.getResources().getBoolean(
- R.bool.config_enableUpdateableTimeZoneRules);
- if (startRulesManagerService) {
- traceBeginAndSlog("StartTimeZoneRulesManagerService");
- mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- }
-
traceBeginAndSlog("StartAudioService");
mSystemServiceManager.startService(AudioService.Lifecycle.class);
traceEnd();
@@ -1326,6 +1324,19 @@
}
traceEnd();
+ // timezone.RulesManagerService will prevent a device starting up if the chain of trust
+ // required for safe time zone updates might be broken. RuleManagerService cannot do
+ // this check when mOnlyCore == true, so we don't enable the service in this case.
+ // This service requires that JobSchedulerService is already started when it starts.
+ final boolean startRulesManagerService =
+ !mOnlyCore && context.getResources().getBoolean(
+ R.bool.config_enableUpdateableTimeZoneRules);
+ if (startRulesManagerService) {
+ traceBeginAndSlog("StartTimeZoneRulesManagerService");
+ mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ }
+
if (!disableNetwork && !disableNetworkTime) {
traceBeginAndSlog("StartNetworkTimeUpdateService");
try {
@@ -1617,6 +1628,7 @@
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
final MmsServiceBroker mmsServiceF = mmsService;
+ final IpSecService ipSecServiceF = ipSecService;
final WindowManagerService windowManagerF = wm;
// We now tell the activity manager it is okay to run third party
@@ -1681,6 +1693,13 @@
.networkScoreAndNetworkManagementServiceReady();
}
traceEnd();
+ traceBeginAndSlog("MakeIpSecServiceReady");
+ try {
+ if (ipSecServiceF != null) ipSecServiceF.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making IpSec Service ready", e);
+ }
+ traceEnd();
traceBeginAndSlog("MakeNetworkStatsServiceReady");
try {
if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
index 38142d3..7d73e82 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -107,7 +107,7 @@
mFakeIntentHelper.assertNotInitialized();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
@Test
@@ -119,7 +119,7 @@
mPackageTracker.start();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
try {
// This call should also not be allowed and will throw an exception if tracking is
@@ -129,7 +129,7 @@
} catch (IllegalStateException expected) {}
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
@Test
@@ -141,14 +141,14 @@
mPackageTracker.start();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Receiving a check result when tracking is disabled should cause the storage to be
// reset.
mPackageTracker.recordCheckResult(null /* checkToken */, true /* success */);
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Assert the storage was reset.
checkPackageStorageStatusIsInitialOrReset();
@@ -166,13 +166,13 @@
mPackageTracker.start();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Receiving a check result when tracking is disabled should cause the storage to be reset.
mPackageTracker.recordCheckResult(createArbitraryCheckToken(), true /* success */);
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Assert the storage was reset.
checkPackageStorageStatusIsInitialOrReset();
@@ -195,7 +195,7 @@
mFakeIntentHelper.assertNotInitialized();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
@Test
@@ -215,7 +215,7 @@
mFakeIntentHelper.assertNotInitialized();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
@Test
@@ -235,7 +235,7 @@
mFakeIntentHelper.assertNotInitialized();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
@Test
@@ -255,7 +255,7 @@
mFakeIntentHelper.assertNotInitialized();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
@Test
@@ -289,7 +289,7 @@
mFakeIntentHelper.assertUpdateNotTriggered();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Assert the storage was not touched.
checkPackageStorageStatusIsInitialOrReset();
@@ -325,7 +325,7 @@
mFakeIntentHelper.assertUpdateNotTriggered();
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Assert the storage was not touched.
checkPackageStorageStatusIsInitialOrReset();
@@ -416,7 +416,7 @@
mPackageTracker.recordCheckResult(null /* checkToken */, success);
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Assert the storage was reset.
checkPackageStorageStatusIsInitialOrReset();
@@ -627,7 +627,7 @@
mPackageTracker.recordCheckResult(token1, true /* success */);
// Reliability triggering should still be enabled.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Check the expected storage state.
checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
@@ -743,7 +743,7 @@
// Under the covers we expect it to fail to update because the storage should recognize that
// the token is no longer valid.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Peek inside the package tracker to make sure it is tracking failure counts properly.
assertEquals(1, mPackageTracker.getCheckFailureCountForTests());
@@ -766,7 +766,7 @@
checkPackageStorageStatusIsInitialOrReset();
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did trigger an update.
checkUpdateCheckTriggered(packageVersions);
@@ -803,7 +803,7 @@
checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did not attempt to trigger an update.
mFakeIntentHelper.assertUpdateNotTriggered();
@@ -843,7 +843,7 @@
checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did trigger an update.
checkUpdateCheckTriggered(currentPackageVersions);
@@ -890,7 +890,7 @@
for (int i = 0; i < retriesAllowed + 1; i++) {
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did trigger an update.
checkUpdateCheckTriggered(currentPackageVersions);
@@ -912,9 +912,9 @@
// Check reliability triggering is in the correct state.
if (i <= retriesAllowed) {
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
} else {
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
}
}
}
@@ -950,7 +950,7 @@
// Fail (retries - 1) times.
for (int i = 0; i < retriesAllowed - 1; i++) {
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did trigger an update.
checkUpdateCheckTriggered(currentPackageVersions);
@@ -971,11 +971,11 @@
currentPackageVersions);
// Check reliability triggering is still enabled.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
}
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did trigger an update.
checkUpdateCheckTriggered(currentPackageVersions);
@@ -1023,7 +1023,7 @@
checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did trigger an update.
checkUpdateCheckTriggered(currentPackageVersions);
@@ -1033,18 +1033,18 @@
mFakeClock.incrementClock(checkDelayMillis - 1);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did not trigger an update.
mFakeIntentHelper.assertUpdateNotTriggered();
checkPackageStorageStatus(PackageStatus.CHECK_STARTED, currentPackageVersions);
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Increment the clock slightly more. Should now consider the response overdue.
mFakeClock.incrementClock(2);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Triggering should have happened.
checkUpdateCheckTriggered(currentPackageVersions);
@@ -1096,7 +1096,7 @@
mFakeClock.incrementClock(checkDelayMillis + 1);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker triggered an update.
checkUpdateCheckTriggered(newPackageVersions);
@@ -1154,18 +1154,18 @@
mFakeClock.incrementClock(checkDelayMillis - 1);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Assert the PackageTracker did not trigger an update.
mFakeIntentHelper.assertUpdateNotTriggered();
checkPackageStorageStatus(PackageStatus.CHECK_STARTED, newPackageVersions);
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Increment the clock slightly more. Should now consider the response overdue.
mFakeClock.incrementClock(2);
// Simulate a reliability trigger.
- mFakeIntentHelper.simulateReliabilityTrigger();
+ mPackageTracker.triggerUpdateIfNeeded(false /* packageChanged */);
// Triggering should have happened.
checkUpdateCheckTriggered(newPackageVersions);
@@ -1202,7 +1202,7 @@
// If an update check was triggered reliability triggering should always be enabled to
// ensure that it can be completed if it fails.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Check the expected storage state.
checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions);
@@ -1210,7 +1210,7 @@
private void checkUpdateCheckFailed(PackageVersions packageVersions) {
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
// Assert the storage was updated.
checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
@@ -1218,7 +1218,7 @@
private void checkUpdateCheckSuccessful(PackageVersions packageVersions) {
// Check reliability triggering state.
- mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ mFakeIntentHelper.assertReliabilityTriggerNotScheduled();
// Assert the storage was updated.
checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
@@ -1345,7 +1345,7 @@
mFakeIntentHelper.assertInitialized(UPDATE_APP_PACKAGE_NAME, DATA_APP_PACKAGE_NAME);
// Assert that reliability tracking is always enabled after initialization.
- mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ mFakeIntentHelper.assertReliabilityTriggerScheduled();
}
private void checkPackageStorageStatus(
@@ -1368,34 +1368,34 @@
*/
private static class FakeIntentHelper implements IntentHelper {
- private Listener mListener;
+ private PackageTracker mPackageTracker;
private String mUpdateAppPackageName;
private String mDataAppPackageName;
private CheckToken mLastToken;
- private boolean mReliabilityTriggeringEnabled;
+ private boolean mReliabilityTriggerScheduled;
@Override
public void initialize(String updateAppPackageName, String dataAppPackageName,
- Listener listener) {
+ PackageTracker packageTracker) {
assertNotNull(updateAppPackageName);
assertNotNull(dataAppPackageName);
- assertNotNull(listener);
- mListener = listener;
+ assertNotNull(packageTracker);
+ mPackageTracker = packageTracker;
mUpdateAppPackageName = updateAppPackageName;
mDataAppPackageName = dataAppPackageName;
}
public void assertInitialized(
String expectedUpdateAppPackageName, String expectedDataAppPackageName) {
- assertNotNull(mListener);
+ assertNotNull(mPackageTracker);
assertEquals(expectedUpdateAppPackageName, mUpdateAppPackageName);
assertEquals(expectedDataAppPackageName, mDataAppPackageName);
}
public void assertNotInitialized() {
- assertNull(mListener);
+ assertNull(mPackageTracker);
}
@Override
@@ -1407,21 +1407,21 @@
}
@Override
- public void enableReliabilityTriggering() {
- mReliabilityTriggeringEnabled = true;
+ public void scheduleReliabilityTrigger(long minimumDelayMillis) {
+ mReliabilityTriggerScheduled = true;
}
@Override
- public void disableReliabilityTriggering() {
- mReliabilityTriggeringEnabled = false;
+ public void unscheduleReliabilityTrigger() {
+ mReliabilityTriggerScheduled = false;
}
- public void assertReliabilityTriggeringEnabled() {
- assertTrue(mReliabilityTriggeringEnabled);
+ public void assertReliabilityTriggerScheduled() {
+ assertTrue(mReliabilityTriggerScheduled);
}
- public void assertReliabilityTriggeringDisabled() {
- assertFalse(mReliabilityTriggeringEnabled);
+ public void assertReliabilityTriggerNotScheduled() {
+ assertFalse(mReliabilityTriggerScheduled);
}
public void assertUpdateTriggered() {
@@ -1440,11 +1440,7 @@
}
public void simulatePackageUpdatedEvent() {
- mListener.triggerUpdateIfNeeded(true);
- }
-
- public void simulateReliabilityTrigger() {
- mListener.triggerUpdateIfNeeded(false);
+ mPackageTracker.triggerUpdateIfNeeded(true /* packageChanged */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
index 2887e3b..d09d0c8 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -585,7 +585,39 @@
verifyNoPackageTrackerCallsMade();
// Set up the installer.
- configureStageUninstallExpectation(true /* success */);
+ configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageUninstallCalled();
+ verifyPackageTrackerCalled(token, true /* success */);
+
+ // Check the callback was called.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestUninstall_asyncNothingInstalled() throws Exception {
+ configureCallerHasPermission();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the uninstall.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ callback.assertNoResultReceived();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+
+ // Set up the installer.
+ configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_NOTHING_INSTALLED);
// Simulate the async execution.
mFakeExecutor.simulateAsyncExecutionOfLastCommand();
@@ -613,7 +645,7 @@
callback.assertNoResultReceived();
// Set up the installer.
- configureStageUninstallExpectation(true /* success */);
+ configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS);
// Simulate the async execution.
mFakeExecutor.simulateAsyncExecutionOfLastCommand();
@@ -644,7 +676,7 @@
callback.assertNoResultReceived();
// Set up the installer.
- configureStageUninstallExpectation(false /* success */);
+ configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_FAIL);
// Simulate the async execution.
mFakeExecutor.simulateAsyncExecutionOfLastCommand();
@@ -849,8 +881,8 @@
.thenReturn(resultCode);
}
- private void configureStageUninstallExpectation(boolean success) throws Exception {
- doReturn(success).when(mMockTimeZoneDistroInstaller).stageUninstall();
+ private void configureStageUninstallExpectation(int resultCode) throws Exception {
+ doReturn(resultCode).when(mMockTimeZoneDistroInstaller).stageUninstall();
}
private void verifyStageInstallCalled() throws Exception {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3c841a4..386a3a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -332,14 +332,29 @@
public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
/**
- * Flag indicating whether we should downgrade/terminate VT calls and disable VT when
- * data enabled changed (e.g. reach data limit or turn off data).
+ * When {@code true}, changes to the mobile data enabled switch will not cause the VT
+ * registration state to change. That is, turning on or off mobile data will not cause VT to be
+ * enabled or disabled.
+ * When {@code false}, disabling mobile data will cause VT to be de-registered.
+ * <p>
+ * See also {@link #KEY_VILTE_DATA_IS_METERED_BOOL}.
* @hide
*/
public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS =
"ignore_data_enabled_changed_for_video_calls";
/**
+ * Flag indicating whether data used for a video call over LTE is metered or not.
+ * <p>
+ * When {@code true}, if the device hits the data limit or data is disabled during a ViLTE call,
+ * the call will be downgraded to audio-only (or paused if
+ * {@link #KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL} is {@code true}).
+ *
+ * @hide
+ */
+ public static final String KEY_VILTE_DATA_IS_METERED_BOOL = "vilte_data_is_metered_bool";
+
+ /**
* Flag specifying whether WFC over IMS should be available for carrier: independent of
* carrier provisioning. If false: hard disabled. If true: then depends on carrier
* provisioning, availability etc.
@@ -1525,7 +1540,8 @@
sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
- sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, false);
+ sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
+ sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
deleted file mode 100644
index 302bc6a..0000000
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.telephony.mbms.DownloadStateCallback;
-import android.telephony.mbms.FileInfo;
-import android.telephony.mbms.DownloadRequest;
-import android.telephony.mbms.InternalDownloadManagerCallback;
-import android.telephony.mbms.InternalDownloadStateCallback;
-import android.telephony.mbms.MbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsDownloadReceiver;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsTempFileProvider;
-import android.telephony.mbms.MbmsUtils;
-import android.telephony.mbms.vendor.IMbmsDownloadService;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-/**
- * This class provides functionality for file download over MBMS.
- */
-public class MbmsDownloadManager {
- private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
-
- /**
- * Service action which must be handled by the middleware implementing the MBMS file download
- * interface.
- * @hide
- */
- @SystemApi
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
- "android.telephony.action.EmbmsDownload";
-
- /**
- * Integer extra that Android will attach to the intent supplied via
- * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
- * Indicates the result code of the download. One of
- * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
- * {@link #RESULT_IO_ERROR}.
- *
- * This extra may also be used by the middleware when it is sending intents to the app.
- */
- public static final String EXTRA_MBMS_DOWNLOAD_RESULT =
- "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
-
- /**
- * {@link FileInfo} extra that Android will attach to the intent supplied via
- * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
- * Indicates the file for which the download result is for. Never null.
- *
- * This extra may also be used by the middleware when it is sending intents to the app.
- */
- public static final String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
-
- /**
- * {@link Uri} extra that Android will attach to the intent supplied via
- * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
- * Indicates the location of the successfully
- * downloaded file. Will always be set to a non-null value if
- * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
- */
- public static final String EXTRA_MBMS_COMPLETED_FILE_URI =
- "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
-
- /**
- * The default directory name for all MBMS temp files. If you call
- * {@link #download(DownloadRequest, DownloadStateCallback, Handler)} without first calling
- * {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the
- * path returned by {@link Context#getFilesDir()}.
- */
- public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
-
- /**
- * Indicates that the download was successful.
- */
- public static final int RESULT_SUCCESSFUL = 1;
-
- /**
- * Indicates that the download was cancelled via {@link #cancelDownload(DownloadRequest)}.
- */
- public static final int RESULT_CANCELLED = 2;
-
- /**
- * Indicates that the download will not be completed due to the expiration of its download
- * window on the carrier's network.
- */
- public static final int RESULT_EXPIRED = 3;
-
- /**
- * Indicates that the download will not be completed due to an I/O error incurred while
- * writing to temp files. This commonly indicates that the device is out of storage space,
- * but may indicate other conditions as well (such as an SD card being removed).
- */
- public static final int RESULT_IO_ERROR = 4;
- // TODO - more results!
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
- STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
- public @interface DownloadStatus {}
-
- /**
- * Indicates that the middleware has no information on the file.
- */
- public static final int STATUS_UNKNOWN = 0;
-
- /**
- * Indicates that the file is actively downloading.
- */
- public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
-
- /**
- * TODO: I don't know...
- */
- public static final int STATUS_PENDING_DOWNLOAD = 2;
-
- /**
- * Indicates that the file is being repaired after the download being interrupted.
- */
- public static final int STATUS_PENDING_REPAIR = 3;
-
- /**
- * Indicates that the file is waiting to download because its download window has not yet
- * started.
- */
- public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
-
- private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
-
- private final Context mContext;
- private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
- }
- };
-
- private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
- private final InternalDownloadManagerCallback mInternalCallback;
-
- private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback,
- int subscriptionId, Handler handler) {
- mContext = context;
- mSubscriptionId = subscriptionId;
- if (handler == null) {
- handler = new Handler(Looper.myLooper());
- }
- mInternalCallback = new InternalDownloadManagerCallback(callback, handler);
- }
-
- /**
- * Create a new MbmsDownloadManager using the system default data subscription ID and default
- * {@link Handler}
- * See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
- */
- public static MbmsDownloadManager create(Context context,
- MbmsDownloadManagerCallback callback)
- throws MbmsException {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
- }
-
- /**
- * Create a new MbmsDownloadManager using the system default data subscription ID.
- * See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
- */
- public static MbmsDownloadManager create(Context context,
- MbmsDownloadManagerCallback callback, Handler handler)
- throws MbmsException {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
- }
-
- /**
- * Create a new MbmsDownloadManager using the default {@link Handler}
- * See {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
- */
- public static MbmsDownloadManager create(Context context,
- MbmsDownloadManagerCallback callback, int subscriptionId)
- throws MbmsException {
- return create(context, callback, subscriptionId, null);
- }
-
- /**
- * Create a new MbmsDownloadManager using the given subscription ID.
- *
- * Note that this call will bind a remote service and that may take a bit. The instance of
- * {@link MbmsDownloadManager} that is returned will not be ready for use until
- * {@link MbmsDownloadManagerCallback#onMiddlewareReady()} is called on the provided callback.
- * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown.
- *
- * This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
- *
- * You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this
- * method while there is an active instance of {@link MbmsDownloadManager} in your process
- * (in other words, one that has not had {@link #dispose()} called on it), this method will
- * throw an {@link MbmsException}. If you call this method in a different process
- * running under the same UID, an error will be indicated via
- * {@link MbmsDownloadManagerCallback#onError(int, String)}.
- *
- * Note that initialization may fail asynchronously. If you wish to try again after you
- * receive such an asynchronous error, you must call dispose() on the instance of
- * {@link MbmsDownloadManager} that you received before calling this method again.
- *
- * @param context The instance of {@link Context} to use
- * @param listener A callback to get asynchronous error messages and file service updates.
- * @param subscriptionId The data subscription ID to use
- */
- public static MbmsDownloadManager create(Context context,
- MbmsDownloadManagerCallback listener, int subscriptionId, Handler handler)
- throws MbmsException {
- if (!sIsInitialized.compareAndSet(false, true)) {
- throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
- }
- MbmsDownloadManager mdm =
- new MbmsDownloadManager(context, listener, subscriptionId, handler);
- try {
- mdm.bindAndInitialize();
- } catch (MbmsException e) {
- sIsInitialized.set(false);
- throw e;
- }
- return mdm;
- }
-
- private void bindAndInitialize() throws MbmsException {
- MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- IMbmsDownloadService downloadService =
- IMbmsDownloadService.Stub.asInterface(service);
- int result;
- try {
- result = downloadService.initialize(mSubscriptionId, mInternalCallback);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Service died before initialization");
- sIsInitialized.set(false);
- return;
- } catch (RuntimeException e) {
- Log.e(LOG_TAG, "Runtime exception during initialization");
- sendErrorToApp(
- MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
- e.toString());
- sIsInitialized.set(false);
- return;
- }
- if (result != MbmsException.SUCCESS) {
- sendErrorToApp(result, "Error returned during initialization");
- sIsInitialized.set(false);
- return;
- }
- try {
- downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
- } catch (RemoteException e) {
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
- "Middleware lost during initialization");
- sIsInitialized.set(false);
- return;
- }
- mService.set(downloadService);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- sIsInitialized.set(false);
- mService.set(null);
- }
- });
- }
-
- /**
- * An inspection API to retrieve the list of available
- * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
- * The results are returned asynchronously via a call to
- * {@link MbmsDownloadManagerCallback#onFileServicesUpdated(List)}
- *
- * The serviceClasses argument lets the app filter on types of programming and is opaque data
- * negotiated beforehand between the app and the carrier.
- *
- * This may throw an {@link MbmsException} containing one of the following errors:
- * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
- *
- * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#onError(int, String)}
- * callback can include any of the errors except:
- * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE}
- *
- * @param classList A list of service classes which the app wishes to receive
- * {@link MbmsDownloadManagerCallback#onFileServicesUpdated(List)} callbacks
- * about. Subsequent calls to this method will replace this list of service
- * classes (i.e. the middleware will no longer send updates for services
- * matching classes only in the old list).
- */
- public void getFileServices(List<String> classList) throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
- try {
- int returnCode = downloadService.getFileServices(mSubscriptionId, classList);
- if (returnCode != MbmsException.SUCCESS) {
- throw new MbmsException(returnCode);
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Remote process died");
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Sets the temp file root for downloads.
- * All temp files created for the middleware to write to will be contained in the specified
- * directory. Applications that wish to specify a location only need to call this method once
- * as long their data is persisted in storage -- the argument will be stored both in a
- * local instance of {@link android.content.SharedPreferences} and by the middleware.
- *
- * If this method is not called at least once before calling
- * {@link #download(DownloadRequest, DownloadStateCallback, Handler)}, the framework
- * will default to a directory formed by the concatenation of the app's files directory and
- * {@link MbmsDownloadManager#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
- *
- * Before calling this method, the app must cancel all of its pending
- * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
- * an {@link MbmsException} will be thrown with code
- * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
- * provided directory is the same as what has been previously configured.
- *
- * The {@link File} supplied as a root temp file directory must already exist. If not, an
- * {@link IllegalArgumentException} will be thrown.
- * @param tempFileRootDirectory A directory to place temp files in.
- */
- public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory)
- throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
- if (!tempFileRootDirectory.exists()) {
- throw new IllegalArgumentException("Provided directory does not exist");
- }
- if (!tempFileRootDirectory.isDirectory()) {
- throw new IllegalArgumentException("Provided File is not a directory");
- }
- String filePath;
- try {
- filePath = tempFileRootDirectory.getCanonicalPath();
- } catch (IOException e) {
- throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
- }
-
- try {
- int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
- if (result != MbmsException.SUCCESS) {
- throw new MbmsException(result);
- }
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
-
- SharedPreferences prefs = mContext.getSharedPreferences(
- MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
- prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
- }
-
- /**
- * Retrieves the currently configured temp file root directory. Returns the file that was
- * configured via {@link #setTempFileRootDirectory(File)} or the default directory
- * {@link #download(DownloadRequest, DownloadStateCallback, Handler)} was called without ever
- * setting the temp file root. If neither method has been called since the last time the app's
- * shared preferences were reset, returns {@code null}.
- *
- * @return A {@link File} pointing to the configured temp file directory, or null if not yet
- * configured.
- */
- public @Nullable File getTempFileRootDirectory() {
- SharedPreferences prefs = mContext.getSharedPreferences(
- MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
- String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
- if (path != null) {
- return new File(path);
- }
- return null;
- }
-
- /**
- * Requests a download of a file that is available via multicast.
- *
- * May throw an {@link IllegalArgumentException}
- *
- * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
- * this method will create a directory at the default location defined at
- * {@link MbmsDownloadManager#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
- * file root directory.
- *
- * Asynchronous errors through the listener include any of the errors
- *
- * @param request The request that specifies what should be downloaded
- * @param stateCallback Optional listener that will be provided progress updates
- * if the app is running. If {@code null}, no callbacks will be
- * provided.
- * @param handler A handler that calls to {@code stateCallback} should be called on. If
- * null, defaults to the handler provided via
- * {@link #create(Context, MbmsDownloadManagerCallback, int, Handler)}
- */
- public void download(DownloadRequest request, @Nullable DownloadStateCallback stateCallback,
- Handler handler)
- throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- // Check to see whether the app's set a temp root dir yet, and set it if not.
- SharedPreferences prefs = mContext.getSharedPreferences(
- MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
- if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
- File tempRootDirectory = new File(mContext.getFilesDir(),
- DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
- tempRootDirectory.mkdirs();
- setTempFileRootDirectory(tempRootDirectory);
- }
- InternalDownloadStateCallback internalCallback = null;
- if (stateCallback != null) {
- internalCallback = new InternalDownloadStateCallback(stateCallback,
- handler == null ? mInternalCallback.getHandler() : handler);
- }
-
- checkValidDownloadDestination(request);
- writeDownloadRequestToken(request);
- try {
- downloadService.download(request, internalCallback);
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Returns a list of pending {@link DownloadRequest}s that originated from this application.
- * A pending request is one that was issued via
- * {@link #download(DownloadRequest, DownloadStateCallback, Handler)} but not cancelled through
- * {@link #cancelDownload(DownloadRequest)}.
- * @return A list, possibly empty, of {@link DownloadRequest}s
- */
- public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- return downloadService.listPendingDownloads(mSubscriptionId);
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Attempts to cancel the specified {@link DownloadRequest}.
- *
- * If the middleware is not aware of the specified download request, an MbmsException will be
- * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
- *
- * If this method returns without throwing an exception, you may assume that cancellation
- * was successful.
- * @param downloadRequest The download request that you wish to cancel.
- */
- public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- int result = downloadService.cancelDownload(downloadRequest);
- if (result != MbmsException.SUCCESS) {
- throw new MbmsException(result);
- }
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- deleteDownloadRequestToken(downloadRequest);
- }
-
- /**
- * Gets information about the status of a file pending download.
- *
- * If the middleware has not yet been properly initialized or if it has no records of the
- * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
- * {@link #STATUS_UNKNOWN} will be returned.
- *
- * @param downloadRequest The download request to query.
- * @param fileInfo The particular file within the request to get information on.
- * @return The status of the download.
- */
- @DownloadStatus
- public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo)
- throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- return downloadService.getDownloadStatus(downloadRequest, fileInfo);
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Resets the middleware's knowledge of previously-downloaded files in this download request.
- *
- * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
- * files whose server-reported hash matches one of the already-downloaded files. This means
- * that if the file is accidentally deleted by the user or by the app, the middleware will
- * not try to download it again.
- * This method will reset the middleware's cache of hashes for the provided
- * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
- * when available.
- * This will not interrupt in-progress downloads.
- *
- * If the middleware is not aware of the specified download request, an MbmsException will be
- * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
- *
- * May throw a {@link MbmsException} with error code
- * @param downloadRequest The request to re-download files for.
- */
- public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- try {
- int result = downloadService.resetDownloadKnowledge(downloadRequest);
- if (result != MbmsException.SUCCESS) {
- throw new MbmsException(result);
- }
- } catch (RemoteException e) {
- mService.set(null);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- public void dispose() {
- try {
- IMbmsDownloadService downloadService = mService.get();
- if (downloadService == null) {
- Log.i(LOG_TAG, "Service already dead");
- return;
- }
- downloadService.dispose(mSubscriptionId);
- } catch (RemoteException e) {
- // Ignore
- Log.i(LOG_TAG, "Remote exception while disposing of service");
- } finally {
- mService.set(null);
- sIsInitialized.set(false);
- }
- }
-
- private void writeDownloadRequestToken(DownloadRequest request) {
- File token = getDownloadRequestTokenPath(request);
- if (!token.getParentFile().exists()) {
- token.getParentFile().mkdirs();
- }
- if (token.exists()) {
- Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
- return;
- }
- try {
- if (!token.createNewFile()) {
- throw new RuntimeException("Failed to create download token for request "
- + request);
- }
- } catch (IOException e) {
- throw new RuntimeException("Failed to create download token for request " + request
- + " due to IOException " + e);
- }
- }
-
- private void deleteDownloadRequestToken(DownloadRequest request) {
- File token = getDownloadRequestTokenPath(request);
- if (!token.isFile()) {
- Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
- return;
- }
- if (!token.delete()) {
- Log.w(LOG_TAG, "Couldn't delete download token at " + token);
- }
- }
-
- private File getDownloadRequestTokenPath(DownloadRequest request) {
- File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
- request.getFileServiceId());
- String downloadTokenFileName = request.getHash()
- + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
- return new File(tempFileLocation, downloadTokenFileName);
- }
-
- /**
- * Verifies the following:
- * If a request is multi-part,
- * 1. Destination Uri must exist and be a directory
- * 2. Directory specified must contain no files.
- * Otherwise
- * 1. The file specified by the destination Uri must not exist.
- */
- private void checkValidDownloadDestination(DownloadRequest request) {
- File toFile = new File(request.getDestinationUri().getSchemeSpecificPart());
- if (request.isMultipartDownload()) {
- if (!toFile.isDirectory()) {
- throw new IllegalArgumentException("Multipart download must specify valid " +
- "destination directory.");
- }
- if (toFile.listFiles().length > 0) {
- throw new IllegalArgumentException("Destination directory must be clear of all " +
- "files.");
- }
- } else {
- if (toFile.exists()) {
- throw new IllegalArgumentException("Destination file must not exist.");
- }
- }
- }
-
- private void sendErrorToApp(int errorCode, String message) {
- try {
- mInternalCallback.error(errorCode, message);
- } catch (RemoteException e) {
- // Ignore, should not happen locally.
- }
- }
-}
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
new file mode 100644
index 0000000..ebac041
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadStateCallback;
+import android.telephony.mbms.FileInfo;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.InternalDownloadSessionCallback;
+import android.telephony.mbms.InternalDownloadStateCallback;
+import android.telephony.mbms.MbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsDownloadReceiver;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsTempFileProvider;
+import android.telephony.mbms.MbmsUtils;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/**
+ * This class provides functionality for file download over MBMS.
+ */
+public class MbmsDownloadSession implements AutoCloseable {
+ private static final String LOG_TAG = MbmsDownloadSession.class.getSimpleName();
+
+ /**
+ * Service action which must be handled by the middleware implementing the MBMS file download
+ * interface.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String MBMS_DOWNLOAD_SERVICE_ACTION =
+ "android.telephony.action.EmbmsDownload";
+
+ /**
+ * Integer extra that Android will attach to the intent supplied via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+ * Indicates the result code of the download. One of
+ * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
+ * {@link #RESULT_IO_ERROR}.
+ *
+ * This extra may also be used by the middleware when it is sending intents to the app.
+ */
+ public static final String EXTRA_MBMS_DOWNLOAD_RESULT =
+ "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
+
+ /**
+ * {@link FileInfo} extra that Android will attach to the intent supplied via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+ * Indicates the file for which the download result is for. Never null.
+ *
+ * This extra may also be used by the middleware when it is sending intents to the app.
+ */
+ public static final String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
+
+ /**
+ * {@link Uri} extra that Android will attach to the intent supplied via
+ * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
+ * Indicates the location of the successfully downloaded file within the temp file root set
+ * via {@link #setTempFileRootDirectory(File)}.
+ * While you may use this file in-place, it is highly encouraged that you move
+ * this file to a different location after receiving the download completion intent, as this
+ * file resides within the temp file directory.
+ *
+ * Will always be set to a non-null value if
+ * {@link #EXTRA_MBMS_DOWNLOAD_RESULT} is set to {@link #RESULT_SUCCESSFUL}.
+ */
+ public static final String EXTRA_MBMS_COMPLETED_FILE_URI =
+ "android.telephony.extra.MBMS_COMPLETED_FILE_URI";
+
+ /**
+ * Extra containing the {@link DownloadRequest} for which the download result or file
+ * descriptor request is for. Must not be null.
+ */
+ public static final String EXTRA_MBMS_DOWNLOAD_REQUEST =
+ "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
+
+ /**
+ * The default directory name for all MBMS temp files. If you call
+ * {@link #download(DownloadRequest)} without first calling
+ * {@link #setTempFileRootDirectory(File)}, this directory will be created for you under the
+ * path returned by {@link Context#getFilesDir()}.
+ */
+ public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot";
+
+ /**
+ * Indicates that the download was successful.
+ */
+ public static final int RESULT_SUCCESSFUL = 1;
+
+ /**
+ * Indicates that the download was cancelled via {@link #cancelDownload(DownloadRequest)}.
+ */
+ public static final int RESULT_CANCELLED = 2;
+
+ /**
+ * Indicates that the download will not be completed due to the expiration of its download
+ * window on the carrier's network.
+ */
+ public static final int RESULT_EXPIRED = 3;
+
+ /**
+ * Indicates that the download will not be completed due to an I/O error incurred while
+ * writing to temp files. This commonly indicates that the device is out of storage space,
+ * but may indicate other conditions as well (such as an SD card being removed).
+ */
+ public static final int RESULT_IO_ERROR = 4;
+ // TODO - more results!
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD,
+ STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW})
+ public @interface DownloadStatus {}
+
+ /**
+ * Indicates that the middleware has no information on the file.
+ */
+ public static final int STATUS_UNKNOWN = 0;
+
+ /**
+ * Indicates that the file is actively downloading.
+ */
+ public static final int STATUS_ACTIVELY_DOWNLOADING = 1;
+
+ /**
+ * TODO: I don't know...
+ */
+ public static final int STATUS_PENDING_DOWNLOAD = 2;
+
+ /**
+ * Indicates that the file is being repaired after the download being interrupted.
+ */
+ public static final int STATUS_PENDING_REPAIR = 3;
+
+ /**
+ * Indicates that the file is waiting to download because its download window has not yet
+ * started.
+ */
+ public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4;
+
+ private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
+ private final Context mContext;
+ private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
+ }
+ };
+
+ private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null);
+ private final InternalDownloadSessionCallback mInternalCallback;
+ private final Map<DownloadStateCallback, InternalDownloadStateCallback>
+ mInternalDownloadCallbacks = new HashMap<>();
+
+ private MbmsDownloadSession(Context context, MbmsDownloadSessionCallback callback,
+ int subscriptionId, Handler handler) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ mInternalCallback = new InternalDownloadSessionCallback(callback, handler);
+ }
+
+ /**
+ * Create a new {@link MbmsDownloadSession} using the system default data subscription ID.
+ * See {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)}
+ */
+ public static MbmsDownloadSession create(@NonNull Context context,
+ @NonNull MbmsDownloadSessionCallback callback, @NonNull Handler handler) {
+ return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+ }
+
+ /**
+ * Create a new MbmsDownloadManager using the given subscription ID.
+ *
+ * Note that this call will bind a remote service and that may take a bit. The instance of
+ * {@link MbmsDownloadSession} that is returned will not be ready for use until
+ * {@link MbmsDownloadSessionCallback#onMiddlewareReady()} is called on the provided callback.
+ * If you attempt to use the instance before it is ready, an {@link IllegalStateException}
+ * will be thrown or an error will be delivered through
+ * {@link MbmsDownloadSessionCallback#onError(int, String)}.
+ *
+ * This also may throw an {@link IllegalArgumentException}.
+ *
+ * You may only have one instance of {@link MbmsDownloadSession} per UID. If you call this
+ * method while there is an active instance of {@link MbmsDownloadSession} in your process
+ * (in other words, one that has not had {@link #close()} called on it), this method will
+ * throw an {@link IllegalStateException}. If you call this method in a different process
+ * running under the same UID, an error will be indicated via
+ * {@link MbmsDownloadSessionCallback#onError(int, String)}.
+ *
+ * Note that initialization may fail asynchronously. If you wish to try again after you
+ * receive such an asynchronous error, you must call {@link #close()} on the instance of
+ * {@link MbmsDownloadSession} that you received before calling this method again.
+ *
+ * @param context The instance of {@link Context} to use
+ * @param callback A callback to get asynchronous error messages and file service updates.
+ * @param subscriptionId The data subscription ID to use
+ * @param handler The {@link Handler} on which callbacks should be enqueued.
+ * @return A new instance of {@link MbmsDownloadSession}, or null if an error occurred during
+ * setup.
+ */
+ public static @Nullable MbmsDownloadSession create(@NonNull Context context,
+ final @NonNull MbmsDownloadSessionCallback callback,
+ int subscriptionId, @NonNull Handler handler) {
+ if (!sIsInitialized.compareAndSet(false, true)) {
+ throw new IllegalStateException("Cannot have two active instances");
+ }
+ MbmsDownloadSession session =
+ new MbmsDownloadSession(context, callback, subscriptionId, handler);
+ final int result = session.bindAndInitialize();
+ if (result != MbmsErrors.SUCCESS) {
+ sIsInitialized.set(false);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(result, null);
+ }
+ });
+ return null;
+ }
+ return session;
+ }
+
+ private int bindAndInitialize() {
+ return MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IMbmsDownloadService downloadService =
+ IMbmsDownloadService.Stub.asInterface(service);
+ int result;
+ try {
+ result = downloadService.initialize(mSubscriptionId, mInternalCallback);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Service died before initialization");
+ sIsInitialized.set(false);
+ return;
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "Runtime exception during initialization");
+ sendErrorToApp(
+ MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+ e.toString());
+ sIsInitialized.set(false);
+ return;
+ }
+ if (result != MbmsErrors.SUCCESS) {
+ sendErrorToApp(result, "Error returned during initialization");
+ sIsInitialized.set(false);
+ return;
+ }
+ try {
+ downloadService.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+ "Middleware lost during initialization");
+ sIsInitialized.set(false);
+ return;
+ }
+ mService.set(downloadService);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ sIsInitialized.set(false);
+ mService.set(null);
+ }
+ });
+ }
+
+ /**
+ * An inspection API to retrieve the list of available
+ * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised.
+ * The results are returned asynchronously via a call to
+ * {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)}
+ *
+ * Asynchronous error codes via the {@link MbmsDownloadSessionCallback#onError(int, String)}
+ * callback may include any of the errors that are not specific to the streaming use-case.
+ *
+ * May throw an {@link IllegalStateException} or {@link IllegalArgumentException}.
+ *
+ * @param classList A list of service classes which the app wishes to receive
+ * {@link MbmsDownloadSessionCallback#onFileServicesUpdated(List)} callbacks
+ * about. Subsequent calls to this method will replace this list of service
+ * classes (i.e. the middleware will no longer send updates for services
+ * matching classes only in the old list).
+ * Values in this list should be negotiated with the wireless carrier prior
+ * to using this API.
+ */
+ public void requestUpdateFileServices(@NonNull List<String> classList) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+ try {
+ int returnCode = downloadService.requestUpdateFileServices(mSubscriptionId, classList);
+ if (returnCode != MbmsErrors.SUCCESS) {
+ sendErrorToApp(returnCode, null);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Sets the temp file root for downloads.
+ * All temp files created for the middleware to write to will be contained in the specified
+ * directory. Applications that wish to specify a location only need to call this method once
+ * as long their data is persisted in storage -- the argument will be stored both in a
+ * local instance of {@link android.content.SharedPreferences} and by the middleware.
+ *
+ * If this method is not called at least once before calling
+ * {@link #download(DownloadRequest)}, the framework
+ * will default to a directory formed by the concatenation of the app's files directory and
+ * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}.
+ *
+ * Before calling this method, the app must cancel all of its pending
+ * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done,
+ * you will receive an asynchronous error with code
+ * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the
+ * provided directory is the same as what has been previously configured.
+ *
+ * The {@link File} supplied as a root temp file directory must already exist. If not, an
+ * {@link IllegalArgumentException} will be thrown. In addition, as an additional sanity
+ * check, an {@link IllegalArgumentException} will be thrown if you attempt to set the temp
+ * file root directory to one of your data roots (the value of {@link Context#getDataDir()},
+ * {@link Context#getFilesDir()}, or {@link Context#getCacheDir()}).
+ * @param tempFileRootDirectory A directory to place temp files in.
+ */
+ public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+ try {
+ validateTempFileRootSanity(tempFileRootDirectory);
+ } catch (IOException e) {
+ throw new IllegalStateException("Got IOException checking directory sanity");
+ }
+ String filePath;
+ try {
+ filePath = tempFileRootDirectory.getCanonicalPath();
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e);
+ }
+
+ try {
+ int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath);
+ if (result != MbmsErrors.SUCCESS) {
+ sendErrorToApp(result, null);
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return;
+ }
+
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+ prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply();
+ }
+
+ private void validateTempFileRootSanity(File tempFileRootDirectory) throws IOException {
+ if (!tempFileRootDirectory.exists()) {
+ throw new IllegalArgumentException("Provided directory does not exist");
+ }
+ if (!tempFileRootDirectory.isDirectory()) {
+ throw new IllegalArgumentException("Provided File is not a directory");
+ }
+ String canonicalTempFilePath = tempFileRootDirectory.getCanonicalPath();
+ if (mContext.getDataDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+ throw new IllegalArgumentException("Temp file root cannot be your data dir");
+ }
+ if (mContext.getCacheDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+ throw new IllegalArgumentException("Temp file root cannot be your cache dir");
+ }
+ if (mContext.getFilesDir().getCanonicalPath().equals(canonicalTempFilePath)) {
+ throw new IllegalArgumentException("Temp file root cannot be your files dir");
+ }
+ }
+ /**
+ * Retrieves the currently configured temp file root directory. Returns the file that was
+ * configured via {@link #setTempFileRootDirectory(File)} or the default directory
+ * {@link #download(DownloadRequest)} was called without ever
+ * setting the temp file root. If neither method has been called since the last time the app's
+ * shared preferences were reset, returns {@code null}.
+ *
+ * @return A {@link File} pointing to the configured temp file directory, or null if not yet
+ * configured.
+ */
+ public @Nullable File getTempFileRootDirectory() {
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+ String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null);
+ if (path != null) {
+ return new File(path);
+ }
+ return null;
+ }
+
+ /**
+ * Requests the download of a file or set of files that the carrier has indicated to be
+ * available.
+ *
+ * May throw an {@link IllegalArgumentException}
+ *
+ * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed,
+ * this method will create a directory at the default location defined at
+ * {@link MbmsDownloadSession#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp
+ * file root directory.
+ *
+ * Asynchronous errors through the callback may include any error not specific to the
+ * streaming use-case.
+ * @param request The request that specifies what should be downloaded.
+ */
+ public void download(@NonNull DownloadRequest request) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ // Check to see whether the app's set a temp root dir yet, and set it if not.
+ SharedPreferences prefs = mContext.getSharedPreferences(
+ MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0);
+ if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) {
+ File tempRootDirectory = new File(mContext.getFilesDir(),
+ DEFAULT_TOP_LEVEL_TEMP_DIRECTORY);
+ tempRootDirectory.mkdirs();
+ setTempFileRootDirectory(tempRootDirectory);
+ }
+
+ writeDownloadRequestToken(request);
+ try {
+ downloadService.download(request);
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Returns a list of pending {@link DownloadRequest}s that originated from this application.
+ * A pending request is one that was issued via
+ * {@link #download(DownloadRequest)} but not cancelled through
+ * {@link #cancelDownload(DownloadRequest)}.
+ * @return A list, possibly empty, of {@link DownloadRequest}s
+ */
+ public @NonNull List<DownloadRequest> listPendingDownloads() {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ return downloadService.listPendingDownloads(mSubscriptionId);
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Registers a callback for a {@link DownloadRequest} previously requested via
+ * {@link #download(DownloadRequest)}. This callback will only be called as long as both this
+ * app and the middleware are both running -- if either one stops, no further calls on the
+ * provided {@link DownloadStateCallback} will be enqueued.
+ *
+ * If the middleware is not aware of the specified download request,
+ * this method will throw an {@link IllegalArgumentException}.
+ *
+ * @param request The {@link DownloadRequest} that you want updates on.
+ * @param callback The callback that should be called when the middleware has information to
+ * share on the download.
+ * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
+ */
+ public void registerStateCallback(@NonNull DownloadRequest request,
+ @NonNull DownloadStateCallback callback,
+ @NonNull Handler handler) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ InternalDownloadStateCallback internalCallback =
+ new InternalDownloadStateCallback(callback, handler);
+
+ try {
+ int result = downloadService.registerStateCallback(request, internalCallback);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ return;
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return;
+ }
+ mInternalDownloadCallbacks.put(callback, internalCallback);
+ }
+
+ /**
+ * Un-register a callback previously registered via
+ * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback, Handler)}. After
+ * this method is called, no further callbacks will be enqueued on the {@link Handler}
+ * provided upon registration, even if this method throws an exception.
+ *
+ * If the middleware is not aware of the specified download request,
+ * this method will throw an {@link IllegalArgumentException}.
+ *
+ * @param request The {@link DownloadRequest} provided during registration
+ * @param callback The callback provided during registration.
+ */
+ public void unregisterStateCallback(@NonNull DownloadRequest request,
+ @NonNull DownloadStateCallback callback) {
+ try {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ InternalDownloadStateCallback internalCallback =
+ mInternalDownloadCallbacks.get(callback);
+
+ try {
+ int result = downloadService.unregisterStateCallback(request, internalCallback);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ } finally {
+ InternalDownloadStateCallback internalCallback =
+ mInternalDownloadCallbacks.remove(callback);
+ if (internalCallback != null) {
+ internalCallback.stop();
+ }
+ }
+ }
+
+ /**
+ * Attempts to cancel the specified {@link DownloadRequest}.
+ *
+ * If the middleware is not aware of the specified download request,
+ * this method will throw an {@link IllegalArgumentException}.
+ *
+ * @param downloadRequest The download request that you wish to cancel.
+ */
+ public void cancelDownload(@NonNull DownloadRequest downloadRequest) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ int result = downloadService.cancelDownload(downloadRequest);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ return;
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return;
+ }
+ deleteDownloadRequestToken(downloadRequest);
+ }
+
+ /**
+ * Gets information about the status of a file pending download.
+ *
+ * If there was a problem communicating with the middleware or if it has no records of the
+ * file indicated by {@code fileInfo} being associated with {@code downloadRequest},
+ * {@link #STATUS_UNKNOWN} will be returned.
+ *
+ * @param downloadRequest The download request to query.
+ * @param fileInfo The particular file within the request to get information on.
+ * @return The status of the download.
+ */
+ @DownloadStatus
+ public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ return downloadService.getDownloadStatus(downloadRequest, fileInfo);
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return STATUS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Resets the middleware's knowledge of previously-downloaded files in this download request.
+ *
+ * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download
+ * files whose server-reported hash matches one of the already-downloaded files. This means
+ * that if the file is accidentally deleted by the user or by the app, the middleware will
+ * not try to download it again.
+ * This method will reset the middleware's cache of hashes for the provided
+ * {@link DownloadRequest}, so that previously downloaded content will be downloaded again
+ * when available.
+ * This will not interrupt in-progress downloads.
+ *
+ * This is distinct from cancelling and re-issuing the download request -- if you cancel and
+ * re-issue, the middleware will not clear its cache of download state information.
+ *
+ * If the middleware is not aware of the specified download request, an
+ * {@link IllegalArgumentException} will be thrown.
+ *
+ * @param downloadRequest The request to re-download files for.
+ */
+ public void resetDownloadKnowledge(DownloadRequest downloadRequest) {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ try {
+ int result = downloadService.resetDownloadKnowledge(downloadRequest);
+ if (result != MbmsErrors.SUCCESS) {
+ if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
+ throw new IllegalArgumentException("Unknown download request.");
+ }
+ sendErrorToApp(result, null);
+ }
+ } catch (RemoteException e) {
+ mService.set(null);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Terminates this instance.
+ *
+ * After this method returns,
+ * no further callbacks originating from the middleware will be enqueued on the provided
+ * instance of {@link MbmsDownloadSessionCallback}, but callbacks that have already been
+ * enqueued will still be delivered.
+ *
+ * It is safe to call {@link #create(Context, MbmsDownloadSessionCallback, int, Handler)} to
+ * obtain another instance of {@link MbmsDownloadSession} immediately after this method
+ * returns.
+ *
+ * May throw an {@link IllegalStateException}
+ */
+ @Override
+ public void close() {
+ try {
+ IMbmsDownloadService downloadService = mService.get();
+ if (downloadService == null) {
+ Log.i(LOG_TAG, "Service already dead");
+ return;
+ }
+ downloadService.dispose(mSubscriptionId);
+ } catch (RemoteException e) {
+ // Ignore
+ Log.i(LOG_TAG, "Remote exception while disposing of service");
+ } finally {
+ mService.set(null);
+ sIsInitialized.set(false);
+ mInternalCallback.stop();
+ }
+ }
+
+ private void writeDownloadRequestToken(DownloadRequest request) {
+ File token = getDownloadRequestTokenPath(request);
+ if (!token.getParentFile().exists()) {
+ token.getParentFile().mkdirs();
+ }
+ if (token.exists()) {
+ Log.w(LOG_TAG, "Download token " + token.getName() + " already exists");
+ return;
+ }
+ try {
+ if (!token.createNewFile()) {
+ throw new RuntimeException("Failed to create download token for request "
+ + request);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create download token for request " + request
+ + " due to IOException " + e);
+ }
+ }
+
+ private void deleteDownloadRequestToken(DownloadRequest request) {
+ File token = getDownloadRequestTokenPath(request);
+ if (!token.isFile()) {
+ Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token);
+ return;
+ }
+ if (!token.delete()) {
+ Log.w(LOG_TAG, "Couldn't delete download token at " + token);
+ }
+ }
+
+ private File getDownloadRequestTokenPath(DownloadRequest request) {
+ File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext,
+ request.getFileServiceId());
+ String downloadTokenFileName = request.getHash()
+ + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX;
+ return new File(tempFileLocation, downloadTokenFileName);
+ }
+
+ private void sendErrorToApp(int errorCode, String message) {
+ try {
+ mInternalCallback.onError(errorCode, message);
+ } catch (RemoteException e) {
+ // Ignore, should not happen locally.
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
deleted file mode 100644
index b6b253e..0000000
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony;
-
-import android.annotation.SdkConstant;
-import android.annotation.SystemApi;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.telephony.mbms.InternalStreamingManagerCallback;
-import android.telephony.mbms.InternalStreamingServiceCallback;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsUtils;
-import android.telephony.mbms.StreamingService;
-import android.telephony.mbms.StreamingServiceCallback;
-import android.telephony.mbms.StreamingServiceInfo;
-import android.telephony.mbms.vendor.IMbmsStreamingService;
-import android.util.Log;
-
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-/**
- * This class provides functionality for streaming media over MBMS.
- */
-public class MbmsStreamingManager {
- private static final String LOG_TAG = "MbmsStreamingManager";
-
- /**
- * Service action which must be handled by the middleware implementing the MBMS streaming
- * interface.
- * @hide
- */
- @SystemApi
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String MBMS_STREAMING_SERVICE_ACTION =
- "android.telephony.action.EmbmsStreaming";
-
- private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
-
- private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
- private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- sIsInitialized.set(false);
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification");
- }
- };
-
- private InternalStreamingManagerCallback mInternalCallback;
-
- private final Context mContext;
- private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
-
- /** @hide */
- private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback,
- int subscriptionId, Handler handler) {
- mContext = context;
- mSubscriptionId = subscriptionId;
- if (handler == null) {
- handler = new Handler(Looper.getMainLooper());
- }
- mInternalCallback = new InternalStreamingManagerCallback(callback, handler);
- }
-
- /**
- * Create a new MbmsStreamingManager using the given subscription ID.
- *
- * Note that this call will bind a remote service. You may not call this method on your app's
- * main thread. This may throw an {@link MbmsException}, indicating errors that may happen
- * during the initialization or binding process.
- *
- *
- * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this
- * method while there is an active instance of {@link MbmsStreamingManager} in your process
- * (in other words, one that has not had {@link #dispose()} called on it), this method will
- * throw an {@link MbmsException}. If you call this method in a different process
- * running under the same UID, an error will be indicated via
- * {@link MbmsStreamingManagerCallback#onError(int, String)}.
- *
- * Note that initialization may fail asynchronously. If you wish to try again after you
- * receive such an asynchronous error, you must call dispose() on the instance of
- * {@link MbmsStreamingManager} that you received before calling this method again.
- *
- * @param context The {@link Context} to use.
- * @param callback A callback object on which you wish to receive results of asynchronous
- * operations.
- * @param subscriptionId The subscription ID to use.
- * @param handler The handler you wish to receive callbacks on. If null, callbacks will be
- * processed on the main looper (in other words, the looper returned from
- * {@link Looper#getMainLooper()}).
- */
- public static MbmsStreamingManager create(Context context,
- MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler)
- throws MbmsException {
- if (!sIsInitialized.compareAndSet(false, true)) {
- throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE);
- }
- MbmsStreamingManager manager = new MbmsStreamingManager(context, callback,
- subscriptionId, handler);
- try {
- manager.bindAndInitialize();
- } catch (MbmsException e) {
- sIsInitialized.set(false);
- throw e;
- }
- return manager;
- }
-
- /**
- * Create a new MbmsStreamingManager using the system default data subscription ID.
- * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
- */
- public static MbmsStreamingManager create(Context context,
- MbmsStreamingManagerCallback callback, Handler handler)
- throws MbmsException {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
- }
-
- /**
- * Create a new MbmsStreamingManager using the system default data subscription ID and
- * default {@link Handler}.
- * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
- */
- public static MbmsStreamingManager create(Context context,
- MbmsStreamingManagerCallback callback)
- throws MbmsException {
- return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null);
- }
-
- /**
- * Terminates this instance, ending calls to the registered listener. Also terminates
- * any streaming services spawned from this instance.
- *
- * May throw an {@link IllegalStateException}
- */
- public void dispose() {
- try {
- IMbmsStreamingService streamingService = mService.get();
- if (streamingService == null) {
- // Ignore and return, assume already disposed.
- return;
- }
- streamingService.dispose(mSubscriptionId);
- } catch (RemoteException e) {
- // Ignore for now
- } finally {
- mService.set(null);
- sIsInitialized.set(false);
- }
- }
-
- /**
- * An inspection API to retrieve the list of streaming media currently be advertised.
- * The results are returned asynchronously through the previously registered callback.
- * serviceClasses lets the app filter on types of programming and is opaque data between
- * the app and the carrier.
- *
- * Multiple calls replace the list of serviceClasses of interest.
- *
- * This may throw an {@link MbmsException} containing any error in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors},
- * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
- *
- * May also throw an unchecked {@link IllegalArgumentException} or an
- * {@link IllegalStateException}
- *
- * @param classList A list of streaming service classes that the app would like updates on.
- */
- public void getStreamingServices(List<String> classList) throws MbmsException {
- IMbmsStreamingService streamingService = mService.get();
- if (streamingService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
- try {
- int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList);
- if (returnCode != MbmsException.SUCCESS) {
- throw new MbmsException(returnCode);
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Remote process died");
- mService.set(null);
- sIsInitialized.set(false);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
- }
-
- /**
- * Starts streaming a requested service, reporting status to the indicated callback.
- * Returns an object used to control that stream. The stream may not be ready for consumption
- * immediately upon return from this method -- wait until the streaming state has been
- * reported via
- * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
- *
- * May throw an
- * {@link MbmsException} containing any of the error codes in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors},
- * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}.
- *
- * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
- *
- * Asynchronous errors through the callback include any of the errors in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors} or
- * {@link android.telephony.mbms.MbmsException.StreamingErrors}.
- *
- * @param serviceInfo The information about the service to stream.
- * @param callback A callback that'll be called when something about the stream changes.
- * @param handler A handler that calls to {@code callback} should be called on. If null,
- * defaults to the handler provided via
- * {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}.
- * @return An instance of {@link StreamingService} through which the stream can be controlled.
- */
- public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
- StreamingServiceCallback callback, Handler handler) throws MbmsException {
- IMbmsStreamingService streamingService = mService.get();
- if (streamingService == null) {
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND);
- }
-
- InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
- callback, handler == null ? mInternalCallback.getHandler() : handler);
-
- StreamingService serviceForApp = new StreamingService(
- mSubscriptionId, streamingService, serviceInfo, serviceCallback);
-
- try {
- int returnCode = streamingService.startStreaming(
- mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
- if (returnCode != MbmsException.SUCCESS) {
- throw new MbmsException(returnCode);
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Remote process died");
- mService.set(null);
- sIsInitialized.set(false);
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- }
-
- return serviceForApp;
- }
-
- private void bindAndInitialize() throws MbmsException {
- MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- IMbmsStreamingService streamingService =
- IMbmsStreamingService.Stub.asInterface(service);
- int result;
- try {
- result = streamingService.initialize(mInternalCallback,
- mSubscriptionId);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Service died before initialization");
- sendErrorToApp(
- MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
- e.toString());
- sIsInitialized.set(false);
- return;
- } catch (RuntimeException e) {
- Log.e(LOG_TAG, "Runtime exception during initialization");
- sendErrorToApp(
- MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
- e.toString());
- sIsInitialized.set(false);
- return;
- }
- if (result != MbmsException.SUCCESS) {
- sendErrorToApp(result, "Error returned during initialization");
- sIsInitialized.set(false);
- return;
- }
- try {
- streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
- } catch (RemoteException e) {
- sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST,
- "Middleware lost during initialization");
- sIsInitialized.set(false);
- return;
- }
- mService.set(streamingService);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- sIsInitialized.set(false);
- mService.set(null);
- }
- });
- }
-
- private void sendErrorToApp(int errorCode, String message) {
- try {
- mInternalCallback.error(errorCode, message);
- } catch (RemoteException e) {
- // Ignore, should not happen locally.
- }
- }
-}
diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java
new file mode 100644
index 0000000..a8c4607
--- /dev/null
+++ b/telephony/java/android/telephony/MbmsStreamingSession.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.mbms.InternalStreamingSessionCallback;
+import android.telephony.mbms.InternalStreamingServiceCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
+import android.telephony.mbms.MbmsUtils;
+import android.telephony.mbms.StreamingService;
+import android.telephony.mbms.StreamingServiceCallback;
+import android.telephony.mbms.StreamingServiceInfo;
+import android.telephony.mbms.vendor.IMbmsStreamingService;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+/**
+ * This class provides functionality for streaming media over MBMS.
+ */
+public class MbmsStreamingSession implements AutoCloseable {
+ private static final String LOG_TAG = "MbmsStreamingSession";
+
+ /**
+ * Service action which must be handled by the middleware implementing the MBMS streaming
+ * interface.
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String MBMS_STREAMING_SERVICE_ACTION =
+ "android.telephony.action.EmbmsStreaming";
+
+ private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
+
+ private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
+ private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ sIsInitialized.set(false);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification");
+ }
+ };
+
+ private InternalStreamingSessionCallback mInternalCallback;
+ private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>();
+
+ private final Context mContext;
+ private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
+
+ /** @hide */
+ private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback,
+ int subscriptionId, Handler handler) {
+ mContext = context;
+ mSubscriptionId = subscriptionId;
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ mInternalCallback = new InternalStreamingSessionCallback(callback, handler);
+ }
+
+ /**
+ * Create a new {@link MbmsStreamingSession} using the given subscription ID.
+ *
+ * Note that this call will bind a remote service. You may not call this method on your app's
+ * main thread.
+ *
+ * You may only have one instance of {@link MbmsStreamingSession} per UID. If you call this
+ * method while there is an active instance of {@link MbmsStreamingSession} in your process
+ * (in other words, one that has not had {@link #close()} called on it), this method will
+ * throw an {@link IllegalStateException}. If you call this method in a different process
+ * running under the same UID, an error will be indicated via
+ * {@link MbmsStreamingSessionCallback#onError(int, String)}.
+ *
+ * Note that initialization may fail asynchronously. If you wish to try again after you
+ * receive such an asynchronous error, you must call {@link #close()} on the instance of
+ * {@link MbmsStreamingSession} that you received before calling this method again.
+ *
+ * @param context The {@link Context} to use.
+ * @param callback A callback object on which you wish to receive results of asynchronous
+ * operations.
+ * @param subscriptionId The subscription ID to use.
+ * @param handler The handler you wish to receive callbacks on.
+ * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred.
+ */
+ public static @Nullable MbmsStreamingSession create(@NonNull Context context,
+ final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId,
+ @NonNull Handler handler) {
+ if (!sIsInitialized.compareAndSet(false, true)) {
+ throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession");
+ }
+ MbmsStreamingSession session = new MbmsStreamingSession(context, callback,
+ subscriptionId, handler);
+
+ final int result = session.bindAndInitialize();
+ if (result != MbmsErrors.SUCCESS) {
+ sIsInitialized.set(false);
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(result, null);
+ }
+ });
+ return null;
+ }
+ return session;
+ }
+
+ /**
+ * Create a new {@link MbmsStreamingSession} using the system default data subscription ID.
+ * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}.
+ */
+ public static MbmsStreamingSession create(@NonNull Context context,
+ @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) {
+ return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler);
+ }
+
+ /**
+ * Terminates this instance. Also terminates
+ * any streaming services spawned from this instance as if
+ * {@link StreamingService#stopStreaming()} had been called on them. After this method returns,
+ * no further callbacks originating from the middleware will be enqueued on the provided
+ * instance of {@link MbmsStreamingSessionCallback}, but callbacks that have already been
+ * enqueued will still be delivered.
+ *
+ * It is safe to call {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)} to
+ * obtain another instance of {@link MbmsStreamingSession} immediately after this method
+ * returns.
+ *
+ * May throw an {@link IllegalStateException}
+ */
+ public void close() {
+ try {
+ IMbmsStreamingService streamingService = mService.get();
+ if (streamingService == null) {
+ // Ignore and return, assume already disposed.
+ return;
+ }
+ streamingService.dispose(mSubscriptionId);
+ for (StreamingService s : mKnownActiveStreamingServices) {
+ s.getCallback().stop();
+ }
+ mKnownActiveStreamingServices.clear();
+ } catch (RemoteException e) {
+ // Ignore for now
+ } finally {
+ mService.set(null);
+ sIsInitialized.set(false);
+ mInternalCallback.stop();
+ }
+ }
+
+ /**
+ * An inspection API to retrieve the list of streaming media currently be advertised.
+ * The results are returned asynchronously via
+ * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback
+ * provided upon creation.
+ *
+ * Multiple calls replace the list of service classes of interest.
+ *
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
+ *
+ * @param serviceClassList A list of streaming service classes that the app would like updates
+ * on. The exact names of these classes should be negotiated with the
+ * wireless carrier separately.
+ */
+ public void requestUpdateStreamingServices(List<String> serviceClassList) {
+ IMbmsStreamingService streamingService = mService.get();
+ if (streamingService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+ try {
+ int returnCode = streamingService.requestUpdateStreamingServices(
+ mSubscriptionId, serviceClassList);
+ if (returnCode != MbmsErrors.SUCCESS) {
+ sendErrorToApp(returnCode, null);
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService.set(null);
+ sIsInitialized.set(false);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ }
+ }
+
+ /**
+ * Starts streaming a requested service, reporting status to the indicated callback.
+ * Returns an object used to control that stream. The stream may not be ready for consumption
+ * immediately upon return from this method -- wait until the streaming state has been
+ * reported via
+ * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)}
+ *
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ *
+ * Asynchronous errors through the callback include any of the errors in
+ * {@link MbmsErrors.GeneralErrors} or
+ * {@link MbmsErrors.StreamingErrors}.
+ *
+ * @param serviceInfo The information about the service to stream.
+ * @param callback A callback that'll be called when something about the stream changes.
+ * @param handler A handler that calls to {@code callback} should be called on.
+ * @return An instance of {@link StreamingService} through which the stream can be controlled.
+ * May be {@code null} if an error occurred.
+ */
+ public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo,
+ StreamingServiceCallback callback, @NonNull Handler handler) {
+ IMbmsStreamingService streamingService = mService.get();
+ if (streamingService == null) {
+ throw new IllegalStateException("Middleware not yet bound");
+ }
+
+ InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback(
+ callback, handler);
+
+ StreamingService serviceForApp = new StreamingService(
+ mSubscriptionId, streamingService, this, serviceInfo, serviceCallback);
+ mKnownActiveStreamingServices.add(serviceForApp);
+
+ try {
+ int returnCode = streamingService.startStreaming(
+ mSubscriptionId, serviceInfo.getServiceId(), serviceCallback);
+ if (returnCode != MbmsErrors.SUCCESS) {
+ sendErrorToApp(returnCode, null);
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Remote process died");
+ mService.set(null);
+ sIsInitialized.set(false);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return null;
+ }
+
+ return serviceForApp;
+ }
+
+ /** @hide */
+ public void onStreamingServiceStopped(StreamingService service) {
+ mKnownActiveStreamingServices.remove(service);
+ }
+
+ private int bindAndInitialize() {
+ return MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ IMbmsStreamingService streamingService =
+ IMbmsStreamingService.Stub.asInterface(service);
+ int result;
+ try {
+ result = streamingService.initialize(mInternalCallback,
+ mSubscriptionId);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Service died before initialization");
+ sendErrorToApp(
+ MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+ e.toString());
+ sIsInitialized.set(false);
+ return;
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "Runtime exception during initialization");
+ sendErrorToApp(
+ MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE,
+ e.toString());
+ sIsInitialized.set(false);
+ return;
+ }
+ if (result != MbmsErrors.SUCCESS) {
+ sendErrorToApp(result, "Error returned during initialization");
+ sIsInitialized.set(false);
+ return;
+ }
+ try {
+ streamingService.asBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST,
+ "Middleware lost during initialization");
+ sIsInitialized.set(false);
+ return;
+ }
+ mService.set(streamingService);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ sIsInitialized.set(false);
+ mService.set(null);
+ }
+ });
+ }
+
+ private void sendErrorToApp(int errorCode, String message) {
+ try {
+ mInternalCallback.onError(errorCode, message);
+ } catch (RemoteException e) {
+ // Ignore, should not happen locally.
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 8705446..1b942de 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -77,9 +77,28 @@
public static final int TOA_International = 0x91;
public static final int TOA_Unknown = 0x81;
+ /*
+ * The BCD extended type used to determine the extended char for the digit which is greater than
+ * 9.
+ *
+ * see TS 51.011 section 10.5.1 EF_ADN(Abbreviated dialling numbers)
+ */
+ public static final int BCD_EXTENDED_TYPE_EF_ADN = 1;
+
+ /*
+ * The BCD extended type used to determine the extended char for the digit which is greater than
+ * 9.
+ *
+ * see TS 24.008 section 10.5.4.7 Called party BCD number
+ */
+ public static final int BCD_EXTENDED_TYPE_CALLED_PARTY = 2;
+
static final String LOG_TAG = "PhoneNumberUtils";
private static final boolean DBG = false;
+ private static final String BCD_EF_ADN_EXTENDED = "*#,N;";
+ private static final String BCD_CALLED_PARTY_EXTENDED = "*#abc";
+
/*
* global-phone-number = ["+"] 1*( DIGIT / written-sep )
* written-sep = ("-"/".")
@@ -799,11 +818,33 @@
*
* @return partial string on invalid decode
*
- * FIXME(mkf) support alphanumeric address type
- * currently implemented in SMSMessage.getAddress()
+ * @deprecated use {@link #calledPartyBCDToString(byte[], int, int, int)} instead. Calling this
+ * method is equivalent to calling {@link #calledPartyBCDToString(byte[], int, int)} with
+ * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
*/
- public static String
- calledPartyBCDToString (byte[] bytes, int offset, int length) {
+ @Deprecated
+ public static String calledPartyBCDToString(byte[] bytes, int offset, int length) {
+ return calledPartyBCDToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
+ }
+
+ /**
+ * 3GPP TS 24.008 10.5.4.7
+ * Called Party BCD Number
+ *
+ * See Also TS 51.011 10.5.1 "dialing number/ssc string"
+ * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)"
+ *
+ * @param bytes the data buffer
+ * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte
+ * @param length is the number of bytes including TOA byte
+ * and must be at least 2
+ * @param bcdExtType used to determine the extended bcd coding
+ * @see #BCD_EXTENDED_TYPE_EF_ADN
+ * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
+ *
+ */
+ public static String calledPartyBCDToString(
+ byte[] bytes, int offset, int length, int bcdExtType) {
boolean prependPlus = false;
StringBuilder ret = new StringBuilder(1 + length * 2);
@@ -817,7 +858,7 @@
}
internalCalledPartyBCDFragmentToString(
- ret, bytes, offset + 1, length - 1);
+ ret, bytes, offset + 1, length - 1, bcdExtType);
if (prependPlus && ret.length() == 0) {
// If the only thing there is a prepended plus, return ""
@@ -902,14 +943,13 @@
return ret.toString();
}
- private static void
- internalCalledPartyBCDFragmentToString(
- StringBuilder sb, byte [] bytes, int offset, int length) {
+ private static void internalCalledPartyBCDFragmentToString(
+ StringBuilder sb, byte [] bytes, int offset, int length, int bcdExtType) {
for (int i = offset ; i < length + offset ; i++) {
byte b;
char c;
- c = bcdToChar((byte)(bytes[i] & 0xf));
+ c = bcdToChar((byte)(bytes[i] & 0xf), bcdExtType);
if (c == 0) {
return;
@@ -930,7 +970,7 @@
break;
}
- c = bcdToChar(b);
+ c = bcdToChar(b, bcdExtType);
if (c == 0) {
return;
}
@@ -943,49 +983,65 @@
/**
* Like calledPartyBCDToString, but field does not start with a
* TOA byte. For example: SIM ADN extension fields
+ *
+ * @deprecated use {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} instead.
+ * Calling this method is equivalent to calling
+ * {@link #calledPartyBCDFragmentToString(byte[], int, int, int)} with
+ * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
*/
+ @Deprecated
+ public static String calledPartyBCDFragmentToString(byte[] bytes, int offset, int length) {
+ return calledPartyBCDFragmentToString(bytes, offset, length, BCD_EXTENDED_TYPE_EF_ADN);
+ }
- public static String
- calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) {
+ /**
+ * Like calledPartyBCDToString, but field does not start with a
+ * TOA byte. For example: SIM ADN extension fields
+ */
+ public static String calledPartyBCDFragmentToString(
+ byte[] bytes, int offset, int length, int bcdExtType) {
StringBuilder ret = new StringBuilder(length * 2);
-
- internalCalledPartyBCDFragmentToString(ret, bytes, offset, length);
-
+ internalCalledPartyBCDFragmentToString(ret, bytes, offset, length, bcdExtType);
return ret.toString();
}
- /** returns 0 on invalid value */
- private static char
- bcdToChar(byte b) {
+ /**
+ * Returns the correspond character for given {@code b} based on {@code bcdExtType}, or 0 on
+ * invalid code.
+ */
+ private static char bcdToChar(byte b, int bcdExtType) {
if (b < 0xa) {
- return (char)('0' + b);
- } else switch (b) {
- case 0xa: return '*';
- case 0xb: return '#';
- case 0xc: return PAUSE;
- case 0xd: return WILD;
-
- default: return 0;
+ return (char) ('0' + b);
}
+
+ String extended = null;
+ if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
+ extended = BCD_EF_ADN_EXTENDED;
+ } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
+ extended = BCD_CALLED_PARTY_EXTENDED;
+ }
+ if (extended == null || b - 0xa >= extended.length()) {
+ return 0;
+ }
+
+ return extended.charAt(b - 0xa);
}
- private static int
- charToBCD(char c) {
- if (c >= '0' && c <= '9') {
+ private static int charToBCD(char c, int bcdExtType) {
+ if ('0' <= c && c <= '9') {
return c - '0';
- } else if (c == '*') {
- return 0xa;
- } else if (c == '#') {
- return 0xb;
- } else if (c == PAUSE) {
- return 0xc;
- } else if (c == WILD) {
- return 0xd;
- } else if (c == WAIT) {
- return 0xe;
- } else {
- throw new RuntimeException ("invalid char for BCD " + c);
}
+
+ String extended = null;
+ if (BCD_EXTENDED_TYPE_EF_ADN == bcdExtType) {
+ extended = BCD_EF_ADN_EXTENDED;
+ } else if (BCD_EXTENDED_TYPE_CALLED_PARTY == bcdExtType) {
+ extended = BCD_CALLED_PARTY_EXTENDED;
+ }
+ if (extended == null || extended.indexOf(c) == -1) {
+ throw new RuntimeException("invalid char for BCD " + c);
+ }
+ return 0xa + extended.indexOf(c);
}
/**
@@ -1034,40 +1090,60 @@
*
* Returns null if network portion is empty.
*/
- public static byte[]
- networkPortionToCalledPartyBCD(String s) {
+ public static byte[] networkPortionToCalledPartyBCD(String s) {
String networkPortion = extractNetworkPortion(s);
- return numberToCalledPartyBCDHelper(networkPortion, false);
+ return numberToCalledPartyBCDHelper(
+ networkPortion, false, BCD_EXTENDED_TYPE_EF_ADN);
}
/**
* Same as {@link #networkPortionToCalledPartyBCD}, but includes a
* one-byte length prefix.
*/
- public static byte[]
- networkPortionToCalledPartyBCDWithLength(String s) {
+ public static byte[] networkPortionToCalledPartyBCDWithLength(String s) {
String networkPortion = extractNetworkPortion(s);
- return numberToCalledPartyBCDHelper(networkPortion, true);
+ return numberToCalledPartyBCDHelper(
+ networkPortion, true, BCD_EXTENDED_TYPE_EF_ADN);
}
/**
* Convert a dialing number to BCD byte array
*
- * @param number dialing number string
- * if the dialing number starts with '+', set to international TOA
+ * @param number dialing number string. If the dialing number starts with '+', set to
+ * international TOA
+ *
+ * @return BCD byte array
+ *
+ * @deprecated use {@link #numberToCalledPartyBCD(String, int)} instead. Calling this method
+ * is equivalent to calling {@link #numberToCalledPartyBCD(String, int)} with
+ * {@link #BCD_EXTENDED_TYPE_EF_ADN} as the extended type.
+ */
+ @Deprecated
+ public static byte[] numberToCalledPartyBCD(String number) {
+ return numberToCalledPartyBCD(number, BCD_EXTENDED_TYPE_EF_ADN);
+ }
+
+ /**
+ * Convert a dialing number to BCD byte array
+ *
+ * @param number dialing number string. If the dialing number starts with '+', set to
+ * international TOA
+ * @param bcdExtType used to determine the extended bcd coding
+ * @see #BCD_EXTENDED_TYPE_EF_ADN
+ * @see #BCD_EXTENDED_TYPE_CALLED_PARTY
+ *
* @return BCD byte array
*/
- public static byte[]
- numberToCalledPartyBCD(String number) {
- return numberToCalledPartyBCDHelper(number, false);
+ public static byte[] numberToCalledPartyBCD(String number, int bcdExtType) {
+ return numberToCalledPartyBCDHelper(number, false, bcdExtType);
}
/**
* If includeLength is true, prepend a one-byte length value to
* the return array.
*/
- private static byte[]
- numberToCalledPartyBCDHelper(String number, boolean includeLength) {
+ private static byte[] numberToCalledPartyBCDHelper(
+ String number, boolean includeLength, int bcdExtType) {
int numberLenReal = number.length();
int numberLenEffective = numberLenReal;
boolean hasPlus = number.indexOf('+') != -1;
@@ -1087,7 +1163,8 @@
char c = number.charAt(i);
if (c == '+') continue;
int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
- result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
+ result[extraBytes + (digitCount >> 1)] |=
+ (byte)((charToBCD(c, bcdExtType) & 0x0F) << shift);
digitCount++;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b7a7d8e..b9429b9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -106,8 +106,6 @@
public static final String MODEM_ACTIVITY_RESULT_KEY =
BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY;
- private static ITelephonyRegistry sRegistry;
-
/**
* The allowed states of Wi-Fi calling.
*
@@ -178,11 +176,6 @@
mContext = context;
}
mSubscriptionManager = SubscriptionManager.from(mContext);
-
- if (sRegistry == null) {
- sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
- "telephony.registry"));
- }
}
/** @hide */
@@ -869,6 +862,20 @@
public static final String EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC =
"android.telephony.event.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC";
+ /**
+ * {@link android.telecom.Connection} event used to indicate that an outgoing call has been
+ * forwarded to another number.
+ * <p>
+ * Sent in response to an IMS supplementary service notification indicating the call has been
+ * forwarded.
+ * <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_CALL_FORWARDED =
+ "android.telephony.event.EVENT_CALL_FORWARDED";
+
/* Visual voicemail protocols */
/**
@@ -3439,6 +3446,10 @@
return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
}
+ private ITelephonyRegistry getTelephonyRegistry() {
+ return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+ }
+
//
//
// PhoneStateListener
@@ -3478,12 +3489,16 @@
if (listener.mSubId == null) {
listener.mSubId = mSubId;
}
- sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),
- listener.callback, events, notifyNow);
+
+ ITelephonyRegistry registry = getTelephonyRegistry();
+ if (registry != null) {
+ registry.listenForSubscriber(listener.mSubId, getOpPackageName(),
+ listener.callback, events, notifyNow);
+ } else {
+ Rlog.w(TAG, "telephony registry not ready.");
+ }
} catch (RemoteException ex) {
// system process dead
- } catch (NullPointerException ex) {
- // system process dead
}
}
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 268e1da..5a57f32 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -55,12 +55,10 @@
/** @hide */
private static class OpaqueDataContainer implements Serializable {
- private final String destinationUri;
private final String appIntent;
private final int version;
- public OpaqueDataContainer(String destinationUri, String appIntent, int version) {
- this.destinationUri = destinationUri;
+ public OpaqueDataContainer(String appIntent, int version) {
this.appIntent = appIntent;
this.version = version;
}
@@ -69,7 +67,6 @@
public static class Builder {
private String fileServiceId;
private Uri source;
- private Uri dest;
private int subscriptionId;
private String appIntent;
private int version = CURRENT_VERSION;
@@ -104,21 +101,6 @@
}
/**
- * Sets the destination URI for the download request to be built. The middleware should
- * not set this directly.
- * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested
- * final destination of the download.
- */
- public Builder setDest(Uri dest) {
- if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) {
- throw new IllegalArgumentException("Destination uri must not exceed length " +
- MAX_DESTINATION_URI_SIZE);
- }
- this.dest = dest;
- return this;
- }
-
- /**
* Set the subscription ID on which the file(s) should be downloaded.
* @param subscriptionId
*/
@@ -159,7 +141,6 @@
OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject();
version = dataContainer.version;
appIntent = dataContainer.appIntent;
- dest = Uri.parse(dataContainer.destinationUri);
} catch (IOException e) {
// Really should never happen
Log.e(LOG_TAG, "Got IOException trying to parse opaque data");
@@ -172,24 +153,21 @@
}
public DownloadRequest build() {
- return new DownloadRequest(fileServiceId, source, dest,
- subscriptionId, appIntent, version);
+ return new DownloadRequest(fileServiceId, source, subscriptionId, appIntent, version);
}
}
private final String fileServiceId;
private final Uri sourceUri;
- private final Uri destinationUri;
private final int subscriptionId;
private final String serializedResultIntentForApp;
private final int version;
private DownloadRequest(String fileServiceId,
- Uri source, Uri dest,
- int sub, String appIntent, int version) {
+ Uri source, int sub,
+ String appIntent, int version) {
this.fileServiceId = fileServiceId;
sourceUri = source;
- destinationUri = dest;
subscriptionId = sub;
serializedResultIntentForApp = appIntent;
this.version = version;
@@ -202,7 +180,6 @@
private DownloadRequest(DownloadRequest dr) {
fileServiceId = dr.fileServiceId;
sourceUri = dr.sourceUri;
- destinationUri = dr.destinationUri;
subscriptionId = dr.subscriptionId;
serializedResultIntentForApp = dr.serializedResultIntentForApp;
version = dr.version;
@@ -211,7 +188,6 @@
private DownloadRequest(Parcel in) {
fileServiceId = in.readString();
sourceUri = in.readParcelable(getClass().getClassLoader());
- destinationUri = in.readParcelable(getClass().getClassLoader());
subscriptionId = in.readInt();
serializedResultIntentForApp = in.readString();
version = in.readInt();
@@ -224,7 +200,6 @@
public void writeToParcel(Parcel out, int flags) {
out.writeString(fileServiceId);
out.writeParcelable(sourceUri, flags);
- out.writeParcelable(destinationUri, flags);
out.writeInt(subscriptionId);
out.writeString(serializedResultIntentForApp);
out.writeInt(version);
@@ -245,14 +220,6 @@
}
/**
- * For use by the client app only.
- * @return The URI of the final destination of the download.
- */
- public Uri getDestinationUri() {
- return destinationUri;
- }
-
- /**
* @return The subscription ID on which to perform MBMS operations.
*/
public int getSubscriptionId() {
@@ -285,7 +252,7 @@
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
OpaqueDataContainer container = new OpaqueDataContainer(
- destinationUri.toString(), serializedResultIntentForApp, version);
+ serializedResultIntentForApp, version);
stream.writeObject(container);
stream.flush();
return byteArrayOutputStream.toByteArray();
@@ -351,7 +318,6 @@
if (version >= 1) {
// Hash the source URI, destination URI, and the app intent
digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
- digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8));
digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
}
// Add updates for future versions here
@@ -372,13 +338,12 @@
version == request.version &&
Objects.equals(fileServiceId, request.fileServiceId) &&
Objects.equals(sourceUri, request.sourceUri) &&
- Objects.equals(destinationUri, request.destinationUri) &&
Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp);
}
@Override
public int hashCode() {
- return Objects.hash(fileServiceId, sourceUri, destinationUri,
+ return Objects.hash(fileServiceId, sourceUri,
subscriptionId, serializedResultIntentForApp, version);
}
}
diff --git a/telephony/java/android/telephony/mbms/DownloadStateCallback.java b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
index 0508a38..86920bd 100644
--- a/telephony/java/android/telephony/mbms/DownloadStateCallback.java
+++ b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
@@ -16,14 +16,12 @@
package android.telephony.mbms;
-import android.os.Handler;
-import android.telephony.MbmsDownloadManager;
+import android.telephony.MbmsDownloadSession;
/**
* A optional listener class used by download clients to track progress. Apps should extend this
* class and pass an instance into
- * {@link android.telephony.MbmsDownloadManager#download(
- * DownloadRequest, DownloadStateCallback, Handler)}
+ * {@link MbmsDownloadSession#download(DownloadRequest)}
*
* This is optionally specified when requesting a download and will only be called while the app
* is running.
@@ -58,7 +56,7 @@
* may not have been able to get a list of them in advance.
* @param state The current state of the download.
*/
- public void onStateChanged(DownloadRequest request, FileInfo fileInfo,
- @MbmsDownloadManager.DownloadStatus int state) {
+ public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+ @MbmsDownloadSession.DownloadStatus int state) {
}
}
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 9300ef9..d8d7f48 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -58,7 +58,7 @@
FileServiceInfo(Parcel in) {
super(in);
files = new ArrayList<FileInfo>();
- in.readList(files, null);
+ in.readList(files, FileInfo.class.getClassLoader());
}
@Override
diff --git a/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl b/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl
index d62247b..cebc70d 100755
--- a/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl
@@ -29,8 +29,9 @@
* Gives progress callbacks for a given DownloadRequest. Includes a FileInfo
* as the list of files may not have been known at request-time.
*/
- void progress(in DownloadRequest request, in FileInfo fileInfo, int currentDownloadSize,
- int fullDownloadSize, int currentDecodedSize, int fullDecodedSize);
+ void onProgressUpdated(in DownloadRequest request, in FileInfo fileInfo,
+ int currentDownloadSize, int fullDownloadSize,
+ int currentDecodedSize, int fullDecodedSize);
- void state(in DownloadRequest request, in FileInfo fileInfo, int state);
+ void onStateUpdated(in DownloadRequest request, in FileInfo fileInfo, int state);
}
diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl
similarity index 80%
rename from telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
rename to telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl
index ac2f202..0d813a7 100755
--- a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl
@@ -24,11 +24,11 @@
* The interface the clients top-level file download listener will satisfy.
* @hide
*/
-oneway interface IMbmsDownloadManagerCallback
+oneway interface IMbmsDownloadSessionCallback
{
- void error(int errorCode, String message);
+ void onError(int errorCode, String message);
- void fileServicesUpdated(in List<FileServiceInfo> services);
+ void onFileServicesUpdated(in List<FileServiceInfo> services);
- void middlewareReady();
+ void onMiddlewareReady();
}
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
similarity index 80%
rename from telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
rename to telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
index 007aee7..0bf0ebc 100755
--- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
@@ -24,11 +24,11 @@
* The interface the clients top-level streaming listener will satisfy.
* @hide
*/
-oneway interface IMbmsStreamingManagerCallback
+oneway interface IMbmsStreamingSessionCallback
{
- void error(int errorCode, String message);
+ void onError(int errorCode, String message);
- void streamingServicesUpdated(in List<StreamingServiceInfo> services);
+ void onStreamingServicesUpdated(in List<StreamingServiceInfo> services);
- void middlewareReady();
+ void onMiddlewareReady();
}
diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
index 0952fbe..164cefb 100755
--- a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
+++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
@@ -20,9 +20,9 @@
* @hide
*/
oneway interface IStreamingServiceCallback {
- void error(int errorCode, String message);
- void streamStateUpdated(int state, int reason);
- void mediaDescriptionUpdated();
- void broadcastSignalStrengthUpdated(int signalStrength);
- void streamMethodUpdated(int methodType);
+ void onError(int errorCode, String message);
+ void onStreamStateUpdated(int state, int reason);
+ void onMediaDescriptionUpdated();
+ void onBroadcastSignalStrengthUpdated(int signalStrength);
+ void onStreamMethodUpdated(int methodType);
}
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
similarity index 67%
rename from telephony/java/android/telephony/mbms/InternalDownloadManagerCallback.java
rename to telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
index fe2d719..a7a5958 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java
@@ -22,19 +22,24 @@
import java.util.List;
/** @hide */
-public class InternalDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
+public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub {
private final Handler mHandler;
- private final MbmsDownloadManagerCallback mAppCallback;
+ private final MbmsDownloadSessionCallback mAppCallback;
+ private volatile boolean mIsStopped = false;
- public InternalDownloadManagerCallback(MbmsDownloadManagerCallback appCallback,
+ public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback,
Handler handler) {
mAppCallback = appCallback;
mHandler = handler;
}
@Override
- public void error(final int errorCode, final String message) throws RemoteException {
+ public void onError(final int errorCode, final String message) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -44,7 +49,11 @@
}
@Override
- public void fileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException {
+ public void onFileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -54,7 +63,11 @@
}
@Override
- public void middlewareReady() throws RemoteException {
+ public void onMiddlewareReady() throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -66,4 +79,8 @@
public Handler getHandler() {
return mHandler;
}
+
+ public void stop() {
+ mIsStopped = true;
+ }
}
diff --git a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java
index 32be16b..8702952 100644
--- a/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java
@@ -25,6 +25,7 @@
public class InternalDownloadStateCallback extends IDownloadStateCallback.Stub {
private final Handler mHandler;
private final DownloadStateCallback mAppCallback;
+ private volatile boolean mIsStopped = false;
public InternalDownloadStateCallback(DownloadStateCallback appCallback, Handler handler) {
mAppCallback = appCallback;
@@ -32,9 +33,13 @@
}
@Override
- public void progress(final DownloadRequest request, final FileInfo fileInfo,
+ public void onProgressUpdated(final DownloadRequest request, final FileInfo fileInfo,
final int currentDownloadSize, final int fullDownloadSize, final int currentDecodedSize,
final int fullDecodedSize) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -45,13 +50,21 @@
}
@Override
- public void state(final DownloadRequest request, final FileInfo fileInfo, final int state)
- throws RemoteException {
+ public void onStateUpdated(final DownloadRequest request, final FileInfo fileInfo,
+ final int state) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
- mAppCallback.onStateChanged(request, fileInfo, state);
+ mAppCallback.onStateUpdated(request, fileInfo, state);
}
});
}
+
+ public void stop() {
+ mIsStopped = true;
+ }
}
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
index bb337b2..eb6579ce 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java
@@ -23,6 +23,7 @@
public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub {
private final StreamingServiceCallback mAppCallback;
private final Handler mHandler;
+ private volatile boolean mIsStopped = false;
public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) {
mAppCallback = appCallback;
@@ -30,7 +31,11 @@
}
@Override
- public void error(int errorCode, String message) throws RemoteException {
+ public void onError(final int errorCode, final String message) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -40,7 +45,11 @@
}
@Override
- public void streamStateUpdated(int state, int reason) throws RemoteException {
+ public void onStreamStateUpdated(final int state, final int reason) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -50,7 +59,11 @@
}
@Override
- public void mediaDescriptionUpdated() throws RemoteException {
+ public void onMediaDescriptionUpdated() throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -60,7 +73,11 @@
}
@Override
- public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException {
+ public void onBroadcastSignalStrengthUpdated(final int signalStrength) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -70,7 +87,11 @@
}
@Override
- public void streamMethodUpdated(int methodType) throws RemoteException {
+ public void onStreamMethodUpdated(final int methodType) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -78,4 +99,8 @@
}
});
}
+
+ public void stop() {
+ mIsStopped = true;
+ }
}
diff --git a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
similarity index 68%
rename from telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java
rename to telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
index b52df8c..d782d12 100644
--- a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java
@@ -18,25 +18,27 @@
import android.os.Handler;
import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
-import android.telephony.mbms.StreamingServiceInfo;
import java.util.List;
/** @hide */
-public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
+public class InternalStreamingSessionCallback extends IMbmsStreamingSessionCallback.Stub {
private final Handler mHandler;
- private final MbmsStreamingManagerCallback mAppCallback;
+ private final MbmsStreamingSessionCallback mAppCallback;
+ private volatile boolean mIsStopped = false;
- public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback,
+ public InternalStreamingSessionCallback(MbmsStreamingSessionCallback appCallback,
Handler handler) {
mAppCallback = appCallback;
mHandler = handler;
}
@Override
- public void error(int errorCode, String message) throws RemoteException {
+ public void onError(final int errorCode, final String message) throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -46,8 +48,12 @@
}
@Override
- public void streamingServicesUpdated(List<StreamingServiceInfo> services)
+ public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services)
throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -57,7 +63,11 @@
}
@Override
- public void middlewareReady() throws RemoteException {
+ public void onMiddlewareReady() throws RemoteException {
+ if (mIsStopped) {
+ return;
+ }
+
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -69,4 +79,8 @@
public Handler getHandler() {
return mHandler;
}
+
+ public void stop() {
+ mIsStopped = true;
+ }
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 6a3e852..61415b5 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -25,7 +25,7 @@
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
-import android.telephony.MbmsDownloadManager;
+import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.vendor.VendorUtils;
import android.util.Log;
@@ -36,8 +36,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -114,8 +116,19 @@
@SystemApi
public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5;
+ /**
+ * Indicates that the manager was unable to notify the app of the completed download.
+ * This is a fatal result code and no result extras should be expected.
+ * @hide
+ */
+ @SystemApi
+ public static final int RESULT_APP_NOTIFICATION_ERROR = 6;
+
+
private static final String LOG_TAG = "MbmsDownloadReceiver";
private static final String TEMP_FILE_SUFFIX = ".embms.temp";
+ private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files";
+
private static final int MAX_TEMP_FILE_RETRIES = 5;
private String mFileProviderAuthorityCache = null;
@@ -148,11 +161,11 @@
private boolean verifyIntentContents(Context context, Intent intent) {
if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) {
- if (!intent.hasExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT)) {
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT)) {
Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
return false;
}
- if (!intent.hasExtra(VendorUtils.EXTRA_REQUEST)) {
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
return false;
}
@@ -160,7 +173,7 @@
Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring.");
return false;
}
- if (!intent.hasExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO)) {
+ if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO)) {
Log.w(LOG_TAG, "Download result did not include the associated file info. " +
"Ignoring.");
return false;
@@ -170,7 +183,8 @@
"temp file. Ignoring.");
return false;
}
- DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST);
+ DownloadRequest request = intent.getParcelableExtra(
+ MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX;
File expectedTokenFile = new File(
MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()),
@@ -210,20 +224,25 @@
}
private void moveDownloadedFile(Context context, Intent intent) {
- DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST);
+ DownloadRequest request = intent.getParcelableExtra(
+ MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
Intent intentForApp = request.getIntentForApp();
+ if (intentForApp == null) {
+ Log.i(LOG_TAG, "Malformed app notification intent");
+ setResultCode(RESULT_APP_NOTIFICATION_ERROR);
+ return;
+ }
- int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT,
- MbmsDownloadManager.RESULT_CANCELLED);
- intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_DOWNLOAD_RESULT, result);
+ int result = intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+ MbmsDownloadSession.RESULT_CANCELLED);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT, result);
- if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) {
+ if (result != MbmsDownloadSession.RESULT_SUCCESSFUL) {
Log.i(LOG_TAG, "Download request indicated a failed download. Aborting.");
context.sendBroadcast(intentForApp);
return;
}
- Uri destinationUri = request.getDestinationUri();
Uri finalTempFile = intent.getParcelableExtra(VendorUtils.EXTRA_FINAL_URI);
if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) {
Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile);
@@ -232,24 +251,31 @@
}
FileInfo completedFileInfo =
- (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO);
- String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo);
+ (FileInfo) intent.getParcelableExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO);
+ Path stagingDirectory = FileSystems.getDefault().getPath(
+ MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath(),
+ TEMP_FILE_STAGING_LOCATION);
- Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath);
- if (finalFileLocation == null) {
+ Uri stagedFileLocation;
+ try {
+ stagedFileLocation = stageTempFile(finalTempFile, stagingDirectory);
+ } catch (IOException e) {
Log.w(LOG_TAG, "Failed to move temp file to final destination");
setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR);
return;
}
- intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_COMPLETED_FILE_URI, finalFileLocation);
- intentForApp.putExtra(MbmsDownloadManager.EXTRA_MBMS_FILE_INFO, completedFileInfo);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_COMPLETED_FILE_URI,
+ stagedFileLocation);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_FILE_INFO, completedFileInfo);
+ intentForApp.putExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST, request);
context.sendBroadcast(intentForApp);
setResultCode(RESULT_OK);
}
private void cleanupPostMove(Context context, Intent intent) {
- DownloadRequest request = intent.getParcelableExtra(VendorUtils.EXTRA_REQUEST);
+ DownloadRequest request = intent.getParcelableExtra(
+ MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST);
if (request == null) {
Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring.");
return;
@@ -403,63 +429,22 @@
}
}
- private static String calculateDestinationFileRelativePath(DownloadRequest request,
- FileInfo info) {
- List<String> filePathComponents = info.getUri().getPathSegments();
- List<String> requestPathComponents = request.getSourceUri().getPathSegments();
- Iterator<String> filePathIter = filePathComponents.iterator();
- Iterator<String> requestPathIter = requestPathComponents.iterator();
-
- StringBuilder pathBuilder = new StringBuilder();
- // Iterate through the segments of the carrier's URI to the file, along with the segments
- // of the source URI specified in the download request. The relative path is calculated
- // as the tail of the file's URI that does not match the path segments in the source URI.
- while (filePathIter.hasNext()) {
- String currFilePathComponent = filePathIter.next();
- if (requestPathIter.hasNext()) {
- String requestFilePathComponent = requestPathIter.next();
- if (requestFilePathComponent.equals(currFilePathComponent)) {
- continue;
- }
- }
- pathBuilder.append(currFilePathComponent);
- pathBuilder.append('/');
- }
- // remove the trailing slash
- if (pathBuilder.length() > 0) {
- pathBuilder.deleteCharAt(pathBuilder.length() - 1);
- }
- return pathBuilder.toString();
- }
-
/*
- * Moves a tempfile located at fromPath to a new location at toPath. If
- * toPath is a directory, the destination file will be located at relativePath
- * underneath toPath.
+ * Moves a tempfile located at fromPath to a new location in the staging directory.
*/
- private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) {
+ private static Uri stageTempFile(Uri fromPath, Path stagingDirectory) throws IOException {
if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) {
Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme");
return null;
}
- if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) {
- Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme");
- return null;
- }
- File fromFile = new File(fromPath.getSchemeSpecificPart());
- File toFile = new File(toPath.getSchemeSpecificPart());
- if (toFile.isDirectory()) {
- toFile = new File(toFile, relativePath);
+ Path fromFile = FileSystems.getDefault().getPath(fromPath.getPath());
+ if (!Files.isDirectory(stagingDirectory)) {
+ Files.createDirectory(stagingDirectory);
}
- toFile.getParentFile().mkdirs();
+ Path result = Files.move(fromFile, stagingDirectory.resolve(fromFile.getFileName()));
- if (fromFile.renameTo(toFile)) {
- return Uri.fromFile(toFile);
- } else if (manualMove(fromFile, toFile)) {
- return Uri.fromFile(toFile);
- }
- return null;
+ return Uri.fromFile(result.toFile());
}
private static boolean verifyTempFilePath(Context context, String serviceId,
@@ -511,7 +496,7 @@
private String getMiddlewarePackageCached(Context context) {
if (mMiddlewarePackageNameCache == null) {
mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context,
- MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
+ MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION).packageName;
}
return mMiddlewarePackageNameCache;
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java
similarity index 75%
rename from telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
rename to telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java
index 24c039e..77dea6f 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java
@@ -16,7 +16,7 @@
package android.telephony.mbms;
-import android.telephony.MbmsDownloadManager;
+import android.telephony.MbmsDownloadSession;
import java.util.List;
@@ -24,11 +24,11 @@
* A callback class that apps should use to receive information on file downloads over
* cell-broadcast.
*/
-public class MbmsDownloadManagerCallback {
+public class MbmsDownloadSessionCallback {
/**
* Indicates that the middleware has encountered an asynchronous error.
- * @param errorCode Any error code listed in {@link MbmsException}
+ * @param errorCode Any error code listed in {@link MbmsErrors}
* @param message A message, intended for debugging purposes, describing the error in further
* detail.
*/
@@ -41,8 +41,9 @@
*
* This will only be called after the application has requested a list of file services and
* specified a service class list of interest via
- * {@link MbmsDownloadManager#getFileServices(List)}. If there are subsequent calls to
- * {@link MbmsDownloadManager#getFileServices(List)}, this method may not be called again if
+ * {@link MbmsDownloadSession#requestUpdateFileServices(List)}. If there are subsequent calls to
+ * {@link MbmsDownloadSession#requestUpdateFileServices(List)},
+ * this method may not be called again if
* the list of service classes would remain the same.
*
* @param services The most recently updated list of available file services.
@@ -55,9 +56,9 @@
* Called to indicate that the middleware has been initialized and is ready.
*
* Before this method is called, calling any method on an instance of
- * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException}
- * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
- * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+ * {@link MbmsDownloadSession} will result in an {@link IllegalStateException}
+ * being thrown or {@link #onError(int, String)} being called with error code
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
public void onMiddlewareReady() {
// default implementation empty
diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsErrors.java
similarity index 87%
rename from telephony/java/android/telephony/mbms/MbmsException.java
rename to telephony/java/android/telephony/mbms/MbmsErrors.java
index 56cd8de..af0af24 100644
--- a/telephony/java/android/telephony/mbms/MbmsException.java
+++ b/telephony/java/android/telephony/mbms/MbmsErrors.java
@@ -16,7 +16,9 @@
package android.telephony.mbms;
-public class MbmsException extends Exception {
+import android.telephony.MbmsStreamingSession;
+
+public class MbmsErrors {
/** Indicates that the operation was successful. */
public static final int SUCCESS = 0;
@@ -30,8 +32,8 @@
/**
* Indicates that the app attempted to perform an operation on an instance of
- * TODO link android.telephony.MbmsDownloadManager or
- * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware.
+ * {@link android.telephony.MbmsDownloadSession} or
+ * {@link MbmsStreamingSession} without being bound to the middleware.
*/
public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2;
@@ -46,8 +48,7 @@
private InitializationErrors() {}
/**
* Indicates that the app tried to create more than one instance each of
- * {@link android.telephony.MbmsStreamingManager} or
- * TODO link android.telephony.MbmsDownloadManager
+ * {@link MbmsStreamingSession} or {@link android.telephony.MbmsDownloadSession}.
*/
public static final int ERROR_DUPLICATE_INITIALIZE = 101;
/** Indicates that the app is not authorized to access media via MBMS.*/
@@ -64,8 +65,8 @@
private GeneralErrors() {}
/**
* Indicates that the app attempted to perform an operation before receiving notification
- * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()}
- * or TODO: link MbmsDownloadManagerCallback#middlewareReady
+ * that the middleware is ready via {@link MbmsStreamingSessionCallback#onMiddlewareReady()}
+ * or {@link MbmsDownloadSessionCallback#onMiddlewareReady()}.
*/
public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201;
/**
@@ -107,7 +108,7 @@
/**
* Indicates that the app called
- * {@link android.telephony.MbmsStreamingManager#startStreaming(
+ * {@link MbmsStreamingSession#startStreaming(
* StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)}
* more than once for the same {@link StreamingServiceInfo}.
*/
@@ -129,15 +130,5 @@
public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402;
}
- private final int mErrorCode;
-
- /** @hide */
- public MbmsException(int errorCode) {
- super();
- mErrorCode = errorCode;
- }
-
- public int getErrorCode() {
- return mErrorCode;
- }
+ private MbmsErrors() {}
}
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
similarity index 75%
rename from telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
rename to telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
index b31ffa7..5c130a0 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingSessionCallback.java
@@ -16,25 +16,26 @@
package android.telephony.mbms;
+import android.annotation.Nullable;
import android.content.Context;
-import android.os.RemoteException;
-import android.telephony.MbmsStreamingManager;
+import android.os.Handler;
+import android.telephony.MbmsStreamingSession;
import java.util.List;
/**
* A callback class that is used to receive information from the middleware on MBMS streaming
* services. An instance of this object should be passed into
- * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}.
+ * {@link MbmsStreamingSession#create(Context, MbmsStreamingSessionCallback, int, Handler)}.
*/
-public class MbmsStreamingManagerCallback {
+public class MbmsStreamingSessionCallback {
/**
* Called by the middleware when it has detected an error condition. The possible error codes
- * are listed in {@link MbmsException}.
+ * are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
- public void onError(int errorCode, String message) {
+ public void onError(int errorCode, @Nullable String message) {
// default implementation empty
}
@@ -47,8 +48,7 @@
* call with the same service class list would return different
* results.
*
- * @param services a List of StreamingServiceInfos
- *
+ * @param services The list of available services.
*/
public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
// default implementation empty
@@ -58,9 +58,9 @@
* Called to indicate that the middleware has been initialized and is ready.
*
* Before this method is called, calling any method on an instance of
- * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException}
- * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}
- * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+ * {@link MbmsStreamingSession} will result in an {@link IllegalStateException} or an error
+ * delivered via {@link #onError(int, String)} with error code
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}.
*/
public void onMiddlewareReady() {
// default implementation empty
diff --git a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
index 190ec8b..689becd 100644
--- a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
+++ b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java
@@ -27,7 +27,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
-import android.telephony.MbmsDownloadManager;
+import android.telephony.MbmsDownloadSession;
import java.io.File;
import java.io.FileNotFoundException;
@@ -181,7 +181,7 @@
return new File(storedTempFileRoot).getCanonicalFile();
} else {
return new File(context.getFilesDir(),
- MbmsDownloadManager.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();
+ MbmsDownloadSession.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY).getCanonicalFile();
}
} catch (IOException e) {
throw new RuntimeException("Unable to canonicalize temp file root path " + e);
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index 4b913f8..d38d8a7 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -68,19 +68,20 @@
return downloadServices.get(0).serviceInfo;
}
- public static void startBinding(Context context, String serviceAction,
- ServiceConnection serviceConnection) throws MbmsException {
+ public static int startBinding(Context context, String serviceAction,
+ ServiceConnection serviceConnection) {
Intent bindIntent = new Intent();
ServiceInfo mbmsServiceInfo =
MbmsUtils.getMiddlewareServiceInfo(context, serviceAction);
if (mbmsServiceInfo == null) {
- throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE);
+ return MbmsErrors.ERROR_NO_UNIQUE_MIDDLEWARE;
}
bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo));
context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
+ return MbmsErrors.SUCCESS;
}
/**
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 5764cfb..9a01ed0 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -16,6 +16,8 @@
package android.telephony.mbms;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -26,12 +28,13 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
/**
* Describes a cell-broadcast service. This class should not be instantiated directly -- use
- * {@link StreamingServiceInfo} or TODO link FileServiceInfo
+ * {@link StreamingServiceInfo} or {@link FileServiceInfo}
*/
public class ServiceInfo {
// arbitrary limit on the number of locale -> name pairs we support
@@ -58,6 +61,13 @@
if (newLocales.size() > MAP_LIMIT) {
throw new RuntimeException("bad locales length " + newLocales.size());
}
+
+ for (Locale l : newLocales) {
+ if (!newNames.containsKey(l)) {
+ throw new IllegalArgumentException("A name must be provided for each locale");
+ }
+ }
+
names = new HashMap(newNames.size());
names.putAll(newNames);
className = newClassName;
@@ -114,16 +124,25 @@
}
/**
- * User displayable names listed by language. Do not modify the map returned from this method.
+ * Get the user-displayable name for this cell-broadcast service corresponding to the
+ * provided {@link Locale}.
+ * @param locale The {@link Locale} in which you want the name of the service. This must be a
+ * value from the list returned by {@link #getLocales()} -- an
+ * {@link java.util.NoSuchElementException} may be thrown otherwise.
+ * @return The {@link CharSequence} providing the name of the service in the given
+ * {@link Locale}
*/
- public Map<Locale, String> getNames() {
- return names;
+ public @NonNull CharSequence getNameForLocale(@NonNull Locale locale) {
+ if (!names.containsKey(locale)) {
+ throw new NoSuchElementException("Locale not supported");
+ }
+ return names.get(locale);
}
/**
* The class name for this service - used to categorize and filter
*/
- public String getClassName() {
+ public String getServiceClassName() {
return className;
}
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index 1d66bac..ec9134a 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -17,8 +17,10 @@
package android.telephony.mbms;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.net.Uri;
import android.os.RemoteException;
+import android.telephony.MbmsStreamingSession;
import android.telephony.mbms.vendor.IMbmsStreamingService;
import android.util.Log;
@@ -27,7 +29,7 @@
/**
* Class used to represent a single MBMS stream. After a stream has been started with
- * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
* StreamingServiceCallback, android.os.Handler)},
* this class is used to hold information about the stream and control it.
*/
@@ -63,7 +65,7 @@
/**
* State changed due to a call to {@link #stopStreaming()} or
- * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo,
+ * {@link MbmsStreamingSession#startStreaming(StreamingServiceInfo,
* StreamingServiceCallback, android.os.Handler)}
*/
public static final int REASON_BY_USER_REQUEST = 1;
@@ -101,6 +103,7 @@
public final static int UNICAST_METHOD = 2;
private final int mSubscriptionId;
+ private final MbmsStreamingSession mParentSession;
private final StreamingServiceInfo mServiceInfo;
private final InternalStreamingServiceCallback mCallback;
@@ -111,25 +114,25 @@
*/
public StreamingService(int subscriptionId,
IMbmsStreamingService service,
+ MbmsStreamingSession session,
StreamingServiceInfo streamingServiceInfo,
InternalStreamingServiceCallback callback) {
mSubscriptionId = subscriptionId;
+ mParentSession = session;
mService = service;
mServiceInfo = streamingServiceInfo;
mCallback = callback;
}
/**
- * Retreive the Uri used to play this stream.
+ * Retrieve the Uri used to play this stream.
*
- * This may throw a {@link MbmsException} with the error code
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}.
*
- * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
- *
- * @return The {@link Uri} to pass to the streaming client.
+ * @return The {@link Uri} to pass to the streaming client, or {@code null} if an error
+ * occurred.
*/
- public Uri getPlaybackUri() throws MbmsException {
+ public @Nullable Uri getPlaybackUri() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
@@ -139,25 +142,26 @@
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+ mParentSession.onStreamingServiceStopped(this);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ return null;
}
}
/**
- * Retreive the info for this StreamingService.
+ * Retrieve the {@link StreamingServiceInfo} corresponding to this stream.
*/
public StreamingServiceInfo getInfo() {
return mServiceInfo;
}
/**
- * Stop streaming this service.
- * This may throw a {@link MbmsException} with the error code
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
+ * Stop streaming this service. Further operations on this object will fail with an
+ * {@link IllegalStateException}.
*
- * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
+ * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*/
- public void stopStreaming() throws MbmsException {
+ public void stopStreaming() {
if (mService == null) {
throw new IllegalStateException("No streaming service attached");
}
@@ -167,32 +171,22 @@
} catch (RemoteException e) {
Log.w(LOG_TAG, "Remote process died");
mService = null;
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
+ sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null);
+ } finally {
+ mParentSession.onStreamingServiceStopped(this);
}
}
- /**
- * Disposes of this stream. Further operations on this object will fail with an
- * {@link IllegalStateException}.
- *
- * This may throw a {@link MbmsException} with the error code
- * {@link MbmsException#ERROR_MIDDLEWARE_LOST}
- * May also throw an {@link IllegalStateException}
- */
- public void dispose() throws MbmsException {
- if (mService == null) {
- throw new IllegalStateException("No streaming service attached");
- }
+ /** @hide */
+ public InternalStreamingServiceCallback getCallback() {
+ return mCallback;
+ }
+ private void sendErrorToApp(int errorCode, String message) {
try {
- mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId());
+ mCallback.onError(errorCode, message);
} catch (RemoteException e) {
- Log.w(LOG_TAG, "Remote process died");
- throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST);
- } catch (IllegalArgumentException e) {
- throw new IllegalStateException("StreamingService state inconsistent with middleware");
- } finally {
- mService = null;
+ // Ignore, should not happen locally.
}
}
}
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
index b72c715..0903824 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -16,6 +16,8 @@
package android.telephony.mbms;
+import android.annotation.Nullable;
+
/**
* A callback class for use when the application is actively streaming content. The middleware
* will provide updates on the status of the stream via this callback.
@@ -33,11 +35,11 @@
/**
* Called by the middleware when it has detected an error condition in this stream. The
- * possible error codes are listed in {@link MbmsException}.
+ * possible error codes are listed in {@link MbmsErrors}.
* @param errorCode The error code.
* @param message A human-readable message generated by the middleware for debugging purposes.
*/
- public void onError(int errorCode, String message) {
+ public void onError(int errorCode, @Nullable String message) {
// default implementation empty
}
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
index f29499d..ed5e826 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -20,7 +20,7 @@
import android.net.Uri;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.FileInfo;
-import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.IMbmsDownloadSessionCallback;
import android.telephony.mbms.IDownloadStateCallback;
/**
@@ -28,13 +28,18 @@
*/
interface IMbmsDownloadService
{
- int initialize(int subId, IMbmsDownloadManagerCallback listener);
+ int initialize(int subId, IMbmsDownloadSessionCallback listener);
- int getFileServices(int subId, in List<String> serviceClasses);
+ int requestUpdateFileServices(int subId, in List<String> serviceClasses);
int setTempFileRootDirectory(int subId, String rootDirectoryPath);
- int download(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
+ int download(in DownloadRequest downloadRequest);
+
+ int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
+
+ int unregisterStateCallback(in DownloadRequest downloadRequest,
+ IDownloadStateCallback listener);
List<DownloadRequest> listPendingDownloads(int subscriptionId);
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
index 4dd4292..c90ffc7 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
@@ -17,7 +17,7 @@
package android.telephony.mbms.vendor;
import android.net.Uri;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IMbmsStreamingSessionCallback;
import android.telephony.mbms.IStreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
@@ -26,18 +26,16 @@
*/
interface IMbmsStreamingService
{
- int initialize(IMbmsStreamingManagerCallback listener, int subId);
+ int initialize(IMbmsStreamingSessionCallback callback, int subId);
- int getStreamingServices(int subId, in List<String> serviceClasses);
+ int requestUpdateStreamingServices(int subId, in List<String> serviceClasses);
int startStreaming(int subId, String serviceId,
- IStreamingServiceCallback listener);
+ IStreamingServiceCallback callback);
Uri getPlaybackUri(int subId, String serviceId);
void stopStreaming(int subId, String serviceId);
- void disposeStream(int subId, String serviceId);
-
void dispose(int subId);
}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index f38c93c..d845a57 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -20,17 +20,21 @@
import android.annotation.SystemApi;
import android.content.Intent;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
-import android.telephony.mbms.DownloadStateCallback;
+import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStateCallback;
import android.telephony.mbms.FileInfo;
import android.telephony.mbms.FileServiceInfo;
import android.telephony.mbms.IDownloadStateCallback;
-import android.telephony.mbms.IMbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsDownloadManagerCallback;
-import android.telephony.mbms.MbmsException;
+import android.telephony.mbms.IMbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsDownloadSessionCallback;
+import android.telephony.mbms.MbmsErrors;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Base class for MbmsDownloadService. The middleware should return an instance of this object from
@@ -39,21 +43,24 @@
*/
@SystemApi
public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+ private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
+ private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
+
/**
* Initialize the download service for this app and subId, registering the listener.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which
* will be intercepted and passed to the app as
- * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+ * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
- * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
- * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
- * {@link IMbmsDownloadManagerCallback#error(int, String)}.
+ * May return any value from {@link MbmsErrors.InitializationErrors}
+ * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+ * {@link IMbmsDownloadSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
- public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback)
+ public int initialize(int subscriptionId, MbmsDownloadSessionCallback callback)
throws RemoteException {
return 0;
}
@@ -64,7 +71,7 @@
*/
@Override
public final int initialize(final int subscriptionId,
- final IMbmsDownloadManagerCallback callback) throws RemoteException {
+ final IMbmsDownloadSessionCallback callback) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -73,11 +80,11 @@
}
}, 0);
- return initialize(subscriptionId, new MbmsDownloadManagerCallback() {
+ return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
@Override
public void onError(int errorCode, String message) {
try {
- callback.error(errorCode, message);
+ callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -86,7 +93,7 @@
@Override
public void onFileServicesUpdated(List<FileServiceInfo> services) {
try {
- callback.fileServicesUpdated(services);
+ callback.onFileServicesUpdated(services);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -95,7 +102,7 @@
@Override
public void onMiddlewareReady() {
try {
- callback.middlewareReady();
+ callback.onMiddlewareReady();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -106,7 +113,7 @@
/**
* Registers serviceClasses of interest with the appName/subId key.
* Starts async fetching data on streaming services of matching classes to be reported
- * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)}
+ * later via {@link IMbmsDownloadSessionCallback#onFileServicesUpdated(List)}
*
* Note that subsequent calls with the same uid and subId will replace
* the service class list.
@@ -117,11 +124,11 @@
* @param serviceClasses The service classes that the app wishes to get info on. The strings
* may contain arbitrary data as negotiated between the app and the
* carrier.
- * @return One of {@link MbmsException#SUCCESS} or
- * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
+ * @return One of {@link MbmsErrors#SUCCESS} or
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY},
*/
@Override
- public int getFileServices(int subscriptionId, List<String> serviceClasses)
+ public int requestUpdateFileServices(int subscriptionId, List<String> serviceClasses)
throws RemoteException {
return 0;
}
@@ -133,13 +140,13 @@
*
* If the calling app (as identified by the calling UID) currently has any pending download
* requests that have not been canceled, the middleware must return
- * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
+ * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here.
*
* @param subscriptionId The subscription id the download is operating under.
* @param rootDirectoryPath The path to the app's temp file root directory.
- * @return {@link MbmsException#SUCCESS},
- * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
- * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
+ * @return {@link MbmsErrors#SUCCESS},
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or
+ * {@link MbmsErrors.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}
*/
@Override
public int setTempFileRootDirectory(int subscriptionId,
@@ -155,12 +162,32 @@
* this is not the case, an {@link IllegalStateException} may be thrown.
*
* @param downloadRequest An object describing the set of files to be downloaded.
- * @param callback A callback through which the middleware can provide progress updates to
- * the app while both are still running.
- * @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors}
- * or {@link MbmsException#SUCCESS}
+ * @return Any error from {@link MbmsErrors.GeneralErrors}
+ * or {@link MbmsErrors#SUCCESS}
*/
- public int download(DownloadRequest downloadRequest, DownloadStateCallback callback) {
+ @Override
+ public int download(DownloadRequest downloadRequest) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Registers a download state callbacks for the provided {@link DownloadRequest}.
+ *
+ * This method is called by the app when it wants to request updates on the progress or
+ * status of the download.
+ *
+ * If the middleware is not aware of a download having been requested with the provided
+ *
+ * {@link DownloadRequest} in the past,
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
+ * must be returned.
+ *
+ * @param downloadRequest The {@link DownloadRequest} that was used to initiate the download
+ * for which progress updates are being requested.
+ * @param callback The callback object to use.
+ */
+ public int registerStateCallback(DownloadRequest downloadRequest,
+ DownloadStateCallback callback) throws RemoteException {
return 0;
}
@@ -169,36 +196,101 @@
* @hide
*/
@Override
- public final int download(DownloadRequest downloadRequest, IDownloadStateCallback callback)
+ public final int registerStateCallback(
+ final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
throws RemoteException {
final int uid = Binder.getCallingUid();
- callback.asBinder().linkToDeath(new DeathRecipient() {
+ DeathRecipient deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ mDownloadCallbackBinderMap.remove(callback.asBinder());
+ mDownloadCallbackDeathRecipients.remove(callback.asBinder());
}
- }, 0);
+ };
+ mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+ callback.asBinder().linkToDeath(deathRecipient, 0);
- return download(downloadRequest, new DownloadStateCallback() {
+ DownloadStateCallback exposedCallback = new DownloadStateCallback() {
@Override
public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
fullDecodedSize) {
try {
- callback.progress(request, fileInfo, currentDownloadSize, fullDownloadSize,
+ callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+ fullDownloadSize,
currentDecodedSize, fullDecodedSize);
} catch (RemoteException e) {
onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
}
}
- });
+
+ @Override
+ public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+ @MbmsDownloadSession.DownloadStatus int state) {
+ try {
+ callback.onStateUpdated(request, fileInfo, state);
+ } catch (RemoteException e) {
+ onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+ }
+ }
+ };
+
+ mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+
+ return registerStateCallback(downloadRequest, exposedCallback);
}
+ /**
+ * Un-registers a download state callbacks for the provided {@link DownloadRequest}.
+ *
+ * This method is called by the app when it no longer wants to request updates on the
+ * download.
+ *
+ * If the middleware is not aware of a download having been requested with the provided
+ * {@link DownloadRequest} in the past,
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}
+ * must be returned.
+ *
+ * @param downloadRequest The {@link DownloadRequest} that was used to register the callback
+ * @param callback The callback object that
+ * {@link #registerStateCallback(DownloadRequest, DownloadStateCallback)}
+ * was called with.
+ */
+ public int unregisterStateCallback(DownloadRequest downloadRequest,
+ DownloadStateCallback callback) throws RemoteException {
+ return 0;
+ }
+
+ /**
+ * Actual AIDL implementation -- hides the callback AIDL from the API.
+ * @hide
+ */
+ @Override
+ public final int unregisterStateCallback(
+ final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
+ throws RemoteException {
+ DeathRecipient deathRecipient =
+ mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+ if (deathRecipient == null) {
+ throw new IllegalArgumentException("Unknown callback");
+ }
+
+ callback.asBinder().unlinkToDeath(deathRecipient, 0);
+
+ DownloadStateCallback exposedCallback =
+ mDownloadCallbackBinderMap.remove(callback.asBinder());
+ if (exposedCallback == null) {
+ throw new IllegalArgumentException("Unknown callback");
+ }
+
+ return unregisterStateCallback(downloadRequest, exposedCallback);
+ }
/**
* Returns a list of pending {@link DownloadRequest}s that originated from the calling
* application, identified by its uid. A pending request is one that was issued via
- * {@link #download(DownloadRequest, DownloadStateCallback)} but not cancelled through
+ * {@link #download(DownloadRequest)} but not cancelled through
* {@link #cancelDownload(DownloadRequest)}.
* The middleware must return a non-null result synchronously or throw an exception
* inheriting from {@link RuntimeException}.
@@ -214,13 +306,13 @@
* Issues a request to cancel the specified download request.
*
* If the middleware is unable to cancel the request for whatever reason, it should return
- * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app
+ * synchronously with an error. If this method returns {@link MbmsErrors#SUCCESS}, the app
* will no longer be expecting any more file-completed intents from the middleware for this
* {@link DownloadRequest}.
* @param downloadRequest The request to cancel
- * @return {@link MbmsException#SUCCESS},
- * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
- * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
+ * @return {@link MbmsErrors#SUCCESS},
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST},
+ * {@link MbmsErrors.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}
*/
@Override
public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException {
@@ -232,7 +324,7 @@
*
* If the middleware has not yet been properly initialized or if it has no records of the
* file indicated by {@code fileInfo} being associated with {@code downloadRequest},
- * {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned.
+ * {@link MbmsDownloadSession#STATUS_UNKNOWN} must be returned.
*
* @param downloadRequest The download request to query.
* @param fileInfo The particular file within the request to get information on.
@@ -252,7 +344,7 @@
* In addition, current in-progress downloads must not be interrupted.
*
* If the middleware is not aware of the specified download request, return
- * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
+ * {@link MbmsErrors.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}.
*
* @param downloadRequest The request to re-download files for.
*/
@@ -266,7 +358,7 @@
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
- * {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used
+ * {@link #initialize(int, IMbmsDownloadSessionCallback)} should no longer be used
* after this method has been called by the app.
*
* Any download requests issued by the app should remain in effect until the app calls
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index 626a681..f8f370a 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -22,10 +22,10 @@
import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
-import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IMbmsStreamingSessionCallback;
import android.telephony.mbms.IStreamingServiceCallback;
-import android.telephony.mbms.MbmsException;
-import android.telephony.mbms.MbmsStreamingManagerCallback;
+import android.telephony.mbms.MbmsErrors;
+import android.telephony.mbms.MbmsStreamingSessionCallback;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceCallback;
import android.telephony.mbms.StreamingServiceInfo;
@@ -44,16 +44,16 @@
*
* May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which
* will be intercepted and passed to the app as
- * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
+ * {@link MbmsErrors.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE}
*
- * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors}
- * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via
- * {@link IMbmsStreamingManagerCallback#error(int, String)}.
+ * May return any value from {@link MbmsErrors.InitializationErrors}
+ * or {@link MbmsErrors#SUCCESS}. Non-successful error codes will be passed to the app via
+ * {@link IMbmsStreamingSessionCallback#onError(int, String)}.
*
* @param callback The callback to use to communicate with the app.
* @param subscriptionId The subscription ID to use.
*/
- public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId)
+ public int initialize(MbmsStreamingSessionCallback callback, int subscriptionId)
throws RemoteException {
return 0;
}
@@ -63,7 +63,7 @@
* @hide
*/
@Override
- public final int initialize(final IMbmsStreamingManagerCallback callback,
+ public final int initialize(final IMbmsStreamingSessionCallback callback,
final int subscriptionId) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@@ -73,20 +73,20 @@
}
}, 0);
- return initialize(new MbmsStreamingManagerCallback() {
+ return initialize(new MbmsStreamingSessionCallback() {
@Override
- public void onError(int errorCode, String message) {
+ public void onError(final int errorCode, final String message) {
try {
- callback.error(errorCode, message);
+ callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) {
+ public void onStreamingServicesUpdated(final List<StreamingServiceInfo> services) {
try {
- callback.streamingServicesUpdated(services);
+ callback.onStreamingServicesUpdated(services);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -95,7 +95,7 @@
@Override
public void onMiddlewareReady() {
try {
- callback.middlewareReady();
+ callback.onMiddlewareReady();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -107,7 +107,7 @@
/**
* Registers serviceClasses of interest with the appName/subId key.
* Starts async fetching data on streaming services of matching classes to be reported
- * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)}
+ * later via {@link IMbmsStreamingSessionCallback#onStreamingServicesUpdated(List)}
*
* Note that subsequent calls with the same uid and subId will replace
* the service class list.
@@ -118,11 +118,11 @@
* @param serviceClasses The service classes that the app wishes to get info on. The strings
* may contain arbitrary data as negotiated between the app and the
* carrier.
- * @return {@link MbmsException#SUCCESS} or any of the errors in
- * {@link android.telephony.mbms.MbmsException.GeneralErrors}
+ * @return {@link MbmsErrors#SUCCESS} or any of the errors in
+ * {@link MbmsErrors.GeneralErrors}
*/
@Override
- public int getStreamingServices(int subscriptionId,
+ public int requestUpdateStreamingServices(int subscriptionId,
List<String> serviceClasses) throws RemoteException {
return 0;
}
@@ -130,14 +130,14 @@
/**
* Starts streaming on a particular service. This method may perform asynchronous work. When
* the middleware is ready to send bits to the frontend, it should inform the app via
- * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+ * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
* @param subscriptionId The subscription id to use.
* @param serviceId The ID of the streaming service that the app has requested.
* @param callback The callback object on which the app wishes to receive updates.
- * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors}
+ * @return Any error in {@link MbmsErrors.GeneralErrors}
*/
public int startStreaming(int subscriptionId, String serviceId,
StreamingServiceCallback callback) throws RemoteException {
@@ -150,8 +150,8 @@
* @hide
*/
@Override
- public int startStreaming(int subscriptionId, String serviceId,
- IStreamingServiceCallback callback) throws RemoteException {
+ public int startStreaming(final int subscriptionId, String serviceId,
+ final IStreamingServiceCallback callback) throws RemoteException {
final int uid = Binder.getCallingUid();
callback.asBinder().linkToDeath(new DeathRecipient() {
@Override
@@ -162,19 +162,19 @@
return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
@Override
- public void onError(int errorCode, String message) {
+ public void onError(final int errorCode, final String message) {
try {
- callback.error(errorCode, message);
+ callback.onError(errorCode, message);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onStreamStateUpdated(@StreamingService.StreamingState int state,
- @StreamingService.StreamingStateChangeReason int reason) {
+ public void onStreamStateUpdated(@StreamingService.StreamingState final int state,
+ @StreamingService.StreamingStateChangeReason final int reason) {
try {
- callback.streamStateUpdated(state, reason);
+ callback.onStreamStateUpdated(state, reason);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -183,25 +183,25 @@
@Override
public void onMediaDescriptionUpdated() {
try {
- callback.mediaDescriptionUpdated();
+ callback.onMediaDescriptionUpdated();
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onBroadcastSignalStrengthUpdated(int signalStrength) {
+ public void onBroadcastSignalStrengthUpdated(final int signalStrength) {
try {
- callback.broadcastSignalStrengthUpdated(signalStrength);
+ callback.onBroadcastSignalStrengthUpdated(signalStrength);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
}
@Override
- public void onStreamMethodUpdated(int methodType) {
+ public void onStreamMethodUpdated(final int methodType) {
try {
- callback.streamMethodUpdated(methodType);
+ callback.onStreamMethodUpdated(methodType);
} catch (RemoteException e) {
onAppCallbackDied(uid, subscriptionId);
}
@@ -228,7 +228,11 @@
/**
* Stop streaming the stream identified by {@code serviceId}. Notification of the resulting
* stream state change should be reported to the app via
- * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}.
+ * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}.
+ *
+ * In addition, the callback provided via
+ * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
+ * used after this method has called by the app.
*
* May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
*
@@ -241,27 +245,10 @@
}
/**
- * Dispose of the stream identified by {@code serviceId} for the app identified by the
- * {@code appName} and {@code subscriptionId} arguments along with the caller's uid.
- * No notification back to the app is required for this operation, and the callback provided via
- * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be
- * used after this method has called by the app.
- *
- * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}
- *
- * @param subscriptionId The subscription id to use.
- * @param serviceId The ID of the streaming service that the app wishes to dispose of.
- */
- @Override
- public void disposeStream(int subscriptionId, String serviceId)
- throws RemoteException {
- }
-
- /**
* Signals that the app wishes to dispose of the session identified by the
* {@code subscriptionId} argument and the caller's uid. No notification back to the
* app is required for this operation, and the corresponding callback provided via
- * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used
+ * {@link #initialize(IMbmsStreamingSessionCallback, int)} should no longer be used
* after this method has been called by the app.
*
* May throw an {@link IllegalStateException}
diff --git a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
index 7b54aa8..8fb27b2 100644
--- a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
+++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
@@ -22,7 +22,7 @@
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.net.Uri;
-import android.telephony.mbms.DownloadRequest;
+import android.telephony.MbmsDownloadSession;
import android.telephony.mbms.MbmsDownloadReceiver;
import java.io.File;
@@ -39,9 +39,9 @@
/**
* The MBMS middleware should send this when a download of single file has completed or
* failed. Mandatory extras are
- * {@link android.telephony.MbmsDownloadManager#EXTRA_MBMS_DOWNLOAD_RESULT}
- * {@link android.telephony.MbmsDownloadManager#EXTRA_MBMS_FILE_INFO}
- * {@link #EXTRA_REQUEST}
+ * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+ * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
+ * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
* {@link #EXTRA_TEMP_LIST}
* {@link #EXTRA_FINAL_URI}
*/
@@ -121,12 +121,6 @@
"android.telephony.mbms.extra.TEMP_FILES_IN_USE";
/**
- * Extra containing the {@link DownloadRequest} for which the download result or file
- * descriptor request is for. Must not be null.
- */
- public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST";
-
- /**
* Extra containing a single {@link Uri} indicating the path to the temp file in which the
* decoded downloaded file resides. Must not be null.
*/
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 36abfc9..489c208 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -19,7 +19,9 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PersistableBundle;
import android.telecom.VideoProfile;
+import android.util.Log;
import com.android.internal.telephony.PhoneConstants;
@@ -216,6 +218,29 @@
public int mServiceType;
public int mCallType;
public int mRestrictCause = CALL_RESTRICT_CAUSE_NONE;
+
+ /**
+ * Extras associated with this {@link ImsCallProfile}.
+ * <p>
+ * Valid data types include:
+ * <ul>
+ * <li>{@link Integer} (and int)</li>
+ * <li>{@link Long} (and long)</li>
+ * <li>{@link Double} (and double)</li>
+ * <li>{@link String}</li>
+ * <li>{@code int[]}</li>
+ * <li>{@code long[]}</li>
+ * <li>{@code double[]}</li>
+ * <li>{@code String[]}</li>
+ * <li>{@link PersistableBundle}</li>
+ * <li>{@link Boolean} (and boolean)</li>
+ * <li>{@code boolean[]}</li>
+ * <li>Other {@link Parcelable} classes in the {@code android.*} namespace.</li>
+ * </ul>
+ * <p>
+ * Invalid types will be removed when the {@link ImsCallProfile} is parceled for transmit across
+ * a {@link android.os.Binder}.
+ */
public Bundle mCallExtras;
public ImsStreamMediaProfile mMediaProfile;
@@ -315,16 +340,17 @@
@Override
public void writeToParcel(Parcel out, int flags) {
+ Bundle filteredExtras = maybeCleanseExtras(mCallExtras);
out.writeInt(mServiceType);
out.writeInt(mCallType);
- out.writeParcelable(mCallExtras, 0);
+ out.writeBundle(filteredExtras);
out.writeParcelable(mMediaProfile, 0);
}
private void readFromParcel(Parcel in) {
mServiceType = in.readInt();
mCallType = in.readInt();
- mCallExtras = in.readParcelable(null);
+ mCallExtras = in.readBundle();
mMediaProfile = in.readParcelable(null);
}
@@ -465,6 +491,31 @@
}
/**
+ * Cleanses a {@link Bundle} to ensure that it contains only data of type:
+ * 1. Primitive data types (e.g. int, bool, and other values determined by
+ * {@link android.os.PersistableBundle#isValidType(Object)}).
+ * 2. Other Bundles.
+ * 3. {@link Parcelable} objects in the {@code android.*} namespace.
+ * @param extras the source {@link Bundle}
+ * @return where all elements are valid types the source {@link Bundle} is returned unmodified,
+ * otherwise a copy of the {@link Bundle} with the invalid elements is returned.
+ */
+ private Bundle maybeCleanseExtras(Bundle extras) {
+ if (extras == null) {
+ return null;
+ }
+
+ int startSize = extras.size();
+ Bundle filtered = extras.filterValues();
+ int endSize = filtered.size();
+ if (startSize != endSize) {
+ Log.i(TAG, "maybeCleanseExtras: " + (startSize - endSize) + " extra values were "
+ + "removed - only primitive types and system parcelables are permitted.");
+ }
+ return filtered;
+ }
+
+ /**
* Determines if a video state is set in a video state bit-mask.
*
* @param videoState The video state bit mask.
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 629173d..7a53ef6 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -161,7 +161,7 @@
// Second byte is the MSG_LEN, length of the message
// See 3GPP2 C.S0023 3.4.27
- int size = data[1];
+ int size = data[1] & 0xFF;
// Note: Data may include trailing FF's. That's OK; message
// should still parse correctly.
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
index 2fbf7ed..bd8c83e 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -17,6 +17,7 @@
package com.android.internal.telephony.gsm;
import android.telephony.PhoneNumberUtils;
+
import java.text.ParseException;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsAddress;
@@ -71,8 +72,11 @@
// Make sure the final unused BCD digit is 0xf
origBytes[length - 1] |= 0xf0;
}
- address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
- OFFSET_TOA, length - OFFSET_TOA);
+ address = PhoneNumberUtils.calledPartyBCDToString(
+ origBytes,
+ OFFSET_TOA,
+ length - OFFSET_TOA,
+ PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
// And restore origBytes
origBytes[length - 1] = lastByte;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index d4098d9..1ca19e0 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -535,8 +535,8 @@
} else {
// SC address
try {
- ret = PhoneNumberUtils
- .calledPartyBCDToString(mPdu, mCur, len);
+ ret = PhoneNumberUtils.calledPartyBCDToString(
+ mPdu, mCur, len, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
} catch (RuntimeException tr) {
Rlog.d(LOG_TAG, "invalid SC address: ", tr);
ret = null;
diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java
index 541f91ad..22d88fb 100644
--- a/tests/net/java/android/net/ip/IpManagerTest.java
+++ b/tests/net/java/android/net/ip/IpManagerTest.java
@@ -180,7 +180,8 @@
// Add N - 1 addresses
for (int i = 0; i < lastAddr; i++) {
mObserver.addressUpdated(iface, new LinkAddress(addresses[i]));
- verify(mCb, timeout(100).times(1)).onLinkPropertiesChange(any());
+ verify(mCb, timeout(100)).onLinkPropertiesChange(any());
+ reset(mCb);
}
// Add Nth address
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index 9115378..0a5a6aa 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -60,7 +60,7 @@
NsdManager mManager;
- long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+ long mTimeoutMs = 200; // non-final so that tests can adjust the value.
@Before
public void setUp() throws Exception {
@@ -74,7 +74,7 @@
@After
public void tearDown() throws Exception {
- waitForIdleHandler(mServiceHandler, mTimeoutMs);
+ mServiceHandler.waitForIdle(mTimeoutMs);
mServiceHandler.chan.disconnect();
mServiceHandler.stop();
if (mManager != null) {
@@ -334,9 +334,10 @@
}
int verifyRequest(int expectedMessageType) {
+ mServiceHandler.waitForIdle(mTimeoutMs);
verify(mServiceHandler, timeout(mTimeoutMs)).handleMessage(any());
reset(mServiceHandler);
- Message received = mServiceHandler.lastMessage;
+ Message received = mServiceHandler.getLastMessage();
assertEquals(NsdManager.nameOf(expectedMessageType), NsdManager.nameOf(received.what));
return received.arg2;
}
@@ -347,31 +348,43 @@
// Implements the server side of AsyncChannel connection protocol
public static class MockServiceHandler extends Handler {
- public Context mContext;
+ public final Context context;
public AsyncChannel chan;
- public volatile Message lastMessage;
+ public Message lastMessage;
- MockServiceHandler(Looper looper, Context context) {
- super(looper);
- mContext = context;
+ MockServiceHandler(Looper l, Context c) {
+ super(l);
+ context = c;
+ }
+
+ synchronized Message getLastMessage() {
+ return lastMessage;
+ }
+
+ synchronized void setLastMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ }
+
+ void waitForIdle(long timeoutMs) {
+ waitForIdleHandler(this, timeoutMs);
}
@Override
public void handleMessage(Message msg) {
- lastMessage = obtainMessage();
- lastMessage.copyFrom(msg);
+ setLastMessage(msg);
if (msg.what == AsyncChannel.CMD_CHANNEL_FULL_CONNECTION) {
chan = new AsyncChannel();
- chan.connect(mContext, this, msg.replyTo);
+ chan.connect(context, this, msg.replyTo);
chan.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
}
}
- public void stop() {
+ void stop() {
getLooper().quitSafely();
}
- public static MockServiceHandler create(Context context) {
+ static MockServiceHandler create(Context context) {
HandlerThread t = new HandlerThread("mock-service-handler");
t.start();
return new MockServiceHandler(t.getLooper(), context);
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
new file mode 100644
index 0000000..7a2344317
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.Objects;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RingBufferTest {
+
+ @Test
+ public void testEmptyRingBuffer() {
+ RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);
+
+ assertArraysEqual(new String[0], buffer.toArray());
+ }
+
+ @Test
+ public void testIncorrectConstructorArguments() {
+ try {
+ RingBuffer<String> buffer = new RingBuffer<>(String.class, -10);
+ fail("Should not be able to create a negative capacity RingBuffer");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ RingBuffer<String> buffer = new RingBuffer<>(String.class, 0);
+ fail("Should not be able to create a 0 capacity RingBuffer");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testRingBufferWithNoWrapping() {
+ RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);
+
+ buffer.append("a");
+ buffer.append("b");
+ buffer.append("c");
+ buffer.append("d");
+ buffer.append("e");
+
+ String[] expected = {"a", "b", "c", "d", "e"};
+ assertArraysEqual(expected, buffer.toArray());
+ }
+
+ @Test
+ public void testRingBufferWithCapacity1() {
+ RingBuffer<String> buffer = new RingBuffer<>(String.class, 1);
+
+ buffer.append("a");
+ assertArraysEqual(new String[]{"a"}, buffer.toArray());
+
+ buffer.append("b");
+ assertArraysEqual(new String[]{"b"}, buffer.toArray());
+
+ buffer.append("c");
+ assertArraysEqual(new String[]{"c"}, buffer.toArray());
+
+ buffer.append("d");
+ assertArraysEqual(new String[]{"d"}, buffer.toArray());
+
+ buffer.append("e");
+ assertArraysEqual(new String[]{"e"}, buffer.toArray());
+ }
+
+ @Test
+ public void testRingBufferWithWrapping() {
+ int capacity = 100;
+ RingBuffer<String> buffer = new RingBuffer<>(String.class, capacity);
+
+ buffer.append("a");
+ buffer.append("b");
+ buffer.append("c");
+ buffer.append("d");
+ buffer.append("e");
+
+ String[] expected1 = {"a", "b", "c", "d", "e"};
+ assertArraysEqual(expected1, buffer.toArray());
+
+ String[] expected2 = new String[capacity];
+ int firstIndex = 0;
+ int lastIndex = capacity - 1;
+
+ expected2[firstIndex] = "e";
+ for (int i = 1; i < capacity; i++) {
+ buffer.append("x");
+ expected2[i] = "x";
+ }
+ assertArraysEqual(expected2, buffer.toArray());
+
+ buffer.append("x");
+ expected2[firstIndex] = "x";
+ assertArraysEqual(expected2, buffer.toArray());
+
+ for (int i = 0; i < 10; i++) {
+ for (String s : expected2) {
+ buffer.append(s);
+ }
+ }
+ assertArraysEqual(expected2, buffer.toArray());
+
+ buffer.append("a");
+ expected2[lastIndex] = "a";
+ assertArraysEqual(expected2, buffer.toArray());
+ }
+
+ static <T> void assertArraysEqual(T[] expected, T[] got) {
+ if (expected.length != got.length) {
+ fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
+ + " did not have the same length");
+ }
+
+ for (int i = 0; i < expected.length; i++) {
+ if (!Objects.equals(expected[i], got[i])) {
+ fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
+ + " were not equal");
+ }
+ }
+ }
+}
diff --git a/tests/net/java/com/android/internal/util/TestUtils.java b/tests/net/java/com/android/internal/util/TestUtils.java
index c9fa340..6db01d3 100644
--- a/tests/net/java/com/android/internal/util/TestUtils.java
+++ b/tests/net/java/com/android/internal/util/TestUtils.java
@@ -30,8 +30,7 @@
* Block until the given Handler thread becomes idle, or until timeoutMs has passed.
*/
public static void waitForIdleHandler(HandlerThread handlerThread, long timeoutMs) {
- // TODO: convert to getThreadHandler once it is available on aosp
- waitForIdleLooper(handlerThread.getLooper(), timeoutMs);
+ waitForIdleHandler(handlerThread.getThreadHandler(), timeoutMs);
}
/**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8816d43..9c498c7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -19,6 +19,8 @@
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -683,7 +685,8 @@
public WrappedNetworkMonitor(Context context, Handler handler,
NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
IpConnectivityLog log) {
- super(context, handler, networkAgentInfo, defaultRequest, log);
+ super(context, handler, networkAgentInfo, defaultRequest, log,
+ NetworkMonitor.NetworkMonitorSettings.DEFAULT);
}
@Override
@@ -781,6 +784,13 @@
return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
}
+ @Override
+ public boolean hasService(String name) {
+ // Currenty, the only relevant service that ConnectivityService checks for is
+ // ETHERNET_SERVICE.
+ return Context.ETHERNET_SERVICE.equals(name);
+ }
+
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
return mLastCreatedNetworkMonitor;
}
@@ -928,6 +938,13 @@
// will fail. Failing here is much easier to debug.
assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
+ assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
+ assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
+
+ // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
+ // mocks, this assert exercises the ConnectivityService code path that ensures that
+ // TYPE_ETHERNET is supported if the ethernet service is running.
+ assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET));
}
@SmallTest
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index eff04ab..2624176 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity;
+import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
+import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
import static com.android.server.connectivity.MetricsTestUtil.aBool;
import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
import static com.android.server.connectivity.MetricsTestUtil.aLong;
@@ -31,29 +33,41 @@
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.ETHERNET;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.MULTIPLE;
import static com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.WIFI;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
+import android.net.metrics.ConnectStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
import android.net.metrics.DnsEvent;
+import android.net.metrics.DnsEvent;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
+import android.net.metrics.WakeupStats;
+import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
import java.util.Arrays;
import java.util.List;
-import junit.framework.TestCase;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
-public class IpConnectivityEventBuilderTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityEventBuilderTest {
- @SmallTest
+ @Test
public void testLinkLayerInferrence() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
@@ -182,7 +196,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testDefaultNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DefaultNetworkEvent.class),
@@ -223,7 +237,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testDhcpClientEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpClientEvent.class),
@@ -249,7 +263,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testDhcpErrorEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpErrorEvent.class),
@@ -274,7 +288,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testIpManagerEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpManagerEvent.class),
@@ -300,7 +314,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testIpReachabilityEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
@@ -324,7 +338,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(NetworkEvent.class),
@@ -353,7 +367,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testValidationProbeEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ValidationProbeEvent.class),
@@ -380,7 +394,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testApfProgramEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfProgramEvent.class),
@@ -414,7 +428,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testApfStatsSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfStats.class),
@@ -457,7 +471,7 @@
verifySerialization(want, ev);
}
- @SmallTest
+ @Test
public void testRaEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(RaEvent.class),
@@ -490,11 +504,49 @@
verifySerialization(want, ev);
}
+ @Test
+ public void testWakeupStatsSerialization() {
+ WakeupStats stats = new WakeupStats("wlan0");
+ stats.totalWakeups = 14;
+ stats.applicationWakeups = 5;
+ stats.nonApplicationWakeups = 1;
+ stats.rootWakeups = 2;
+ stats.systemWakeups = 3;
+ stats.noUidWakeups = 3;
+
+ IpConnectivityEvent got = IpConnectivityEventBuilder.toProto(stats);
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 4",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 5",
+ " duration_sec: 0",
+ " no_uid_wakeups: 3",
+ " non_application_wakeups: 1",
+ " root_wakeups: 2",
+ " system_wakeups: 3",
+ " total_wakeups: 14",
+ " >",
+ ">",
+ "version: 2\n");
+
+ verifySerialization(want, got);
+ }
+
static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
+ List<IpConnectivityEvent> protoInput =
+ IpConnectivityEventBuilder.toProto(Arrays.asList(input));
+ verifySerialization(want, protoInput.toArray(new IpConnectivityEvent[0]));
+ }
+
+ static void verifySerialization(String want, IpConnectivityEvent... input) {
try {
- List<IpConnectivityEvent> proto =
- IpConnectivityEventBuilder.toProto(Arrays.asList(input));
- byte[] got = IpConnectivityEventBuilder.serialize(0, proto);
+ byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input));
IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString());
} catch (Exception e) {
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index cc18b7f..a395c48 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -224,6 +224,15 @@
dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
+ // iface, uid
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10123);
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10008);
+ wakeupEvent("wlan0", -1);
+ wakeupEvent("wlan0", 10008);
+ wakeupEvent("rmnet0", 1000);
+
String want = String.join("\n",
"dropped_events: 0",
"events <",
@@ -405,6 +414,38 @@
" return_codes: 0",
" >",
">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 2",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 2",
+ " duration_sec: 0",
+ " no_uid_wakeups: 0",
+ " non_application_wakeups: 0",
+ " root_wakeups: 0",
+ " system_wakeups: 1",
+ " total_wakeups: 3",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 4",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 1",
+ " duration_sec: 0",
+ " no_uid_wakeups: 1",
+ " non_application_wakeups: 0",
+ " root_wakeups: 0",
+ " system_wakeups: 2",
+ " total_wakeups: 4",
+ " >",
+ ">",
"version: 2\n");
verifySerialization(want, getdump("flush"));
@@ -425,6 +466,11 @@
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
+ void wakeupEvent(String iface, int uid) throws Exception {
+ String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+ mNetdListener.onWakeupEvent(prefix, uid, uid, 0);
+ }
+
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index e3f46a4..dfe31bd 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -85,6 +85,32 @@
}
@Test
+ public void testRequiresClat() throws Exception {
+ final int[] supportedTypes = {
+ ConnectivityManager.TYPE_MOBILE,
+ ConnectivityManager.TYPE_WIFI,
+ ConnectivityManager.TYPE_ETHERNET,
+ };
+
+ // NetworkInfo doesn't allow setting the State directly, but rather
+ // requires setting DetailedState in order set State as a side-effect.
+ final NetworkInfo.DetailedState[] supportedDetailedStates = {
+ NetworkInfo.DetailedState.CONNECTED,
+ NetworkInfo.DetailedState.SUSPENDED,
+ };
+
+ for (int type : supportedTypes) {
+ mNai.networkInfo.setType(type);
+ for (NetworkInfo.DetailedState state : supportedDetailedStates) {
+ mNai.networkInfo.setDetailedState(state, "reason", "extraInfo");
+ assertTrue(
+ String.format("requiresClat expected for type=%d state=%s", type, state),
+ Nat464Xlat.requiresClat(mNai));
+ }
+ }
+ }
+
+ @Test
public void testNormalStartAndStop() throws Exception {
Nat464Xlat nat = makeNat464Xlat();
ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class);
@@ -100,7 +126,6 @@
mLooper.dispatchNext();
verify(mNms).getInterfaceConfig(eq(STACKED_IFACE));
- verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(false));
verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
assertFalse(c.getValue().getStackedLinks().isEmpty());
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -110,7 +135,6 @@
nat.stop();
verify(mNms).stopClatd(eq(BASE_IFACE));
- verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(true));
// Stacked interface removed notification arrives.
nat.interfaceRemoved(STACKED_IFACE);
@@ -141,7 +165,6 @@
mLooper.dispatchNext();
verify(mNms).getInterfaceConfig(eq(STACKED_IFACE));
- verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(false));
verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertFalse(c.getValue().getStackedLinks().isEmpty());
assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -153,7 +176,6 @@
verify(mNms).unregisterObserver(eq(nat));
verify(mNms).stopClatd(eq(BASE_IFACE));
- verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(true));
verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
assertTrue(c.getValue().getStackedLinks().isEmpty());
assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 46f395e..6723601 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -19,6 +19,7 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -37,9 +38,11 @@
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.DNSLookupBatch;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
+
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -47,6 +50,7 @@
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,6 +79,118 @@
}
@Test
+ public void testWakeupEventLogging() throws Exception {
+ final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
+
+ // Assert no events
+ String[] events1 = listNetdEvent();
+ assertEquals(new String[]{""}, events1);
+
+ long now = System.currentTimeMillis();
+ String prefix = "iface:wlan0";
+ int[] uids = { 10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004 };
+ for (int uid : uids) {
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
+ }
+
+ String[] events2 = listNetdEvent();
+ int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line
+ assertEquals(expectedLength2, events2.length);
+ assertContains(events2[0], "WakeupStats");
+ assertContains(events2[0], "wlan0");
+ for (int i = 0; i < uids.length; i++) {
+ String got = events2[i+1];
+ assertContains(got, "WakeupEvent");
+ assertContains(got, "wlan0");
+ assertContains(got, "uid: " + uids[i]);
+ }
+
+ int uid = 20000;
+ for (int i = 0; i < BUFFER_LENGTH * 2; i++) {
+ long ts = now + 10;
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts);
+ }
+
+ String[] events3 = listNetdEvent();
+ int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line
+ assertEquals(expectedLength3, events3.length);
+ assertContains(events2[0], "WakeupStats");
+ assertContains(events2[0], "wlan0");
+ for (int i = 1; i < expectedLength3; i++) {
+ String got = events3[i];
+ assertContains(got, "WakeupEvent");
+ assertContains(got, "wlan0");
+ assertContains(got, "uid: " + uid);
+ }
+
+ uid = 45678;
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now);
+
+ String[] events4 = listNetdEvent();
+ String lastEvent = events4[events4.length - 1];
+ assertContains(lastEvent, "WakeupEvent");
+ assertContains(lastEvent, "wlan0");
+ assertContains(lastEvent, "uid: " + uid);
+ }
+
+ @Test
+ public void testWakeupStatsLogging() throws Exception {
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10123);
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("rmnet0", 10008);
+ wakeupEvent("wlan0", -1);
+ wakeupEvent("wlan0", 10008);
+ wakeupEvent("rmnet0", 1000);
+ wakeupEvent("wlan0", 10004);
+ wakeupEvent("wlan0", 1000);
+ wakeupEvent("wlan0", 0);
+ wakeupEvent("wlan0", -1);
+ wakeupEvent("rmnet0", 10052);
+ wakeupEvent("wlan0", 0);
+ wakeupEvent("rmnet0", 1000);
+ wakeupEvent("wlan0", 1010);
+
+ String got = flushStatistics();
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 2",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 3",
+ " duration_sec: 0",
+ " no_uid_wakeups: 0",
+ " non_application_wakeups: 0",
+ " root_wakeups: 0",
+ " system_wakeups: 2",
+ " total_wakeups: 5",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 4",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " wakeup_stats <",
+ " application_wakeups: 2",
+ " duration_sec: 0",
+ " no_uid_wakeups: 2",
+ " non_application_wakeups: 1",
+ " root_wakeups: 2",
+ " system_wakeups: 3",
+ " total_wakeups: 10",
+ " >",
+ ">",
+ "version: 2\n");
+ assertEquals(want, got);
+ }
+
+ @Test
public void testDnsLogging() throws Exception {
asyncDump(100);
@@ -297,6 +413,11 @@
mNetdEventListenerService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
+ void wakeupEvent(String iface, int uid) throws Exception {
+ String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
+ mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, 0);
+ }
+
void asyncDump(long durationMs) throws Exception {
final long stop = System.currentTimeMillis() + durationMs;
final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
@@ -329,4 +450,15 @@
}
return log.toString();
}
+
+ String[] listNetdEvent() throws Exception {
+ StringWriter buffer = new StringWriter();
+ PrintWriter writer = new PrintWriter(buffer);
+ mNetdEventListenerService.list(writer);
+ return buffer.toString().split("\\n");
+ }
+
+ static void assertContains(String got, String want) {
+ assertTrue(got + " did not contain \"" + want + "\"", got.contains(want));
+ }
}
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
new file mode 100644
index 0000000..27a897d
--- /dev/null
+++ b/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.metrics.IpConnectivityLog;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.TelephonyManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkMonitorTest {
+
+ static final int TEST_ID = 60; // should be less than min netid 100
+
+ @Mock Context mContext;
+ @Mock Handler mHandler;
+ @Mock IpConnectivityLog mLogger;
+ @Mock NetworkAgentInfo mAgent;
+ @Mock NetworkMonitor.NetworkMonitorSettings mSettings;
+ @Mock NetworkRequest mRequest;
+ @Mock TelephonyManager mTelephony;
+ @Mock WifiManager mWifi;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mAgent.network()).thenReturn(new Network(TEST_ID));
+ when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
+ when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
+ }
+
+ NetworkMonitor makeMonitor() {
+ return new NetworkMonitor(mContext, mHandler, mAgent, mRequest, mLogger, mSettings);
+ }
+
+ @Test
+ public void testCreatingNetworkMonitor() {
+ NetworkMonitor monitor = makeMonitor();
+ }
+}
+
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 506d9e5..4f18da7 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,13 +27,17 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.net.NetworkInfo.DetailedState;
import android.net.UidRange;
-import android.os.Build;
+import android.net.VpnService;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.UserHandle;
@@ -43,24 +47,25 @@
import android.util.ArrayMap;
import android.util.ArraySet;
+import com.android.internal.R;
import com.android.internal.net.VpnConfig;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Set;
-
import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
/**
* Tests for {@link Vpn}.
*
* Build, install and run with:
- * runtest --path src/com/android/server/connectivity/VpnTest.java
+ * runtest --path java/com/android/server/connectivity/VpnTest.java
*/
public class VpnTest extends AndroidTestCase {
private static final String TAG = "VpnTest";
@@ -113,10 +118,13 @@
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
.thenReturn(mNotificationManager);
+ when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
+ .thenReturn(Resources.getSystem().getString(
+ R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
// Used by {@link Notification.Builder}
ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+ applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
doNothing().when(mNetService).registerObserver(any());
@@ -315,6 +323,40 @@
}
@SmallTest
+ public void testIsAlwaysOnPackageSupported() throws Exception {
+ final Vpn vpn = createVpn(primaryUser.id);
+
+ ApplicationInfo appInfo = new ApplicationInfo();
+ when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(primaryUser.id)))
+ .thenReturn(appInfo);
+
+ ServiceInfo svcInfo = new ServiceInfo();
+ ResolveInfo resInfo = new ResolveInfo();
+ resInfo.serviceInfo = svcInfo;
+ when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
+ eq(primaryUser.id)))
+ .thenReturn(Collections.singletonList(resInfo));
+
+ // null package name should return false
+ assertFalse(vpn.isAlwaysOnPackageSupported(null));
+
+ // Pre-N apps are not supported
+ appInfo.targetSdkVersion = VERSION_CODES.M;
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+
+ // N+ apps are supported by default
+ appInfo.targetSdkVersion = VERSION_CODES.N;
+ assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+
+ // Apps that opt out explicitly are not supported
+ appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
+ Bundle metaData = new Bundle();
+ metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
+ svcInfo.metaData = metaData;
+ assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+ }
+
+ @SmallTest
public void testNotificationShownForAlwaysOnApp() {
final UserHandle userHandle = UserHandle.of(primaryUser.id);
final Vpn vpn = createVpn(primaryUser.id);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 2199a13..2a8efc0 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -676,6 +676,35 @@
offload.setUpstreamLinkProperties(upstreamLp);
}
+ // Pretend that some local prefixes and downstreams have been added
+ // (and removed, for good measure).
+ final Set<IpPrefix> minimumLocalPrefixes = new HashSet<>();
+ for (String s : new String[]{
+ "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64"}) {
+ minimumLocalPrefixes.add(new IpPrefix(s));
+ }
+ offload.setLocalPrefixes(minimumLocalPrefixes);
+
+ final LinkProperties usbLinkProperties = new LinkProperties();
+ usbLinkProperties.setInterfaceName(RNDIS0);
+ usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
+ usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ offload.notifyDownstreamLinkProperties(usbLinkProperties);
+
+ final LinkProperties wifiLinkProperties = new LinkProperties();
+ wifiLinkProperties.setInterfaceName(WLAN0);
+ wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24"));
+ wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX)));
+ wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ // Use a benchmark prefix (RFC 5180 + erratum), since the documentation
+ // prefix is included in the excluded prefix list.
+ wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64"));
+ wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64"));
+ wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64")));
+ offload.notifyDownstreamLinkProperties(wifiLinkProperties);
+
+ offload.removeDownstreamInterface(RNDIS0);
+
// Clear invocation history, especially the getForwardedStats() calls
// that happen with setUpstreamParameters().
clearInvocations(mHardware);
@@ -690,6 +719,17 @@
verifyNoMoreInteractions(mNMService);
// TODO: verify local prefixes and downstreams are also pushed to the HAL.
+ verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
+ ArrayList<String> localPrefixes = mStringArrayCaptor.getValue();
+ assertEquals(4, localPrefixes.size());
+ assertArrayListContains(localPrefixes,
+ // TODO: The logic to find and exclude downstream IP prefixes
+ // is currently in Tethering's OffloadWrapper but must be moved
+ // into OffloadController proper. After this, also check for:
+ // "192.168.43.1/32", "2001:2::1/128", "2001:2::2/128"
+ "127.0.0.0/8", "192.0.2.0/24", "fe80::/64", "2001:db8::/64");
+ verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "192.168.43.0/24");
+ verify(mHardware, times(1)).addDownstreamPrefix(WLAN0, "2001:2::/64");
verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any());
verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong());
verifyNoMoreInteractions(mHardware);
@@ -697,7 +737,7 @@
private static void assertArrayListContains(ArrayList<String> list, String... elems) {
for (String element : elems) {
- assertTrue(list.contains(element));
+ assertTrue(element + " not in list", list.contains(element));
}
}
}