diff options
199 files changed, 7374 insertions, 3919 deletions
diff --git a/Android.bp b/Android.bp index 9e8c384399a6..6b61467b1a27 100644 --- a/Android.bp +++ b/Android.bp @@ -51,6 +51,7 @@ cc_library { } subdirs = [ + "cmds/*", "core/jni", "libs/*", "media/*", diff --git a/Android.mk b/Android.mk index 0e6642d70a28..b92f31e80f07 100644 --- a/Android.mk +++ b/Android.mk @@ -486,9 +486,9 @@ LOCAL_SRC_FILES += \ 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/IDownloadProgressListener.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 \ telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl \ diff --git a/api/current.txt b/api/current.txt index b2181657da7d..5d7dcb11b245 100644 --- a/api/current.txt +++ b/api/current.txt @@ -39347,6 +39347,7 @@ package android.telecom { method public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle); + method public boolean isTtySupported(); method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String); method public void placeCall(android.net.Uri, android.os.Bundle); method public void registerPhoneAccount(android.telecom.PhoneAccount); @@ -39749,13 +39750,42 @@ package android.telephony { field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4 } - 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 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(); + 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 + field public static final int RESULT_EXPIRED = 3; // 0x3 + field public static final int RESULT_IO_ERROR = 4; // 0x4 + field public static final int RESULT_SUCCESSFUL = 1; // 0x1 + field public static final int STATUS_ACTIVELY_DOWNLOADING = 1; // 0x1 + field public static final int STATUS_PENDING_DOWNLOAD = 2; // 0x2 + field public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4; // 0x4 + field public static final int STATUS_PENDING_REPAIR = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + 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 { @@ -39788,8 +39818,10 @@ package android.telephony { 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); @@ -39822,12 +39854,15 @@ package android.telephony { 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 @@ -40110,6 +40145,7 @@ package android.telephony { method public int getPhoneCount(); method public int getPhoneType(); method public android.telephony.ServiceState getServiceState(); + method public android.telephony.SignalStrength getSignalStrength(); method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); method public java.lang.String getSimOperatorName(); @@ -40135,7 +40171,7 @@ package android.telephony { method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); - method public boolean isTtyModeSupported(); + method public deprecated boolean isTtyModeSupported(); method public boolean isVoiceCapable(); method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); @@ -40388,15 +40424,73 @@ package android.telephony.gsm { package android.telephony.mbms { - public class MbmsException extends java.lang.Exception { - method public int getErrorCode(); + 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 java.lang.String getFileServiceId(); + method public static int getMaxAppIntentSize(); + method public static int getMaxDestinationUriSize(); + method public android.net.Uri getSourceUri(); + method public int getSubscriptionId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.DownloadRequest> CREATOR; + } + + public static class DownloadRequest.Builder { + 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 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); + } + + 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 onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int); + } + + public final class FileInfo implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getMimeType(); + method public android.net.Uri getUri(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileInfo> CREATOR; + } + + public final class FileServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable { + method public int describeContents(); + method public java.util.List<android.telephony.mbms.FileInfo> getFiles(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR; + } + + public class MbmsDownloadReceiver extends android.content.BroadcastReceiver { + ctor public MbmsDownloadReceiver(); + method public void onReceive(android.content.Context, android.content.Intent); + } + + 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.GeneralErrors { + 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 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 @@ -40406,39 +40500,38 @@ package android.telephony.mbms { 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 928346b0ac07..d5baf1cc6c55 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -43179,13 +43179,43 @@ package android.telephony { field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4 } - 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 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(); + 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"; + field public static final int RESULT_CANCELLED = 2; // 0x2 + field public static final int RESULT_EXPIRED = 3; // 0x3 + field public static final int RESULT_IO_ERROR = 4; // 0x4 + field public static final int RESULT_SUCCESSFUL = 1; // 0x1 + field public static final int STATUS_ACTIVELY_DOWNLOADING = 1; // 0x1 + field public static final int STATUS_PENDING_DOWNLOAD = 2; // 0x2 + field public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4; // 0x4 + field public static final int STATUS_PENDING_REPAIR = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + 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"; } @@ -43219,8 +43249,10 @@ package android.telephony { 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); @@ -43253,12 +43285,15 @@ package android.telephony { 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 @@ -43531,8 +43566,8 @@ package android.telephony { } public class TelephonyManager { - method public void answerRingingCall(); - method public void call(java.lang.String, java.lang.String); + method public deprecated void answerRingingCall(); + method public deprecated void call(java.lang.String, java.lang.String); method public boolean canChangeDtmfToneLength(); method public int checkCarrierPrivilegesForPackage(java.lang.String); method public int checkCarrierPrivilegesForPackageAnyPhone(java.lang.String); @@ -43542,7 +43577,7 @@ package android.telephony { method public boolean disableDataConnectivity(); method public boolean enableDataConnectivity(); method public void enableVideoCalling(boolean); - method public boolean endCall(); + method public deprecated boolean endCall(); method public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers(int); method public int getCallState(); @@ -43583,6 +43618,7 @@ package android.telephony { method public int getPhoneCount(); method public int getPhoneType(); method public android.telephony.ServiceState getServiceState(); + method public android.telephony.SignalStrength getSignalStrength(); method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); method public java.lang.String getSimOperatorName(); @@ -43617,7 +43653,7 @@ package android.telephony { method public boolean isRadioOn(); method public boolean isRinging(); method public boolean isSmsCapable(); - method public boolean isTtyModeSupported(); + method public deprecated boolean isTtyModeSupported(); method public boolean isVideoCallingEnabled(); method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean isVoiceCapable(); @@ -43642,7 +43678,7 @@ package android.telephony { method public boolean setVoiceMailNumber(java.lang.String, java.lang.String); method public void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); method public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); - method public void silenceRinger(); + method public deprecated void silenceRinger(); method public boolean supplyPin(java.lang.String); method public int[] supplyPinReportResult(java.lang.String); method public boolean supplyPuk(java.lang.String, java.lang.String); @@ -43902,15 +43938,85 @@ package android.telephony.ims { package android.telephony.mbms { - public class MbmsException extends java.lang.Exception { - method public int getErrorCode(); + 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 java.lang.String getFileServiceId(); + method public static int getMaxAppIntentSize(); + method public static int getMaxDestinationUriSize(); + method public byte[] getOpaqueData(); + method public android.net.Uri getSourceUri(); + method public int getSubscriptionId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.DownloadRequest> CREATOR; + } + + public static class DownloadRequest.Builder { + 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 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); + method public android.telephony.mbms.DownloadRequest.Builder setSource(android.net.Uri); + method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int); + } + + 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 onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int); + } + + public final class FileInfo implements android.os.Parcelable { + ctor public FileInfo(android.net.Uri, java.lang.String); + method public int describeContents(); + method public java.lang.String getMimeType(); + method public android.net.Uri getUri(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileInfo> CREATOR; + } + + public final class FileServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable { + ctor public FileServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>); + method public int describeContents(); + method public java.util.List<android.telephony.mbms.FileInfo> getFiles(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR; + } + + 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 + field public static final int RESULT_MALFORMED_INTENT = 2; // 0x2 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5; // 0x5 + } + + 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.GeneralErrors { + 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 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 @@ -43920,39 +44026,38 @@ package android.telephony.mbms { 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 @@ -43984,22 +44089,62 @@ package android.telephony.mbms { field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR; } + public final class UriPathPair implements android.os.Parcelable { + method public int describeContents(); + method public android.net.Uri getContentUri(); + method public android.net.Uri getFilePathUri(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.UriPathPair> CREATOR; + } + } package android.telephony.mbms.vendor { + public class MbmsDownloadServiceBase extends android.os.Binder { + 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) throws android.os.RemoteException; + method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) 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; } + public class VendorUtils { + ctor public VendorUtils(); + method public static android.content.ComponentName getAppReceiverFromPackageName(android.content.Context, java.lang.String); + field public static final java.lang.String ACTION_CLEANUP = "android.telephony.mbms.action.CLEANUP"; + field public static final java.lang.String ACTION_DOWNLOAD_RESULT_INTERNAL = "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL"; + field public static final java.lang.String ACTION_FILE_DESCRIPTOR_REQUEST = "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST"; + field public static final java.lang.String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT"; + field public static final java.lang.String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI"; + 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_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"; + field public static final java.lang.String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST"; + } + } package android.test { diff --git a/api/test-current.txt b/api/test-current.txt index 1dfca8ee14f3..5296a653da19 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -39569,6 +39569,7 @@ package android.telecom { method public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); method public boolean isOutgoingCallPermitted(android.telecom.PhoneAccountHandle); + method public boolean isTtySupported(); method public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, java.lang.String); method public void placeCall(android.net.Uri, android.os.Bundle); method public void registerPhoneAccount(android.telecom.PhoneAccount); @@ -39971,13 +39972,42 @@ package android.telephony { field public static final int STATUS_UNKNOWN_ERROR = 4; // 0x4 } - 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 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(); + 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 + field public static final int RESULT_EXPIRED = 3; // 0x3 + field public static final int RESULT_IO_ERROR = 4; // 0x4 + field public static final int RESULT_SUCCESSFUL = 1; // 0x1 + field public static final int STATUS_ACTIVELY_DOWNLOADING = 1; // 0x1 + field public static final int STATUS_PENDING_DOWNLOAD = 2; // 0x2 + field public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4; // 0x4 + field public static final int STATUS_PENDING_REPAIR = 3; // 0x3 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + 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 { @@ -40010,8 +40040,10 @@ package android.telephony { 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); @@ -40044,12 +40076,15 @@ package android.telephony { 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 @@ -40332,6 +40367,7 @@ package android.telephony { method public int getPhoneCount(); method public int getPhoneType(); method public android.telephony.ServiceState getServiceState(); + method public android.telephony.SignalStrength getSignalStrength(); method public java.lang.String getSimCountryIso(); method public java.lang.String getSimOperator(); method public java.lang.String getSimOperatorName(); @@ -40357,7 +40393,7 @@ package android.telephony { method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); - method public boolean isTtyModeSupported(); + method public deprecated boolean isTtyModeSupported(); method public boolean isVoiceCapable(); method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); @@ -40610,15 +40646,73 @@ package android.telephony.gsm { package android.telephony.mbms { - public class MbmsException extends java.lang.Exception { - method public int getErrorCode(); + 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 java.lang.String getFileServiceId(); + method public static int getMaxAppIntentSize(); + method public static int getMaxDestinationUriSize(); + method public android.net.Uri getSourceUri(); + method public int getSubscriptionId(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.DownloadRequest> CREATOR; + } + + public static class DownloadRequest.Builder { + 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 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); + } + + 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 onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int); + } + + public final class FileInfo implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getMimeType(); + method public android.net.Uri getUri(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileInfo> CREATOR; + } + + public final class FileServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable { + method public int describeContents(); + method public java.util.List<android.telephony.mbms.FileInfo> getFiles(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.mbms.FileServiceInfo> CREATOR; + } + + public class MbmsDownloadReceiver extends android.content.BroadcastReceiver { + ctor public MbmsDownloadReceiver(); + method public void onReceive(android.content.Context, android.content.Intent); + } + + 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.GeneralErrors { + 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 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 @@ -40628,39 +40722,38 @@ package android.telephony.mbms { 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/am/Android.bp b/cmds/am/Android.bp new file mode 100644 index 000000000000..7eb4edfecbf9 --- /dev/null +++ b/cmds/am/Android.bp @@ -0,0 +1,11 @@ +// Copyright 2008 The Android Open Source Project +// + +cc_library_host_static { + name: "libinstrumentation", + srcs: ["**/*.proto"], + proto: { + type: "full", + export_proto_headers: true, + }, +} diff --git a/cmds/am/Android.mk b/cmds/am/Android.mk index 5586dd4e5b18..9411c3203ab8 100644 --- a/cmds/am/Android.mk +++ b/cmds/am/Android.mk @@ -16,14 +16,3 @@ LOCAL_SRC_FILES := am LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MODULE_TAGS := optional include $(BUILD_PREBUILT) - - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ - $(call all-proto-files-under, proto) -LOCAL_MODULE := libinstrumentation -LOCAL_PROTOC_OPTIMIZE_TYPE := full -LOCAL_EXPORT_C_INCLUDE_DIRS := \ - $(call intermediates-dir-for,STATIC_LIBRARIES,libinstrumentation,HOST,,,)/proto/$(LOCAL_PATH)/proto -include $(BUILD_HOST_STATIC_LIBRARY) - diff --git a/cmds/hid/Android.bp b/cmds/hid/Android.bp new file mode 100644 index 000000000000..2b7963aa9425 --- /dev/null +++ b/cmds/hid/Android.bp @@ -0,0 +1 @@ +subdirs = ["jni"] diff --git a/cmds/hid/jni/Android.bp b/cmds/hid/jni/Android.bp new file mode 100644 index 000000000000..05c3099b27ff --- /dev/null +++ b/cmds/hid/jni/Android.bp @@ -0,0 +1,18 @@ +cc_library_shared { + name: "libhidcommand_jni", + + srcs: ["com_android_commands_hid_Device.cpp"], + + shared_libs: [ + "libandroid_runtime", + "liblog", + "libnativehelper", + "libutils", + ], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], +} diff --git a/cmds/hid/jni/Android.mk b/cmds/hid/jni/Android.mk deleted file mode 100644 index d41d39d27f5b..000000000000 --- a/cmds/hid/jni/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - com_android_commands_hid_Device.cpp - -LOCAL_C_INCLUDES := \ - $(JNI_H_INCLUDE) \ - frameworks/base/core/jni - -LOCAL_SHARED_LIBRARIES := \ - libandroid_runtime \ - liblog \ - libnativehelper \ - libutils - -LOCAL_MODULE := libhidcommand_jni -LOCAL_MODULE_TAGS := optional - -LOCAL_CFLAGS += -Wall -Wextra -Werror - -include $(BUILD_SHARED_LIBRARY) diff --git a/cmds/idmap/create.cpp b/cmds/idmap/create.cpp index 524db14f7aab..f415f8f5dd75 100644 --- a/cmds/idmap/create.cpp +++ b/cmds/idmap/create.cpp @@ -104,13 +104,17 @@ fail: } } - uint32_t cached_target_crc, cached_overlay_crc; + uint32_t version, cached_target_crc, cached_overlay_crc; String8 cached_target_path, cached_overlay_path; - if (!ResTable::getIdmapInfo(buf, N, NULL, &cached_target_crc, &cached_overlay_crc, + if (!ResTable::getIdmapInfo(buf, N, &version, &cached_target_crc, &cached_overlay_crc, &cached_target_path, &cached_overlay_path)) { return true; } + if (version != ResTable::IDMAP_CURRENT_VERSION) { + return true; + } + if (cached_target_path != target_apk_path) { return true; } diff --git a/cmds/idmap/inspect.cpp b/cmds/idmap/inspect.cpp index 154cb25a02a1..20005e2766d8 100644 --- a/cmds/idmap/inspect.cpp +++ b/cmds/idmap/inspect.cpp @@ -284,7 +284,9 @@ namespace { if (err != NO_ERROR) { return err; } - print("", "entry", data32, "%s/%s", type.string(), name.string()); + if (data32 != ResTable_type::NO_ENTRY) { + print("", "entry", data32, "%s/%s", type.string(), name.string()); + } } } diff --git a/cmds/interrupter/Android.bp b/cmds/interrupter/Android.bp new file mode 100644 index 000000000000..d68e7fe37535 --- /dev/null +++ b/cmds/interrupter/Android.bp @@ -0,0 +1,11 @@ +cc_library_shared { + name: "interrupter", + host_supported: true, + srcs: ["interrupter.c"], + cflags: [ + "-Wall", + "-Werror", + "-Wunused", + "-Wunreachable-code", + ], +} diff --git a/cmds/interrupter/Android.mk b/cmds/interrupter/Android.mk deleted file mode 100644 index 97a96bfc8e25..000000000000 --- a/cmds/interrupter/Android.mk +++ /dev/null @@ -1,23 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - interrupter.c -LOCAL_MODULE := interrupter -LOCAL_MODULE_TAGS := eng tests -LOCAL_LDFLAGS := -ldl -LOCAL_CFLAGS := -Wall -Werror -Wunused -Wunreachable-code - -include $(BUILD_SHARED_LIBRARY) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - interrupter.c -LOCAL_MODULE := interrupter -LOCAL_MODULE_TAGS := eng tests -LOCAL_LDFLAGS := -ldl -LOCAL_CFLAGS := -Wall -Werror -Wunused -Wunreachable-code - -include $(BUILD_HOST_SHARED_LIBRARY) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 6f326de76150..80554fff75ac 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -961,7 +961,7 @@ public class ResourcesManager { // TODO(adamlesinski): Make this accept more than just overlay directories. final void applyNewResourceDirsLocked(@NonNull final String baseCodePath, - @NonNull final String[] newResourceDirs) { + @Nullable final String[] newResourceDirs) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#applyNewResourceDirsLocked"); diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index 611531c4f7c4..faab000a8997 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -125,7 +125,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; - private IBluetoothA2dpSink mService; + private volatile IBluetoothA2dpSink mService; private BluetoothAdapter mAdapter; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = @@ -240,15 +240,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -279,15 +280,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -297,15 +299,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -315,15 +318,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -333,16 +337,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -359,16 +363,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { if (VDBG) log("getAudioConfig(" + device + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getAudioConfig(device); + return service.getAudioConfig(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return null; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; } @@ -389,20 +393,20 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -421,16 +425,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.PRIORITY_OFF; } @@ -442,16 +446,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @param device BluetoothDevice device */ public boolean isA2dpPlaying(BluetoothDevice device) { - if (mService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothA2dpSink service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.isA2dpPlaying(device); + return service.isA2dpPlaying(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -485,7 +489,6 @@ public final class BluetoothA2dpSink implements BluetoothProfile { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service)); - if (mServiceListener != null) { mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK, BluetoothA2dpSink.this); @@ -502,15 +505,11 @@ public final class BluetoothA2dpSink implements BluetoothProfile { }; private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_ON; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private static void log(String msg) { diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java index 7528aa972109..5f0e5d97447e 100644 --- a/core/java/android/bluetooth/BluetoothAvrcpController.java +++ b/core/java/android/bluetooth/BluetoothAvrcpController.java @@ -81,7 +81,7 @@ public final class BluetoothAvrcpController implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; - private IBluetoothAvrcpController mService; + private volatile IBluetoothAvrcpController mService; private BluetoothAdapter mAdapter; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = @@ -179,15 +179,16 @@ public final class BluetoothAvrcpController implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothAvrcpController service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -197,15 +198,16 @@ public final class BluetoothAvrcpController implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothAvrcpController service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -215,16 +217,16 @@ public final class BluetoothAvrcpController implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); - if (mService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothAvrcpController service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -236,9 +238,10 @@ public final class BluetoothAvrcpController implements BluetoothProfile { public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { if (DBG) Log.d(TAG, "getPlayerSettings"); BluetoothAvrcpPlayerSettings settings = null; - if (mService != null && isEnabled()) { + final IBluetoothAvrcpController service = mService; + if (service != null && isEnabled()) { try { - settings = mService.getPlayerSettings(device); + settings = service.getPlayerSettings(device); } catch (RemoteException e) { Log.e(TAG, "Error talking to BT service in getMetadata() " + e); return null; @@ -253,15 +256,16 @@ public final class BluetoothAvrcpController implements BluetoothProfile { */ public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { if (DBG) Log.d(TAG, "setPlayerApplicationSetting"); - if (mService != null && isEnabled()) { + final IBluetoothAvrcpController service = mService; + if (service != null && isEnabled()) { try { - return mService.setPlayerApplicationSetting(plAppSetting); + return service.setPlayerApplicationSetting(plAppSetting); } catch (RemoteException e) { Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -272,23 +276,23 @@ public final class BluetoothAvrcpController implements BluetoothProfile { public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState); - if (mService != null && isEnabled()) { + final IBluetoothAvrcpController service = mService; + if (service != null && isEnabled()) { try { - mService.sendGroupNavigationCmd(device, keyCode, keyState); + service.sendGroupNavigationCmd(device, keyCode, keyState); return; } catch (RemoteException e) { Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e); return; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); } private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothAvrcpController.Stub.asInterface(Binder.allowBlocking(service)); - if (mServiceListener != null) { mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER, BluetoothAvrcpController.this); @@ -305,15 +309,11 @@ public final class BluetoothAvrcpController implements BluetoothProfile { }; private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_ON; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private static void log(String msg) { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 3ab2c4a8f44d..d982bb7ffb43 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -712,7 +712,7 @@ public final class BluetoothDevice implements Parcelable { * getService() called. * TODO: Unify implementation of sService amongst BluetoothFoo API's */ - private static IBluetooth sService; + private static volatile IBluetooth sService; private final String mAddress; @@ -839,12 +839,13 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public String getName() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot get Remote Device name"); return null; } try { - return sService.getRemoteName(this); + return service.getRemoteName(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -859,12 +860,13 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public int getType() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot get Remote Device type"); return DEVICE_TYPE_UNKNOWN; } try { - return sService.getRemoteType(this); + return service.getRemoteType(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -879,12 +881,13 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public String getAlias() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias"); return null; } try { - return sService.getRemoteAlias(this); + return service.getRemoteAlias(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -902,12 +905,13 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setAlias(String alias) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); return false; } try { - return sService.setRemoteAlias(this, alias); + return service.setRemoteAlias(this, alias); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -942,12 +946,13 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public int getBatteryLevel() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "Bluetooth disabled. Cannot get remote device battery level"); return BATTERY_LEVEL_UNKNOWN; } try { - return sService.getBatteryLevel(this); + return service.getBatteryLevel(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -966,7 +971,8 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) public boolean createBond() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device"); return false; } @@ -974,7 +980,7 @@ public final class BluetoothDevice implements Parcelable { Log.i(TAG, "createBond() for device " + getAddress() + " called by pid: " + Process.myPid() + " tid: " + Process.myTid()); - return sService.createBond(this, TRANSPORT_AUTO); + return service.createBond(this, TRANSPORT_AUTO); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -998,7 +1004,8 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean createBond(int transport) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device"); return false; } @@ -1009,7 +1016,7 @@ public final class BluetoothDevice implements Parcelable { Log.i(TAG, "createBond() for device " + getAddress() + " called by pid: " + Process.myPid() + " tid: " + Process.myTid()); - return sService.createBond(this, transport); + return service.createBond(this, transport); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1035,8 +1042,13 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean createBondOutOfBand(int transport, OobData oobData) { + final IBluetooth service = sService; + if (service == null) { + Log.w(TAG, "BT not enabled, createBondOutOfBand failed"); + return false; + } try { - return sService.createBondOutOfBand(this, transport, oobData); + return service.createBondOutOfBand(this, transport, oobData); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1045,8 +1057,13 @@ public final class BluetoothDevice implements Parcelable { /** @hide */ public boolean isBondingInitiatedLocally() { + final IBluetooth service = sService; + if (service == null) { + Log.w(TAG, "BT not enabled, isBondingInitiatedLocally failed"); + return false; + } try { - return sService.isBondingInitiatedLocally(this); + return service.isBondingInitiatedLocally(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1082,7 +1099,8 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean cancelBondProcess() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond"); return false; } @@ -1090,7 +1108,7 @@ public final class BluetoothDevice implements Parcelable { Log.i(TAG, "cancelBondProcess() for device " + getAddress() + " called by pid: " + Process.myPid() + " tid: " + Process.myTid()); - return sService.cancelBondProcess(this); + return service.cancelBondProcess(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1108,7 +1126,8 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean removeBond() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond"); return false; } @@ -1116,7 +1135,7 @@ public final class BluetoothDevice implements Parcelable { Log.i(TAG, "removeBond() for device " + getAddress() + " called by pid: " + Process.myPid() + " tid: " + Process.myTid()); - return sService.removeBond(this); + return service.removeBond(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1134,19 +1153,15 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public int getBondState() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot get bond state"); return BOND_NONE; } try { - return sService.getBondState(this); + return service.getBondState(this); } catch (RemoteException e) { Log.e(TAG, "", e); - } catch (NullPointerException npe) { - // Handle case where bluetooth service proxy - // is already null. - Log.e(TAG, "NullPointerException for getBondState() of device (" - + getAddress() + ")", npe); } return BOND_NONE; } @@ -1160,12 +1175,13 @@ public final class BluetoothDevice implements Parcelable { */ @SystemApi public boolean isConnected() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { // BT is not enabled, we cannot be connected. return false; } try { - return sService.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED; + return service.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED; } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1182,12 +1198,13 @@ public final class BluetoothDevice implements Parcelable { */ @SystemApi public boolean isEncrypted() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { // BT is not enabled, we cannot be connected. return false; } try { - return sService.getConnectionState(this) > CONNECTION_STATE_CONNECTED; + return service.getConnectionState(this) > CONNECTION_STATE_CONNECTED; } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1201,12 +1218,13 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public BluetoothClass getBluetoothClass() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class"); return null; } try { - int classInt = sService.getRemoteClass(this); + int classInt = service.getRemoteClass(this); if (classInt == BluetoothClass.ERROR) return null; return new BluetoothClass(classInt); } catch (RemoteException e) { @@ -1227,12 +1245,13 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public ParcelUuid[] getUuids() { - if (sService == null || !isBluetoothEnabled()) { + final IBluetooth service = sService; + if (service == null || !isBluetoothEnabled()) { Log.e(TAG, "BT not enabled. Cannot get remote device Uuids"); return null; } try { - return sService.getRemoteUuids(this); + return service.getRemoteUuids(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1254,7 +1273,7 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH) public boolean fetchUuidsWithSdp() { - IBluetooth service = sService; + final IBluetooth service = sService; if (service == null || !isBluetoothEnabled()) { Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp"); return false; @@ -1289,12 +1308,13 @@ public final class BluetoothDevice implements Parcelable { */ /** @hide */ public boolean sdpSearch(ParcelUuid uuid) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot query remote device sdp records"); return false; } try { - return sService.sdpSearch(this, uuid); + return service.sdpSearch(this, uuid); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1308,12 +1328,13 @@ public final class BluetoothDevice implements Parcelable { * @return true pin has been set false for error */ public boolean setPin(byte[] pin) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot set Remote Device pin"); return false; } try { - return sService.setPin(this, true, pin.length, pin); + return service.setPin(this, true, pin.length, pin); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1337,12 +1358,13 @@ public final class BluetoothDevice implements Parcelable { */ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPairingConfirmation(boolean confirm) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot set pairing confirmation"); return false; } try { - return sService.setPairingConfirmation(this, confirm); + return service.setPairingConfirmation(this, confirm); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1361,12 +1383,13 @@ public final class BluetoothDevice implements Parcelable { /** @hide */ public boolean cancelPairingUserInput() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { Log.e(TAG, "BT not enabled. Cannot create pairing user input"); return false; } try { - return sService.cancelBondProcess(this); + return service.cancelBondProcess(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1400,11 +1423,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public int getPhonebookAccessPermission() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { return ACCESS_UNKNOWN; } try { - return sService.getPhonebookAccessPermission(this); + return service.getPhonebookAccessPermission(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1421,11 +1445,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setPhonebookAccessPermission(int value) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { return false; } try { - return sService.setPhonebookAccessPermission(this, value); + return service.setPhonebookAccessPermission(this, value); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1440,11 +1465,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public int getMessageAccessPermission() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { return ACCESS_UNKNOWN; } try { - return sService.getMessageAccessPermission(this); + return service.getMessageAccessPermission(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1461,11 +1487,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setMessageAccessPermission(int value) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { return false; } try { - return sService.setMessageAccessPermission(this, value); + return service.setMessageAccessPermission(this, value); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1480,11 +1507,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public int getSimAccessPermission() { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { return ACCESS_UNKNOWN; } try { - return sService.getSimAccessPermission(this); + return service.getSimAccessPermission(this); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1501,11 +1529,12 @@ public final class BluetoothDevice implements Parcelable { * @hide */ public boolean setSimAccessPermission(int value) { - if (sService == null) { + final IBluetooth service = sService; + if (service == null) { return false; } try { - return sService.setSimAccessPermission(this, value); + return service.setSimAccessPermission(this, value); } catch (RemoteException e) { Log.e(TAG, "", e); } diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index 759d772920ba..a2af3422eaec 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -42,7 +42,7 @@ public final class BluetoothGatt implements BluetoothProfile { private static final boolean VDBG = false; private IBluetoothGatt mService; - private BluetoothGattCallback mCallback; + private volatile BluetoothGattCallback mCallback; private Handler mHandler; private int mClientIf; private BluetoothDevice mDevice; @@ -164,8 +164,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onConnectionStateChange(BluetoothGatt.this, + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE, BluetoothProfile.STATE_DISCONNECTED); } @@ -203,8 +204,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status); } } }); @@ -227,8 +229,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status); } } }); @@ -254,8 +257,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onConnectionStateChange(BluetoothGatt.this, status, + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onConnectionStateChange(BluetoothGatt.this, status, profileState); } } @@ -307,8 +311,7 @@ public final class BluetoothGatt implements BluetoothProfile { 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 { @@ -320,8 +323,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onServicesDiscovered(BluetoothGatt.this, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onServicesDiscovered(BluetoothGatt.this, status); } } }); @@ -371,13 +375,13 @@ public final class BluetoothGatt implements BluetoothProfile { return; } - if (status == 0) characteristic.setValue(value); - runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + if (status == 0) characteristic.setValue(value); + callback.onCharacteristicRead(BluetoothGatt.this, characteristic, status); } } @@ -429,8 +433,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status); } } @@ -454,13 +459,13 @@ public final class BluetoothGatt implements BluetoothProfile { handle); if (characteristic == null) return; - characteristic.setValue(value); - runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onCharacteristicChanged(BluetoothGatt.this, + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + characteristic.setValue(value); + callback.onCharacteristicChanged(BluetoothGatt.this, characteristic); } } @@ -489,7 +494,6 @@ public final class BluetoothGatt implements BluetoothProfile { BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); if (descriptor == null) return; - if (status == 0) descriptor.setValue(value); if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) @@ -510,8 +514,10 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + if (status == 0) descriptor.setValue(value); + callback.onDescriptorRead(BluetoothGatt.this, descriptor, status); } } }); @@ -559,8 +565,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); } } }); @@ -587,8 +594,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onReliableWriteCompleted(BluetoothGatt.this, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onReliableWriteCompleted(BluetoothGatt.this, status); } } }); @@ -610,8 +618,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); } } }); @@ -634,8 +643,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onMtuChanged(BluetoothGatt.this, mtu, status); + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onMtuChanged(BluetoothGatt.this, mtu, status); } } }); @@ -660,8 +670,9 @@ public final class BluetoothGatt implements BluetoothProfile { runOrQueueCallback(new Runnable() { @Override public void run() { - if (mCallback != null) { - mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency, + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onConnectionUpdated(BluetoothGatt.this, interval, latency, timeout, status); } } @@ -702,10 +713,9 @@ public final class BluetoothGatt implements BluetoothProfile { * @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; @@ -901,7 +911,7 @@ public final class BluetoothGatt implements BluetoothProfile { /** * 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 22eba351b361..2c8114be3fe3 100644 --- a/core/java/android/bluetooth/BluetoothGattServerCallback.java +++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java @@ -184,7 +184,7 @@ public abstract class BluetoothGattServerCallback { /** * 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 @@ public abstract class BluetoothGattServerCallback { * 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/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index be1ce63cadc2..85550c7720a6 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -306,7 +306,7 @@ public final class BluetoothHeadset implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; - private IBluetoothHeadset mService; + private volatile IBluetoothHeadset mService; private BluetoothAdapter mAdapter; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = @@ -418,15 +418,16 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -457,15 +458,16 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -475,15 +477,16 @@ public final class BluetoothHeadset implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -493,15 +496,16 @@ public final class BluetoothHeadset implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -511,15 +515,16 @@ public final class BluetoothHeadset implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getConnectionState(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -540,19 +545,20 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -571,15 +577,16 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return PRIORITY_OFF; } @@ -605,14 +612,15 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean startVoiceRecognition(BluetoothDevice device) { if (DBG) log("startVoiceRecognition()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.startVoiceRecognition(device); + return service.startVoiceRecognition(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -627,14 +635,15 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean stopVoiceRecognition(BluetoothDevice device) { if (DBG) log("stopVoiceRecognition()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.stopVoiceRecognition(device); + return service.stopVoiceRecognition(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -648,14 +657,15 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean isAudioConnected(BluetoothDevice device) { if (VDBG) log("isAudioConnected()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.isAudioConnected(device); + return service.isAudioConnected(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -674,14 +684,15 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public int getBatteryUsageHint(BluetoothDevice device) { if (VDBG) log("getBatteryUsageHint()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getBatteryUsageHint(device); + return service.getBatteryUsageHint(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return -1; } @@ -704,9 +715,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean acceptIncomingConnect(BluetoothDevice device) { if (DBG) log("acceptIncomingConnect"); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.acceptIncomingConnect(device); + return service.acceptIncomingConnect(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -724,9 +736,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean rejectIncomingConnect(BluetoothDevice device) { if (DBG) log("rejectIncomingConnect"); - if (mService != null) { + final IBluetoothHeadset service = mService; + if (service != null) { try { - return mService.rejectIncomingConnect(device); + return service.rejectIncomingConnect(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -745,9 +758,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); - if (mService != null && !isDisabled()) { + final IBluetoothHeadset service = mService; + if (service != null && !isDisabled()) { try { - return mService.getAudioState(device); + return service.getAudioState(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -770,9 +784,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public void setAudioRouteAllowed(boolean allowed) { if (VDBG) log("setAudioRouteAllowed"); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - mService.setAudioRouteAllowed(allowed); + service.setAudioRouteAllowed(allowed); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -790,9 +805,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean getAudioRouteAllowed() { if (VDBG) log("getAudioRouteAllowed"); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.getAudioRouteAllowed(); + return service.getAudioRouteAllowed(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -812,9 +828,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public void setForceScoAudio(boolean forced) { if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - mService.setForceScoAudio(forced); + service.setForceScoAudio(forced); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -834,14 +851,15 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean isAudioOn() { if (VDBG) log("isAudioOn()"); - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.isAudioOn(); + return service.isAudioOn(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -855,9 +873,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ public boolean connectAudio() { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.connectAudio(); + return service.connectAudio(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -877,9 +896,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ public boolean disconnectAudio() { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.disconnectAudio(); + return service.disconnectAudio(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -903,9 +923,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { if (DBG) log("startScoUsingVirtualVoiceCall()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.startScoUsingVirtualVoiceCall(device); + return service.startScoUsingVirtualVoiceCall(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -926,9 +947,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { if (DBG) log("stopScoUsingVirtualVoiceCall()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.stopScoUsingVirtualVoiceCall(device); + return service.stopScoUsingVirtualVoiceCall(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -949,9 +971,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type) { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - mService.phoneStateChanged(numActive, numHeld, callState, number, type); + service.phoneStateChanged(numActive, numHeld, callState, number, type); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -968,9 +991,10 @@ public final class BluetoothHeadset implements BluetoothProfile { */ public void clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type) { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - mService.clccResponse(index, direction, status, mode, mpty, number, type); + service.clccResponse(index, direction, status, mode, mpty, number, type); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1006,14 +1030,15 @@ public final class BluetoothHeadset implements BluetoothProfile { if (command == null) { throw new IllegalArgumentException("command is null"); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.sendVendorSpecificResultCode(device, command, arg); + return service.sendVendorSpecificResultCode(device, command, arg); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return false; @@ -1027,9 +1052,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ public boolean enableWBS() { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.enableWBS(); + return service.enableWBS(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1048,9 +1074,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ public boolean disableWBS() { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - return mService.disableWBS(); + return service.disableWBS(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1083,9 +1110,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ public void bindResponse(int indId, boolean indStatus) { - if (mService != null && isEnabled()) { + final IBluetoothHeadset service = mService; + if (service != null && isEnabled()) { try { - mService.bindResponse(indId, indStatus); + service.bindResponse(indId, indStatus); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1115,20 +1143,15 @@ public final class BluetoothHeadset implements BluetoothProfile { }; private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_ON; } private boolean isDisabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_OFF; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private static void log(String msg) { diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java index 7ed2d2e98f7f..031287f5ee12 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -76,8 +76,8 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * Intent sent whenever audio state changes. * * <p>It includes two mandatory extras: - * {@link BluetoothProfile.EXTRA_STATE}, - * {@link BluetoothProfile.EXTRA_PREVIOUS_STATE}, + * {@link BluetoothProfile#EXTRA_STATE}, + * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}, * with possible values: * {@link #STATE_AUDIO_CONNECTING}, * {@link #STATE_AUDIO_CONNECTED}, @@ -367,7 +367,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; - private IBluetoothHeadsetClient mService; + private volatile IBluetoothHeadsetClient mService; private BluetoothAdapter mAdapter; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = @@ -478,15 +478,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -499,15 +500,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -519,15 +521,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -541,15 +544,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -562,15 +566,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getConnectionState(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -581,19 +586,20 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -602,15 +608,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return PRIORITY_OFF; } @@ -627,14 +634,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean startVoiceRecognition(BluetoothDevice device) { if (DBG) log("startVoiceRecognition()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.startVoiceRecognition(device); + return service.startVoiceRecognition(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -651,14 +659,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean stopVoiceRecognition(BluetoothDevice device) { if (DBG) log("stopVoiceRecognition()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.stopVoiceRecognition(device); + return service.stopVoiceRecognition(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -670,14 +679,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { if (DBG) log("getCurrentCalls()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getCurrentCalls(device); + return service.getCurrentCalls(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; } @@ -689,14 +699,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public Bundle getCurrentAgEvents(BluetoothDevice device) { if (DBG) log("getCurrentCalls()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getCurrentAgEvents(device); + return service.getCurrentAgEvents(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; } @@ -711,14 +722,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean acceptCall(BluetoothDevice device, int flag) { if (DBG) log("acceptCall()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.acceptCall(device, flag); + return service.acceptCall(device, flag); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -731,14 +743,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean holdCall(BluetoothDevice device) { if (DBG) log("holdCall()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.holdCall(device); + return service.holdCall(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -755,14 +768,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean rejectCall(BluetoothDevice device) { if (DBG) log("rejectCall()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.rejectCall(device); + return service.rejectCall(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -784,14 +798,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { if (DBG) log("terminateCall()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.terminateCall(device, call); + return service.terminateCall(device, call); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -811,14 +826,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean enterPrivateMode(BluetoothDevice device, int index) { if (DBG) log("enterPrivateMode()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.enterPrivateMode(device, index); + return service.enterPrivateMode(device, index); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -837,14 +853,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean explicitCallTransfer(BluetoothDevice device) { if (DBG) log("explicitCallTransfer()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.explicitCallTransfer(device); + return service.explicitCallTransfer(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -859,14 +876,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { if (DBG) log("dial()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.dial(device, number); + return service.dial(device, number); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; } @@ -882,14 +900,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean sendDTMF(BluetoothDevice device, byte code) { if (DBG) log("sendDTMF()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.sendDTMF(device, code); + return service.sendDTMF(device, code); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -907,14 +926,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean getLastVoiceTagNumber(BluetoothDevice device) { if (DBG) log("getLastVoiceTagNumber()"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getLastVoiceTagNumber(device); + return service.getLastVoiceTagNumber(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -925,9 +945,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getAudioState(device); + return service.getAudioState(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -947,9 +968,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { if (VDBG) log("setAudioRouteAllowed"); - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - mService.setAudioRouteAllowed(device, allowed); + service.setAudioRouteAllowed(device, allowed); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -968,9 +990,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { */ public boolean getAudioRouteAllowed(BluetoothDevice device) { if (VDBG) log("getAudioRouteAllowed"); - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getAudioRouteAllowed(device); + return service.getAudioRouteAllowed(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -991,9 +1014,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; */ public boolean connectAudio(BluetoothDevice device) { - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.connectAudio(device); + return service.connectAudio(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1014,9 +1038,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; */ public boolean disconnectAudio(BluetoothDevice device) { - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.disconnectAudio(device); + return service.disconnectAudio(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1034,9 +1059,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return bundle of AG features; null if no service or AG not connected */ public Bundle getCurrentAgFeatures(BluetoothDevice device) { - if (mService != null && isEnabled()) { + final IBluetoothHeadsetClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getCurrentAgFeatures(device); + return service.getCurrentAgFeatures(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1048,7 +1074,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); @@ -1071,15 +1097,11 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { }; private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_ON; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private static void log(String msg) { diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java index dc5f38135aaf..57a019755f8f 100644 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ b/core/java/android/bluetooth/BluetoothHealth.java @@ -176,9 +176,10 @@ public final class BluetoothHealth implements BluetoothProfile { BluetoothHealthAppConfiguration config = new BluetoothHealthAppConfiguration(name, dataType, role, channelType); - if (mService != null) { + final IBluetoothHealth service = mService; + if (service != null) { try { - result = mService.registerAppConfiguration(config, wrapper); + result = service.registerAppConfiguration(config, wrapper); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -200,9 +201,10 @@ public final class BluetoothHealth implements BluetoothProfile { */ public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { boolean result = false; - if (mService != null && isEnabled() && config != null) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled() && config != null) { try { - result = mService.unregisterAppConfiguration(config); + result = service.unregisterAppConfiguration(config); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -228,9 +230,10 @@ public final class BluetoothHealth implements BluetoothProfile { */ public boolean connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config) { - if (mService != null && isEnabled() && isValidDevice(device) && config != null) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled() && isValidDevice(device) && config != null) { try { - return mService.connectChannelToSource(device, config); + return service.connectChannelToSource(device, config); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -256,9 +259,10 @@ public final class BluetoothHealth implements BluetoothProfile { */ public boolean connectChannelToSink(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelType) { - if (mService != null && isEnabled() && isValidDevice(device) && config != null) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled() && isValidDevice(device) && config != null) { try { - return mService.connectChannelToSink(device, config, channelType); + return service.connectChannelToSink(device, config, channelType); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -284,9 +288,10 @@ public final class BluetoothHealth implements BluetoothProfile { */ public boolean disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelId) { - if (mService != null && isEnabled() && isValidDevice(device) && config != null) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled() && isValidDevice(device) && config != null) { try { - return mService.disconnectChannel(device, config, channelId); + return service.disconnectChannel(device, config, channelId); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -312,9 +317,10 @@ public final class BluetoothHealth implements BluetoothProfile { */ public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, BluetoothHealthAppConfiguration config) { - if (mService != null && isEnabled() && isValidDevice(device) && config != null) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled() && isValidDevice(device) && config != null) { try { - return mService.getMainChannelFd(device, config); + return service.getMainChannelFd(device, config); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -341,9 +347,10 @@ public final class BluetoothHealth implements BluetoothProfile { */ @Override public int getConnectionState(BluetoothDevice device) { - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getHealthDeviceConnectionState(device); + return service.getHealthDeviceConnectionState(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -370,15 +377,16 @@ public final class BluetoothHealth implements BluetoothProfile { */ @Override public List<BluetoothDevice> getConnectedDevices() { - if (mService != null && isEnabled()) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedHealthDevices(); + return service.getConnectedHealthDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -401,15 +409,16 @@ public final class BluetoothHealth implements BluetoothProfile { */ @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (mService != null && isEnabled()) { + final IBluetoothHealth service = mService; + if (service != null && isEnabled()) { try { - return mService.getHealthDevicesMatchingConnectionStates(states); + return service.getHealthDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -455,7 +464,7 @@ public final class BluetoothHealth implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; - private IBluetoothHealth mService; + private volatile IBluetoothHealth mService; BluetoothAdapter mAdapter; /** @@ -540,11 +549,8 @@ public final class BluetoothHealth implements BluetoothProfile { return false; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private boolean checkAppParam(String name, int role, int channelType, diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java index a9a9010c7edb..32615761cf8c 100644 --- a/core/java/android/bluetooth/BluetoothInputDevice.java +++ b/core/java/android/bluetooth/BluetoothInputDevice.java @@ -222,7 +222,7 @@ public final class BluetoothInputDevice implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; - private IBluetoothInputDevice mService; + private volatile IBluetoothInputDevice mService; private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { @@ -331,15 +331,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -370,15 +371,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -388,15 +390,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -406,15 +409,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -424,15 +428,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -453,19 +458,20 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -484,15 +490,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.PRIORITY_OFF; } @@ -517,18 +524,13 @@ public final class BluetoothInputDevice implements BluetoothProfile { }; private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_ON; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } - /** * Initiate virtual unplug for a HID input device. * @@ -540,16 +542,17 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean virtualUnplug(BluetoothDevice device) { if (DBG) log("virtualUnplug(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.virtualUnplug(device); + return service.virtualUnplug(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -565,15 +568,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean getProtocolMode(BluetoothDevice device) { if (VDBG) log("getProtocolMode(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getProtocolMode(device); + return service.getProtocolMode(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -588,15 +592,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { if (DBG) log("setProtocolMode(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.setProtocolMode(device, protocolMode); + return service.setProtocolMode(device, protocolMode); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -615,19 +620,19 @@ public final class BluetoothInputDevice implements BluetoothProfile { public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) { if (VDBG) { - log( - "getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId - + "bufferSize=" + bufferSize); + log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + + "bufferSize=" + bufferSize); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getReport(device, reportType, reportId, bufferSize); + return service.getReport(device, reportType, reportId, bufferSize); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -644,15 +649,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setReport(BluetoothDevice device, byte reportType, String report) { if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.setReport(device, reportType, report); + return service.setReport(device, reportType, report); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -668,15 +674,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean sendData(BluetoothDevice device, String report) { if (DBG) log("sendData(" + device + "), report=" + report); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.sendData(device, report); + return service.sendData(device, report); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -691,15 +698,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean getIdleTime(BluetoothDevice device) { if (DBG) log("getIdletime(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getIdleTime(device); + return service.getIdleTime(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -715,15 +723,16 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public boolean setIdleTime(BluetoothDevice device, byte idleTime) { if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothInputDevice service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.setIdleTime(device, idleTime); + return service.setIdleTime(device, idleTime); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothInputHost.java index 15303dc79448..37f04278d461 100644 --- a/core/java/android/bluetooth/BluetoothInputHost.java +++ b/core/java/android/bluetooth/BluetoothInputHost.java @@ -113,7 +113,7 @@ public final class BluetoothInputHost implements BluetoothProfile { private ServiceListener mServiceListener; - private IBluetoothInputHost mService; + private volatile IBluetoothInputHost mService; private BluetoothAdapter mAdapter; @@ -202,24 +202,18 @@ public final class BluetoothInputHost implements BluetoothProfile { } }; - private ServiceConnection mConnection = new ServiceConnection() { - + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { Log.d(TAG, "onServiceConnected()"); - mService = IBluetoothInputHost.Stub.asInterface(service); - if (mServiceListener != null) { mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST, BluetoothInputHost.this); } } - public void onServiceDisconnected(ComponentName className) { Log.d(TAG, "onServiceDisconnected()"); - mService = null; - if (mServiceListener != null) { mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST); } @@ -291,9 +285,10 @@ public final class BluetoothInputHost implements BluetoothProfile { public List<BluetoothDevice> getConnectedDevices() { Log.v(TAG, "getConnectedDevices()"); - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -311,9 +306,10 @@ public final class BluetoothInputHost implements BluetoothProfile { public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states)); - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -331,9 +327,10 @@ public final class BluetoothInputHost implements BluetoothProfile { public int getConnectionState(BluetoothDevice device) { Log.v(TAG, "getConnectionState(): device=" + device); - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -370,13 +367,14 @@ public final class BluetoothInputHost implements BluetoothProfile { return false; } - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { BluetoothHidDeviceAppConfiguration config = new BluetoothHidDeviceAppConfiguration(); BluetoothHidDeviceCallbackWrapper cbw = new BluetoothHidDeviceCallbackWrapper(callback); - result = mService.registerApp(config, sdp, inQos, outQos, cbw); + result = service.registerApp(config, sdp, inQos, outQos, cbw); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -403,9 +401,10 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.unregisterApp(config); + result = service.unregisterApp(config); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -427,9 +426,10 @@ public final class BluetoothInputHost implements BluetoothProfile { public boolean sendReport(BluetoothDevice device, int id, byte[] data) { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.sendReport(device, id, data); + result = service.sendReport(device, id, data); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -454,9 +454,10 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.replyReport(device, type, id, data); + result = service.replyReport(device, type, id, data); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -479,9 +480,10 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.reportError(device, error); + result = service.reportError(device, error); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -502,9 +504,10 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.unplug(device); + result = service.unplug(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -526,9 +529,10 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.connect(device); + result = service.connect(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -549,9 +553,10 @@ public final class BluetoothInputHost implements BluetoothProfile { boolean result = false; - if (mService != null) { + final IBluetoothInputHost service = mService; + if (service != null) { try { - result = mService.disconnect(device); + result = service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 26a9106f49f5..5b55b23680c3 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -43,7 +43,7 @@ public final class BluetoothMap implements BluetoothProfile { public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; - private IBluetoothMap mService; + private volatile IBluetoothMap mService; private final Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; @@ -161,9 +161,10 @@ public final class BluetoothMap implements BluetoothProfile { */ public int getState() { if (VDBG) log("getState()"); - if (mService != null) { + final IBluetoothMap service = mService; + if (service != null) { try { - return mService.getState(); + return service.getState(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -182,9 +183,10 @@ public final class BluetoothMap implements BluetoothProfile { */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); - if (mService != null) { + final IBluetoothMap service = mService; + if (service != null) { try { - return mService.getClient(); + return service.getClient(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -202,9 +204,10 @@ public final class BluetoothMap implements BluetoothProfile { */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); - if (mService != null) { + final IBluetoothMap service = mService; + if (service != null) { try { - return mService.isConnected(device); + return service.isConnected(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -232,15 +235,16 @@ public final class BluetoothMap implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -272,15 +276,16 @@ public final class BluetoothMap implements BluetoothProfile { */ public List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothMap service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -291,15 +296,16 @@ public final class BluetoothMap implements BluetoothProfile { */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothMap service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -310,15 +316,16 @@ public final class BluetoothMap implements BluetoothProfile { */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -335,19 +342,20 @@ public final class BluetoothMap implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -363,15 +371,16 @@ public final class BluetoothMap implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return PRIORITY_OFF; } @@ -403,13 +412,8 @@ public final class BluetoothMap implements BluetoothProfile { log("Bluetooth is Not enabled"); return false; } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } - } diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 3e0c36548c41..af3b662d6a49 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -59,7 +59,7 @@ public final class BluetoothMapClient implements BluetoothProfile { public static final String EXTRA_SENDER_CONTACT_NAME = "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; - private IBluetoothMapClient mService; + private volatile IBluetoothMapClient mService; private final Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; @@ -176,9 +176,10 @@ public final class BluetoothMapClient implements BluetoothProfile { */ public boolean isConnected(BluetoothDevice device) { if (VDBG) Log.d(TAG, "isConnected(" + device + ")"); - if (mService != null) { + final IBluetoothMapClient service = mService; + if (service != null) { try { - return mService.isConnected(device); + return service.isConnected(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -195,9 +196,10 @@ public final class BluetoothMapClient implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE"); - if (mService != null) { + final IBluetoothMapClient service = mService; + if (service != null) { try { - return mService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -216,14 +218,15 @@ public final class BluetoothMapClient implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) Log.d(TAG, "disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -235,15 +238,16 @@ public final class BluetoothMapClient implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (DBG) Log.d(TAG, "getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<>(); } @@ -255,15 +259,16 @@ public final class BluetoothMapClient implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) Log.d(TAG, "getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<>(); } @@ -275,15 +280,16 @@ public final class BluetoothMapClient implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (DBG) Log.d(TAG, "getConnectionState(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -298,19 +304,20 @@ public final class BluetoothMapClient implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -326,15 +333,16 @@ public final class BluetoothMapClient implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) Log.d(TAG, "getPriority(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return PRIORITY_OFF; } @@ -353,9 +361,10 @@ public final class BluetoothMapClient implements BluetoothProfile { public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent) { if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.sendMessage(device, contacts, message, sentIntent, deliveredIntent); + return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -372,9 +381,10 @@ public final class BluetoothMapClient implements BluetoothProfile { */ public boolean getUnreadMessages(BluetoothDevice device) { if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothMapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getUnreadMessages(device); + return service.getUnreadMessages(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -409,12 +419,8 @@ public final class BluetoothMapClient implements BluetoothProfile { return false; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } - } diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 63e83d22178d..866b06308354 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -123,7 +123,7 @@ public final class BluetoothPan implements BluetoothProfile { private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; - private IBluetoothPan mPanService; + private volatile IBluetoothPan mPanService; /** * Create a BluetoothPan proxy object for interacting with the local @@ -238,15 +238,16 @@ public final class BluetoothPan implements BluetoothProfile { */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); - if (mPanService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mPanService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -277,15 +278,16 @@ public final class BluetoothPan implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mPanService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mPanService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; } } - if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -295,15 +297,16 @@ public final class BluetoothPan implements BluetoothProfile { @Override public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); - if (mPanService != null && isEnabled()) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled()) { try { - return mPanService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -313,15 +316,16 @@ public final class BluetoothPan implements BluetoothProfile { @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); - if (mPanService != null && isEnabled()) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled()) { try { - return mPanService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -331,25 +335,25 @@ public final class BluetoothPan implements BluetoothProfile { @Override public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); - if (mPanService != null && isEnabled() - && isValidDevice(device)) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mPanService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mPanService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } public void setBluetoothTethering(boolean value) { if (DBG) log("setBluetoothTethering(" + value + ")"); - - if (mPanService != null && isEnabled()) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled()) { try { - mPanService.setBluetoothTethering(value); + service.setBluetoothTethering(value); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); } @@ -358,10 +362,10 @@ public final class BluetoothPan implements BluetoothProfile { public boolean isTetheringOn() { if (VDBG) log("isTetheringOn()"); - - if (mPanService != null && isEnabled()) { + final IBluetoothPan service = mPanService; + if (service != null && isEnabled()) { try { - return mPanService.isTetheringOn(); + return service.isTetheringOn(); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); } @@ -373,7 +377,6 @@ public final class BluetoothPan implements BluetoothProfile { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected"); mPanService = IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service)); - if (mServiceListener != null) { mServiceListener.onServiceConnected(BluetoothProfile.PAN, BluetoothPan.this); @@ -390,15 +393,11 @@ public final class BluetoothPan implements BluetoothProfile { }; private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; + return mAdapter.getState() == BluetoothAdapter.STATE_ON; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } private static void log(String msg) { diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index 78b7c7b7a70b..19f5198ca71a 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -68,7 +68,7 @@ public class BluetoothPbap { public static final String PBAP_STATE_CHANGED_ACTION = "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED"; - private IBluetoothPbap mService; + private volatile IBluetoothPbap mService; private final Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; @@ -214,9 +214,10 @@ public class BluetoothPbap { */ public int getState() { if (VDBG) log("getState()"); - if (mService != null) { + final IBluetoothPbap service = mService; + if (service != null) { try { - return mService.getState(); + return service.getState(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -235,9 +236,10 @@ public class BluetoothPbap { */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); - if (mService != null) { + final IBluetoothPbap service = mService; + if (service != null) { try { - return mService.getClient(); + return service.getClient(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -255,9 +257,10 @@ public class BluetoothPbap { */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); - if (mService != null) { + final IBluetoothPbap service = mService; + if (service != null) { try { - return mService.isConnected(device); + return service.isConnected(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -275,9 +278,10 @@ public class BluetoothPbap { */ public boolean disconnect() { if (DBG) log("disconnect()"); - if (mService != null) { + final IBluetoothPbap service = mService; + if (service != null) { try { - mService.disconnect(); + service.disconnect(); return true; } catch (RemoteException e) { Log.e(TAG, e.toString()); diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java index b9b372c84843..00a15f3f7087 100644 --- a/core/java/android/bluetooth/BluetoothPbapClient.java +++ b/core/java/android/bluetooth/BluetoothPbapClient.java @@ -42,7 +42,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; - private IBluetoothPbapClient mService; + private volatile IBluetoothPbapClient mService; private final Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; @@ -173,15 +173,16 @@ public final class BluetoothPbapClient implements BluetoothProfile { if (DBG) { log("connect(" + device + ") for PBAP Client."); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.connect(device); + return service.connect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return false; @@ -197,16 +198,17 @@ public final class BluetoothPbapClient implements BluetoothProfile { if (DBG) { log("disconnect(" + device + ")" + new Exception()); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - mService.disconnect(device); + service.disconnect(device); return true; } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return false; @@ -223,15 +225,16 @@ public final class BluetoothPbapClient implements BluetoothProfile { if (DBG) { log("getConnectedDevices()"); } - if (mService != null && isEnabled()) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return new ArrayList<BluetoothDevice>(); @@ -247,15 +250,16 @@ public final class BluetoothPbapClient implements BluetoothProfile { if (DBG) { log("getDevicesMatchingStates()"); } - if (mService != null && isEnabled()) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return new ArrayList<BluetoothDevice>(); @@ -271,15 +275,16 @@ public final class BluetoothPbapClient implements BluetoothProfile { if (DBG) { log("getConnectionState(" + device + ")"); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return BluetoothProfile.STATE_DISCONNECTED; @@ -321,14 +326,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { return false; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) { - return false; - } - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) { - return true; - } - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } /** @@ -339,26 +338,27 @@ public final class BluetoothPbapClient implements BluetoothProfile { * {@link #PRIORITY_OFF}, * * @param device Paired bluetooth device - * @param priority + * @param priority Priority of this profile * @return true if priority is set, false on error */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) { log("setPriority(" + device + ", " + priority + ")"); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return false; @@ -378,15 +378,16 @@ public final class BluetoothPbapClient implements BluetoothProfile { if (VDBG) { log("getPriority(" + device + ")"); } - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothPbapClient service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } - if (mService == null) { + if (service == null) { Log.w(TAG, "Proxy not attached to service"); } return PRIORITY_OFF; diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java index bcdf4938fe2f..48481620c97f 100644 --- a/core/java/android/bluetooth/BluetoothSap.java +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -68,7 +68,7 @@ public final class BluetoothSap implements BluetoothProfile { public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; - private IBluetoothSap mService; + private volatile IBluetoothSap mService; private final Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; @@ -202,9 +202,10 @@ public final class BluetoothSap implements BluetoothProfile { */ public int getState() { if (VDBG) log("getState()"); - if (mService != null) { + final IBluetoothSap service = mService; + if (service != null) { try { - return mService.getState(); + return service.getState(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -224,9 +225,10 @@ public final class BluetoothSap implements BluetoothProfile { */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); - if (mService != null) { + final IBluetoothSap service = mService; + if (service != null) { try { - return mService.getClient(); + return service.getClient(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -246,9 +248,10 @@ public final class BluetoothSap implements BluetoothProfile { */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); - if (mService != null) { + final IBluetoothSap service = mService; + if (service != null) { try { - return mService.isConnected(device); + return service.isConnected(device); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -279,15 +282,16 @@ public final class BluetoothSap implements BluetoothProfile { */ public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothSap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.disconnect(device); + return service.disconnect(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -299,15 +303,16 @@ public final class BluetoothSap implements BluetoothProfile { */ public List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); - if (mService != null && isEnabled()) { + final IBluetoothSap service = mService; + if (service != null && isEnabled()) { try { - return mService.getConnectedDevices(); + return service.getConnectedDevices(); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -319,15 +324,16 @@ public final class BluetoothSap implements BluetoothProfile { */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); - if (mService != null && isEnabled()) { + final IBluetoothSap service = mService; + if (service != null && isEnabled()) { try { - return mService.getDevicesMatchingConnectionStates(states); + return service.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); } @@ -339,15 +345,16 @@ public final class BluetoothSap implements BluetoothProfile { */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothSap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getConnectionState(device); + return service.getConnectionState(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; } @@ -363,19 +370,20 @@ public final class BluetoothSap implements BluetoothProfile { */ public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothSap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { if (priority != BluetoothProfile.PRIORITY_OFF && priority != BluetoothProfile.PRIORITY_ON) { return false; } try { - return mService.setPriority(device, priority); + return service.setPriority(device, priority); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; } @@ -388,19 +396,20 @@ public final class BluetoothSap implements BluetoothProfile { */ public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); - if (mService != null && isEnabled() && isValidDevice(device)) { + final IBluetoothSap service = mService; + if (service != null && isEnabled() && isValidDevice(device)) { try { - return mService.getPriority(device); + return service.getPriority(device); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return PRIORITY_OFF; } } - if (mService == null) Log.w(TAG, "Proxy not attached to service"); + if (service == null) Log.w(TAG, "Proxy not attached to service"); return PRIORITY_OFF; } - private ServiceConnection mConnection = new ServiceConnection() { + private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) log("Proxy object connected"); mService = IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service)); @@ -432,15 +441,8 @@ public final class BluetoothSap implements BluetoothProfile { return false; } - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) { - return false; - } - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) { - return true; - } - return false; + private static boolean isValidDevice(BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } } diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java index 5bfc54d267ca..76cb3f5b548e 100644 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -232,7 +232,7 @@ public final class BluetoothUuid { */ 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/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java index ad9e20b9292c..dd1a61c31644 100644 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -99,7 +99,9 @@ public final class BluetoothLeScanner { /** * Start Bluetooth LE scan with default parameters and no filters. The scan results will be - * delivered through {@code callback}. + * delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen + * off to save power. Scanning is resumed when screen is turned on again. To avoid this, use + * {@link #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}. * <p> * An app must hold * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or @@ -116,6 +118,9 @@ public final class BluetoothLeScanner { /** * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. + * For unfiltered scans, scanning is stopped on screen off to save power. Scanning is + * resumed when screen is turned on again. To avoid this, do filetered scanning by + * using proper {@link ScanFilter}. * <p> * An app must hold * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java index 35ed424aa2db..8fdcba85d33a 100644 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -35,7 +35,7 @@ public final class ScanSettings implements Parcelable { /** * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the - * least power. + * least power. This mode is enforced if the scanning application is not in foreground. */ public static final int SCAN_MODE_LOW_POWER = 0; diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 1bb0fbb74a53..2c9fb23e077a 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -18,14 +18,13 @@ package android.net; import android.annotation.NonNull; import android.annotation.Nullable; -import android.net.ProxyInfo; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; -import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; @@ -71,8 +70,23 @@ public final class LinkProperties implements Parcelable { * @hide */ public static class CompareResult<T> { - public List<T> removed = new ArrayList<T>(); - public List<T> added = new ArrayList<T>(); + public final List<T> removed = new ArrayList<T>(); + public final List<T> added = new ArrayList<T>(); + + public CompareResult() {} + + public CompareResult(Collection<T> oldItems, Collection<T> newItems) { + if (oldItems != null) { + removed.addAll(oldItems); + } + if (newItems != null) { + for (T newItem : newItems) { + if (!removed.remove(newItem)) { + added.add(newItem); + } + } + } + } @Override public String toString() { @@ -504,11 +518,22 @@ public final class LinkProperties implements Parcelable { } /** + * Make sure this LinkProperties instance contains routes that cover the local subnet + * of its link addresses. Add any route that is missing. + * @hide + */ + public void ensureDirectlyConnectedRoutes() { + for (LinkAddress addr: mLinkAddresses) { + addRoute(new RouteInfo(addr, null, mIfaceName)); + } + } + + /** * Returns all the routes on this link and all the links stacked above it. * @hide */ public List<RouteInfo> getAllRoutes() { - List<RouteInfo> routes = new ArrayList(); + List<RouteInfo> routes = new ArrayList<>(); routes.addAll(mRoutes); for (LinkProperties stacked: mStackedLinks.values()) { routes.addAll(stacked.getAllRoutes()); @@ -990,17 +1015,8 @@ public final class LinkProperties implements Parcelable { * are in target but not in mLinkAddresses are placed in the * addedAddresses. */ - CompareResult<LinkAddress> result = new CompareResult<LinkAddress>(); - result.removed = new ArrayList<LinkAddress>(mLinkAddresses); - result.added.clear(); - if (target != null) { - for (LinkAddress newAddress : target.getLinkAddresses()) { - if (! result.removed.remove(newAddress)) { - result.added.add(newAddress); - } - } - } - return result; + return new CompareResult<>(mLinkAddresses, + target != null ? target.getLinkAddresses() : null); } /** @@ -1019,18 +1035,7 @@ public final class LinkProperties implements Parcelable { * are in target but not in mDnses are placed in the * addedAddresses. */ - CompareResult<InetAddress> result = new CompareResult<InetAddress>(); - - result.removed = new ArrayList<InetAddress>(mDnses); - result.added.clear(); - if (target != null) { - for (InetAddress newAddress : target.getDnsServers()) { - if (! result.removed.remove(newAddress)) { - result.added.add(newAddress); - } - } - } - return result; + return new CompareResult<>(mDnses, target != null ? target.getDnsServers() : null); } /** @@ -1048,18 +1053,7 @@ public final class LinkProperties implements Parcelable { * leaving the routes that are different. And route address which * are in target but not in mRoutes are placed in added. */ - CompareResult<RouteInfo> result = new CompareResult<RouteInfo>(); - - result.removed = getAllRoutes(); - result.added.clear(); - if (target != null) { - for (RouteInfo r : target.getAllRoutes()) { - if (! result.removed.remove(r)) { - result.added.add(r); - } - } - } - return result; + return new CompareResult<>(getAllRoutes(), target != null ? target.getAllRoutes() : null); } /** @@ -1077,18 +1071,8 @@ public final class LinkProperties implements Parcelable { * leaving the interface names that are different. And interface names which * are in target but not in this are placed in added. */ - CompareResult<String> result = new CompareResult<String>(); - - result.removed = getAllInterfaceNames(); - result.added.clear(); - if (target != null) { - for (String r : target.getAllInterfaceNames()) { - if (! result.removed.remove(r)) { - result.added.add(r); - } - } - } - return result; + return new CompareResult<>(getAllInterfaceNames(), + target != null ? target.getAllInterfaceNames() : null); } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 0780af602be2..171adc054bbe 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -677,36 +677,33 @@ public class NetworkStats implements Parcelable { entry.tag = left.tag[i]; entry.metered = left.metered[i]; entry.roaming = left.roaming[i]; + entry.rxBytes = left.rxBytes[i]; + entry.rxPackets = left.rxPackets[i]; + entry.txBytes = left.txBytes[i]; + entry.txPackets = left.txPackets[i]; + entry.operations = left.operations[i]; // find remote row that matches, and subtract final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, entry.roaming, i); - if (j == -1) { - // newly appearing row, return entire value - entry.rxBytes = left.rxBytes[i]; - entry.rxPackets = left.rxPackets[i]; - entry.txBytes = left.txBytes[i]; - entry.txPackets = left.txPackets[i]; - entry.operations = left.operations[i]; - } else { - // existing row, subtract remote value - entry.rxBytes = left.rxBytes[i] - right.rxBytes[j]; - entry.rxPackets = left.rxPackets[i] - right.rxPackets[j]; - entry.txBytes = left.txBytes[i] - right.txBytes[j]; - entry.txPackets = left.txPackets[i] - right.txPackets[j]; - entry.operations = left.operations[i] - right.operations[j]; - - if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 - || entry.txPackets < 0 || entry.operations < 0) { - if (observer != null) { - observer.foundNonMonotonic(left, i, right, j, cookie); - } - entry.rxBytes = Math.max(entry.rxBytes, 0); - entry.rxPackets = Math.max(entry.rxPackets, 0); - entry.txBytes = Math.max(entry.txBytes, 0); - entry.txPackets = Math.max(entry.txPackets, 0); - entry.operations = Math.max(entry.operations, 0); + if (j != -1) { + // Found matching row, subtract remote value. + entry.rxBytes -= right.rxBytes[j]; + entry.rxPackets -= right.rxPackets[j]; + entry.txBytes -= right.txBytes[j]; + entry.txPackets -= right.txPackets[j]; + entry.operations -= right.operations[j]; + } + + if (entry.isNegative()) { + if (observer != null) { + observer.foundNonMonotonic(left, i, right, j, cookie); } + entry.rxBytes = Math.max(entry.rxBytes, 0); + entry.rxPackets = Math.max(entry.rxPackets, 0); + entry.txBytes = Math.max(entry.txBytes, 0); + entry.txPackets = Math.max(entry.txPackets, 0); + entry.operations = Math.max(entry.operations, 0); } result.addValues(entry); diff --git a/core/java/android/net/metrics/WakeupEvent.java b/core/java/android/net/metrics/WakeupEvent.java new file mode 100644 index 000000000000..cbf3fc8c81da --- /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 000000000000..d520b9745918 --- /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 unroutedWakeups = 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: + unroutedWakeups++; + 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(", unrouted: ").append(unroutedWakeups) + .append(", ").append(durationSec).append("s)") + .toString(); + } +} diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java index 1e41eea925a5..535bf675cd0e 100644 --- a/core/java/android/net/nsd/NsdManager.java +++ b/core/java/android/net/nsd/NsdManager.java @@ -21,25 +21,24 @@ import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.internal.util.Preconditions.checkStringNotEmpty; import android.annotation.SdkConstant; -import android.annotation.SystemService; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemService; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; import android.os.Messenger; -import android.text.TextUtils; +import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; -import java.util.concurrent.CountDownLatch; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; +import java.util.concurrent.CountDownLatch; + /** * The Network Service Discovery Manager class provides the API to discover services * on a network. As an example, if device A and device B are connected over a Wi-Fi @@ -244,7 +243,7 @@ public final class NsdManager { return name; } - private static int FIRST_LISTENER_KEY = 1; + private static final int FIRST_LISTENER_KEY = 1; private final INsdManager mService; private final Context mContext; @@ -278,6 +277,7 @@ public final class NsdManager { @VisibleForTesting public void disconnect() { mAsyncChannel.disconnect(); + mHandler.getLooper().quitSafely(); } /** @@ -650,7 +650,7 @@ public final class NsdManager { private static void checkServiceInfo(NsdServiceInfo serviceInfo) { checkNotNull(serviceInfo, "NsdServiceInfo cannot be null"); - checkStringNotEmpty(serviceInfo.getServiceName(),"Service name cannot be empty"); + checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty"); checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty"); } } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index ae0b88573e8f..c45eb2e4ac39 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -19,8 +19,8 @@ package android.os; import android.annotation.TestApi; import android.system.Os; import android.system.OsConstants; -import android.util.Log; import android.webkit.WebViewZygote; + import dalvik.system.VMRuntime; /** @@ -417,7 +417,7 @@ public class Process { * * When invokeWith is not null, the process will be started as a fresh app * and not a zygote fork. Note that this is only allowed for uid 0 or when - * debugFlags contains DEBUG_ENABLE_DEBUGGER. + * runtimeFlags contains DEBUG_ENABLE_DEBUGGER. * * @param processClass The class to use as the process's main entry * point. @@ -425,7 +425,7 @@ public class Process { * @param uid The user-id under which the process will run. * @param gid The group-id under which the process will run. * @param gids Additional group-ids associated with the process. - * @param debugFlags Additional flags. + * @param runtimeFlags Additional flags for the runtime. * @param targetSdkVersion The target SDK version for the app. * @param seInfo null-ok SELinux information for the new process. * @param abi non-null the ABI this app should be started with. @@ -442,7 +442,7 @@ public class Process { public static final ProcessStartResult start(final String processClass, final String niceName, int uid, int gid, int[] gids, - int debugFlags, int mountExternal, + int runtimeFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, @@ -451,7 +451,7 @@ public class Process { String invokeWith, String[] zygoteArgs) { return zygoteProcess.start(processClass, niceName, uid, gid, gids, - debugFlags, mountExternal, targetSdkVersion, seInfo, + runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } @@ -459,7 +459,7 @@ public class Process { public static final ProcessStartResult startWebView(final String processClass, final String niceName, int uid, int gid, int[] gids, - int debugFlags, int mountExternal, + int runtimeFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, @@ -468,7 +468,7 @@ public class Process { String invokeWith, String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, - debugFlags, mountExternal, targetSdkVersion, seInfo, + runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 8632194f2b02..560b4b31cdc6 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -16,6 +16,8 @@ package android.os; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.Log; import android.util.MutableInt; @@ -43,17 +45,12 @@ public class SystemProperties { public static final int PROP_VALUE_MAX = 91; + @GuardedBy("sChangeCallbacks") private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>(); @GuardedBy("sRoReads") - private static final HashMap<String, MutableInt> sRoReads; - static { - if (TRACK_KEY_ACCESS) { - sRoReads = new HashMap<>(); - } else { - sRoReads = null; - } - } + private static final HashMap<String, MutableInt> sRoReads = + TRACK_KEY_ACCESS ? new HashMap<>() : null; private static void onKeyAccess(String key) { if (!TRACK_KEY_ACCESS) return; @@ -85,77 +82,102 @@ public class SystemProperties { private static native void native_report_sysprop_change(); /** - * Get the value for the given key. - * @return an empty string if the key isn't found + * Get the String value for the given {@code key}. + * + * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This + * method will crash in native code. + * + * @param key the key to lookup + * @return an empty string if the {@code key} isn't found */ - public static String get(String key) { + @NonNull + public static String get(@NonNull String key) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key); } /** - * Get the value for the given key. - * @return if the key isn't found, return def if it isn't null, or an empty string otherwise + * Get the String value for the given {@code key}. + * + * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This + * method will crash in native code. + * + * @param key the key to lookup + * @param def the default value in case the property is not set or empty + * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty + * string otherwise */ - public static String get(String key, String def) { + @NonNull + public static String get(@NonNull String key, @Nullable String def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get(key, def); } /** - * Get the value for the given key, and return as an integer. + * Get the value for the given {@code key}, and return as an integer. + * * @param key the key to lookup * @param def a default value to return * @return the key parsed as an integer, or def if the key isn't found or * cannot be parsed */ - public static int getInt(String key, int def) { + public static int getInt(@NonNull String key, int def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_int(key, def); } /** - * Get the value for the given key, and return as a long. + * Get the value for the given {@code key}, and return as a long. + * * @param key the key to lookup * @param def a default value to return * @return the key parsed as a long, or def if the key isn't found or * cannot be parsed */ - public static long getLong(String key, long def) { + public static long getLong(@NonNull String key, long def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_long(key, def); } /** - * Get the value for the given key, returned as a boolean. + * Get the value for the given {@code key}, returned as a boolean. * Values 'n', 'no', '0', 'false' or 'off' are considered false. * Values 'y', 'yes', '1', 'true' or 'on' are considered true. * (case sensitive). * If the key does not exist, or has any other value, then the default * result is returned. + * * @param key the key to lookup * @param def a default value to return * @return the key parsed as a boolean, or def if the key isn't found or is * not able to be parsed as a boolean. */ - public static boolean getBoolean(String key, boolean def) { + public static boolean getBoolean(@NonNull String key, boolean def) { if (TRACK_KEY_ACCESS) onKeyAccess(key); return native_get_boolean(key, def); } /** - * Set the value for the given key. - * @throws IllegalArgumentException if the value exceeds 92 characters + * Set the value for the given {@code key} to {@code val}. + * + * @throws IllegalArgumentException if the {@code val} exceeds 91 characters */ - public static void set(String key, String val) { + public static void set(@NonNull String key, @Nullable String val) { if (val != null && val.length() > PROP_VALUE_MAX) { - throw newValueTooLargeException(key, val); + throw new IllegalArgumentException("value of system property '" + key + + "' is longer than " + PROP_VALUE_MAX + " characters: " + val); } if (TRACK_KEY_ACCESS) onKeyAccess(key); native_set(key, val); } - public static void addChangeCallback(Runnable callback) { + /** + * Add a callback that will be run whenever any system property changes. + * + * @param callback The {@link Runnable} that should be executed when a system property + * changes. + */ + public static void addChangeCallback(@NonNull Runnable callback) { synchronized (sChangeCallbacks) { if (sChangeCallbacks.size() == 0) { native_add_change_callback(); @@ -164,7 +186,8 @@ public class SystemProperties { } } - static void callChangeCallbacks() { + @SuppressWarnings("unused") // Called from native code. + private static void callChangeCallbacks() { synchronized (sChangeCallbacks) { //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!"); if (sChangeCallbacks.size() == 0) { @@ -177,11 +200,6 @@ public class SystemProperties { } } - private static IllegalArgumentException newValueTooLargeException(String key, String value) { - return new IllegalArgumentException("value of system property '" + key + "' is longer than " - + PROP_VALUE_MAX + " characters: " + value); - } - /* * Notifies listeners that a system property has changed */ diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 8208438dc62a..93826d808c3a 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -19,9 +19,11 @@ package android.os; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.util.Log; + import com.android.internal.annotations.GuardedBy; import com.android.internal.os.Zygote; import com.android.internal.util.Preconditions; + import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.IOException; @@ -172,7 +174,7 @@ public class ZygoteProcess { * * When invokeWith is not null, the process will be started as a fresh app * and not a zygote fork. Note that this is only allowed for uid 0 or when - * debugFlags contains DEBUG_ENABLE_DEBUGGER. + * runtimeFlags contains DEBUG_ENABLE_DEBUGGER. * * @param processClass The class to use as the process's main entry * point. @@ -180,7 +182,7 @@ public class ZygoteProcess { * @param uid The user-id under which the process will run. * @param gid The group-id under which the process will run. * @param gids Additional group-ids associated with the process. - * @param debugFlags Additional flags. + * @param runtimeFlags Additional flags. * @param targetSdkVersion The target SDK version for the app. * @param seInfo null-ok SELinux information for the new process. * @param abi non-null the ABI this app should be started with. @@ -195,7 +197,7 @@ public class ZygoteProcess { public final Process.ProcessStartResult start(final String processClass, final String niceName, int uid, int gid, int[] gids, - int debugFlags, int mountExternal, + int runtimeFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, @@ -205,7 +207,7 @@ public class ZygoteProcess { String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, - debugFlags, mountExternal, targetSdkVersion, seInfo, + runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, @@ -316,7 +318,7 @@ public class ZygoteProcess { * @param gid a POSIX gid that the new process shuold setgid() to * @param gids null-ok; a list of supplementary group IDs that the * new process should setgroup() to. - * @param debugFlags Additional flags. + * @param runtimeFlags Additional flags for the runtime. * @param targetSdkVersion The target SDK version for the app. * @param seInfo null-ok SELinux information for the new process. * @param abi the ABI the process should use. @@ -330,7 +332,7 @@ public class ZygoteProcess { final String niceName, final int uid, final int gid, final int[] gids, - int debugFlags, int mountExternal, + int runtimeFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, @@ -346,33 +348,7 @@ public class ZygoteProcess { argsForZygote.add("--runtime-args"); argsForZygote.add("--setuid=" + uid); argsForZygote.add("--setgid=" + gid); - if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) { - argsForZygote.add("--enable-jni-logging"); - } - if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) { - argsForZygote.add("--enable-safemode"); - } - if ((debugFlags & Zygote.DEBUG_ENABLE_JDWP) != 0) { - argsForZygote.add("--enable-jdwp"); - } - if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) { - argsForZygote.add("--enable-checkjni"); - } - if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) { - argsForZygote.add("--generate-debug-info"); - } - if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) { - argsForZygote.add("--always-jit"); - } - if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) { - argsForZygote.add("--native-debuggable"); - } - if ((debugFlags & Zygote.DEBUG_JAVA_DEBUGGABLE) != 0) { - argsForZygote.add("--java-debuggable"); - } - if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { - argsForZygote.add("--enable-assert"); - } + argsForZygote.add("--runtime-flags=" + runtimeFlags); if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) { argsForZygote.add("--mount-external-default"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) { @@ -487,8 +463,8 @@ public class ZygoteProcess { * 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"); @@ -507,6 +483,8 @@ public class ZygoteProcess { 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 f4be1289eb70..66475e445efa 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -31,6 +31,7 @@ import android.util.Slog; 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 @@ public class RuntimeInit { * @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 @@ public class RuntimeInit { * 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 @@ public class RuntimeInit { 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 @@ public class RuntimeInit { 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 @@ public class RuntimeInit { 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 e28079fd5cdd..7f46a0c1abde 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.util.Log; 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 @@ class WebViewZygoteInit { } @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 @@ class WebViewZygoteInit { // 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 @@ class WebViewZygoteInit { 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 608bc9ff33d3..89328b21ccf5 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.StructCapUserData; 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 @@ public class WrapperInit { * @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)); + // 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(); - } + // 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 @@ public class WrapperInit { * @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 @@ public class WrapperInit { // 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 91d9d1e43cf7..c85f41774b04 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -28,7 +28,7 @@ import java.lang.reflect.Method; /** @hide */ public final class Zygote { /* - * Bit values for "debugFlags" argument. The definitions are duplicated + * Bit values for "runtimeFlags" argument. The definitions are duplicated * in the native code. */ @@ -75,7 +75,7 @@ public final class Zygote { * fork()ing and and before spawning any threads. * @param gids null-ok; a list of UNIX gids that the new process should * setgroups() to after fork and before spawning any threads. - * @param debugFlags bit flags that enable debugging features. + * @param runtimeFlags bit flags that enable ART features. * @param rlimits null-ok an array of rlimit tuples, with the second * dimension having a length of 3 and representing * (resource, rlim_cur, rlim_max). These are set via the posix @@ -96,14 +96,14 @@ public final class Zygote { * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. */ - public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags, + public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, String instructionSet, String appDataDir) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( - uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, + uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, instructionSet, appDataDir); // Enable tracing as soon as possible for the child process. if (pid == 0) { @@ -116,7 +116,7 @@ public final class Zygote { return pid; } - native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int debugFlags, + native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, String instructionSet, String appDataDir); @@ -137,7 +137,7 @@ public final class Zygote { * fork()ing and and before spawning any threads. * @param gids null-ok; a list of UNIX gids that the new process should * setgroups() to after fork and before spawning any threads. - * @param debugFlags bit flags that enable debugging features. + * @param runtimeFlags bit flags that enable ART features. * @param rlimits null-ok an array of rlimit tuples, with the second * dimension having a length of 3 and representing * (resource, rlim_cur, rlim_max). These are set via the posix @@ -148,13 +148,13 @@ public final class Zygote { * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. */ - public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags, + public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkSystemServer( - uid, gid, gids, debugFlags, rlimits, permittedCapabilities, effectiveCapabilities); + uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); // Enable tracing as soon as we enter the system_server. if (pid == 0) { Trace.setTracingEnabled(true); @@ -163,7 +163,7 @@ public final class Zygote { return pid; } - native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags, + native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities); /** @@ -177,9 +177,9 @@ public final class Zygote { */ native protected static void nativeUnmountStorageOnInit(); - private static void callPostForkChildHooks(int debugFlags, boolean isSystemServer, + private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, String instructionSet) { - VM_HOOKS.postForkChild(debugFlags, isSystemServer, instructionSet); + VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, instructionSet); } /** @@ -221,39 +221,4 @@ public final class Zygote { 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 05f43e4abbf5..0bb7326a682b 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.Credentials; 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.BufferedReader; 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 @@ class ZygoteConnection { private final BufferedReader mSocketReader; private final Credentials peer; private final String abiList; + private boolean isEof; /** * Constructs instance from connected socket. @@ -99,6 +98,8 @@ class ZygoteConnection { Log.e(TAG, "Cannot read peer credentials", ex); throw ex; } + + isEof = false; } /** @@ -111,21 +112,14 @@ class ZygoteConnection { } /** - * 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 @@ class ZygoteConnection { 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. - */ + /** + * 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 }; + int [] fdsToClose = { -1, -1 }; - FileDescriptor fd = mSocket.getFileDescriptor(); + FileDescriptor fd = mSocket.getFileDescriptor(); - if (fd != null) { - fdsToClose[0] = fd.getInt$(); - } + if (fd != null) { + fdsToClose[0] = fd.getInt$(); + } - fd = zygoteServer.getServerSocketFileDescriptor(); + fd = zygoteServer.getServerSocketFileDescriptor(); - if (fd != null) { - fdsToClose[1] = fd.getInt$(); - } + if (fd != null) { + fdsToClose[1] = fd.getInt$(); + } - fd = null; + fd = null; - pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, - parsedArgs.debugFlags, 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); - } + 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 @@ class ZygoteConnection { } } - 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 @@ class ZygoteConnection { * 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 @@ class ZygoteConnection { 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 @@ class ZygoteConnection { 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 @@ class ZygoteConnection { } } + boolean isClosedByPeer() { + return isEof; + } + /** * Handles argument parsing for args related to the zygote spawner. * @@ -363,11 +350,9 @@ class ZygoteConnection { int[] gids; /** - * From --enable-jdwp, --enable-checkjni, --enable-assert, - * --enable-safemode, --generate-debug-info, --enable-jni-logging, - * --java-debuggable, and --native-debuggable. + * From --runtime-flags. */ - int debugFlags; + int runtimeFlags; /** From --mount-external */ int mountExternal = Zygote.MOUNT_EXTERNAL_NONE; @@ -483,26 +468,11 @@ class ZygoteConnection { targetSdkVersionSpecified = true; targetSdkVersion = Integer.parseInt( arg.substring(arg.indexOf('=') + 1)); - } else if (arg.equals("--enable-jdwp")) { - debugFlags |= Zygote.DEBUG_ENABLE_JDWP; - } else if (arg.equals("--enable-safemode")) { - debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE; - } else if (arg.equals("--enable-checkjni")) { - debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; - } else if (arg.equals("--generate-debug-info")) { - debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; - } else if (arg.equals("--always-jit")) { - debugFlags |= Zygote.DEBUG_ALWAYS_JIT; - } else if (arg.equals("--native-debuggable")) { - debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE; - } else if (arg.equals("--java-debuggable")) { - debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE; - } else if (arg.equals("--enable-jni-logging")) { - debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; - } else if (arg.equals("--enable-assert")) { - debugFlags |= Zygote.DEBUG_ENABLE_ASSERT; } else if (arg.equals("--runtime-args")) { seenRuntimeArgs = true; + } else if (arg.startsWith("--runtime-flags=")) { + runtimeFlags = Integer.parseInt( + arg.substring(arg.indexOf('=') + 1)); } else if (arg.startsWith("--seinfo=")) { if (seInfoSpecified) { throw new IllegalArgumentException( @@ -718,7 +688,7 @@ class ZygoteConnection { */ public static void applyDebuggerSystemProperty(Arguments args) { if (RoSystemProperties.DEBUGGABLE) { - args.debugFlags |= Zygote.DEBUG_ENABLE_JDWP; + args.runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP; } } @@ -740,7 +710,7 @@ class ZygoteConnection { int peerUid = peer.getUid(); if (args.invokeWith != null && peerUid != 0 && - (args.debugFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { + (args.runtimeFlags & Zygote.DEBUG_ENABLE_JDWP) == 0) { throw new ZygoteSecurityException("Peer is permitted to specify an" + "explicit invoke-with wrapper command only for debuggable" + "applications."); @@ -770,15 +740,9 @@ class ZygoteConnection { * @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 @@ -795,7 +759,6 @@ class ZygoteConnection { for (FileDescriptor fd: descriptors) { IoUtils.closeQuietly(fd); } - newStderr = System.err; } catch (ErrnoException ex) { Log.e(TAG, "Error reopening stdio", ex); } @@ -812,9 +775,12 @@ class ZygoteConnection { 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 */); } } @@ -826,13 +792,8 @@ class ZygoteConnection { * @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); } @@ -924,11 +885,8 @@ class ZygoteConnection { 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) { @@ -944,20 +902,4 @@ class ZygoteConnection { + "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 c8c7ed9761bb..ee19163f5bf9 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.content.res.TypedArray; 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 @@ public class ZygoteInit { /** * 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 @@ public class ZygoteInit { 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 @@ public class ZygoteInit { /* * 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 @@ public class ZygoteInit { } /** - * 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, @@ -642,7 +643,7 @@ public class ZygoteInit { pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, - parsedArgs.debugFlags, + parsedArgs.runtimeFlags, null, parsedArgs.permittedCapabilities, parsedArgs.effectiveCapabilities); @@ -657,10 +658,10 @@ public class ZygoteInit { } zygoteServer.closeServerSocket(); - handleSystemServerProcess(parsedArgs); + return handleSystemServerProcess(parsedArgs); } - return true; + return null; } /** @@ -691,6 +692,7 @@ public class ZygoteInit { 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 @@ public class ZygoteInit { 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 @@ public class ZygoteInit { * @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 @@ public class ZygoteInit { 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 126d9e7db2ff..8baa15a058de 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.ErrnoException; 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 @@ class ZygoteServer { 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 @@ class ZygoteServer { * 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 @@ class ZygoteServer { 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/jni/Android.bp b/core/jni/Android.bp index abd6278b056a..c5279e10d93f 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -231,6 +231,7 @@ cc_library_shared { "libutils", "libbinder", "libui", + "libgraphicsenv", "libgui", "libsensor", "libinput", diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index 399dec8fc9cf..f749488f39b0 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -16,7 +16,7 @@ #define LOG_TAG "GraphicsEnvironment" -#include <ui/GraphicsEnv.h> +#include <graphicsenv/GraphicsEnv.h> #include <nativehelper/ScopedUtfChars.h> #include "core_jni_helpers.h" diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp index 8844fb0a261f..a94cac0f18f5 100644 --- a/core/jni/android_os_SystemProperties.cpp +++ b/core/jni/android_os_SystemProperties.cpp @@ -17,188 +17,109 @@ #define LOG_TAG "SysPropJNI" +#include "android-base/logging.h" +#include "android-base/properties.h" #include "cutils/properties.h" #include "utils/misc.h" #include <utils/Log.h> #include "jni.h" #include "core_jni_helpers.h" #include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> namespace android { -static jstring SystemProperties_getSS(JNIEnv *env, jobject clazz, - jstring keyJ, jstring defJ) -{ - int len; - const char* key; - char buf[PROPERTY_VALUE_MAX]; - jstring rvJ = NULL; - - if (keyJ == NULL) { - jniThrowNullPointerException(env, "key must not be null."); - goto error; - } - - key = env->GetStringUTFChars(keyJ, NULL); - - len = property_get(key, buf, ""); - if ((len <= 0) && (defJ != NULL)) { - rvJ = defJ; - } else if (len >= 0) { - rvJ = env->NewStringUTF(buf); - } else { - rvJ = env->NewStringUTF(""); +namespace { + +template <typename T, typename Handler> +T ConvertKeyAndForward(JNIEnv *env, jstring keyJ, T defJ, Handler handler) { + std::string key; + { + // Scope the String access. If the handler can throw an exception, + // releasing the string characters late would trigger an abort. + ScopedUtfChars key_utf(env, keyJ); + if (key_utf.c_str() == nullptr) { + return defJ; + } + key = key_utf.c_str(); // This will make a copy, but we can't avoid + // with the existing interface in + // android::base. } - - env->ReleaseStringUTFChars(keyJ, key); - -error: - return rvJ; + return handler(key, defJ); } -static jstring SystemProperties_getS(JNIEnv *env, jobject clazz, - jstring keyJ) +jstring SystemProperties_getSS(JNIEnv *env, jclass clazz, jstring keyJ, + jstring defJ) { - return SystemProperties_getSS(env, clazz, keyJ, NULL); + // Using ConvertKeyAndForward is sub-optimal for copying the key string, + // but improves reuse and reasoning over code. + auto handler = [&](const std::string& key, jstring defJ) { + std::string prop_val = android::base::GetProperty(key, ""); + if (!prop_val.empty()) { + return env->NewStringUTF(prop_val.c_str()); + }; + if (defJ != nullptr) { + return defJ; + } + // This function is specified to never return null (or have an + // exception pending). + return env->NewStringUTF(""); + }; + return ConvertKeyAndForward(env, keyJ, defJ, handler); } -static jint SystemProperties_get_int(JNIEnv *env, jobject clazz, - jstring keyJ, jint defJ) +jstring SystemProperties_getS(JNIEnv *env, jclass clazz, jstring keyJ) { - int len; - const char* key; - char buf[PROPERTY_VALUE_MAX]; - char* end; - jint result = defJ; - - if (keyJ == NULL) { - jniThrowNullPointerException(env, "key must not be null."); - goto error; - } - - key = env->GetStringUTFChars(keyJ, NULL); - - len = property_get(key, buf, ""); - if (len > 0) { - result = strtol(buf, &end, 0); - if (end == buf) { - result = defJ; - } - } - - env->ReleaseStringUTFChars(keyJ, key); - -error: - return result; + return SystemProperties_getSS(env, clazz, keyJ, nullptr); } -static jlong SystemProperties_get_long(JNIEnv *env, jobject clazz, - jstring keyJ, jlong defJ) +template <typename T> +T SystemProperties_get_integral(JNIEnv *env, jclass, jstring keyJ, + T defJ) { - int len; - const char* key; - char buf[PROPERTY_VALUE_MAX]; - char* end; - jlong result = defJ; - - if (keyJ == NULL) { - jniThrowNullPointerException(env, "key must not be null."); - goto error; - } - - key = env->GetStringUTFChars(keyJ, NULL); - - len = property_get(key, buf, ""); - if (len > 0) { - result = strtoll(buf, &end, 0); - if (end == buf) { - result = defJ; - } - } - - env->ReleaseStringUTFChars(keyJ, key); - -error: - return result; + auto handler = [](const std::string& key, T defV) { + return android::base::GetIntProperty<T>(key, defV); + }; + return ConvertKeyAndForward(env, keyJ, defJ, handler); } -static jboolean SystemProperties_get_boolean(JNIEnv *env, jobject clazz, - jstring keyJ, jboolean defJ) +jboolean SystemProperties_get_boolean(JNIEnv *env, jclass, jstring keyJ, + jboolean defJ) { - int len; - const char* key; - char buf[PROPERTY_VALUE_MAX]; - jboolean result = defJ; - - if (keyJ == NULL) { - jniThrowNullPointerException(env, "key must not be null."); - goto error; - } - - key = env->GetStringUTFChars(keyJ, NULL); - - len = property_get(key, buf, ""); - if (len == 1) { - char ch = buf[0]; - if (ch == '0' || ch == 'n') - result = false; - else if (ch == '1' || ch == 'y') - result = true; - } else if (len > 1) { - if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) { - result = false; - } else if (!strcmp(buf, "yes") || !strcmp(buf, "true") || !strcmp(buf, "on")) { - result = true; - } - } - - env->ReleaseStringUTFChars(keyJ, key); - -error: - return result; + auto handler = [](const std::string& key, jboolean defV) -> jboolean { + bool result = android::base::GetBoolProperty(key, defV); + return result ? JNI_TRUE : JNI_FALSE; + }; + return ConvertKeyAndForward(env, keyJ, defJ, handler); } -static void SystemProperties_set(JNIEnv *env, jobject clazz, - jstring keyJ, jstring valJ) +void SystemProperties_set(JNIEnv *env, jobject clazz, jstring keyJ, + jstring valJ) { - int err; - const char* key; - const char* val; - - if (keyJ == NULL) { - jniThrowNullPointerException(env, "key must not be null."); - return ; - } - key = env->GetStringUTFChars(keyJ, NULL); - - if (valJ == NULL) { - val = ""; /* NULL pointer not allowed here */ - } else { - val = env->GetStringUTFChars(valJ, NULL); - } - - err = property_set(key, val); - - env->ReleaseStringUTFChars(keyJ, key); - - if (valJ != NULL) { - env->ReleaseStringUTFChars(valJ, val); - } - - if (err < 0) { + auto handler = [&](const std::string& key, bool) { + std::string val; + if (valJ != nullptr) { + ScopedUtfChars key_utf(env, valJ); + val = key_utf.c_str(); + } + return android::base::SetProperty(key, val); + }; + if (!ConvertKeyAndForward(env, keyJ, true, handler)) { + // Must have been a failure in SetProperty. jniThrowException(env, "java/lang/RuntimeException", "failed to set system property"); } } -static JavaVM* sVM = NULL; -static jclass sClazz = NULL; -static jmethodID sCallChangeCallbacks; +JavaVM* sVM = nullptr; +jclass sClazz = nullptr; +jmethodID sCallChangeCallbacks; -static void do_report_sysprop_change() { +void do_report_sysprop_change() { //ALOGI("Java SystemProperties: VM=%p, Clazz=%p", sVM, sClazz); - if (sVM != NULL && sClazz != NULL) { + if (sVM != nullptr && sClazz != nullptr) { JNIEnv* env; if (sVM->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0) { //ALOGI("Java SystemProperties: calling %p", sCallChangeCallbacks); @@ -207,47 +128,49 @@ static void do_report_sysprop_change() { } } -static void SystemProperties_add_change_callback(JNIEnv *env, jobject clazz) +void SystemProperties_add_change_callback(JNIEnv *env, jobject clazz) { // This is called with the Java lock held. - if (sVM == NULL) { + if (sVM == nullptr) { env->GetJavaVM(&sVM); } - if (sClazz == NULL) { + if (sClazz == nullptr) { sClazz = (jclass) env->NewGlobalRef(clazz); sCallChangeCallbacks = env->GetStaticMethodID(sClazz, "callChangeCallbacks", "()V"); add_sysprop_change_callback(do_report_sysprop_change, -10000); } } -static void SystemProperties_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/) +void SystemProperties_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/) { report_sysprop_change(); } -static const JNINativeMethod method_table[] = { - { "native_get", "(Ljava/lang/String;)Ljava/lang/String;", - (void*) SystemProperties_getS }, - { "native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", - (void*) SystemProperties_getSS }, - { "native_get_int", "(Ljava/lang/String;I)I", - (void*) SystemProperties_get_int }, - { "native_get_long", "(Ljava/lang/String;J)J", - (void*) SystemProperties_get_long }, - { "native_get_boolean", "(Ljava/lang/String;Z)Z", - (void*) SystemProperties_get_boolean }, - { "native_set", "(Ljava/lang/String;Ljava/lang/String;)V", - (void*) SystemProperties_set }, - { "native_add_change_callback", "()V", - (void*) SystemProperties_add_change_callback }, - { "native_report_sysprop_change", "()V", - (void*) SystemProperties_report_sysprop_change }, -}; +} // namespace int register_android_os_SystemProperties(JNIEnv *env) { - return RegisterMethodsOrDie(env, "android/os/SystemProperties", method_table, - NELEM(method_table)); + const JNINativeMethod method_table[] = { + { "native_get", "(Ljava/lang/String;)Ljava/lang/String;", + (void*) SystemProperties_getS }, + { "native_get", + "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (void*) SystemProperties_getSS }, + { "native_get_int", "(Ljava/lang/String;I)I", + (void*) SystemProperties_get_integral<jint> }, + { "native_get_long", "(Ljava/lang/String;J)J", + (void*) SystemProperties_get_integral<jlong> }, + { "native_get_boolean", "(Ljava/lang/String;Z)Z", + (void*) SystemProperties_get_boolean }, + { "native_set", "(Ljava/lang/String;Ljava/lang/String;)V", + (void*) SystemProperties_set }, + { "native_add_change_callback", "()V", + (void*) SystemProperties_add_change_callback }, + { "native_report_sysprop_change", "()V", + (void*) SystemProperties_report_sysprop_change }, + }; + return RegisterMethodsOrDie(env, "android/os/SystemProperties", + method_table, NELEM(method_table)); } }; diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp index 7ec4b8ea0799..5ef2a9e6465c 100644 --- a/core/jni/android_os_VintfObject.cpp +++ b/core/jni/android_os_VintfObject.cpp @@ -56,7 +56,7 @@ static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) { } template<typename T> -static void tryAddSchema(const T* object, const XmlConverter<T>& converter, +static void tryAddSchema(const std::shared_ptr<const T>& object, const XmlConverter<T>& converter, const std::string& description, std::vector<std::string>* cStrings) { if (object == nullptr) { @@ -66,7 +66,7 @@ static void tryAddSchema(const T* object, const XmlConverter<T>& converter, } } -static void tryAddHalNamesAndVersions(const HalManifest *manifest, +static void tryAddHalNamesAndVersions(const std::shared_ptr<const HalManifest>& manifest, const std::string& description, std::set<std::string> *output) { if (manifest == nullptr) { @@ -119,7 +119,7 @@ static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, j } static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) { - const HalManifest *manifest = VintfObject::GetDeviceHalManifest(); + std::shared_ptr<const HalManifest> manifest = VintfObject::GetDeviceHalManifest(); if (manifest == nullptr || manifest->type() != SchemaType::DEVICE) { LOG(WARNING) << __FUNCTION__ << "Cannot get device manifest"; return nullptr; @@ -129,7 +129,7 @@ static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) { } static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) { - const HalManifest *manifest = VintfObject::GetFrameworkHalManifest(); + std::shared_ptr<const HalManifest> manifest = VintfObject::GetFrameworkHalManifest(); if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) { LOG(WARNING) << __FUNCTION__ << "Cannot get framework manifest"; return nullptr; diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp index 19220cf05adb..315eac1b9414 100644 --- a/core/jni/android_os_VintfRuntimeInfo.cpp +++ b/core/jni/android_os_VintfRuntimeInfo.cpp @@ -32,7 +32,7 @@ using vintf::VintfObject; #define MAP_STRING_METHOD(javaMethod, cppString) \ static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) \ { \ - const RuntimeInfo *info = VintfObject::GetRuntimeInfo(); \ + std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(); \ if (info == nullptr) return nullptr; \ return env->NewStringUTF((cppString).c_str()); \ } \ @@ -50,7 +50,7 @@ MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbV static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz) { - const RuntimeInfo *info = VintfObject::GetRuntimeInfo(); + std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(); if (info == nullptr) return 0; return static_cast<jlong>(info->kernelSepolicyVersion()); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 914688e3e885..b08f031681c5 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -477,7 +477,7 @@ static void FillFileDescriptorVector(JNIEnv* env, // Utility routine to fork zygote and specialize the child process. static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids, - jint debug_flags, jobjectArray javaRlimits, + jint runtime_flags, jobjectArray javaRlimits, jlong permittedCapabilities, jlong effectiveCapabilities, jint mount_external, jstring java_se_info, jstring java_se_name, @@ -658,7 +658,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra UnsetSigChldHandler(); - env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags, + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, is_system_server, instructionSet); if (env->ExceptionCheck()) { RuntimeAbort(env, __LINE__, "Error calling post fork hooks."); @@ -700,7 +700,7 @@ static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jcl static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, - jint debug_flags, jobjectArray rlimits, + jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, jintArray fdsToClose, jintArray fdsToIgnore, @@ -744,17 +744,17 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( // available. capabilities &= GetEffectiveCapabilityMask(env); - return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags, + return ForkAndSpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, mount_external, se_info, se_name, false, fdsToClose, fdsToIgnore, instructionSet, appDataDir); } static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, - jint debug_flags, jobjectArray rlimits, jlong permittedCapabilities, + jint runtime_flags, jobjectArray rlimits, jlong permittedCapabilities, jlong effectiveCapabilities) { pid_t pid = ForkAndSpecializeCommon(env, uid, gid, gids, - debug_flags, rlimits, + runtime_flags, rlimits, permittedCapabilities, effectiveCapabilities, MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, NULL, NULL, NULL, NULL); diff --git a/core/res/res/values-mcc505-mnc01/config.xml b/core/res/res/values-mcc505-mnc01/config.xml index ff065856a643..5a5b8f78478e 100644 --- a/core/res/res/values-mcc505-mnc01/config.xml +++ b/core/res/res/values-mcc505-mnc01/config.xml @@ -31,15 +31,6 @@ <item>9</item> </integer-array> - <!-- String containing the apn value for tethering. May be overriden by secure settings - TETHER_DUN_APN. Value is a comma separated series of strings: - "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type", - Or string format of ApnSettingV3. - note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" --> - <string-array translatable="false" name="config_tether_apndata"> - <item>Telstra Tethering,telstra.internet,,,,,,,,,505,01,,DUN</item> - </string-array> - <!--Thresholds for LTE dbm in status bar--> <integer-array translatable="false" name="config_lteDbmThresholds"> <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 899551371f30..70c39afbc441 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -296,6 +296,17 @@ less than 0x600 --> <bool translatable="false" name="config_apfDrop802_3Frames">true</bool> + <!-- An array of Black listed EtherType, packets with EtherTypes within this array + will be dropped + TODO: need to put proper values, these are for testing purposes only --> + <integer-array translatable="false" name="config_apfEthTypeBlackList"> + <item>0x88A2</item> + <item>0x88A4</item> + <item>0x88B8</item> + <item>0x88CD</item> + <item>0x88E3</item> + </integer-array> + <!-- Default value for ConnectivityManager.getMultipathPreference() on metered networks. Actual device behaviour is controlled by Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE. This is the default value of that setting. --> @@ -1493,16 +1504,16 @@ <integer translatable="false" name="config_bluetooth_max_advertisers">0</integer> <!-- Idle current for bluetooth controller. 0 by default--> - <integer translatable="false" name="config_bluetooth_idle_cur_ma">1</integer> + <integer translatable="false" name="config_bluetooth_idle_cur_ma">0</integer> <!-- Rx current for bluetooth controller. 0 by default--> - <integer translatable="false" name="config_bluetooth_rx_cur_ma">2</integer> + <integer translatable="false" name="config_bluetooth_rx_cur_ma">0</integer> <!-- Tx current for bluetooth controller. 0 by default--> - <integer translatable="false" name="config_bluetooth_tx_cur_ma">3</integer> + <integer translatable="false" name="config_bluetooth_tx_cur_ma">0</integer> <!-- Operating volatage for bluetooth controller. 0 by default--> - <integer translatable="false" name="config_bluetooth_operating_voltage_mv">4</integer> + <integer translatable="false" name="config_bluetooth_operating_voltage_mv">0</integer> <!-- Whether supported profiles should be reloaded upon enabling bluetooth --> <bool name="config_bluetooth_reload_supported_profiles_when_enabled">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c5275017a7cf..600c82f87d90 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1837,6 +1837,7 @@ <java-symbol type="integer" name="config_networkWakeupPacketMark" /> <java-symbol type="integer" name="config_networkWakeupPacketMask" /> <java-symbol type="bool" name="config_apfDrop802_3Frames" /> + <java-symbol type="array" name="config_apfEthTypeBlackList" /> <java-symbol type="integer" name="config_networkMeteredMultipathPreference" /> <java-symbol type="integer" name="config_notificationsBatteryFullARGB" /> <java-symbol type="integer" name="config_notificationsBatteryLedOff" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 7e9f561e68d5..f5b350b053d4 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -181,7 +181,7 @@ <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963" /> + <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="46645|5050|26259|50025|50052|9963|76551" /> <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288" /> diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java index d5f632190d06..bc2b9a632b72 100644 --- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java +++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java @@ -19,15 +19,23 @@ package android.net; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.LinkProperties.CompareResult; import android.net.LinkProperties.ProvisioningChange; import android.net.RouteInfo; import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; +import android.util.ArraySet; + import junit.framework.TestCase; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; public class LinkPropertiesTest extends TestCase { @@ -678,4 +686,104 @@ public class LinkPropertiesTest extends TestCase { stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress)); assertTrue(v6lp.isReachable(DNS1)); } + + @SmallTest + public void testLinkPropertiesEnsureDirectlyConnectedRoutes() { + // IPv4 case: no route added initially + LinkProperties rmnet0 = new LinkProperties(); + rmnet0.setInterfaceName("rmnet0"); + rmnet0.addLinkAddress(new LinkAddress("10.0.0.2/8")); + RouteInfo directRoute0 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null, + rmnet0.getInterfaceName()); + + // Since no routes is added explicitly, getAllRoutes() should return empty. + assertTrue(rmnet0.getAllRoutes().isEmpty()); + rmnet0.ensureDirectlyConnectedRoutes(); + // ensureDirectlyConnectedRoutes() should have added the missing local route. + assertEqualRoutes(Collections.singletonList(directRoute0), rmnet0.getAllRoutes()); + + // IPv4 case: both direct and default routes added initially + LinkProperties rmnet1 = new LinkProperties(); + rmnet1.setInterfaceName("rmnet1"); + rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8")); + RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null, + NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName()); + RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null, + rmnet1.getInterfaceName()); + rmnet1.addRoute(defaultRoute1); + rmnet1.addRoute(directRoute1); + + // Check added routes + assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes()); + // ensureDirectlyConnectedRoutes() shouldn't change the routes since direct connected + // route is already part of the configuration. + rmnet1.ensureDirectlyConnectedRoutes(); + assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes()); + + // IPv6 case: only default routes added initially + LinkProperties rmnet2 = new LinkProperties(); + rmnet2.setInterfaceName("rmnet2"); + rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64")); + rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64")); + RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null, + NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName()); + RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null, + rmnet2.getInterfaceName()); + RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null, + rmnet2.getInterfaceName()); + rmnet2.addRoute(defaultRoute2); + + assertEqualRoutes(Arrays.asList(defaultRoute2), rmnet2.getAllRoutes()); + rmnet2.ensureDirectlyConnectedRoutes(); + assertEqualRoutes(Arrays.asList(defaultRoute2, directRoute2, linkLocalRoute2), + rmnet2.getAllRoutes()); + + // Corner case: no interface name + LinkProperties rmnet3 = new LinkProperties(); + rmnet3.addLinkAddress(new LinkAddress("192.168.0.2/24")); + RouteInfo directRoute3 = new RouteInfo(new IpPrefix("192.168.0.0/24"), null, + rmnet3.getInterfaceName()); + + assertTrue(rmnet3.getAllRoutes().isEmpty()); + rmnet3.ensureDirectlyConnectedRoutes(); + assertEqualRoutes(Collections.singletonList(directRoute3), rmnet3.getAllRoutes()); + + } + + @SmallTest + public void testCompareResult() { + // Either adding or removing items + testCompareResult(Arrays.asList(1), Arrays.asList(1, 2, 3, 4), + Arrays.asList(2, 3, 4), new ArrayList<>()); + testCompareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4), + new ArrayList<>(), Arrays.asList(3, 4)); + + + // adding and removing items at the same time + testCompareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(2, 3, 4, 5), + Arrays.asList(1), Arrays.asList(5)); + testCompareResult(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), + Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6)); + + // null cases + testCompareResult(Arrays.asList(1, 2, 3), null, Arrays.asList(1, 2, 3), new ArrayList<>()); + testCompareResult(null, Arrays.asList(3, 2, 1), new ArrayList<>(), Arrays.asList(1, 2, 3)); + testCompareResult(null, null, new ArrayList<>(), new ArrayList<>()); + } + + private void assertEqualRoutes(Collection<RouteInfo> expected, Collection<RouteInfo> actual) { + Set<RouteInfo> expectedSet = new ArraySet<>(expected); + Set<RouteInfo> actualSet = new ArraySet<>(actual); + // Duplicated entries in actual routes are considered failures + assertEquals(actual.size(), actualSet.size()); + + 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); + assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added)); + assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed))); + } } diff --git a/core/tests/systemproperties/run_core_systemproperties_test.sh b/core/tests/systemproperties/run_core_systemproperties_test.sh index d39adbbfa390..9b1fe4be666b 100755 --- a/core/tests/systemproperties/run_core_systemproperties_test.sh +++ b/core/tests/systemproperties/run_core_systemproperties_test.sh @@ -15,7 +15,7 @@ fi if [[ $rebuild == true ]]; then make -j4 FrameworksCoreSystemPropertiesTests - TESTAPP=${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreSystemPropertiesTests.apk + TESTAPP=${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreSystemPropertiesTests/FrameworksCoreSystemPropertiesTests.apk COMMAND="adb install -r $TESTAPP" echo $COMMAND $COMMAND diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java index 544a96727217..282b0011eede 100644 --- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java +++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java @@ -51,6 +51,11 @@ public class SystemPropertiesTest extends TestCase { value = SystemProperties.get(KEY, "default"); assertEquals("default", value); + // null default value is the same as "". + SystemProperties.set(KEY, null); + value = SystemProperties.get(KEY, "default"); + assertEquals("default", value); + SystemProperties.set(KEY, "SA"); value = SystemProperties.get(KEY, "default"); assertEquals("SA", value); @@ -62,7 +67,78 @@ public class SystemPropertiesTest extends TestCase { value = SystemProperties.get(KEY, "default"); assertEquals("default", value); + // null value is the same as "". + SystemProperties.set(KEY, "SA"); + SystemProperties.set(KEY, null); + value = SystemProperties.get(KEY, "default"); + assertEquals("default", value); + value = SystemProperties.get(KEY); assertEquals("", value); } + + private static void testInt(String setVal, int defValue, int expected) { + SystemProperties.set(KEY, setVal); + int value = SystemProperties.getInt(KEY, defValue); + assertEquals(expected, value); + } + + private static void testLong(String setVal, long defValue, long expected) { + SystemProperties.set(KEY, setVal); + long value = SystemProperties.getLong(KEY, defValue); + assertEquals(expected, value); + } + + @SmallTest + public void testIntegralProperties() throws Exception { + testInt("", 123, 123); + testInt("", 0, 0); + testInt("", -123, -123); + + testInt("123", 124, 123); + testInt("0", 124, 0); + testInt("-123", 124, -123); + + testLong("", 3147483647L, 3147483647L); + testLong("", 0, 0); + testLong("", -3147483647L, -3147483647L); + + testLong("3147483647", 124, 3147483647L); + testLong("0", 124, 0); + testLong("-3147483647", 124, -3147483647L); + } + + @SmallTest + @SuppressWarnings("null") + public void testNullKey() throws Exception { + try { + SystemProperties.get(null); + fail("Expected NullPointerException"); + } catch (NullPointerException npe) { + } + + try { + SystemProperties.get(null, "default"); + fail("Expected NullPointerException"); + } catch (NullPointerException npe) { + } + + try { + SystemProperties.set(null, "value"); + fail("Expected NullPointerException"); + } catch (NullPointerException npe) { + } + + try { + SystemProperties.getInt(null, 0); + fail("Expected NullPointerException"); + } catch (NullPointerException npe) { + } + + try { + SystemProperties.getLong(null, 0); + fail("Expected NullPointerException"); + } catch (NullPointerException npe) { + } + } } diff --git a/core/tests/utiltests/jni/Android.bp b/core/tests/utiltests/jni/Android.bp index e9a4144e8164..b0b09c27e325 100644 --- a/core/tests/utiltests/jni/Android.bp +++ b/core/tests/utiltests/jni/Android.bp @@ -17,7 +17,6 @@ cc_library_shared { shared_libs: [
"libcutils",
],
- clang: true,
stl: "libc++",
srcs: [
"registration.cpp",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 0782269d7de1..7a0ef2b770ce 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -59,7 +59,6 @@ namespace android { #endif #define IDMAP_MAGIC 0x504D4449 -#define IDMAP_CURRENT_VERSION 0x00000001 #define APP_PACKAGE_ID 0x7f #define SYS_PACKAGE_ID 0x01 @@ -246,11 +245,11 @@ static bool assertIdmapHeader(const void* idmap, size_t size) { } const uint32_t version = htodl(*(reinterpret_cast<const uint32_t*>(idmap) + 1)); - if (version != IDMAP_CURRENT_VERSION) { + if (version != ResTable::IDMAP_CURRENT_VERSION) { // We are strict about versions because files with this format are // auto-generated and don't need backwards compatibility. ALOGW("idmap: version mismatch in header (is 0x%08x, expected 0x%08x)", - version, IDMAP_CURRENT_VERSION); + version, ResTable::IDMAP_CURRENT_VERSION); return false; } return true; @@ -6855,7 +6854,7 @@ status_t ResTable::createIdmap(const ResTable& overlay, uint32_t* data = (uint32_t*)*outData; *data++ = htodl(IDMAP_MAGIC); - *data++ = htodl(IDMAP_CURRENT_VERSION); + *data++ = htodl(ResTable::IDMAP_CURRENT_VERSION); *data++ = htodl(targetCrc); *data++ = htodl(overlayCrc); const char* paths[] = { targetPath, overlayPath }; diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 7a6e37d41b7c..66c66c251d9b 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1933,6 +1933,7 @@ public: void** outData, size_t* outSize) const; static const size_t IDMAP_HEADER_SIZE_BYTES = 4 * sizeof(uint32_t) + 2 * 256; + static const uint32_t IDMAP_CURRENT_VERSION = 0x00000001; // Retrieve idmap meta-data. // diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index a873d66803e7..99b424568a1f 100644 --- a/libs/androidfw/include/androidfw/StringPiece.h +++ b/libs/androidfw/include/androidfw/StringPiece.h @@ -37,6 +37,7 @@ class BasicStringPiece { public: using const_iterator = const TChar*; using difference_type = size_t; + using size_type = size_t; // End of string marker. constexpr static const size_t npos = static_cast<size_t>(-1); diff --git a/libs/usb/Android.bp b/libs/usb/Android.bp new file mode 100644 index 000000000000..b8f29043e597 --- /dev/null +++ b/libs/usb/Android.bp @@ -0,0 +1 @@ +subdirs = ["tests/*"] diff --git a/libs/usb/tests/AccessoryChat/Android.bp b/libs/usb/tests/AccessoryChat/Android.bp new file mode 100644 index 000000000000..4af6274b7ece --- /dev/null +++ b/libs/usb/tests/AccessoryChat/Android.bp @@ -0,0 +1 @@ +subdirs = ["accessorychat"] diff --git a/libs/usb/tests/AccessoryChat/accessorychat/Android.bp b/libs/usb/tests/AccessoryChat/accessorychat/Android.bp new file mode 100644 index 000000000000..5613745e966b --- /dev/null +++ b/libs/usb/tests/AccessoryChat/accessorychat/Android.bp @@ -0,0 +1,30 @@ +cc_binary { + name: "accessorychat", + host_supported: true, + + srcs: ["accessorychat.c"], + cflags: [ + "-Werror", + "-Wno-unused-parameter", + ], + + target: { + android: { + shared_libs: [ + "libusbhost", + "libcutils", + ], + }, + host: { + static_libs: [ + "libusbhost", + "libcutils", + ], + + cflags: ["-O0"], + }, + darwin: { + enabled: false, + }, + }, +} diff --git a/libs/usb/tests/AccessoryChat/accessorychat/Android.mk b/libs/usb/tests/AccessoryChat/accessorychat/Android.mk deleted file mode 100644 index 51f2111f1e0b..000000000000 --- a/libs/usb/tests/AccessoryChat/accessorychat/Android.mk +++ /dev/null @@ -1,35 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -# Build for Linux (desktop) host -ifeq ($(HOST_OS),linux) - -include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := accessorychat.c - -LOCAL_MODULE := accessorychat - -LOCAL_STATIC_LIBRARIES := libusbhost libcutils -LOCAL_LDLIBS += -lpthread -LOCAL_CFLAGS := -g -O0 - -include $(BUILD_HOST_EXECUTABLE) - -endif - -# Build for device -include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := accessorychat.c - -LOCAL_MODULE := accessorychat - -LOCAL_SHARED_LIBRARIES := libusbhost libcutils - -include $(BUILD_EXECUTABLE) diff --git a/libs/usb/tests/accessorytest/Android.bp b/libs/usb/tests/accessorytest/Android.bp new file mode 100644 index 000000000000..c6340e32e60c --- /dev/null +++ b/libs/usb/tests/accessorytest/Android.bp @@ -0,0 +1,28 @@ +cc_binary_host { + name: "accessorytest", + + srcs: [ + "accessory.c", + "audio.c", + "hid.c", + "usb.c", + ], + + static_libs: [ + "libusbhost", + "libcutils", + "libtinyalsa", + ], + cflags: [ + "-O0", + "-Wno-unused-parameter", + "-Werror", + ], + + target: { + darwin: { + // Build for Linux host only + enabled: false, + }, + }, +} diff --git a/libs/usb/tests/accessorytest/Android.mk b/libs/usb/tests/accessorytest/Android.mk deleted file mode 100644 index 6d9a9460c675..000000000000 --- a/libs/usb/tests/accessorytest/Android.mk +++ /dev/null @@ -1,25 +0,0 @@ -LOCAL_PATH:= $(call my-dir) - -# Build for Linux host only -ifeq ($(HOST_OS),linux) - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional - -LOCAL_SRC_FILES := accessory.c \ - audio.c \ - hid.c \ - usb.c - -LOCAL_C_INCLUDES += external/tinyalsa/include - -LOCAL_MODULE := accessorytest - -LOCAL_STATIC_LIBRARIES := libusbhost libcutils libtinyalsa -LOCAL_LDLIBS += -lpthread -LOCAL_CFLAGS := -g -O0 - -include $(BUILD_HOST_EXECUTABLE) - -endif diff --git a/libs/usb/tests/accessorytest/accessory.h b/libs/usb/tests/accessorytest/accessory.h index 55c4550f79e0..e86986eb403f 100644 --- a/libs/usb/tests/accessorytest/accessory.h +++ b/libs/usb/tests/accessorytest/accessory.h @@ -19,7 +19,7 @@ int init_audio(unsigned int ic, unsigned int id, unsigned int oc, unsigned int od); void init_hid(); -void usb_run(int enable_accessory); +void usb_run(uintptr_t enable_accessory); struct usb_device* usb_wait_for_device(); diff --git a/libs/usb/tests/accessorytest/hid.c b/libs/usb/tests/accessorytest/hid.c index b70d678543ca..283755dbf215 100644 --- a/libs/usb/tests/accessorytest/hid.c +++ b/libs/usb/tests/accessorytest/hid.c @@ -139,7 +139,7 @@ static void open_hid(const char* name) fprintf(stderr, "opened /dev/%s\n", name); pthread_t th; - pthread_create(&th, NULL, hid_thread, (void *)fd); + pthread_create(&th, NULL, hid_thread, (void *)(uintptr_t)fd); } static void* inotify_thread(void* arg) diff --git a/libs/usb/tests/accessorytest/usb.c b/libs/usb/tests/accessorytest/usb.c index ac72b35b908e..1a161adf8ac2 100644 --- a/libs/usb/tests/accessorytest/usb.c +++ b/libs/usb/tests/accessorytest/usb.c @@ -219,7 +219,7 @@ struct usb_device* usb_wait_for_device() { return device; } -void usb_run(int enable_accessory) { +void usb_run(uintptr_t enable_accessory) { struct usb_host_context* context = usb_host_init(); usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)enable_accessory); diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java index c1f03fd81151..c79c018d34a6 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -170,7 +170,8 @@ public class CaptivePortalLoginActivity extends Activity { } private void done(boolean success) { - if (DBG) logd(String.format("Result success %b for %s", success, mUrl.toString())); + if (DBG) logd(String.format("Result success %b for %s", success, + mUrl != null ? mUrl.toString() : "null")); if (success) { // Trigger re-evaluation upon success http response code CarrierActionUtils.applyCarrierAction( @@ -226,7 +227,8 @@ public class CaptivePortalLoginActivity extends Activity { int httpResponseCode = 500; int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE); try { - urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl); + urlConnection = (HttpURLConnection) mNetwork.openConnection( + new URL(mCm.getCaptivePortalServerUrl())); urlConnection.setInstanceFollowRedirects(false); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); @@ -234,6 +236,7 @@ public class CaptivePortalLoginActivity extends Activity { urlConnection.getInputStream(); httpResponseCode = urlConnection.getResponseCode(); } catch (IOException e) { + loge(e.getMessage()); } finally { if (urlConnection != null) urlConnection.disconnect(); TrafficStats.setThreadStatsTag(oldTag); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index e279a09c2bd2..5f302c6a5035 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -332,8 +332,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } public int getProfileConnectionState(LocalBluetoothProfile profile) { - if (mProfileConnectionState == null || - mProfileConnectionState.get(profile) == null) { + if (mProfileConnectionState.get(profile) == null) { // If cache is empty make the binder call to get the state int state = profile.getConnectionStatus(mDevice); mProfileConnectionState.put(profile, state); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index 0cf890037f55..fe03fba5bfda 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -58,9 +58,19 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override public void onSimStateChanged(int subId, int slotId, State simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - resetState(); - }; + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + switch(simState) { + // If the SIM is removed, then we must remove the keyguard. It will be put up + // again when the PUK locked SIM is re-entered. + case ABSENT: { + KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId); + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + break; + } + default: + resetState(); + } + } }; public KeyguardSimPinView(Context context) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index fb3cee74ab68..826a03d1b658 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -60,9 +60,23 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override public void onSimStateChanged(int subId, int slotId, State simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - resetState(); - }; + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + switch(simState) { + // If the SIM is removed, then we must remove the keyguard. It will be put up + // again when the PUK locked SIM is re-entered. + case ABSENT: + // intentional fall-through + // If the SIM is unlocked via a key sequence through the emergency dialer, it will + // move into the READY state and the PUK lock keyguard should be removed. + case READY: { + KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId); + mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + break; + } + default: + resetState(); + } + } }; public KeyguardSimPukView(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index 2473335e6e15..cef7c8ac9a8c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -170,7 +170,8 @@ public class QSFooter extends FrameLayout implements int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space); mAnimator = new Builder() - .addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0) + .addFloat(mSettingsContainer, "translationX", + isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0) .addFloat(mSettingsButton, "rotation", -120, 0) .build(); if (mAlarmShowing) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 0a0d2ce00a1b..bdc5e7d75b48 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -556,7 +556,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta @Override public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) { - if (viewHolder.getItemViewType() == TYPE_EDIT) { + if (viewHolder.getItemViewType() == TYPE_EDIT || viewHolder.getItemViewType() == TYPE_DIVIDER) { return makeMovementFlags(0, 0); } int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.RIGHT diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 672f2c2df06e..3495f570cc2c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -439,15 +439,22 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { public static class DrawableIcon extends Icon { protected final Drawable mDrawable; + protected final Drawable mInvisibleDrawable; public DrawableIcon(Drawable drawable) { mDrawable = drawable; + mInvisibleDrawable = drawable.getConstantState().newDrawable(); } @Override public Drawable getDrawable(Context context) { return mDrawable; } + + @Override + public Drawable getInvisibleDrawable(Context context) { + return mInvisibleDrawable; + } } public static class DrawableIconWithRes extends DrawableIcon { 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 f58fe8290a43..55498af7a54b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -4486,7 +4486,7 @@ public class StatusBar extends SystemUI implements DemoMode, animateCollapsePanels(); return true; } - if (mKeyguardUserSwitcher.hideIfNotSimple(true)) { + if (mKeyguardUserSwitcher != null && mKeyguardUserSwitcher.hideIfNotSimple(true)) { return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java index efce87177faa..b21a5e86ee6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java @@ -182,6 +182,7 @@ public class MobileSignalController extends SignalController< mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G); mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G); mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G); + mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G); if (!mConfig.showAtLeast3G) { mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN, diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto index 76c54185a774..d997a80cda83 100644 --- a/proto/src/ipconnectivity.proto +++ b/proto/src/ipconnectivity.proto @@ -23,7 +23,7 @@ message NetworkId { // It is not intended to map one to one to the TRANSPORT_* constants defined in // android.net.NetworkCapabilities. Instead it is intended to be used as // a dimension field for metrics events and aggregated metrics. -// Next tag: 7 +// Next tag: 10 enum LinkLayer { // An unknown link layer technology. UNKNOWN = 0; @@ -32,6 +32,9 @@ enum LinkLayer { CELLULAR = 2; ETHERNET = 3; WIFI = 4; + WIFI_P2P = 7; + WIFI_NAN = 8; // Also known as WiFi Aware + LOWPAN = 9; // Indicates that the link layer dimension is not relevant for the metrics or // event considered. @@ -47,16 +50,18 @@ message Pair { optional int32 value = 2; }; -// Logs changes in the system default network. Changes can be 1) acquiring a -// default network with no previous default, 2) a switch of the system default -// network to a new default network, 3) a loss of the system default network. -// This message is associated to android.net.metrics.DefaultNetworkEvent. +// An event record when the system default network disconnects or the system +// switches to a new default network. +// Next tag: 10. message DefaultNetworkEvent { - // A value of 0 means this is a loss of the system default network. - optional NetworkId network_id = 1; - // A value of 0 means there was no previous default network. - optional NetworkId previous_network_id = 2; + // Reason why this network stopped being the default. + enum LostReason { + UNKNOWN = 0; + OUTSCORED = 1; + INVALIDATION = 2; + DISCONNECT = 3; + }; // Whether the network supports IPv4, IPv6, or both. enum IPSupport { @@ -66,17 +71,52 @@ message DefaultNetworkEvent { DUAL = 3; }; + // Duration in milliseconds when this network was the default. + // Since version 4 + optional int64 default_network_duration_ms = 5; + + // Duration in milliseconds without a default network before this network + // became the default. + // Since version 4 + optional int64 no_default_network_duration_ms = 6; + + // Network score of this network when it became the default network. + // Since version 4 + optional int64 initial_score = 7; + + // Network score of this network when it stopped being the default network. + // Since version 4 + optional int64 final_score = 8; + + // Best available information about IP support of this default network. + // Since version 4 + optional IPSupport ip_support = 9; + + + // Deprecated fields + + // A value of 0 means this is a loss of the system default network. + // Deprecated since version 3. Replaced by top level network_id. + optional NetworkId network_id = 1 [deprecated = true]; + + // A value of 0 means there was no previous default network. + // Deprecated since version 3. Replaced by previous_default_network_id. + optional NetworkId previous_network_id = 2 [deprecated = true]; + // Best available information about IP support of the previous network when // disconnecting or switching to a new default network. - optional IPSupport previous_network_ip_support = 3; + // Deprecated since version 3. Replaced by ip_support field. + optional IPSupport previous_network_ip_support = 3 [deprecated = true]; // The transport types of the new default network, represented by // TRANSPORT_* constants as defined in NetworkCapabilities. - repeated int32 transport_types = 4; + // Deprecated since version 3. Replaced by top-level transports field. + repeated int32 transport_types = 4 [deprecated = true]; }; // Logs IpReachabilityMonitor probe events and NUD_FAILED events. // This message is associated to android.net.metrics.IpReachabilityEvent. +// Next tag: 3. message IpReachabilityEvent { // The interface name (wlan, rmnet, lo, ...) on which the probe was sent. // Deprecated since version 2, to be replaced by link_layer field. @@ -91,6 +131,7 @@ message IpReachabilityEvent { // Logs NetworkMonitor and ConnectivityService events related to the state of // a network: connection, evaluation, validation, lingering, and disconnection. // This message is associated to android.net.metrics.NetworkEvent. +// Next tag: 4. message NetworkEvent { // The id of the network on which this event happened. // Deprecated since version 3. @@ -108,6 +149,7 @@ message NetworkEvent { // Logs individual captive portal probing events that are performed when // evaluating or reevaluating networks for Internet connectivity. // This message is associated to android.net.metrics.ValidationProbeEvent. +// Next tag: 5. message ValidationProbeEvent { // The id of the network for which the probe was sent. // Deprecated since version 3. @@ -124,26 +166,64 @@ message ValidationProbeEvent { optional int32 probe_result = 4; } -// Logs DNS lookup latencies. Repeated fields must have the same length. +// Logs DNS lookup latencies. // This message is associated to android.net.metrics.DnsEvent. -// Deprecated since version 2. +// Next tag: 11 message DNSLookupBatch { + + // The time it took for successful DNS lookups to complete. + // The number of repeated values can be less than getaddrinfo_query_count + // + gethostbyname_query_count in case of event rate-limiting. + repeated int32 latencies_ms = 4; + + // The total number of getaddrinfo queries. + // Since version 4. + optional int64 getaddrinfo_query_count = 5; + + // The total number of gethostbyname queries. + // Since version 4. + optional int64 gethostbyname_query_count = 6; + + // The total number of getaddrinfo errors. + // Since version 4. + optional int64 getaddrinfo_error_count = 7; + + // The total number of gethostbyname errors. + // Since version 4. + optional int64 gethostbyname_error_count = 8; + + // Counts of all errors returned by getaddrinfo. + // The Pair key field is the getaddrinfo error value. + // The value field is the count for that return value. + // Since version 4 + repeated Pair getaddrinfo_errors = 9; + + // Counts of all errors returned by gethostbyname. + // The Pair key field is the gethostbyname errno value. + // the Pair value field is the count for that errno code. + // Since version 4 + repeated Pair gethostbyname_errors = 10; + + + // Deprecated fields + // The id of the network on which the DNS lookups took place. - optional NetworkId network_id = 1; + // Deprecated since version 3. + optional NetworkId network_id = 1 [deprecated = true]; // The types of the DNS lookups, as defined in android.net.metrics.DnsEvent. - repeated int32 event_types = 2; + // Deprecated since version 3. + repeated int32 event_types = 2 [deprecated = true]; // The return values of the DNS resolver for each DNS lookups. - repeated int32 return_codes = 3; - - // The time it took for each DNS lookups to complete. - repeated int32 latencies_ms = 4; + // Deprecated since version 3. + repeated int32 return_codes = 3 [deprecated = true]; }; // Represents a collections of DNS lookup latencies and counters for a // particular combination of DNS query type and return code. // Since version 2. +// Next tag: 7. message DNSLatencies { // The type of the DNS lookups, as defined in android.net.metrics.DnsEvent. // Acts as a key for a set of DNS query results. @@ -203,6 +283,7 @@ message ConnectStatistics { // state transition or a response packet parsing error. // This message is associated to android.net.metrics.DhcpClientEvent and // android.net.metrics.DhcpErrorEvent. +// Next tag: 5 message DHCPEvent { // The interface name (wlan, rmnet, lo, ...) on which the event happened. // Deprecated since version 2, to be replaced by link_layer field. @@ -255,7 +336,7 @@ message ApfProgramEvent { // Represents Router Advertisement listening statistics for an interface with // Android Packet Filter enabled. // Since version 1. -// Next tag: 12 +// Next tag: 15 message ApfStatistics { // The time interval in milliseconds these stastistics cover. optional int64 duration_ms = 1; @@ -288,12 +369,28 @@ message ApfStatistics { // The total number of APF program updates triggered when disabling the // multicast filter. Since version 3. + // Since version 4. optional int32 program_updates_allowing_multicast = 11; + + // The total number of packets processed by the APF interpreter. + // Since version 4. + optional int32 total_packet_processed = 12; + + // The total number of packets dropped by the APF interpreter. + // Since version 4. + optional int32 total_packet_dropped = 13; + + // List of hardware counters collected by the APF interpreter. + // The Pair key is the counter id, defined in android.net.metrics.ApfStats. + // The Pair value is the counter value. + // Since version 4. + repeated Pair hardware_counters = 14; } // Represents the reception of a Router Advertisement packet for an interface // with Android Packet Filter enabled. // Since version 1. +// Next tag: 7. message RaEvent { // All lifetime values are expressed in seconds. The default value for an // option lifetime that was not present in the RA option list is -1. @@ -322,6 +419,7 @@ message RaEvent { // Represents an IP provisioning event in IpManager and how long the // provisioning action took. // This message is associated to android.net.metrics.IpManagerEvent. +// Next tag: 4. message IpProvisioningEvent { // The interface name (wlan, rmnet, lo, ...) on which the probe was sent. // Deprecated since version 2, to be replaced by link_layer field. @@ -335,8 +433,80 @@ message IpProvisioningEvent { optional int32 latency_ms = 3; } +// Represents statistics from a single android Network. +// Since version 4. Replace NetworkEvent. +// Next tag: 9. +message NetworkStats { + + // Duration of this Network lifecycle in milliseconds. + optional int64 duration_ms = 1; + + // Information about IP support of this network. + optional DefaultNetworkEvent.IPSupport ip_support = 2; + + // True if the network was validated at least once. + optional bool ever_validated = 3; + + // True if a captive portal was found at least once on this network. + optional bool portal_found = 4; + + // Total number of times no connectivity was reported for this network. + optional int32 no_connectivity_reports = 5; + + // Total number of validation attempts. + optional int32 validation_attempts = 6; + + // Results from all validation attempts. + // The Pair key is the result: + // 0 -> unvalidated + // 1 -> validated + // 2 -> captive portal + // The Pair value is the duration of the validation attempts in milliseconds. + repeated Pair validation_events = 7; + + // Time series of validation states in time order. + // The Pair key is the state: + // 0 -> unvalidated + // 1 -> validated + // 2 -> captive portal, + // The Pair value is the duration of that state in milliseconds. + 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 sockets. + optional int64 unrouted_wakeups = 7; +} + // Represents one of the IP connectivity event defined in this file. -// Next tag: 19 +// Next tag: 20 message IpConnectivityEvent { // Time in ms when the event was recorded. optional int64 time_ms = 1; @@ -370,14 +540,13 @@ message IpConnectivityEvent { oneof event { // An event about the system default network. - // The link_layer field is not relevant for this event and set to NONE. DefaultNetworkEvent default_network_event = 2; // An IP reachability probe event. IpReachabilityEvent ip_reachability_event = 3; // A network lifecycle event. - NetworkEvent network_event = 4; + NetworkEvent network_event = 4 [deprecated = true]; // A batch of DNS lookups. // Deprecated in the nyc-mr2 release since version 2,and replaced by @@ -407,10 +576,17 @@ message IpConnectivityEvent { // An RA packet reception event. RaEvent ra_event = 11; + + // Network statistics. + NetworkStats network_stats = 19; + + // Ingress packet wakeup statistics. + WakeupStats wakeup_stats = 20; }; }; // The information about IP connectivity events. +// Next tag: 4. message IpConnectivityLog { // An array of IP connectivity events. repeated IpConnectivityEvent events = 1; @@ -424,5 +600,6 @@ message IpConnectivityLog { // nyc-mr1: not populated, implicitly 1. // nyc-mr2: 2. // oc: 3. + // oc-dr1: 4. optional int32 version = 3; }; diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index 7e82edaae3e5..b1ac5891dafa 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -690,6 +690,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { } break; case MotionEvent.ACTION_UP: { + mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { transitionToDelegatingState(!mShortcutTriggered); @@ -703,7 +704,6 @@ class MagnificationGestureHandler implements EventStreamTransformation { if (mLastDownEvent == null) { return; } - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, mTapDistanceSlop, 0)) { transitionToDelegatingState(true); diff --git a/services/core/Android.mk b/services/core/Android.mk index 7fca21f71a6a..f61fd550885b 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -4,7 +4,10 @@ include $(CLEAR_VARS) LOCAL_MODULE := services.core -LOCAL_AIDL_INCLUDES := system/netd/server/binder +LOCAL_AIDL_INCLUDES := \ + frameworks/native/aidl/binder \ + system/netd/server/binder + LOCAL_SRC_FILES += \ $(call all-java-files-under,java) \ diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index fb20533837af..31fc874db2fb 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -458,6 +458,11 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int MAX_WAKELOCK_LOGS = 20; private final LocalLog mWakelockLogs = new LocalLog(MAX_WAKELOCK_LOGS); + private int mTotalWakelockAcquisitions = 0; + private int mTotalWakelockReleases = 0; + private long mTotalWakelockDurationMs = 0; + private long mMaxWakelockDurationMs = 0; + private long mLastWakeLockAcquireTimestamp = 0; // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results private static final int MAX_VALIDATION_LOGS = 10; @@ -1959,6 +1964,14 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); pw.println("NetTransition WakeLock activity (most recent first):"); pw.increaseIndent(); + pw.println("total acquisitions: " + mTotalWakelockAcquisitions); + pw.println("total releases: " + mTotalWakelockReleases); + pw.println("cumulative duration: " + (mTotalWakelockDurationMs / 1000) + "s"); + pw.println("longest duration: " + (mMaxWakelockDurationMs / 1000) + "s"); + if (mTotalWakelockAcquisitions > mTotalWakelockReleases) { + long duration = SystemClock.elapsedRealtime() - mLastWakeLockAcquireTimestamp; + pw.println("currently holding WakeLock for: " + (duration / 1000) + "s"); + } mWakelockLogs.reverseDump(fd, pw, args); pw.decreaseIndent(); } @@ -3012,6 +3025,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } mNetTransitionWakeLock.acquire(); + mLastWakeLockAcquireTimestamp = SystemClock.elapsedRealtime(); + mTotalWakelockAcquisitions++; } mWakelockLogs.log("ACQUIRE for " + forWhom); Message msg = mHandler.obtainMessage(EVENT_EXPIRE_NET_TRANSITION_WAKELOCK); @@ -3044,6 +3059,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } mNetTransitionWakeLock.release(); + long lockDuration = SystemClock.elapsedRealtime() - mLastWakeLockAcquireTimestamp; + mTotalWakelockDurationMs += lockDuration; + mMaxWakelockDurationMs = Math.max(mMaxWakelockDurationMs, lockDuration); + mTotalWakelockReleases++; } mWakelockLogs.log(String.format("RELEASE (%s)", event)); } @@ -4316,11 +4335,13 @@ public class ConnectivityService extends IConnectivityManager.Stub int currentScore, NetworkMisc networkMisc) { enforceConnectivityInternalPermission(); + LinkProperties lp = new LinkProperties(linkProperties); + lp.ensureDirectlyConnectedRoutes(); // TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network // satisfies mDefaultRequest. final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(), - new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties( - linkProperties), new NetworkCapabilities(networkCapabilities), currentScore, + new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, + new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this); synchronized (this) { nai.networkMonitor.systemReady = mSystemReady; @@ -4414,12 +4435,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId, NetworkCapabilities caps) { - CompareResult<String> interfaceDiff = new CompareResult<String>(); - if (oldLp != null) { - interfaceDiff = oldLp.compareAllInterfaceNames(newLp); - } else if (newLp != null) { - interfaceDiff.added = newLp.getAllInterfaceNames(); - } + CompareResult<String> interfaceDiff = new CompareResult<String>( + oldLp != null ? oldLp.getAllInterfaceNames() : null, + newLp != null ? newLp.getAllInterfaceNames() : null); for (String iface : interfaceDiff.added) { try { if (DBG) log("Adding iface " + iface + " to network " + netId); @@ -4445,12 +4463,10 @@ public class ConnectivityService extends IConnectivityManager.Stub * @return true if routes changed between oldLp and newLp */ private boolean updateRoutes(LinkProperties newLp, LinkProperties oldLp, int netId) { - CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>(); - if (oldLp != null) { - routeDiff = oldLp.compareAllRoutes(newLp); - } else if (newLp != null) { - routeDiff.added = newLp.getAllRoutes(); - } + // Compare the route diff to determine which routes should be added and removed. + CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>( + oldLp != null ? oldLp.getAllRoutes() : null, + newLp != null ? newLp.getAllRoutes() : null); // add routes before removing old in case it helps with continuous connectivity @@ -4616,6 +4632,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) { + if (mNetworkForNetId.get(nai.network.netId) != nai) { + // Ignore updates for disconnected networks + return; + } + // newLp is already a defensive copy. + newLp.ensureDirectlyConnectedRoutes(); if (VDBG) { log("Update of LinkProperties for " + nai.name() + "; created=" + nai.created + diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java index f5f773214124..b5a8332375f4 100644 --- a/services/core/java/com/android/server/NativeDaemonConnector.java +++ b/services/core/java/com/android/server/NativeDaemonConnector.java @@ -24,11 +24,13 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.util.LocalLog; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.server.power.ShutdownThread; import com.google.android.collect.Lists; import java.io.FileDescriptor; @@ -136,6 +138,12 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo listenToSocket(); } catch (Exception e) { loge("Error in NativeDaemonConnector: " + e); + String shutdownAct = SystemProperties.get( + ShutdownThread.SHUTDOWN_ACTION_PROPERTY, ""); + if (shutdownAct != null && shutdownAct.length() > 0) { + // The device is in middle of shutdown. + break; + } SystemClock.sleep(5000); } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 3e4453204b14..caa2d5112bf0 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -76,6 +76,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.INetworkActivityListener; import android.os.INetworkManagementService; +import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; @@ -1897,38 +1898,34 @@ public class NetworkManagementService extends INetworkManagementService.Stub return new NetworkStats(SystemClock.elapsedRealtime(), 0); } - final NativeDaemonEvent[] events; + final PersistableBundle bundle; try { - events = mConnector.executeForList("bandwidth", "gettetherstats"); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + bundle = mNetdService.tetherGetStats(); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException("problem parsing tethering stats: ", e); } - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); - for (NativeDaemonEvent event : events) { - if (event.getCode() != TetheringStatsListResult) continue; - // 114 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets - final StringTokenizer tok = new StringTokenizer(event.getMessage()); - try { - final String ifaceIn = tok.nextToken(); - final String ifaceOut = tok.nextToken(); + final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), + bundle.size()); + final NetworkStats.Entry entry = new NetworkStats.Entry(); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - entry.iface = ifaceOut; + for (String iface : bundle.keySet()) { + long[] statsArray = bundle.getLongArray(iface); + try { + entry.iface = iface; entry.uid = UID_TETHERING; entry.set = SET_DEFAULT; entry.tag = TAG_NONE; - entry.rxBytes = Long.parseLong(tok.nextToken()); - entry.rxPackets = Long.parseLong(tok.nextToken()); - entry.txBytes = Long.parseLong(tok.nextToken()); - entry.txPackets = Long.parseLong(tok.nextToken()); + entry.rxBytes = statsArray[INetd.TETHER_STATS_RX_BYTES]; + entry.rxPackets = statsArray[INetd.TETHER_STATS_RX_PACKETS]; + entry.txBytes = statsArray[INetd.TETHER_STATS_TX_BYTES]; + entry.txPackets = statsArray[INetd.TETHER_STATS_TX_PACKETS]; stats.combineValues(entry); - } catch (NoSuchElementException e) { - throw new IllegalStateException("problem parsing tethering stats: " + event); - } catch (NumberFormatException e) { - throw new IllegalStateException("problem parsing tethering stats: " + event); + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalStateException("invalid tethering stats for " + iface, e); } } + return stats; } diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java index 3c8c699a65bb..1517887efec2 100644 --- a/services/core/java/com/android/server/RecoverySystemService.java +++ b/services/core/java/com/android/server/RecoverySystemService.java @@ -285,8 +285,9 @@ public final class RecoverySystemService extends SystemService { // Send the BCB commands if it's to setup BCB. if (isSetup) { - dos.writeInt(command.length()); - dos.writeBytes(command); + byte[] cmdUtf8 = command.getBytes("UTF-8"); + dos.writeInt(cmdUtf8.length); + dos.write(cmdUtf8, 0, cmdUtf8.length); dos.flush(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5f679db3287d..c699a56282ca 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3836,38 +3836,38 @@ public class ActivityManagerService extends IActivityManager.Stub uid = 0; } } - int debugFlags = 0; + int runtimeFlags = 0; if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - debugFlags |= Zygote.DEBUG_ENABLE_JDWP; - debugFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE; + runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP; + runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE; // Also turn on CheckJNI for debuggable apps. It's quite // awkward to turn on otherwise. - debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; + runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } // Run the app in safe mode if its manifest requests so or the // system is booted in safe mode. if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 || mSafeMode == true) { - debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE; + runtimeFlags |= Zygote.DEBUG_ENABLE_SAFEMODE; } if ("1".equals(SystemProperties.get("debug.checkjni"))) { - debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; + runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info"); if ("true".equals(genDebugInfoProperty)) { - debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; + runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; } if ("1".equals(SystemProperties.get("debug.jni.logging"))) { - debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; + runtimeFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; } if ("1".equals(SystemProperties.get("debug.assert"))) { - debugFlags |= Zygote.DEBUG_ENABLE_ASSERT; + runtimeFlags |= Zygote.DEBUG_ENABLE_ASSERT; } if (mNativeDebuggingApp != null && mNativeDebuggingApp.equals(app.processName)) { // Enable all debug flags required by the native debugger. - debugFlags |= Zygote.DEBUG_ALWAYS_JIT; // Don't interpret anything - debugFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; // Generate debug info - debugFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE; // Disbale optimizations + runtimeFlags |= Zygote.DEBUG_ALWAYS_JIT; // Don't interpret anything + runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; // Generate debug info + runtimeFlags |= Zygote.DEBUG_NATIVE_DEBUGGABLE; // Disbale optimizations mNativeDebuggingApp = null; } @@ -3917,12 +3917,12 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessStartResult startResult; if (hostingType.equals("webview_service")) { startResult = startWebView(entryPoint, - app.processName, uid, uid, gids, debugFlags, mountExternal, + app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, entryPointArgs); } else { startResult = Process.start(entryPoint, - app.processName, uid, uid, gids, debugFlags, mountExternal, + app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, entryPointArgs); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 5dee91de0e77..22330e66e126 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -20,6 +20,7 @@ import static android.net.NetworkCapabilities.MAX_TRANSPORT; import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; @@ -37,6 +38,7 @@ 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.os.Parcelable; import android.util.SparseArray; import android.util.SparseIntArray; @@ -114,6 +116,22 @@ final public class IpConnectivityEventBuilder { 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.unroutedWakeups = in.unroutedWakeups; + 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; @@ -362,29 +380,46 @@ final public class IpConnectivityEventBuilder { TRANSPORT_LINKLAYER_MAP[TRANSPORT_BLUETOOTH] = IpConnectivityLogClass.BLUETOOTH; TRANSPORT_LINKLAYER_MAP[TRANSPORT_ETHERNET] = IpConnectivityLogClass.ETHERNET; TRANSPORT_LINKLAYER_MAP[TRANSPORT_VPN] = IpConnectivityLogClass.UNKNOWN; - // TODO: change mapping TRANSPORT_WIFI_AWARE -> WIFI_AWARE - TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.UNKNOWN; + TRANSPORT_LINKLAYER_MAP[TRANSPORT_WIFI_AWARE] = IpConnectivityLogClass.WIFI_NAN; + TRANSPORT_LINKLAYER_MAP[TRANSPORT_LOWPAN] = IpConnectivityLogClass.LOWPAN; }; private static int ifnameToLinkLayer(String ifname) { // Do not try to catch all interface names with regexes, instead only catch patterns that // are cheap to check, and otherwise fallback on postprocessing in aggregation layer. - for (int i = 0; i < IFNAME_LINKLAYER_MAP.size(); i++) { - String pattern = IFNAME_LINKLAYER_MAP.valueAt(i); + for (int i = 0; i < KNOWN_PREFIX; i++) { + String pattern = IFNAME_PREFIXES[i]; if (ifname.startsWith(pattern)) { - return IFNAME_LINKLAYER_MAP.keyAt(i); + return IFNAME_LINKLAYERS[i]; } } return IpConnectivityLogClass.UNKNOWN; } - private static final SparseArray<String> IFNAME_LINKLAYER_MAP = new SparseArray<String>(); + private static final int KNOWN_PREFIX = 7; + private static final String[] IFNAME_PREFIXES = new String[KNOWN_PREFIX]; + private static final int[] IFNAME_LINKLAYERS = new int[KNOWN_PREFIX]; static { - IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.CELLULAR, "rmnet"); - IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.WIFI, "wlan"); - IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.BLUETOOTH, "bt-pan"); - // TODO: rekey to USB - IFNAME_LINKLAYER_MAP.put(IpConnectivityLogClass.ETHERNET, "usb"); - // TODO: add mappings for nan -> WIFI_AWARE and p2p -> WIFI_P2P + // Ordered from most likely link layer to least likely. + IFNAME_PREFIXES[0] = "rmnet"; + IFNAME_LINKLAYERS[0] = IpConnectivityLogClass.CELLULAR; + + IFNAME_PREFIXES[1] = "wlan"; + IFNAME_LINKLAYERS[1] = IpConnectivityLogClass.WIFI; + + IFNAME_PREFIXES[2] = "bt-pan"; + IFNAME_LINKLAYERS[2] = IpConnectivityLogClass.BLUETOOTH; + + IFNAME_PREFIXES[3] = "p2p"; + IFNAME_LINKLAYERS[3] = IpConnectivityLogClass.WIFI_P2P; + + IFNAME_PREFIXES[4] = "aware"; + IFNAME_LINKLAYERS[4] = IpConnectivityLogClass.WIFI_NAN; + + IFNAME_PREFIXES[5] = "eth"; + IFNAME_LINKLAYERS[5] = IpConnectivityLogClass.ETHERNET; + + IFNAME_PREFIXES[6] = "wpan"; + IFNAME_LINKLAYERS[6] = IpConnectivityLogClass.LOWPAN; } } diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 10c8b8b1e0aa..e6585ad194ec 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -33,7 +33,7 @@ import java.util.Objects; /** * Class to manage a 464xlat CLAT daemon. Nat464Xlat is not thread safe and should be manipulated - * from a consistent and unique thread context. It is the responsability of ConnectivityService to + * from a consistent and unique thread context. It is the responsibility of ConnectivityService to * call into this class from its own Handler thread. * * @hide @@ -60,7 +60,9 @@ public class Nat464Xlat extends BaseNetworkObserver { private enum State { IDLE, // start() not called. Base iface and stacked iface names are null. STARTING, // start() called. Base iface and stacked iface names are known. - RUNNING; // start() called, and the stacked iface is known to be up. + RUNNING, // start() called, and the stacked iface is known to be up. + STOPPING; // stop() called, this Nat464Xlat is still registered as a network observer for + // the stacked interface. } private String mBaseIface; @@ -81,6 +83,8 @@ public class Nat464Xlat extends BaseNetworkObserver { // 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(); // We only run clat on networks that don't have a native IPv4 address. final boolean hasIPv4Address = @@ -111,18 +115,70 @@ public class Nat464Xlat extends BaseNetworkObserver { } /** - * Sets internal state. + * @return true if clatd has been stopped. + */ + public boolean isStopping() { + return mState == State.STOPPING; + } + + /** + * Start clatd, register this Nat464Xlat as a network observer for the stacked interface, + * and set internal state. */ private void enterStartingState(String baseIface) { + try { + mNMService.registerObserver(this); + } catch(RemoteException e) { + Slog.e(TAG, + "startClat: Can't register interface observer for clat on " + mNetwork.name()); + return; + } + try { + mNMService.startClatd(baseIface); + } catch(RemoteException|IllegalStateException e) { + Slog.e(TAG, "Error starting clatd on " + baseIface, e); + } mIface = CLAT_PREFIX + baseIface; mBaseIface = baseIface; mState = State.STARTING; } /** - * Clears internal state. + * Enter running state just after getting confirmation that the stacked interface is up, and + * turn ND offload off if on WiFi. + */ + private void enterRunningState() { + maybeSetIpv6NdOffload(mBaseIface, false); + mState = State.RUNNING; + } + + /** + * 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) { + Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e); + } + + mState = State.STOPPING; + } + + /** + * Unregister as a base observer for the stacked interface, and clear internal state. */ private void enterIdleState() { + try { + mNMService.unregisterObserver(this); + } catch(RemoteException|IllegalStateException e) { + Slog.e(TAG, "Error unregistering clatd observer on " + mBaseIface, e); + } + mIface = null; mBaseIface = null; mState = State.IDLE; @@ -142,27 +198,14 @@ public class Nat464Xlat extends BaseNetworkObserver { return; } - try { - mNMService.registerObserver(this); - } catch(RemoteException e) { - Slog.e(TAG, "startClat: Can't register interface observer for clat on " + mNetwork); - return; - } - String baseIface = mNetwork.linkProperties.getInterfaceName(); if (baseIface == null) { Slog.e(TAG, "startClat: Can't start clat on null interface"); return; } // TODO: should we only do this if mNMService.startClatd() succeeds? + Slog.i(TAG, "Starting clatd on " + baseIface); enterStartingState(baseIface); - - Slog.i(TAG, "Starting clatd on " + mBaseIface); - try { - mNMService.startClatd(mBaseIface); - } catch(RemoteException|IllegalStateException e) { - Slog.e(TAG, "Error starting clatd on " + mBaseIface, e); - } } /** @@ -170,18 +213,15 @@ public class Nat464Xlat extends BaseNetworkObserver { */ public void stop() { if (!isStarted()) { - Slog.e(TAG, "stopClat: already stopped or not started"); return; } - Slog.i(TAG, "Stopping clatd on " + mBaseIface); - try { - mNMService.stopClatd(mBaseIface); - } catch(RemoteException|IllegalStateException e) { - Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e); + + boolean wasStarting = isStarting(); + enterStoppingState(); + if (wasStarting) { + enterIdleState(); } - // When clatd stops and its interface is deleted, handleInterfaceRemoved() will trigger - // ConnectivityService#handleUpdateLinkProperties and call enterIdleState(). } /** @@ -255,55 +295,52 @@ public class Nat464Xlat extends BaseNetworkObserver { if (!isStarting() || !up || !Objects.equals(mIface, iface)) { return; } + LinkAddress clatAddress = getLinkAddress(iface); if (clatAddress == null) { - Slog.e(TAG, "cladAddress was null for stacked iface " + iface); + Slog.e(TAG, "clatAddress was null for stacked iface " + iface); return; } - mState = State.RUNNING; + Slog.i(TAG, String.format("interface %s is up, adding stacked link %s on top of %s", mIface, mIface, mBaseIface)); - - maybeSetIpv6NdOffload(mBaseIface, false); + enterRunningState(); LinkProperties lp = new LinkProperties(mNetwork.linkProperties); lp.addStackedLink(makeLinkProperties(clatAddress)); - mNetwork.connService.handleUpdateLinkProperties(mNetwork, lp); + mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp); } /** * Removes stacked link on base link and transitions to IDLE state. */ private void handleInterfaceRemoved(String iface) { - if (!isRunning() || !Objects.equals(mIface, iface)) { + if (!Objects.equals(mIface, iface)) { + return; + } + if (!isRunning() && !isStopping()) { return; } Slog.i(TAG, "interface " + iface + " removed"); - // The interface going away likely means clatd has crashed. Ask netd to stop it, - // because otherwise when we try to start it again on the same base interface netd - // will complain that it's already started. - try { - mNMService.unregisterObserver(this); - // TODO: add STOPPING state to avoid calling stopClatd twice. - mNMService.stopClatd(mBaseIface); - } catch(RemoteException|IllegalStateException e) { - Slog.e(TAG, "Error stopping clatd on " + mBaseIface, e); + if (!isStopping()) { + // Ensure clatd is stopped if stop() has not been called: this likely means that clatd + // has crashed. + enterStoppingState(); } - maybeSetIpv6NdOffload(mBaseIface, true); - LinkProperties lp = new LinkProperties(mNetwork.linkProperties); - lp.removeStackedLink(mIface); enterIdleState(); - mNetwork.connService.handleUpdateLinkProperties(mNetwork, lp); + LinkProperties lp = new LinkProperties(mNetwork.linkProperties); + lp.removeStackedLink(iface); + mNetwork.connService().handleUpdateLinkProperties(mNetwork, lp); } @Override public void interfaceLinkStateChanged(String iface, boolean up) { - mNetwork.handler.post(() -> { handleInterfaceLinkStateChanged(iface, up); }); + mNetwork.handler().post(() -> { handleInterfaceLinkStateChanged(iface, up); }); } @Override public void interfaceRemoved(String iface) { - mNetwork.handler.post(() -> { handleInterfaceRemoved(iface); }); + mNetwork.handler().post(() -> { handleInterfaceRemoved(iface); }); } @Override diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 4094083f138c..6f7ace2f6527 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,9 +27,12 @@ import android.net.metrics.ConnectStats; 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; @@ -59,12 +64,28 @@ public class NetdEventListenerService extends INetdEventListener.Stub { 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 WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH]; + @GuardedBy("this") + private long mWakeupEventCursor = 0; + private final ConnectivityManager mCm; @GuardedBy("this") @@ -137,11 +158,62 @@ public class NetdEventListenerService extends INetdEventListener.Stub { @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(); + } + + addWakupEvent(iface, timestampMs, uid); + } + + @GuardedBy("this") + private void addWakupEvent(String iface, long timestampMs, int uid) { + int index = wakeupEventIndex(mWakeupEventCursor); + mWakeupEventCursor++; + WakeupEvent event = new WakeupEvent(); + event.iface = iface; + event.timestampMs = timestampMs; + event.uid = uid; + mWakeupEvents[index] = event; + WakeupStats stats = mWakeupStats.get(iface); + if (stats == null) { + stats = new WakeupStats(iface); + mWakeupStats.put(iface, stats); + } + stats.countEvent(event); + } + + @GuardedBy("this") + private WakeupEvent[] getWakeupEvents() { + int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length); + WakeupEvent[] out = new WakeupEvent[length]; + // Reverse iteration from youngest event to oldest event. + long inCursor = mWakeupEventCursor - 1; + int outIdx = out.length - 1; + while (outIdx >= 0) { + out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)]; + } + return out; + } + + private static int wakeupEventIndex(long cursor) { + return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH); } 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 +225,22 @@ public class NetdEventListenerService extends INetdEventListener.Stub { } 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 : getWakeupEvents()) { + 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 +251,13 @@ public class NetdEventListenerService extends INetdEventListener.Stub { 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 7c4ef0f0f3b9..a4d7242086bf 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -249,9 +249,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private static final String TAG = ConnectivityService.class.getSimpleName(); private static final boolean VDBG = false; - public final ConnectivityService connService; + private final ConnectivityService mConnService; private final Context mContext; - final Handler handler; + private final Handler mHandler; public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, @@ -263,13 +263,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { linkProperties = lp; networkCapabilities = nc; currentScore = score; - this.connService = connService; + mConnService = connService; mContext = context; - this.handler = handler; - networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest); + mHandler = handler; + networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest); networkMisc = misc; } + public ConnectivityService connService() { + return mConnService; + } + + public Handler handler() { + 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. @@ -432,7 +444,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private boolean ignoreWifiUnvalidationPenalty() { boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); - boolean avoidBadWifi = connService.avoidBadWifi() || avoidUnvalidated; + boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated; return isWifi && !avoidBadWifi && everValidated; } @@ -516,8 +528,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } if (newExpiry > 0) { - mLingerMessage = connService.makeWakeupMessage( - mContext, handler, + mLingerMessage = mConnService.makeWakeupMessage( + mContext, mHandler, "NETWORK_LINGER_COMPLETE." + network.netId, EVENT_NETWORK_LINGER_COMPLETE, this); mLingerMessage.schedule(newExpiry); diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index d3a93542c740..8b886d6b3cff 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_DISMISSED; 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 @@ public class NetworkMonitor extends StateMachine { // 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { } } - 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); + } + + public static String getCaptivePortalServerHttpUrl( + NetworkMonitorSettings settings, Context context) { + return settings.getSetting( + context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL); } - private URL[] makeCaptivePortalFallbackUrls(Context context) { + 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 @@ public class NetworkMonitor extends StateMachine { 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 @@ public class NetworkMonitor extends StateMachine { */ 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 @@ public class NetworkMonitor extends StateMachine { 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/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java index 6d5c428e58f8..5eafe5f9f64f 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -30,6 +30,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkStats; import android.net.RouteInfo; +import android.net.netlink.ConntrackMessage; +import android.net.netlink.NetlinkConstants; +import android.net.netlink.NetlinkSocket; +import android.net.util.IpUtils; import android.net.util.SharedLog; import android.os.Handler; import android.os.Looper; @@ -37,10 +41,12 @@ import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.SystemClock; import android.provider.Settings; +import android.system.ErrnoException; +import android.system.OsConstants; import android.text.TextUtils; -import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; import java.net.Inet4Address; import java.net.Inet6Address; @@ -63,6 +69,9 @@ import java.util.concurrent.TimeUnit; */ public class OffloadController { private static final String TAG = OffloadController.class.getSimpleName(); + private static final boolean DBG = false; + private static final String ANYIP = "0.0.0.0"; + private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); private final Handler mHandler; private final OffloadHardwareInterface mHwInterface; @@ -94,6 +103,9 @@ public class OffloadController { // includes upstream interfaces that have a quota set. private HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); + private int mNatUpdateCallbacksReceived; + private int mNatUpdateNetlinkErrors; + public OffloadController(Handler h, OffloadHardwareInterface hwi, ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) { mHandler = h; @@ -113,12 +125,12 @@ public class OffloadController { } } - public void start() { - if (started()) return; + public boolean start() { + if (started()) return true; if (isOffloadDisabled()) { mLog.i("tethering offload disabled"); - return; + return false; } if (!mConfigInitialized) { @@ -126,11 +138,14 @@ public class OffloadController { if (!mConfigInitialized) { mLog.i("tethering offload config not supported"); stop(); - return; + return false; } } mControlInitialized = mHwInterface.initOffloadControl( + // OffloadHardwareInterface guarantees that these callback + // methods are called on the handler passed to it, which is the + // same as mHandler, as coordinated by the setup in Tethering. new OffloadHardwareInterface.ControlCallback() { @Override public void onStarted() { @@ -148,6 +163,14 @@ public class OffloadController { public void onStoppedUnsupported() { if (!started()) return; mLog.log("onStoppedUnsupported"); + // Poll for statistics and trigger a sweep of tethering + // stats by observers. This might not succeed, but it's + // worth trying anyway. We need to do this because from + // this point on we continue with software forwarding, + // and we need to synchronize stats and limits between + // software and hardware forwarding. + updateStatsForAllUpstreams(); + forceTetherStatsPoll(); } @Override @@ -155,11 +178,15 @@ public class OffloadController { if (!started()) return; mLog.log("onSupportAvailable"); - // [1] Poll for statistics and notify NetworkStats - // [2] (Re)Push all state: - // [a] push local prefixes - // [b] push downstreams - // [c] push upstream parameters + // [1] Poll for statistics and trigger a sweep of stats + // by observers. We need to do this to ensure that any + // limits set take into account any software tethering + // traffic that has been happening in the meantime. + updateStatsForAllUpstreams(); + forceTetherStatsPoll(); + // [2] (Re)Push all state. + // TODO: computeAndPushLocalPrefixes() + // TODO: push all downstream state. pushUpstreamParameters(null); } @@ -181,12 +208,7 @@ public class OffloadController { // The stats for the previous upstream were already updated on this thread // just after the upstream was changed, so they are also up-to-date. updateStatsForCurrentUpstream(); - - try { - mNms.tetherLimitReached(mStatsProvider); - } catch (RemoteException e) { - mLog.e("Cannot report data limit reached: " + e); - } + forceTetherStatsPoll(); } @Override @@ -194,15 +216,20 @@ public class OffloadController { String srcAddr, int srcPort, String dstAddr, int dstPort) { if (!started()) return; - mLog.log(String.format("NAT timeout update: %s (%s,%s) -> (%s,%s)", - proto, srcAddr, srcPort, dstAddr, dstPort)); + updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort); } }); - if (!mControlInitialized) { + + final boolean isStarted = started(); + if (!isStarted) { mLog.i("tethering offload control not supported"); stop(); + } else { + mLog.log("tethering offload started"); + mNatUpdateCallbacksReceived = 0; + mNatUpdateNetlinkErrors = 0; } - mLog.log("tethering offload started"); + return isStarted; } public void stop() { @@ -218,6 +245,10 @@ public class OffloadController { if (wasStarted) mLog.log("tethering offload stopped"); } + private boolean started() { + return mConfigInitialized && mControlInitialized; + } + private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub { @Override public NetworkStats getTetherStats(int how) { @@ -305,13 +336,33 @@ public class OffloadController { maybeUpdateStats(currentUpstreamInterface()); } + private void updateStatsForAllUpstreams() { + // In practice, there should only ever be a single digit number of + // upstream interfaces over the lifetime of an active tethering session. + // Roughly speaking, imagine a very ambitious one or two of each of the + // following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ]. + for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { + maybeUpdateStats(kv.getKey()); + } + } + + private void forceTetherStatsPoll() { + try { + mNms.tetherLimitReached(mStatsProvider); + } catch (RemoteException e) { + mLog.e("Cannot report data limit reached: " + e); + } + } + public void setUpstreamLinkProperties(LinkProperties lp) { if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return; - String prevUpstream = (mUpstreamLinkProperties != null) ? - mUpstreamLinkProperties.getInterfaceName() : null; + final String prevUpstream = currentUpstreamInterface(); mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null; + // Make sure we record this interface in the ForwardedStats map. + final String iface = currentUpstreamInterface(); + if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS); // TODO: examine return code and decide what to do if programming // upstream parameters fails (probably just wait for a subsequent @@ -373,21 +424,21 @@ public class OffloadController { mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0); } - private boolean started() { - return mConfigInitialized && mControlInitialized; - } - private boolean pushUpstreamParameters(String prevUpstream) { - if (mUpstreamLinkProperties == null) { + final String iface = currentUpstreamInterface(); + + if (TextUtils.isEmpty(iface)) { + final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null); + // Update stats after we've told the hardware to stop forwarding so + // we don't miss packets. maybeUpdateStats(prevUpstream); - return mHwInterface.setUpstreamParameters(null, null, null, null); + return rval; } // A stacked interface cannot be an upstream for hardware offload. // Consequently, we examine only the primary interface name, look at // getAddresses() rather than getAllAddresses(), and check getRoutes() // rather than getAllRoutes(). - final String iface = mUpstreamLinkProperties.getInterfaceName(); final ArrayList<String> v6gateways = new ArrayList<>(); String v4addr = null; String v4gateway = null; @@ -483,10 +534,113 @@ public class OffloadController { pw.println("Offload disabled"); return; } - pw.println("Offload HALs " + (started() ? "started" : "not started")); + final boolean isStarted = started(); + pw.println("Offload HALs " + (isStarted ? "started" : "not started")); LinkProperties lp = mUpstreamLinkProperties; String upstream = (lp != null) ? lp.getInterfaceName() : null; pw.println("Current upstream: " + upstream); pw.println("Exempt prefixes: " + mLastLocalPrefixStrs); + pw.println("NAT timeout update callbacks received during the " + + (isStarted ? "current" : "last") + + " offload session: " + + mNatUpdateCallbacksReceived); + pw.println("NAT timeout update netlink errors during the " + + (isStarted ? "current" : "last") + + " offload session: " + + mNatUpdateNetlinkErrors); + } + + private void updateNatTimeout( + int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) { + final String protoName = protoNameFor(proto); + if (protoName == null) { + mLog.e("Unknown NAT update callback protocol: " + proto); + return; + } + + final Inet4Address src = parseIPv4Address(srcAddr); + if (src == null) { + mLog.e("Failed to parse IPv4 address: " + srcAddr); + return; + } + + if (!IpUtils.isValidUdpOrTcpPort(srcPort)) { + mLog.e("Invalid src port: " + srcPort); + return; + } + + final Inet4Address dst = parseIPv4Address(dstAddr); + if (dst == null) { + mLog.e("Failed to parse IPv4 address: " + dstAddr); + return; + } + + if (!IpUtils.isValidUdpOrTcpPort(dstPort)) { + mLog.e("Invalid dst port: " + dstPort); + return; + } + + mNatUpdateCallbacksReceived++; + if (DBG) { + mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)", + protoName, srcAddr, srcPort, dstAddr, dstPort)); + } + + final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto); + final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest( + proto, src, srcPort, dst, dstPort, timeoutSec); + + try { + NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg); + } catch (ErrnoException e) { + mNatUpdateNetlinkErrors++; + mLog.e("Error updating NAT conntrack entry: " + e + + ", msg: " + NetlinkConstants.hexify(msg)); + mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived); + mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors); + } + } + + private static Inet4Address parseIPv4Address(String addrString) { + try { + final InetAddress ip = InetAddress.parseNumericAddress(addrString); + // TODO: Consider other sanitization steps here, including perhaps: + // not eql to 0.0.0.0 + // not within 169.254.0.0/16 + // not within ::ffff:0.0.0.0/96 + // not within ::/96 + // et cetera. + if (ip instanceof Inet4Address) { + return (Inet4Address) ip; + } + } catch (IllegalArgumentException iae) {} + return null; + } + + private static String protoNameFor(int proto) { + // OsConstants values are not constant expressions; no switch statement. + if (proto == OsConstants.IPPROTO_UDP) { + return "UDP"; + } else if (proto == OsConstants.IPPROTO_TCP) { + return "TCP"; + } + return null; + } + + private static int connectionTimeoutUpdateSecondsFor(int proto) { + // TODO: Replace this with more thoughtful work, perhaps reading from + // and maybe writing to any required + // + // /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_* + // /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream} + // + // entries. TBD. + if (proto == OsConstants.IPPROTO_TCP) { + // Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established + return 432000; + } else { + // Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream + return 180; + } } } diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 865a98902d0b..76195c4ac7d4 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -21,10 +21,12 @@ import static com.android.internal.util.BitUtils.uint16; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; +import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; import android.os.Handler; import android.os.RemoteException; import android.net.util.SharedLog; +import android.system.OsConstants; import java.util.ArrayList; @@ -107,6 +109,10 @@ public class OffloadHardwareInterface { mLog.e("tethering offload control not supported: " + e); return false; } + if (mOffloadControl == null) { + mLog.e("tethering IOffloadControl.getService() returned null"); + return false; + } } final String logmsg = String.format("initOffloadControl(%s)", @@ -327,13 +333,24 @@ public class OffloadHardwareInterface { public void updateTimeout(NatTimeoutUpdate params) { handler.post(() -> { controlCb.onNatTimeoutUpdate( - params.proto, + networkProtocolToOsConstant(params.proto), params.src.addr, uint16(params.src.port), params.dst.addr, uint16(params.dst.port)); }); } } + private static int networkProtocolToOsConstant(int proto) { + switch (proto) { + case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP; + case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP; + default: + // The caller checks this value and will log an error. Just make + // sure it won't collide with valid OsContants.IPPROTO_* values. + return -Math.abs(proto); + } + } + private static class CbResults { boolean success; String errMsg; diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index 57d2502c6dc7..17adb1a74e30 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -27,6 +27,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.ip.InterfaceController; import android.net.ip.RouterAdvertisementDaemon; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.NetdService; @@ -107,8 +108,10 @@ public class TetherInterfaceStateMachine extends StateMachine { private final SharedLog mLog; private final INetworkManagementService mNMService; + private final INetd mNetd; private final INetworkStatsService mStatsService; private final IControlsTethering mTetherController; + private final InterfaceController mInterfaceCtrl; private final String mIfaceName; private final int mInterfaceType; @@ -136,8 +139,11 @@ public class TetherInterfaceStateMachine extends StateMachine { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNMService = nMService; + // TODO: This should be passed in for testability. + mNetd = NetdService.getInstance(); mStatsService = statsService; mTetherController = tetherController; + mInterfaceCtrl = new InterfaceController(ifaceName, nMService, mNetd, mLog); mIfaceName = ifaceName; mInterfaceType = interfaceType; mLinkProperties = new LinkProperties(); @@ -179,6 +185,7 @@ public class TetherInterfaceStateMachine extends StateMachine { private void stopIPv4() { configureIPv4(false); } + // TODO: Refactor this in terms of calls to InterfaceController. private boolean configureIPv4(boolean enabled) { if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")"); @@ -381,8 +388,8 @@ public class TetherInterfaceStateMachine extends StateMachine { private void configureLocalIPv6Dns( HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { - final INetd netd = NetdService.getInstance(); - if (netd == null) { + // TODO: Is this really necessary? Can we not fail earlier if INetd cannot be located? + if (mNetd == null) { if (newDnses != null) newDnses.clear(); mLog.e("No netd service instance available; not setting local IPv6 addresses"); return; @@ -391,11 +398,8 @@ public class TetherInterfaceStateMachine extends StateMachine { // [1] Remove deprecated local DNS IP addresses. if (!deprecatedDnses.isEmpty()) { for (Inet6Address dns : deprecatedDnses) { - final String dnsString = dns.getHostAddress(); - try { - netd.interfaceDelAddress(mIfaceName, dnsString, RFC7421_PREFIX_LENGTH); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to remove local dns IP " + dnsString + ": " + e); + if (!mInterfaceCtrl.removeAddress(dns, RFC7421_PREFIX_LENGTH)) { + mLog.e("Failed to remove local dns IP " + dns); } mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH)); @@ -410,11 +414,8 @@ public class TetherInterfaceStateMachine extends StateMachine { } for (Inet6Address dns : addedDnses) { - final String dnsString = dns.getHostAddress(); - try { - netd.interfaceAddAddress(mIfaceName, dnsString, RFC7421_PREFIX_LENGTH); - } catch (ServiceSpecificException | RemoteException e) { - mLog.e("Failed to add local dns IP " + dnsString + ": " + e); + if (!mInterfaceCtrl.addAddress(dns, RFC7421_PREFIX_LENGTH)) { + mLog.e("Failed to add local dns IP " + dns); newDnses.remove(dns); } @@ -423,7 +424,7 @@ public class TetherInterfaceStateMachine extends StateMachine { } try { - netd.tetherApplyDnsInterfaces(); + mNetd.tetherApplyDnsInterfaces(); } catch (ServiceSpecificException | RemoteException e) { mLog.e("Failed to update local DNS caching server"); if (newDnses != null) newDnses.clear(); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index d9ca00c1cb77..ea260a08c2e6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4184,7 +4184,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } public static String getString(int uid, long procStateSeq) { - return "UID=" + uid + " procStateSeq=" + procStateSeq; + return "UID=" + uid + " Seq=" + procStateSeq; } private int increaseNext(int next, int increment) { diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 04d91f882d04..807c343d0d10 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -92,26 +92,10 @@ class IdmapManager { return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())).isFile(); } - boolean isDangerous(@NonNull final PackageInfo overlayPackage, final int userId) { - // unused userId: see comment in OverlayManagerServiceImpl.removeIdmapIfPossible - return isDangerous(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath())); - } - private String getIdmapPath(@NonNull final String baseCodePath) { final StringBuilder sb = new StringBuilder("/data/resource-cache/"); sb.append(baseCodePath.substring(1).replace('/', '@')); sb.append("@idmap"); return sb.toString(); } - - private boolean isDangerous(@NonNull final String idmapPath) { - try (DataInputStream dis = new DataInputStream(new FileInputStream(idmapPath))) { - final int magic = dis.readInt(); - final int version = dis.readInt(); - final int dangerous = dis.readInt(); - return dangerous != 0; - } catch (IOException e) { - return true; - } - } } diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java index 11928b964ec5..6db70cd870db 100644 --- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java +++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java @@ -53,20 +53,34 @@ final class IntentHelperImpl implements IntentHelper { // 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); + + // 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(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 */); } diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java index 50f27ed67a36..3ad4419c7273 100644 --- a/services/core/java/com/android/server/timezone/RulesManagerService.java +++ b/services/core/java/com/android/server/timezone/RulesManagerService.java @@ -343,16 +343,20 @@ public final class RulesManagerService extends IRulesManager.Stub { @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 +364,7 @@ public final class RulesManagerService extends IRulesManager.Stub { 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); } diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp index f9cbd1601290..9a17635721b8 100644 --- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp +++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp @@ -113,7 +113,7 @@ static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_c hidl_handle h1(handleFromFileDescriptor(std::move(fd1))), h2(handleFromFileDescriptor(std::move(fd2))); - bool rval; + bool rval(false); hidl_string msg; const auto status = configInterface->setHandles(h1, h2, [&rval, &msg](bool success, const hidl_string& errMsg) { @@ -123,6 +123,8 @@ static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_c if (!status.isOk() || !rval) { ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'", status.description().c_str(), msg.c_str()); + // If status is somehow not ok, make sure rval captures this too. + rval = false; } return rval; diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 8cb2df761fbe..190b3a613ca1 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -180,6 +180,7 @@ public class ApfFilter { private static final int ETH_DEST_ADDR_OFFSET = 0; private static final int ETH_ETHERTYPE_OFFSET = 12; private static final int ETH_TYPE_MIN = 0x0600; + private static final int ETH_TYPE_MAX = 0xFFFF; private static final byte[] ETH_BROADCAST_MAC_ADDRESS = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; // TODO: Make these offsets relative to end of link-layer header; don't include ETH_HEADER_LEN. @@ -232,6 +233,9 @@ public class ApfFilter { private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; // Do not log ApfProgramEvents whose actual lifetimes was less than this. private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2; + // Limit on the Black List size to cap on program usage for this + // TODO: Select a proper max length + private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; private final ApfCapabilities mApfCapabilities; private final IpManager.Callback mIpManagerCallback; @@ -247,6 +251,8 @@ public class ApfFilter { @GuardedBy("this") private boolean mMulticastFilter; private final boolean mDrop802_3Frames; + private final int[] mEthTypeBlackList; + // Our IPv4 address, if we have just one, otherwise null. @GuardedBy("this") private byte[] mIPv4Address; @@ -257,12 +263,16 @@ public class ApfFilter { @VisibleForTesting ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface, IpManager.Callback ipManagerCallback, boolean multicastFilter, - boolean ieee802_3Filter, IpConnectivityLog log) { + boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) { mApfCapabilities = apfCapabilities; mIpManagerCallback = ipManagerCallback; mNetworkInterface = networkInterface; mMulticastFilter = multicastFilter; mDrop802_3Frames = ieee802_3Filter; + + // Now fill the black list from the passed array + mEthTypeBlackList = filterEthTypeBlackList(ethTypeBlackList); + mMetricsLog = log; // TODO: ApfFilter should not generate programs until IpManager sends provisioning success. @@ -278,6 +288,35 @@ public class ApfFilter { return mUniqueCounter++; } + @GuardedBy("this") + private static int[] filterEthTypeBlackList(int[] ethTypeBlackList) { + ArrayList<Integer> bl = new ArrayList<Integer>(); + + for (int p : ethTypeBlackList) { + // Check if the protocol is a valid ether type + if ((p < ETH_TYPE_MIN) || (p > ETH_TYPE_MAX)) { + continue; + } + + // Check if the protocol is not repeated in the passed array + if (bl.contains(p)) { + continue; + } + + // Check if list reach its max size + if (bl.size() == APF_MAX_ETH_TYPE_BLACK_LIST_LEN) { + Log.w(TAG, "Passed EthType Black List size too large (" + bl.size() + + ") using top " + APF_MAX_ETH_TYPE_BLACK_LIST_LEN + " protocols"); + break; + } + + // Now add the protocol to the list + bl.add(p); + } + + return bl.stream().mapToInt(Integer::intValue).toArray(); + } + /** * Attempt to start listening for RAs and, if RAs are received, generating and installing * filters to ignore useless RAs. @@ -891,6 +930,7 @@ public class ApfFilter { * Begin generating an APF program to: * <ul> * <li>Drop/Pass 802.3 frames (based on policy) + * <li>Drop packets with EtherType within the Black List * <li>Drop ARP requests not for us, if mIPv4Address is set, * <li>Drop IPv4 broadcast packets, except DHCP destined to our MAC, * <li>Drop IPv4 multicast packets, if mMulticastFilter, @@ -914,6 +954,8 @@ public class ApfFilter { // // if it's a 802.3 Frame (ethtype < 0x0600): // drop or pass based on configurations + // if it has a ether-type that belongs to the black list + // drop // if it's ARP: // insert ARP filter to drop or pass these appropriately // if it's IPv4: @@ -931,6 +973,11 @@ public class ApfFilter { gen.addJumpIfR0LessThan(ETH_TYPE_MIN, gen.DROP_LABEL); } + // Handle ether-type black list + for (int p : mEthTypeBlackList) { + gen.addJumpIfR0Equals(p, gen.DROP_LABEL); + } + // Add ARP filters: String skipArpFiltersLabel = "skipArpFilters"; gen.addJumpIfR0NotEquals(ETH_P_ARP, skipArpFiltersLabel); @@ -1115,7 +1162,7 @@ public class ApfFilter { */ public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities, NetworkInterface networkInterface, IpManager.Callback ipManagerCallback, - boolean multicastFilter, boolean ieee802_3Filter) { + boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) { if (apfCapabilities == null || networkInterface == null) return null; if (apfCapabilities.apfVersionSupported == 0) return null; if (apfCapabilities.maximumApfProgramSize < 512) { @@ -1132,7 +1179,7 @@ public class ApfFilter { return null; } return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback, - multicastFilter, ieee802_3Filter, new IpConnectivityLog()); + multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog()); } public synchronized void shutdown() { diff --git a/services/net/java/android/net/ip/InterfaceController.java b/services/net/java/android/net/ip/InterfaceController.java new file mode 100644 index 000000000000..02e4f875230a --- /dev/null +++ b/services/net/java/android/net/ip/InterfaceController.java @@ -0,0 +1,162 @@ +/* + * 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.ip; + +import android.net.INetd; +import android.net.InterfaceConfiguration; +import android.net.LinkAddress; +import android.net.util.NetdService; +import android.net.util.SharedLog; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.system.OsConstants; + +import java.net.InetAddress; + + +/** + * Encapsulates the multiple IP configuration operations performed on an interface. + * + * TODO: refactor/eliminate the redundant ways to set and clear addresses. + * + * @hide + */ +public class InterfaceController { + private final static boolean DBG = false; + + private final String mIfName; + private final INetworkManagementService mNMS; + private final INetd mNetd; + private final SharedLog mLog; + + public InterfaceController(String ifname, INetworkManagementService nms, INetd netd, + SharedLog log) { + mIfName = ifname; + mNMS = nms; + mNetd = netd; + mLog = log; + } + + public boolean setIPv4Address(LinkAddress address) { + final InterfaceConfiguration ifcg = new InterfaceConfiguration(); + ifcg.setLinkAddress(address); + try { + mNMS.setInterfaceConfig(mIfName, ifcg); + if (DBG) mLog.log("IPv4 configuration succeeded"); + } catch (IllegalStateException | RemoteException e) { + logError("IPv4 configuration failed: %s", e); + return false; + } + return true; + } + + public boolean clearIPv4Address() { + try { + final InterfaceConfiguration ifcg = new InterfaceConfiguration(); + ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0")); + mNMS.setInterfaceConfig(mIfName, ifcg); + } catch (IllegalStateException | RemoteException e) { + logError("Failed to clear IPv4 address on interface %s: %s", mIfName, e); + return false; + } + return true; + } + + public boolean enableIPv6() { + try { + mNMS.enableIpv6(mIfName); + } catch (IllegalStateException | RemoteException e) { + logError("enabling IPv6 failed: %s", e); + return false; + } + return true; + } + + public boolean disableIPv6() { + try { + mNMS.disableIpv6(mIfName); + } catch (IllegalStateException | RemoteException e) { + logError("disabling IPv6 failed: %s", e); + return false; + } + return true; + } + + public boolean setIPv6PrivacyExtensions(boolean enabled) { + try { + mNMS.setInterfaceIpv6PrivacyExtensions(mIfName, enabled); + } catch (IllegalStateException | RemoteException e) { + logError("error setting IPv6 privacy extensions: %s", e); + return false; + } + return true; + } + + public boolean setIPv6AddrGenModeIfSupported(int mode) { + try { + mNMS.setIPv6AddrGenMode(mIfName, mode); + } catch (RemoteException e) { + logError("Unable to set IPv6 addrgen mode: %s", e); + return false; + } catch (ServiceSpecificException e) { + if (e.errorCode != OsConstants.EOPNOTSUPP) { + logError("Unable to set IPv6 addrgen mode: %s", e); + return false; + } + } + return true; + } + + public boolean addAddress(LinkAddress addr) { + return addAddress(addr.getAddress(), addr.getPrefixLength()); + } + + public boolean addAddress(InetAddress ip, int prefixLen) { + try { + mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen); + } catch (ServiceSpecificException | RemoteException e) { + logError("failed to add %s/%d: %s", ip, prefixLen, e); + return false; + } + return true; + } + + public boolean removeAddress(InetAddress ip, int prefixLen) { + try { + mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen); + } catch (ServiceSpecificException | RemoteException e) { + logError("failed to remove %s/%d: %s", ip, prefixLen, e); + return false; + } + return true; + } + + public boolean clearAllAddresses() { + try { + mNMS.clearInterfaceAddresses(mIfName); + } catch (Exception e) { + logError("Failed to clear addresses: %s", e); + return false; + } + return true; + } + + private void logError(String fmt, Object... args) { + mLog.e(String.format(fmt, args)); + } +} diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index facdb8510759..b1eb0854e334 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -22,7 +22,6 @@ import com.android.internal.util.WakeupMessage; import android.content.Context; import android.net.DhcpResults; import android.net.INetd; -import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties.ProvisioningChange; @@ -43,9 +42,7 @@ import android.os.INetworkManagementService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.ServiceSpecificException; import android.os.SystemClock; -import android.system.OsConstants; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -94,7 +91,6 @@ import java.util.stream.Collectors; */ public class IpManager extends StateMachine { private static final boolean DBG = false; - private static final boolean VDBG = false; // For message logging. private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class }; @@ -526,17 +522,18 @@ public class IpManager extends StateMachine { public static final String DUMP_ARG = "ipmanager"; public static final String DUMP_ARG_CONFIRM = "confirm"; - private static final int CMD_STOP = 1; - private static final int CMD_START = 2; - private static final int CMD_CONFIRM = 3; - private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 4; + private static final int CMD_TERMINATE_AFTER_STOP = 1; + private static final int CMD_STOP = 2; + private static final int CMD_START = 3; + private static final int CMD_CONFIRM = 4; + private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; // Sent by NetlinkTracker to communicate netlink events. - private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5; - private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6; - private static final int CMD_UPDATE_HTTP_PROXY = 7; - private static final int CMD_SET_MULTICAST_FILTER = 8; - private static final int EVENT_PROVISIONING_TIMEOUT = 9; - private static final int EVENT_DHCPACTION_TIMEOUT = 10; + private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; + private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; + private static final int CMD_UPDATE_HTTP_PROXY = 8; + private static final int CMD_SET_MULTICAST_FILTER = 9; + private static final int EVENT_PROVISIONING_TIMEOUT = 10; + private static final int EVENT_DHCPACTION_TIMEOUT = 11; private static final int MAX_LOG_RECORDS = 500; private static final int MAX_PACKET_RECORDS = 100; @@ -568,7 +565,7 @@ public class IpManager extends StateMachine { private final LocalLog mConnectivityPacketLog; private final MessageHandlingLogger mMsgStateLogger; private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - private final INetd mNetd; + private final InterfaceController mInterfaceCtrl; private NetworkInterface mNetworkInterface; @@ -612,12 +609,13 @@ public class IpManager extends StateMachine { mClatInterfaceName = CLAT_PREFIX + ifName; mCallback = new LoggingCallbackWrapper(callback); mNwService = nwService; - mNetd = netd; mLog = new SharedLog(MAX_LOG_RECORDS, mTag); mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS); mMsgStateLogger = new MessageHandlingLogger(); + mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog); + mNetlinkTracker = new NetlinkTracker( mInterfaceName, new NetlinkTracker.Callback() { @@ -704,6 +702,16 @@ public class IpManager extends StateMachine { mMultinetworkPolicyTracker.start(); } + private void stopStateMachineUpdaters() { + try { + mNwService.unregisterObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't unregister NetlinkTracker: %s", e); + } + + mMultinetworkPolicyTracker.shutdown(); + } + @Override protected void onQuitting() { mCallback.onQuit(); @@ -712,8 +720,7 @@ public class IpManager extends StateMachine { // Shut down this IpManager instance altogether. public void shutdown() { stop(); - mMultinetworkPolicyTracker.shutdown(); - quit(); + sendMessage(CMD_TERMINATE_AFTER_STOP); } public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { @@ -858,7 +865,7 @@ public class IpManager extends StateMachine { final String richerLogLine = getWhatToString(msg.what) + " " + logLine; mLog.log(richerLogLine); - if (VDBG) { + if (DBG) { Log.d(mTag, richerLogLine); } @@ -1013,19 +1020,19 @@ public class IpManager extends StateMachine { private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) { switch (delta) { case GAINED_PROVISIONING: - if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); } + if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); } recordMetric(IpManagerEvent.PROVISIONING_OK); mCallback.onProvisioningSuccess(newLp); break; case LOST_PROVISIONING: - if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); } + if (DBG) { Log.d(mTag, "onProvisioningFailure()"); } recordMetric(IpManagerEvent.PROVISIONING_FAIL); mCallback.onProvisioningFailure(newLp); break; default: - if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); } + if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); } mCallback.onLinkPropertiesChange(newLp); break; } @@ -1113,7 +1120,7 @@ public class IpManager extends StateMachine { addAllReachableDnsServers(newLp, config.dnsServers); } final LinkProperties oldLp = mLinkProperties; - if (VDBG) { + if (DBG) { Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", netlinkLinkProperties, newLp, oldLp)); } @@ -1148,35 +1155,12 @@ public class IpManager extends StateMachine { return (delta != ProvisioningChange.LOST_PROVISIONING); } - private boolean setIPv4Address(LinkAddress address) { - final InterfaceConfiguration ifcg = new InterfaceConfiguration(); - ifcg.setLinkAddress(address); - try { - mNwService.setInterfaceConfig(mInterfaceName, ifcg); - if (VDBG) Log.d(mTag, "IPv4 configuration succeeded"); - } catch (IllegalStateException | RemoteException e) { - logError("IPv4 configuration failed: %s", e); - return false; - } - return true; - } - - private void clearIPv4Address() { - try { - final InterfaceConfiguration ifcg = new InterfaceConfiguration(); - ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0")); - mNwService.setInterfaceConfig(mInterfaceName, ifcg); - } catch (IllegalStateException | RemoteException e) { - logError("Failed to clear IPv4 address on interface %s: %s", mInterfaceName, e); - } - } - private void handleIPv4Success(DhcpResults dhcpResults) { mDhcpResults = new DhcpResults(dhcpResults); final LinkProperties newLp = assembleLinkProperties(); final ProvisioningChange delta = setLinkProperties(newLp); - if (VDBG) { + if (DBG) { Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); } mCallback.onNewDhcpResults(dhcpResults); @@ -1190,9 +1174,9 @@ public class IpManager extends StateMachine { // that could trigger a call to this function. If we missed handling // that message in StartedState for some reason we would still clear // any addresses upon entry to StoppedState. - clearIPv4Address(); + mInterfaceCtrl.clearIPv4Address(); mDhcpResults = null; - if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); } + if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); } mCallback.onNewDhcpResults(null); handleProvisioningFailure(); @@ -1229,7 +1213,7 @@ public class IpManager extends StateMachine { // If we have a StaticIpConfiguration attempt to apply it and // handle the result accordingly. if (mConfiguration.mStaticIpConfig != null) { - if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { + if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); } else { return false; @@ -1244,46 +1228,16 @@ public class IpManager extends StateMachine { return true; } - private void setIPv6AddrGenModeIfSupported() throws RemoteException { - try { - mNwService.setIPv6AddrGenMode(mInterfaceName, mConfiguration.mIPv6AddrGenMode); - } catch (ServiceSpecificException e) { - if (e.errorCode != OsConstants.EOPNOTSUPP) { - logError("Unable to set IPv6 addrgen mode: %s", e); - } - } - } - private boolean startIPv6() { - // Set privacy extensions. - try { - mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true); - - setIPv6AddrGenModeIfSupported(); - mNwService.enableIpv6(mInterfaceName); - } catch (IllegalStateException | RemoteException | ServiceSpecificException e) { - logError("Unable to change interface settings: %s", e); - return false; - } - - return true; + return mInterfaceCtrl.setIPv6PrivacyExtensions(true) && + mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) && + mInterfaceCtrl.enableIPv6(); } private boolean applyInitialConfig(InitialConfiguration config) { - if (mNetd == null) { - logError("tried to add %s to %s but INetd was null", config, mInterfaceName); - return false; - } - // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { - try { - mNetd.interfaceAddAddress( - mInterfaceName, addr.getAddress().getHostAddress(), addr.getPrefixLength()); - } catch (ServiceSpecificException | RemoteException e) { - logError("failed to add %s to %s: %s", addr, mInterfaceName, e); - return false; - } + if (!mInterfaceCtrl.addAddress(addr)) return false; } return true; @@ -1320,17 +1274,8 @@ public class IpManager extends StateMachine { // - we don't get IPv4 routes from netlink // so we neither react to nor need to wait for changes in either. - try { - mNwService.disableIpv6(mInterfaceName); - } catch (Exception e) { - logError("Failed to disable IPv6: %s", e); - } - - try { - mNwService.clearInterfaceAddresses(mInterfaceName); - } catch (Exception e) { - logError("Failed to clear addresses: %s", e); - } + mInterfaceCtrl.disableIPv6(); + mInterfaceCtrl.clearAllAddresses(); } class StoppedState extends State { @@ -1348,6 +1293,11 @@ public class IpManager extends StateMachine { @Override public boolean processMessage(Message msg) { switch (msg.what) { + case CMD_TERMINATE_AFTER_STOP: + stopStateMachineUpdaters(); + quit(); + break; + case CMD_STOP: break; @@ -1404,7 +1354,7 @@ public class IpManager extends StateMachine { break; case DhcpClient.CMD_CLEAR_LINKADDRESS: - clearIPv4Address(); + mInterfaceCtrl.clearIPv4Address(); break; case DhcpClient.CMD_ON_QUIT: @@ -1494,8 +1444,11 @@ public class IpManager extends StateMachine { boolean filter802_3Frames = mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + int[] ethTypeBlackList = mContext.getResources().getIntArray( + R.array.config_apfEthTypeBlackList); + mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface, - mCallback, mMulticastFiltering, filter802_3Frames); + mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList); // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. if (mApfFilter == null) { @@ -1657,12 +1610,12 @@ public class IpManager extends StateMachine { break; case DhcpClient.CMD_CLEAR_LINKADDRESS: - clearIPv4Address(); + mInterfaceCtrl.clearIPv4Address(); break; case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { final LinkAddress ipAddress = (LinkAddress) msg.obj; - if (setIPv4Address(ipAddress)) { + if (mInterfaceCtrl.setIPv4Address(ipAddress)) { mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); } else { logError("Failed to set IPv4 address."); diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java index e833f6a03bce..714b35a03396 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java @@ -205,44 +205,14 @@ public class IpReachabilityMonitor { final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage( 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null); - int errno = -OsConstants.EPROTO; - try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) { - final long IO_TIMEOUT = 300L; - nlSocket.connectToKernel(); - nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT); - final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT); - // recvMessage() guaranteed to not return null if it did not throw. - final NetlinkMessage response = NetlinkMessage.parse(bytes); - if (response != null && response instanceof NetlinkErrorMessage && - (((NetlinkErrorMessage) response).getNlMsgError() != null)) { - errno = ((NetlinkErrorMessage) response).getNlMsgError().error; - if (errno != 0) { - // TODO: consider ignoring EINVAL (-22), which appears to be - // normal when probing a neighbor for which the kernel does - // not already have / no longer has a link layer address. - Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + response.toString()); - } - } else { - String errmsg; - if (response == null) { - bytes.position(0); - errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes); - } else { - errmsg = response.toString(); - } - Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg); - } + try { + NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg); } catch (ErrnoException e) { - Log.e(TAG, "Error " + msgSnippet, e); - errno = -e.errno; - } catch (InterruptedIOException e) { - Log.e(TAG, "Error " + msgSnippet, e); - errno = -OsConstants.ETIMEDOUT; - } catch (SocketException e) { - Log.e(TAG, "Error " + msgSnippet, e); - errno = -OsConstants.EIO; + Log.e(TAG, "Error " + msgSnippet + ": " + e); + return -e.errno; } - return errno; + + return 0; } public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) { diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java new file mode 100644 index 000000000000..605c46b3b4e0 --- /dev/null +++ b/services/net/java/android/net/netlink/ConntrackMessage.java @@ -0,0 +1,117 @@ +/* + * 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.netlink; + +import static android.net.netlink.NetlinkConstants.alignedLengthOf; +import static android.net.netlink.StructNlAttr.makeNestedType; +import static android.net.netlink.StructNlAttr.NLA_HEADERLEN; +import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK; +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; +import static android.net.util.NetworkConstants.IPV4_ADDR_LEN; +import static java.nio.ByteOrder.BIG_ENDIAN; + +import android.system.OsConstants; +import android.util.Log; +import libcore.io.SizeOf; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +/** + * A NetlinkMessage subclass for netlink conntrack messages. + * + * see also: <linux_src>/include/uapi/linux/netfilter/nfnetlink_conntrack.h + * + * @hide + */ +public class ConntrackMessage extends NetlinkMessage { + public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + + public static final short NFNL_SUBSYS_CTNETLINK = 1; + public static final short IPCTNL_MSG_CT_NEW = 0; + + // enum ctattr_type + public static final short CTA_TUPLE_ORIG = 1; + public static final short CTA_TUPLE_REPLY = 2; + public static final short CTA_TIMEOUT = 7; + + // enum ctattr_tuple + public static final short CTA_TUPLE_IP = 1; + public static final short CTA_TUPLE_PROTO = 2; + + // enum ctattr_ip + public static final short CTA_IP_V4_SRC = 1; + public static final short CTA_IP_V4_DST = 2; + + // enum ctattr_l4proto + public static final short CTA_PROTO_NUM = 1; + public static final short CTA_PROTO_SRC_PORT = 2; + public static final short CTA_PROTO_DST_PORT = 3; + + public static byte[] newIPv4TimeoutUpdateRequest( + int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) { + // *** STYLE WARNING *** + // + // Code below this point uses extra block indentation to highlight the + // packing of nested tuple netlink attribute types. + final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG, + new StructNlAttr(CTA_TUPLE_IP, + new StructNlAttr(CTA_IP_V4_SRC, src), + new StructNlAttr(CTA_IP_V4_DST, dst)), + new StructNlAttr(CTA_TUPLE_PROTO, + new StructNlAttr(CTA_PROTO_NUM, (byte) proto), + new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN), + new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN))); + + final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN); + + final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength(); + final byte[] bytes = new byte[STRUCT_SIZE + payloadLength]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final ConntrackMessage ctmsg = new ConntrackMessage(); + ctmsg.mHeader.nlmsg_len = bytes.length; + ctmsg.mHeader.nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW; + ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE; + ctmsg.mHeader.nlmsg_seq = 1; + ctmsg.pack(byteBuffer); + + ctaTupleOrig.pack(byteBuffer); + ctaTimeout.pack(byteBuffer); + + return bytes; + } + + protected StructNfGenMsg mNfGenMsg; + + private ConntrackMessage() { + super(new StructNlMsgHdr()); + mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET); + } + + public void pack(ByteBuffer byteBuffer) { + mHeader.pack(byteBuffer); + mNfGenMsg.pack(byteBuffer); + } +} diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java index 657d48c15250..a9e0cd996fbd 100644 --- a/services/net/java/android/net/netlink/NetlinkSocket.java +++ b/services/net/java/android/net/netlink/NetlinkSocket.java @@ -51,6 +51,47 @@ public class NetlinkSocket implements Closeable { private long mLastRecvTimeoutMs; private long mLastSendTimeoutMs; + public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException { + final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage"; + + try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) { + final long IO_TIMEOUT = 300L; + nlSocket.connectToKernel(); + nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT); + final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT); + // recvMessage() guaranteed to not return null if it did not throw. + final NetlinkMessage response = NetlinkMessage.parse(bytes); + if (response != null && response instanceof NetlinkErrorMessage && + (((NetlinkErrorMessage) response).getNlMsgError() != null)) { + final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error; + if (errno != 0) { + // TODO: consider ignoring EINVAL (-22), which appears to be + // normal when probing a neighbor for which the kernel does + // not already have / no longer has a link layer address. + Log.e(TAG, errPrefix + ", errmsg=" + response.toString()); + // Note: convert kernel errnos (negative) into userspace errnos (positive). + throw new ErrnoException(response.toString(), Math.abs(errno)); + } + } else { + final String errmsg; + if (response == null) { + bytes.position(0); + errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes); + } else { + errmsg = response.toString(); + } + Log.e(TAG, errPrefix + ", errmsg=" + errmsg); + throw new ErrnoException(errmsg, OsConstants.EPROTO); + } + } catch (InterruptedIOException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e); + } catch (SocketException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, OsConstants.EIO, e); + } + } + public NetlinkSocket(int nlProto) throws ErrnoException { mDescriptor = Os.socket( OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto); diff --git a/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java b/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java index 02df1313c43f..e784fbb5e0dc 100644 --- a/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java +++ b/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java @@ -36,7 +36,7 @@ import java.nio.ByteOrder; /** - * A NetlinkMessage subclass for netlink error messages. + * A NetlinkMessage subclass for rtnetlink neighbor messages. * * see also: <linux_src>/include/uapi/linux/neighbour.h * diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java new file mode 100644 index 000000000000..99695e23b248 --- /dev/null +++ b/services/net/java/android/net/netlink/StructNfGenMsg.java @@ -0,0 +1,51 @@ +/* + * 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.netlink; + +import libcore.io.SizeOf; + +import java.nio.ByteBuffer; + + +/** + * struct nfgenmsg + * + * see <linux_src>/include/uapi/linux/netfilter/nfnetlink.h + * + * @hide + */ +public class StructNfGenMsg { + public static final int STRUCT_SIZE = 2 + SizeOf.SHORT; + + public static final int NFNETLINK_V0 = 0; + + final public byte nfgen_family; + final public byte version; + final public short res_id; // N.B.: this is big endian in the kernel + + public StructNfGenMsg(byte family) { + nfgen_family = family; + version = (byte) NFNETLINK_V0; + res_id = (short) 0; + } + + public void pack(ByteBuffer byteBuffer) { + byteBuffer.put(nfgen_family); + byteBuffer.put(version); + byteBuffer.putShort(res_id); + } +} diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java index 597a6aa1c9eb..811bdbbe821a 100644 --- a/services/net/java/android/net/netlink/StructNlAttr.java +++ b/services/net/java/android/net/netlink/StructNlAttr.java @@ -34,7 +34,12 @@ import java.nio.ByteBuffer; */ public class StructNlAttr { // Already aligned. - public static final int NLA_HEADERLEN = 4; + public static final int NLA_HEADERLEN = 4; + public static final int NLA_F_NESTED = (1 << 15); + + public static short makeNestedType(short type) { + return (short) (type | NLA_F_NESTED); + } // Return a (length, type) object only, without consuming any bytes in // |byteBuffer| and without copying or interpreting any value bytes. @@ -46,10 +51,17 @@ public class StructNlAttr { } final int baseOffset = byteBuffer.position(); - final StructNlAttr struct = new StructNlAttr(); - struct.nla_len = byteBuffer.getShort(); - struct.nla_type = byteBuffer.getShort(); - struct.mByteOrder = byteBuffer.order(); + // Assume the byte order of the buffer is the expected byte order of the value. + final StructNlAttr struct = new StructNlAttr(byteBuffer.order()); + // The byte order of nla_len and nla_type is always native. + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.nativeOrder()); + try { + struct.nla_len = byteBuffer.getShort(); + struct.nla_type = byteBuffer.getShort(); + } finally { + byteBuffer.order(originalOrder); + } byteBuffer.position(baseOffset); if (struct.nla_len < NLA_HEADERLEN) { @@ -78,13 +90,65 @@ public class StructNlAttr { return struct; } - public short nla_len; + public short nla_len = (short) NLA_HEADERLEN; public short nla_type; public byte[] nla_value; - public ByteOrder mByteOrder; - public StructNlAttr() { - mByteOrder = ByteOrder.nativeOrder(); + // The byte order used to read/write the value member. Netlink length and + // type members are always read/written in native order. + private ByteOrder mByteOrder = ByteOrder.nativeOrder(); + + public StructNlAttr() {} + + public StructNlAttr(ByteOrder byteOrder) { + mByteOrder = byteOrder; + } + + public StructNlAttr(short type, byte value) { + nla_type = type; + setValue(new byte[1]); + nla_value[0] = value; + } + + public StructNlAttr(short type, short value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, short value, ByteOrder order) { + this(order); + nla_type = type; + setValue(new byte[SizeOf.SHORT]); + getValueAsByteBuffer().putShort(value); + } + + public StructNlAttr(short type, int value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, int value, ByteOrder order) { + this(order); + nla_type = type; + setValue(new byte[SizeOf.INT]); + getValueAsByteBuffer().putInt(value); + } + + public StructNlAttr(short type, InetAddress ip) { + nla_type = type; + setValue(ip.getAddress()); + } + + public StructNlAttr(short type, StructNlAttr... nested) { + this(); + nla_type = makeNestedType(type); + + int payloadLength = 0; + for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength(); + setValue(new byte[payloadLength]); + + final ByteBuffer buf = getValueAsByteBuffer(); + for (StructNlAttr nla : nested) { + nla.pack(buf); + } } public int getAlignedLength() { @@ -117,13 +181,25 @@ public class StructNlAttr { } public void pack(ByteBuffer byteBuffer) { + final ByteOrder originalOrder = byteBuffer.order(); final int originalPosition = byteBuffer.position(); - byteBuffer.putShort(nla_len); - byteBuffer.putShort(nla_type); - byteBuffer.put(nla_value); + + byteBuffer.order(ByteOrder.nativeOrder()); + try { + byteBuffer.putShort(nla_len); + byteBuffer.putShort(nla_type); + if (nla_value != null) byteBuffer.put(nla_value); + } finally { + byteBuffer.order(originalOrder); + } byteBuffer.position(originalPosition + getAlignedLength()); } + private void setValue(byte[] value) { + nla_value = value; + nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0)); + } + @Override public String toString() { return "StructNlAttr{ " 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 2887e3bb520f..d09d0c8d9e5c 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 @@ public class RulesManagerServiceTest { 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 @@ public class RulesManagerServiceTest { callback.assertNoResultReceived(); // Set up the installer. - configureStageUninstallExpectation(true /* success */); + configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_SUCCESS); // Simulate the async execution. mFakeExecutor.simulateAsyncExecutionOfLastCommand(); @@ -644,7 +676,7 @@ public class RulesManagerServiceTest { callback.assertNoResultReceived(); // Set up the installer. - configureStageUninstallExpectation(false /* success */); + configureStageUninstallExpectation(TimeZoneDistroInstaller.UNINSTALL_FAIL); // Simulate the async execution. mFakeExecutor.simulateAsyncExecutionOfLastCommand(); @@ -849,8 +881,8 @@ public class RulesManagerServiceTest { .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/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index d0b36c93db2f..fdb1f09bce34 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1307,10 +1307,7 @@ public class TelecomManager { /** * Returns whether TTY is supported on this device. - * - * @hide */ - @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index fe8774fa1f62..3c841a478d68 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -199,7 +199,7 @@ public class CarrierConfigManager { /** Display carrier settings menu if true */ public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; - /** Does not display additional call seting for IMS phone based on GSM Phone */ + /** Does not display additional call setting for IMS phone based on GSM Phone */ public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; /** Show APN Settings for some CDMA carriers */ @@ -1500,6 +1500,14 @@ public class CarrierConfigManager { public static final String IMSI_KEY_EXPIRATION_DAYS_TIME_INT = "imsi_key_expiration_days_time_int"; + /** + * Flag specifying whether IMS registration state menu is shown in Status Info setting, + * default to false. + * @hide + */ + public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = + "show_ims_registration_status_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1697,7 +1705,6 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY, new String[]{ "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" + - "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED," + "com.android.internal.telephony.CARRIER_SIGNAL_RESET" }); sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null); @@ -1753,6 +1760,7 @@ public class CarrierConfigManager { sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); sDefaults.putInt(IMSI_KEY_EXPIRATION_DAYS_TIME_INT, IMSI_ENCRYPTION_DAYS_TIME_DISABLED); sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null); + sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); } /** diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java deleted file mode 100644 index 1e8cf185d4e4..000000000000 --- a/telephony/java/android/telephony/MbmsDownloadManager.java +++ /dev/null @@ -1,586 +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.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.IBinder; -import android.os.RemoteException; -import android.telephony.mbms.DownloadProgressListener; -import android.telephony.mbms.FileInfo; -import android.telephony.mbms.DownloadRequest; -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; - -/** @hide */ -public class MbmsDownloadManager { - private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName(); - - /** @hide */ - // TODO: systemapi - @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) - public static final String MBMS_DOWNLOAD_SERVICE_ACTION = - "android.telephony.action.EmbmsDownload"; - - /** - * Integer extra indicating the result code of the download. One of - * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}. - */ - public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT"; - - /** - * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result - * is for. Must not be null. - */ - public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO"; - - /** - * Extra containing a single {@link Uri} indicating the location of the successfully - * downloaded file. Set on the intent provided via - * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}. - * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to - * {@link #RESULT_SUCCESSFUL}. - */ - public static final String EXTRA_COMPLETED_FILE_URI = - "android.telephony.mbms.extra.COMPLETED_FILE_URI"; - - public static final int RESULT_SUCCESSFUL = 1; - public static final int RESULT_CANCELLED = 2; - public static final int RESULT_EXPIRED = 3; - 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 {} - - public static final int STATUS_UNKNOWN = 0; - public static final int STATUS_ACTIVELY_DOWNLOADING = 1; - public static final int STATUS_PENDING_DOWNLOAD = 2; - public static final int STATUS_PENDING_REPAIR = 3; - 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 MbmsDownloadManagerCallback mCallback; - - private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback, int subId) { - mContext = context; - mCallback = callback; - mSubscriptionId = subId; - } - - /** - * Create a new MbmsDownloadManager using the system default data subscription ID. - * See {@link #create(Context, MbmsDownloadManagerCallback, int)} - * - * @hide - */ - public static MbmsDownloadManager create(Context context, - MbmsDownloadManagerCallback listener) - throws MbmsException { - return create(context, listener, SubscriptionManager.getDefaultSubscriptionId()); - } - - /** - * 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#middlewareReady()} 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#error(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 - * @hide - */ - public static MbmsDownloadManager create(Context context, - MbmsDownloadManagerCallback listener, int subscriptionId) - throws MbmsException { - if (!sIsInitialized.compareAndSet(false, true)) { - throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE); - } - MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId); - 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, mCallback); - } 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#fileServicesUpdated(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#error(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#fileServicesUpdated(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, DownloadProgressListener)}, the framework - * will default to a directory formed by the concatenation of the app's files directory and - * {@link android.telephony.mbms.MbmsTempFileProvider#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, DownloadProgressListener)} 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 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. - * - * downloadListener is an optional callback object which can be used to get progress reports - * of a currently occuring download. Note this can only run while the calling app - * is running, so future downloads will simply result in resultIntents being sent - * for completed or errored-out downloads. A NULL indicates no callbacks are needed. - * - * 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 MbmsTempFileProvider#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 progressListener Optional listener that will be provided progress updates - * if the app is running. - */ - public void download(DownloadRequest request, DownloadProgressListener progressListener) - 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(), - MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY); - tempRootDirectory.mkdirs(); - setTempFileRootDirectory(tempRootDirectory); - } - - checkValidDownloadDestination(request); - writeDownloadRequestToken(request); - try { - downloadService.download(request, progressListener); - } 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, DownloadProgressListener)} 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 { - mCallback.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 000000000000..ebac0419785c --- /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/MbmsStreamingSession.java index b6b253ecaddb..a8c46079148c 100644 --- a/telephony/java/android/telephony/MbmsStreamingManager.java +++ b/telephony/java/android/telephony/MbmsStreamingSession.java @@ -16,6 +16,8 @@ package android.telephony; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.content.ComponentName; @@ -25,18 +27,20 @@ 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.InternalStreamingSessionCallback; import android.telephony.mbms.InternalStreamingServiceCallback; -import android.telephony.mbms.MbmsException; -import android.telephony.mbms.MbmsStreamingManagerCallback; +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; @@ -45,8 +49,8 @@ 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"; +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 @@ -65,98 +69,98 @@ public class MbmsStreamingManager { @Override public void binderDied() { sIsInitialized.set(false); - sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification"); + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Received death notification"); } }; - private InternalStreamingManagerCallback mInternalCallback; + private InternalStreamingSessionCallback mInternalCallback; + private Set<StreamingService> mKnownActiveStreamingServices = new ArraySet<>(); private final Context mContext; private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; /** @hide */ - private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback, + private MbmsStreamingSession(Context context, MbmsStreamingSessionCallback callback, int subscriptionId, Handler handler) { mContext = context; mSubscriptionId = subscriptionId; if (handler == null) { handler = new Handler(Looper.getMainLooper()); } - mInternalCallback = new InternalStreamingManagerCallback(callback, handler); + mInternalCallback = new InternalStreamingSessionCallback(callback, handler); } /** - * Create a new MbmsStreamingManager using the given subscription ID. + * 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. This may throw an {@link MbmsException}, indicating errors that may happen - * during the initialization or binding process. + * main thread. * - * - * 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 + * 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 MbmsStreamingManagerCallback#onError(int, String)}. + * {@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 dispose() on the instance of - * {@link MbmsStreamingManager} that you received before calling this method again. + * 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. If null, callbacks will be - * processed on the main looper (in other words, the looper returned from - * {@link Looper#getMainLooper()}). + * @param handler The handler you wish to receive callbacks on. + * @return An instance of {@link MbmsStreamingSession}, or null if an error occurred. */ - public static MbmsStreamingManager create(Context context, - MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler) - throws MbmsException { + public static @Nullable MbmsStreamingSession create(@NonNull Context context, + final @NonNull MbmsStreamingSessionCallback callback, int subscriptionId, + @NonNull Handler handler) { if (!sIsInitialized.compareAndSet(false, true)) { - throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE); + throw new IllegalStateException("Cannot create two instances of MbmsStreamingSession"); } - MbmsStreamingManager manager = new MbmsStreamingManager(context, callback, + MbmsStreamingSession session = new MbmsStreamingSession(context, callback, subscriptionId, handler); - try { - manager.bindAndInitialize(); - } catch (MbmsException e) { + + final int result = session.bindAndInitialize(); + if (result != MbmsErrors.SUCCESS) { sIsInitialized.set(false); - throw e; + handler.post(new Runnable() { + @Override + public void run() { + callback.onError(result, null); + } + }); + return null; } - return manager; + return session; } /** - * Create a new MbmsStreamingManager using the system default data subscription ID. - * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. + * Create a new {@link MbmsStreamingSession} using the system default data subscription ID. + * See {@link #create(Context, MbmsStreamingSessionCallback, int, Handler)}. */ - public static MbmsStreamingManager create(Context context, - MbmsStreamingManagerCallback callback, Handler handler) - throws MbmsException { + public static MbmsStreamingSession create(@NonNull Context context, + @NonNull MbmsStreamingSessionCallback callback, @NonNull Handler handler) { 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. + * 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 dispose() { + public void close() { try { IMbmsStreamingService streamingService = mService.get(); if (streamingService == null) { @@ -164,47 +168,49 @@ public class MbmsStreamingManager { 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 through the previously registered callback. - * serviceClasses lets the app filter on types of programming and is opaque data between - * the app and the carrier. + * The results are returned asynchronously via + * {@link MbmsStreamingSessionCallback#onStreamingServicesUpdated(List)} on the callback + * provided upon creation. * - * Multiple calls replace the list of serviceClasses of interest. + * Multiple calls replace the list of service classes 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 throw an {@link IllegalArgumentException} or an {@link IllegalStateException}. * - * 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. + * @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 getStreamingServices(List<String> classList) throws MbmsException { + public void requestUpdateStreamingServices(List<String> serviceClassList) { IMbmsStreamingService streamingService = mService.get(); if (streamingService == null) { - throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + throw new IllegalStateException("Middleware not yet bound"); } try { - int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList); - if (returnCode != MbmsException.SUCCESS) { - throw new MbmsException(returnCode); + 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); - throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); } } @@ -215,56 +221,57 @@ public class MbmsStreamingManager { * 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} + * May 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}. + * {@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. If null, - * defaults to the handler provided via - * {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. + * @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 StreamingService startStreaming(StreamingServiceInfo serviceInfo, - StreamingServiceCallback callback, Handler handler) throws MbmsException { + public @Nullable StreamingService startStreaming(StreamingServiceInfo serviceInfo, + StreamingServiceCallback callback, @NonNull Handler handler) { IMbmsStreamingService streamingService = mService.get(); if (streamingService == null) { - throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + throw new IllegalStateException("Middleware not yet bound"); } InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback( - callback, handler == null ? mInternalCallback.getHandler() : handler); + callback, handler); StreamingService serviceForApp = new StreamingService( - mSubscriptionId, streamingService, serviceInfo, serviceCallback); + mSubscriptionId, streamingService, this, serviceInfo, serviceCallback); + mKnownActiveStreamingServices.add(serviceForApp); try { int returnCode = streamingService.startStreaming( mSubscriptionId, serviceInfo.getServiceId(), serviceCallback); - if (returnCode != MbmsException.SUCCESS) { - throw new MbmsException(returnCode); + 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); - throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, null); + return null; } return serviceForApp; } - private void bindAndInitialize() throws MbmsException { - MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION, + /** @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) { @@ -277,19 +284,19 @@ public class MbmsStreamingManager { } catch (RemoteException e) { Log.e(LOG_TAG, "Service died before initialization"); sendErrorToApp( - MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + MbmsErrors.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, + MbmsErrors.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, e.toString()); sIsInitialized.set(false); return; } - if (result != MbmsException.SUCCESS) { + if (result != MbmsErrors.SUCCESS) { sendErrorToApp(result, "Error returned during initialization"); sIsInitialized.set(false); return; @@ -297,7 +304,7 @@ public class MbmsStreamingManager { try { streamingService.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { - sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, + sendErrorToApp(MbmsErrors.ERROR_MIDDLEWARE_LOST, "Middleware lost during initialization"); sIsInitialized.set(false); return; @@ -315,7 +322,7 @@ public class MbmsStreamingManager { private void sendErrorToApp(int errorCode, String message) { try { - mInternalCallback.error(errorCode, message); + 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 87054469c2f9..1b942de860be 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -77,9 +77,28 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils * * @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 @@ public class PhoneNumberUtils } 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 @@ public class PhoneNumberUtils 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 @@ public class PhoneNumberUtils break; } - c = bcdToChar(b); + c = bcdToChar(b, bcdExtType); if (c == 0) { return; } @@ -943,49 +983,65 @@ public class PhoneNumberUtils /** * 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; + return (char) ('0' + b); + } - default: return 0; + 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 @@ public class PhoneNumberUtils * * 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 + * + * @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 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 @@ public class PhoneNumberUtils 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 ab679ff69d11..b7a7d8e3e639 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1647,6 +1647,10 @@ public class TelephonyManager { return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, ""); } + /* + * When adding a network type to the list below, make sure to add the correct icon to + * MobileSignalController.mapIconSets(). + */ /** Network type is unknown */ public static final int NETWORK_TYPE_UNKNOWN = 0; /** Current network is GPRS */ @@ -5086,7 +5090,12 @@ public class TelephonyManager { } } - /** @hide */ + /** + * @deprecated Use {@link android.telecom.TelecomManager#placeCall(Uri address, + * Bundle extras)} instead. + * @hide + */ + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String callingPackage, String number) { @@ -5099,7 +5108,11 @@ public class TelephonyManager { } } - /** @hide */ + /** + * @deprecated Use {@link android.telecom.TelecomManager#endCall()} instead. + * @hide + */ + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.CALL_PHONE) public boolean endCall() { @@ -5113,7 +5126,11 @@ public class TelephonyManager { return false; } - /** @hide */ + /** + * @deprecated Use {@link android.telecom.TelecomManager#acceptRingingCall} instead + * @hide + */ + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void answerRingingCall() { @@ -5126,7 +5143,11 @@ public class TelephonyManager { } } - /** @hide */ + /** + * @deprecated Use {@link android.telecom.TelecomManager#silenceRinger} instead + * @hide + */ + @Deprecated @SystemApi @SuppressLint("Doclava125") public void silenceRinger() { @@ -5681,10 +5702,13 @@ public class TelephonyManager { } /** + * @deprecated Use {link@ android.telecom.TelecomManager#isTtySupported} instead * Whether the phone supports TTY mode. * * @return {@code true} if the device supports TTY mode, and {@code false} otherwise. + * */ + @Deprecated public boolean isTtyModeSupported() { try { ITelephony telephony = getITelephony(); @@ -5776,6 +5800,25 @@ public class TelephonyManager { } /** + * Returns the IMS Registration Status for a particular Subscription ID + * + * @param subId Subscription ID + * @return true if IMS status is registered, false if the IMS status is not registered or a + * RemoteException occurred. + * + * @hide + */ + public boolean isImsRegistered(int subId) { + try { + return getITelephony().isImsRegisteredForSubscriber(subId); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + /** * Returns the Status of Volte * @hide */ @@ -6740,7 +6783,6 @@ public class TelephonyManager { * Get the most recent SignalStrength information reported by the modem. Due * to power saving this information may not always be current. * @return the most recent cached signal strength info from the modem - * @hide */ @Nullable public SignalStrength getSignalStrength() { diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index eae9011e42c8..5a57f3221d3a 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -16,6 +16,7 @@ package android.telephony.mbms; +import android.annotation.SystemApi; import android.content.Intent; import android.net.Uri; import android.os.Parcel; @@ -37,34 +38,27 @@ import java.security.NoSuchAlgorithmException; import java.util.Objects; /** - * A Parcelable class describing a pending Cell-Broadcast download request - * @hide + * Describes a request to download files over cell-broadcast. Instances of this class should be + * created by the app when requesting a download, and instances of this class will be passed back + * to the app when the middleware updates the status of the download. */ -public class DownloadRequest implements Parcelable { +public final class DownloadRequest implements Parcelable { // Version code used to keep token calculation consistent. private static final int CURRENT_VERSION = 1; private static final String LOG_TAG = "MbmsDownloadRequest"; - /** - * Maximum permissible length for the app's download-completion intent, when serialized via - * {@link Intent#toUri(int)}. - */ + /** @hide */ public static final int MAX_APP_INTENT_SIZE = 50000; - /** - * Maximum permissible length for the app's destination path, when serialized via - * {@link Uri#toString()}. - */ + /** @hide */ public static final int MAX_DESTINATION_URI_SIZE = 50000; /** @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; } @@ -73,7 +67,6 @@ public class DownloadRequest implements Parcelable { public static class Builder { private String fileServiceId; private Uri source; - private Uri dest; private int subscriptionId; private String appIntent; private int version = CURRENT_VERSION; @@ -91,8 +84,8 @@ public class DownloadRequest implements Parcelable { /** * Set the service ID for the download request. For use by the middleware only. * @hide - * TODO: systemapi */ + @SystemApi public Builder setServiceId(String serviceId) { fileServiceId = serviceId; return this; @@ -101,7 +94,6 @@ public class DownloadRequest implements Parcelable { /** * Sets the source URI for the download request to be built. * @param source - * @return */ public Builder setSource(Uri source) { this.source = source; @@ -109,25 +101,8 @@ public class DownloadRequest implements Parcelable { } /** - * 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. - * @return - */ - 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 - * @return */ public Builder setSubscriptionId(int subscriptionId) { this.subscriptionId = subscriptionId; @@ -141,7 +116,6 @@ public class DownloadRequest implements Parcelable { * * The middleware should not use this method. * @param intent - * @return */ public Builder setAppIntent(Intent intent) { this.appIntent = intent.toUri(0); @@ -158,17 +132,15 @@ public class DownloadRequest implements Parcelable { * manager code, but is irrelevant to the middleware. * @param data A byte array, the contents of which should have been originally obtained * from {@link DownloadRequest#getOpaqueData()}. - * @return - * TODO: systemapi * @hide */ + @SystemApi public Builder setOpaqueData(byte[] data) { try { ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data)); 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"); @@ -181,24 +153,21 @@ public class DownloadRequest implements Parcelable { } 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; @@ -211,7 +180,6 @@ public class DownloadRequest implements Parcelable { private DownloadRequest(DownloadRequest dr) { fileServiceId = dr.fileServiceId; sourceUri = dr.sourceUri; - destinationUri = dr.destinationUri; subscriptionId = dr.subscriptionId; serializedResultIntentForApp = dr.serializedResultIntentForApp; version = dr.version; @@ -220,7 +188,6 @@ public class DownloadRequest implements Parcelable { 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(); @@ -233,7 +200,6 @@ public class DownloadRequest implements Parcelable { 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); @@ -254,14 +220,6 @@ public class DownloadRequest implements Parcelable { } /** - * 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() { @@ -287,14 +245,14 @@ public class DownloadRequest implements Parcelable { * {@link Builder#setOpaqueData(byte[])}. * @return A byte array of opaque data to persist. * @hide - * TODO: systemapi */ + @SystemApi public byte[] getOpaqueData() { try { 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(); @@ -321,6 +279,22 @@ public class DownloadRequest implements Parcelable { }; /** + * Maximum permissible length for the app's destination path, when serialized via + * {@link Uri#toString()}. + */ + public static int getMaxAppIntentSize() { + return MAX_APP_INTENT_SIZE; + } + + /** + * Maximum permissible length for the app's download-completion intent, when serialized via + * {@link Intent#toUri(int)}. + */ + public static int getMaxDestinationUriSize() { + return MAX_DESTINATION_URI_SIZE; + } + + /** * @hide */ public boolean isMultipartDownload() { @@ -344,7 +318,6 @@ public class DownloadRequest implements Parcelable { 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 @@ -365,13 +338,12 @@ public class DownloadRequest implements Parcelable { 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/DownloadProgressListener.java b/telephony/java/android/telephony/mbms/DownloadStateCallback.java index d91e9ad24a9a..86920bd3b205 100644 --- a/telephony/java/android/telephony/mbms/DownloadProgressListener.java +++ b/telephony/java/android/telephony/mbms/DownloadStateCallback.java @@ -16,18 +16,20 @@ package android.telephony.mbms; -import android.os.RemoteException; +import android.telephony.MbmsDownloadSession; /** - * A optional listener class used by download clients to track progress. - * @hide + * A optional listener class used by download clients to track progress. Apps should extend this + * class and pass an instance into + * {@link MbmsDownloadSession#download(DownloadRequest)} + * + * This is optionally specified when requesting a download and will only be called while the app + * is running. */ -public class DownloadProgressListener extends IDownloadProgressListener.Stub { +public class DownloadStateCallback { + /** - * Gives process callbacks for a given DownloadRequest. - * This is optionally specified when requesting a download and - * only lives while the app is running - it's unlikely to be useful for - * downloads far in the future. + * Called when the middleware wants to report progress for a file in a {@link DownloadRequest}. * * @param request a {@link DownloadRequest}, indicating which download is being referenced. * @param fileInfo a {@link FileInfo} specifying the file to report progress on. Note that @@ -40,9 +42,21 @@ public class DownloadProgressListener extends IDownloadProgressListener.Stub { * @param currentDecodedSize is the number of bytes that have been decoded. * @param fullDecodedSize is the total number of bytes that make up the final decoded content. */ - @Override - public void progress(DownloadRequest request, FileInfo fileInfo, + public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int currentDownloadSize, int fullDownloadSize, - int currentDecodedSize, int fullDecodedSize) throws RemoteException { + int currentDecodedSize, int fullDecodedSize) { + } + + /** + * Gives download state callbacks for a file in a {@link DownloadRequest}. + * + * @param request a {@link DownloadRequest}, indicating which download is being referenced. + * @param fileInfo a {@link FileInfo} specifying the file to report progress on. Note that + * the request may result in many files being downloaded and the client + * may not have been able to get a list of them in advance. + * @param state The current state of the download. + */ + public void onStateUpdated(DownloadRequest request, FileInfo fileInfo, + @MbmsDownloadSession.DownloadStatus int state) { } } diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java index f97131dda427..0d737b589d06 100644 --- a/telephony/java/android/telephony/mbms/FileInfo.java +++ b/telephony/java/android/telephony/mbms/FileInfo.java @@ -16,26 +16,18 @@ package android.telephony.mbms; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; /** - * A Parcelable class Cell-Broadcast downloadable file information. - * @hide + * Describes a single file that is available over MBMS. */ -public class FileInfo implements Parcelable { +public final class FileInfo implements Parcelable { - /** - * The URI into the carriers infrastructure which points to this file. - * This is used internally but is also one of the few pieces of data about the content that is - * exposed and may be needed for disambiguation by the application. - */ private final Uri uri; - /** - * The mime type of the content. - */ private final String mimeType; public static final Parcelable.Creator<FileInfo> CREATOR = @@ -53,8 +45,8 @@ public class FileInfo implements Parcelable { /** * @hide - * TODO: systemapi */ + @SystemApi public FileInfo(Uri uri, String mimeType) { this.uri = uri; this.mimeType = mimeType; @@ -76,10 +68,17 @@ public class FileInfo implements Parcelable { return 0; } + /** + * @return The URI in the carrier's infrastructure which points to this file. Apps should + * negotiate the contents of this URI separately with the carrier. + */ public Uri getUri() { return uri; } + /** + * @return The MIME type of the file. + */ public String getMimeType() { return mimeType; } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index 8afe4d3c5230..d8d7f48a56be 100644 --- a/telephony/java/android/telephony/mbms/FileServiceInfo.java +++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java @@ -16,6 +16,7 @@ package android.telephony.mbms; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -26,13 +27,14 @@ import java.util.Locale; import java.util.Map; /** - * A Parcelable class Cell-Broadcast downloadable file information. - * @hide + * Describes a file service available from the carrier from which files can be downloaded via + * cell-broadcast. */ -public class FileServiceInfo extends ServiceInfo implements Parcelable { +public final class FileServiceInfo extends ServiceInfo implements Parcelable { private final List<FileInfo> files; - /** @hide TODO: systemapi */ + /** @hide */ + @SystemApi public FileServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales, String newServiceId, Date start, Date end, List<FileInfo> newFiles) { @@ -56,7 +58,7 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable { FileServiceInfo(Parcel in) { super(in); files = new ArrayList<FileInfo>(); - in.readList(files, null); + in.readList(files, FileInfo.class.getClassLoader()); } @Override @@ -70,8 +72,12 @@ public class FileServiceInfo extends ServiceInfo implements Parcelable { return 0; } + /** + * @return A list of files available from this service. Note that this list may not be + * exhaustive -- the middleware may not have information on all files that are available. + * Consult the carrier for an authoritative and exhaustive list. + */ public List<FileInfo> getFiles() { return files; } - } diff --git a/telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl b/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl index bb9dc6cfea9f..cebc70d3e884 100755 --- a/telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl +++ b/telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl @@ -23,12 +23,15 @@ import android.telephony.mbms.FileInfo; * The optional interface used by download clients to track progress. * @hide */ -interface IDownloadProgressListener +interface IDownloadStateCallback { /** * 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 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 index ac2f20243896..0d813a7ceea0 100755 --- a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl @@ -24,11 +24,11 @@ import java.util.List; * 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 index 007aee7cf3f2..0bf0ebc484ea 100755 --- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl +++ b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl @@ -24,11 +24,11 @@ import java.util.List; * 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 0952fbea881d..164cefb2d5ef 100755 --- a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl +++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl @@ -20,9 +20,9 @@ package android.telephony.mbms; * @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/InternalDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java new file mode 100644 index 000000000000..a7a5958fff56 --- /dev/null +++ b/telephony/java/android/telephony/mbms/InternalDownloadSessionCallback.java @@ -0,0 +1,86 @@ +/* + * 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.telephony.mbms; + +import android.os.Handler; +import android.os.RemoteException; + +import java.util.List; + +/** @hide */ +public class InternalDownloadSessionCallback extends IMbmsDownloadSessionCallback.Stub { + + private final Handler mHandler; + private final MbmsDownloadSessionCallback mAppCallback; + private volatile boolean mIsStopped = false; + + public InternalDownloadSessionCallback(MbmsDownloadSessionCallback appCallback, + Handler handler) { + mAppCallback = appCallback; + mHandler = handler; + } + + @Override + public void onError(final int errorCode, final String message) throws RemoteException { + if (mIsStopped) { + return; + } + + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onError(errorCode, message); + } + }); + } + + @Override + public void onFileServicesUpdated(final List<FileServiceInfo> services) throws RemoteException { + if (mIsStopped) { + return; + } + + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onFileServicesUpdated(services); + } + }); + } + + @Override + public void onMiddlewareReady() throws RemoteException { + if (mIsStopped) { + return; + } + + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onMiddlewareReady(); + } + }); + } + + 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 new file mode 100644 index 000000000000..8702952cf06b --- /dev/null +++ b/telephony/java/android/telephony/mbms/InternalDownloadStateCallback.java @@ -0,0 +1,70 @@ +/* + * 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.telephony.mbms; + +import android.os.Handler; +import android.os.RemoteException; + +/** + * @hide + */ +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; + mHandler = handler; + } + + @Override + 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() { + mAppCallback.onProgressUpdated(request, fileInfo, currentDownloadSize, + fullDownloadSize, currentDecodedSize, fullDecodedSize); + } + }); + } + + @Override + 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.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 bb337b271cf0..eb6579cec471 100644 --- a/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java +++ b/telephony/java/android/telephony/mbms/InternalStreamingServiceCallback.java @@ -23,6 +23,7 @@ import android.os.RemoteException; 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 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. } @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 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. } @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 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. } @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 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. } @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 @@ public class InternalStreamingServiceCallback extends IStreamingServiceCallback. } @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 class InternalStreamingServiceCallback extends IStreamingServiceCallback. } }); } + + public void stop() { + mIsStopped = true; + } } diff --git a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java index b52df8c0dd84..d782d12c00d6 100644 --- a/telephony/java/android/telephony/mbms/InternalStreamingManagerCallback.java +++ b/telephony/java/android/telephony/mbms/InternalStreamingSessionCallback.java @@ -18,25 +18,27 @@ package android.telephony.mbms; 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 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb } @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 @@ public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb } @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 class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallb public Handler getHandler() { return mHandler; } + + public void stop() { + mIsStopped = true; + } } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java deleted file mode 100644 index 17291d09215d..000000000000 --- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java +++ /dev/null @@ -1,64 +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.mbms; - -import android.os.RemoteException; -import android.telephony.MbmsDownloadManager; - -import java.util.List; - -/** - * A Parcelable class with Cell-Broadcast service information. - * @hide - */ -public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub { - - @Override - public void error(int errorCode, String message) throws RemoteException { - // default implementation empty - } - - /** - * Called to indicate published File Services have changed. - * - * This will only be called after the application has requested - * a list of file services and specified a service class list - * of interest AND the results of a subsequent getFileServices - * call with the same service class list would return different - * results. - * - * @param services a List of FileServiceInfos - * - */ - @Override - public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException { - // default implementation empty - } - - /** - * 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} - */ - @Override - public void middlewareReady() throws RemoteException { - // default implementation empty - } -} diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index ba7d120a3b7c..61415b50b2d2 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -16,6 +16,7 @@ package android.telephony.mbms; +import android.annotation.SystemApi; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -24,8 +25,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import android.telephony.MbmsDownloadManager; -import android.telephony.mbms.vendor.VendorIntents; +import android.telephony.MbmsDownloadSession; +import android.telephony.mbms.vendor.VendorUtils; import android.util.Log; import java.io.File; @@ -35,52 +36,74 @@ import java.io.FileOutputStream; 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; /** - * @hide + * The {@link BroadcastReceiver} responsible for handling intents sent from the middleware. Apps + * that wish to download using MBMS APIs should declare this class in their AndroidManifest.xml as + * follows: +<pre>{@code +<receiver + android:name="android.telephony.mbms.MbmsDownloadReceiver" + android:permission="android.permission.SEND_EMBMS_INTENTS" + android:enabled="true" + android:exported="true"> +</receiver>}</pre> */ public class MbmsDownloadReceiver extends BroadcastReceiver { + /** @hide */ public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token"; + /** @hide */ public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority"; /** - * TODO: @SystemApi all these result codes * Indicates that the requested operation completed without error. + * @hide */ + @SystemApi public static final int RESULT_OK = 0; /** * Indicates that the intent sent had an invalid action. This will be the result if * {@link Intent#getAction()} returns anything other than - * {@link VendorIntents#ACTION_DOWNLOAD_RESULT_INTERNAL}, - * {@link VendorIntents#ACTION_FILE_DESCRIPTOR_REQUEST}, or - * {@link VendorIntents#ACTION_CLEANUP}. + * {@link VendorUtils#ACTION_DOWNLOAD_RESULT_INTERNAL}, + * {@link VendorUtils#ACTION_FILE_DESCRIPTOR_REQUEST}, or + * {@link VendorUtils#ACTION_CLEANUP}. * This is a fatal result code and no result extras should be expected. + * @hide */ + @SystemApi public static final int RESULT_INVALID_ACTION = 1; /** * Indicates that the intent was missing some required extras. * This is a fatal result code and no result extras should be expected. + * @hide */ + @SystemApi public static final int RESULT_MALFORMED_INTENT = 2; /** - * Indicates that the supplied value for {@link VendorIntents#EXTRA_TEMP_FILE_ROOT} + * Indicates that the supplied value for {@link VendorUtils#EXTRA_TEMP_FILE_ROOT} * does not match what the app has stored. * This is a fatal result code and no result extras should be expected. + * @hide */ + @SystemApi public static final int RESULT_BAD_TEMP_FILE_ROOT = 3; /** * Indicates that the manager was unable to move the completed download to its final location. * This is a fatal result code and no result extras should be expected. + * @hide */ + @SystemApi public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4; /** @@ -88,35 +111,48 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { * descriptors. * This is a non-fatal result code -- some file descriptors may still be generated, but there * is no guarantee that they will be the same number as requested. + * @hide */ + @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 int MAX_TEMP_FILE_RETRIES = 5; + private static final String TEMP_FILE_STAGING_LOCATION = "staged_completed_files"; + private static final int MAX_TEMP_FILE_RETRIES = 5; private String mFileProviderAuthorityCache = null; private String mMiddlewarePackageNameCache = null; + /** @hide */ @Override public void onReceive(Context context, Intent intent) { if (!verifyIntentContents(context, intent)) { setResultCode(RESULT_MALFORMED_INTENT); return; } - if (!Objects.equals(intent.getStringExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT), + if (!Objects.equals(intent.getStringExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT), MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) { setResultCode(RESULT_BAD_TEMP_FILE_ROOT); return; } - if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { + if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { moveDownloadedFile(context, intent); cleanupPostMove(context, intent); - } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { + } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { generateTempFiles(context, intent); - } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) { + } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) { cleanupTempFiles(context, intent); } else { setResultCode(RESULT_INVALID_ACTION); @@ -124,30 +160,31 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } private boolean verifyIntentContents(Context context, Intent intent) { - if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { - if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) { + if (VendorUtils.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { + 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(VendorIntents.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; } - if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) { + if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) { Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); return false; } - if (!intent.hasExtra(MbmsDownloadManager.EXTRA_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; } - if (!intent.hasExtra(VendorIntents.EXTRA_FINAL_URI)) { + if (!intent.hasExtra(VendorUtils.EXTRA_FINAL_URI)) { Log.w(LOG_TAG, "Download result did not include the path to the final " + "temp file. Ignoring."); return false; } - DownloadRequest request = intent.getParcelableExtra(VendorIntents.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()), @@ -157,27 +194,27 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { "Expected " + expectedTokenFile); return false; } - } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { - if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) { - Log.w(LOG_TAG, "Temp file request did not include the associated service info." + + } else if (VendorUtils.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { + if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) { + Log.w(LOG_TAG, "Temp file request did not include the associated service id." + " Ignoring."); return false; } - if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) { + if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) { Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); return false; } - } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) { - if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) { - Log.w(LOG_TAG, "Cleanup request did not include the associated service info." + + } else if (VendorUtils.ACTION_CLEANUP.equals(intent.getAction())) { + if (!intent.hasExtra(VendorUtils.EXTRA_SERVICE_ID)) { + Log.w(LOG_TAG, "Cleanup request did not include the associated service id." + " Ignoring."); return false; } - if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) { + if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILE_ROOT)) { Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring."); return false; } - if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE)) { + if (!intent.hasExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE)) { Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " + "Ignoring."); return false; @@ -187,21 +224,26 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } private void moveDownloadedFile(Context context, Intent intent) { - DownloadRequest request = intent.getParcelableExtra(VendorIntents.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_RESULT, - MbmsDownloadManager.RESULT_CANCELLED); - intentForApp.putExtra(MbmsDownloadManager.EXTRA_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(VendorIntents.EXTRA_FINAL_URI); + 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); setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); @@ -209,30 +251,37 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } FileInfo completedFileInfo = - (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_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_COMPLETED_FILE_URI, finalFileLocation); - intentForApp.putExtra(MbmsDownloadManager.EXTRA_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(VendorIntents.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; } - List<Uri> tempFiles = intent.getParcelableExtra(VendorIntents.EXTRA_TEMP_LIST); + List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST); if (tempFiles == null) { return; } @@ -246,16 +295,15 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } private void generateTempFiles(Context context, Intent intent) { - FileServiceInfo serviceInfo = - intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO); - if (serviceInfo == null) { - Log.w(LOG_TAG, "Temp file request did not include the associated service info. " + + String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID); + if (serviceId == null) { + Log.w(LOG_TAG, "Temp file request did not include the associated service id. " + "Ignoring."); setResultCode(RESULT_MALFORMED_INTENT); return; } - int fdCount = intent.getIntExtra(VendorIntents.EXTRA_FD_COUNT, 0); - List<Uri> pausedList = intent.getParcelableExtra(VendorIntents.EXTRA_PAUSED_LIST); + int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0); + List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST); if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) { Log.i(LOG_TAG, "No temp files actually requested. Ending."); @@ -265,22 +313,20 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } ArrayList<UriPathPair> freshTempFiles = - generateFreshTempFiles(context, serviceInfo, fdCount); + generateFreshTempFiles(context, serviceId, fdCount); ArrayList<UriPathPair> pausedFiles = - generateUrisForPausedFiles(context, serviceInfo, pausedList); + generateUrisForPausedFiles(context, serviceId, pausedList); Bundle result = new Bundle(); - result.putParcelableArrayList(VendorIntents.EXTRA_FREE_URI_LIST, freshTempFiles); - result.putParcelableArrayList(VendorIntents.EXTRA_PAUSED_URI_LIST, pausedFiles); + result.putParcelableArrayList(VendorUtils.EXTRA_FREE_URI_LIST, freshTempFiles); + result.putParcelableArrayList(VendorUtils.EXTRA_PAUSED_URI_LIST, pausedFiles); setResultCode(RESULT_OK); setResultExtras(result); } - private ArrayList<UriPathPair> generateFreshTempFiles(Context context, - FileServiceInfo serviceInfo, + private ArrayList<UriPathPair> generateFreshTempFiles(Context context, String serviceId, int freshFdCount) { - File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, - serviceInfo.getServiceId()); + File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId); if (!tempFileDir.exists()) { tempFileDir.mkdirs(); } @@ -324,14 +370,14 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context, - FileServiceInfo serviceInfo, List<Uri> pausedFiles) { + String serviceId, List<Uri> pausedFiles) { if (pausedFiles == null) { return new ArrayList<>(0); } ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size()); for (Uri fileUri : pausedFiles) { - if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) { + if (!verifyTempFilePath(context, serviceId, fileUri)) { Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume"); setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR); continue; @@ -353,12 +399,10 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } private void cleanupTempFiles(Context context, Intent intent) { - FileServiceInfo serviceInfo = - intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO); - File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, - serviceInfo.getServiceId()); + String serviceId = intent.getStringExtra(VendorUtils.EXTRA_SERVICE_ID); + File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, serviceId); final List<Uri> filesInUse = - intent.getParcelableArrayListExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE); + intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_FILES_IN_USE); File[] filesToDelete = tempFileDir.listFiles(new FileFilter() { @Override public boolean accept(File file) { @@ -385,63 +429,22 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } } - 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, @@ -493,7 +496,7 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { 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/MbmsDownloadSessionCallback.java b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java new file mode 100644 index 000000000000..77dea6f309e5 --- /dev/null +++ b/telephony/java/android/telephony/mbms/MbmsDownloadSessionCallback.java @@ -0,0 +1,66 @@ +/* + * 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.mbms; + +import android.telephony.MbmsDownloadSession; + +import java.util.List; + +/** + * A callback class that apps should use to receive information on file downloads over + * cell-broadcast. + */ +public class MbmsDownloadSessionCallback { + + /** + * Indicates that the middleware has encountered an asynchronous error. + * @param errorCode Any error code listed in {@link MbmsErrors} + * @param message A message, intended for debugging purposes, describing the error in further + * detail. + */ + public void onError(int errorCode, String message) { + // default implementation empty + } + + /** + * Called to indicate published File Services have changed. + * + * 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 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. + */ + public void onFileServicesUpdated(List<FileServiceInfo> services) { + // default implementation empty + } + + /** + * 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 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 index 6de5a18ebaf5..af0af24c3619 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 @@ public class MbmsException extends Exception { /** * 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 @@ public class MbmsException extends Exception { 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 @@ public class MbmsException extends Exception { 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 @@ public class MbmsException extends Exception { /** * 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}. */ @@ -116,10 +117,9 @@ public class MbmsException extends Exception { /** * Indicates the errors that are applicable only to the file-download use-case - * TODO: unhide - * @hide */ public static class DownloadErrors { + private DownloadErrors() { } /** * Indicates that the app is not allowed to change the temp file root at this time due to * outstanding download requests. @@ -130,15 +130,5 @@ public class MbmsException extends Exception { 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 index b31ffa72781f..5c130a09773e 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 @@ public class MbmsStreamingManagerCallback { * 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 @@ public class MbmsStreamingManagerCallback { * 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 c4d033bf2886..689becd7169a 100644 --- a/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java +++ b/telephony/java/android/telephony/mbms/MbmsTempFileProvider.java @@ -23,12 +23,11 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.database.Cursor; import android.net.Uri; -import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.telephony.MbmsDownloadSession; import java.io.File; import java.io.FileNotFoundException; @@ -39,7 +38,6 @@ import java.util.Objects; * @hide */ public class MbmsTempFileProvider extends ContentProvider { - public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot"; public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs"; public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root"; @@ -182,8 +180,8 @@ public class MbmsTempFileProvider extends ContentProvider { if (storedTempFileRoot != null) { return new File(storedTempFileRoot).getCanonicalFile(); } else { - return new File(context.getFilesDir(), DEFAULT_TOP_LEVEL_TEMP_DIRECTORY) - .getCanonicalFile(); + return new File(context.getFilesDir(), + 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 4b913f825231..d38d8a712c73 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -68,19 +68,20 @@ public class MbmsUtils { 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 c01604b0a1bf..9a01ed00e0cd 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.HashMap; 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 FileServiceInfo TODO: add link once that's unhidden + * {@link StreamingServiceInfo} or {@link FileServiceInfo} */ public class ServiceInfo { // arbitrary limit on the number of locale -> name pairs we support @@ -58,6 +61,13 @@ public class ServiceInfo { 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 @@ public class ServiceInfo { } /** - * 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 1d66bac0d06c..ec9134a4b855 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 @@ import java.lang.annotation.RetentionPolicy; /** * 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 @@ public class StreamingService { /** * 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 class StreamingService { 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 class StreamingService { */ 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 @@ public class StreamingService { } 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 @@ public class StreamingService { } 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 b72c71572b60..09038244a16a 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 @@ public class StreamingServiceCallback { /** * 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/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java index 7acc270ed729..187e9eedab48 100644 --- a/telephony/java/android/telephony/mbms/UriPathPair.java +++ b/telephony/java/android/telephony/mbms/UriPathPair.java @@ -16,13 +16,20 @@ package android.telephony.mbms; +import android.annotation.SystemApi; import android.content.ContentResolver; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.mbms.vendor.VendorUtils; -/** @hide */ -public class UriPathPair implements Parcelable { +/** + * Wrapper for a pair of {@link Uri}s that describe a temp file used by the middleware to + * download files via cell-broadcast. + * @hide + */ +@SystemApi +public final class UriPathPair implements Parcelable { private final Uri mFilePathUri; private final Uri mContentUri; @@ -40,7 +47,7 @@ public class UriPathPair implements Parcelable { } /** @hide */ - protected UriPathPair(Parcel in) { + private UriPathPair(Parcel in) { mFilePathUri = in.readParcelable(Uri.class.getClassLoader()); mContentUri = in.readParcelable(Uri.class.getClassLoader()); } @@ -57,12 +64,23 @@ public class UriPathPair implements Parcelable { } }; - /** future systemapi */ + /** + * Returns the file-path {@link Uri}. This has scheme {@code file} and points to the actual + * location on disk where the temp file resides. Use this when sending {@link Uri}s back to the + * app in the intents in {@link VendorUtils}. + * @return A {@code file} {@link Uri}. + */ public Uri getFilePathUri() { return mFilePathUri; } - /** future systemapi */ + /** + * Returns the content {@link Uri} that may be used with + * {@link ContentResolver#openFileDescriptor(Uri, String)} to obtain a + * {@link android.os.ParcelFileDescriptor} to a temp file to write to. This {@link Uri} will + * expire if the middleware process dies. + * @return A {@code content} {@link Uri} + */ public Uri getContentUri() { return mContentUri; } diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl index dfcc5f7c8793..ed5e8268fc77 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl @@ -20,21 +20,26 @@ import android.app.PendingIntent; import android.net.Uri; import android.telephony.mbms.DownloadRequest; import android.telephony.mbms.FileInfo; -import android.telephony.mbms.IMbmsDownloadManagerCallback; -import android.telephony.mbms.IDownloadProgressListener; +import android.telephony.mbms.IMbmsDownloadSessionCallback; +import android.telephony.mbms.IDownloadStateCallback; /** * @hide */ 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, IDownloadProgressListener 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 4dd42924ab05..c90ffc7726e4 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 @@ import android.telephony.mbms.StreamingServiceInfo; */ 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 71713d013f97..d845a57b946a 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -17,40 +17,50 @@ package android.telephony.mbms.vendor; import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; -import android.telephony.mbms.DownloadProgressListener; +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.IDownloadProgressListener; -import android.telephony.mbms.IMbmsDownloadManagerCallback; -import android.telephony.mbms.MbmsDownloadManagerCallback; -import android.telephony.mbms.MbmsException; +import android.telephony.mbms.IDownloadStateCallback; +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 extend this base class rather than - * the aidl stub for compatibility + * Base class for MbmsDownloadService. The middleware should return an instance of this object from + * its {@link android.app.Service#onBind(Intent)} method. * @hide - * TODO: future systemapi */ +@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; } @@ -60,22 +70,42 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * @hide */ @Override - public final int initialize(int subscriptionId, - final IMbmsDownloadManagerCallback callback) throws RemoteException { - return initialize(subscriptionId, new MbmsDownloadManagerCallback() { + public final int initialize(final int subscriptionId, + final IMbmsDownloadSessionCallback callback) throws RemoteException { + final int uid = Binder.getCallingUid(); + callback.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + onAppCallbackDied(uid, subscriptionId); + } + }, 0); + + return initialize(subscriptionId, new MbmsDownloadSessionCallback() { @Override - public void error(int errorCode, String message) throws RemoteException { - callback.error(errorCode, message); + public void onError(int errorCode, String message) { + try { + callback.onError(errorCode, message); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } } @Override - public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException { - callback.fileServicesUpdated(services); + public void onFileServicesUpdated(List<FileServiceInfo> services) { + try { + callback.onFileServicesUpdated(services); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } } @Override - public void middlewareReady() throws RemoteException { - callback.middlewareReady(); + public void onMiddlewareReady() { + try { + callback.onMiddlewareReady(); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } } }); } @@ -83,7 +113,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { /** * 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. @@ -94,11 +124,11 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * @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; } @@ -110,13 +140,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * * 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, @@ -132,12 +162,32 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * this is not the case, an {@link IllegalStateException} may be thrown. * * @param downloadRequest An object describing the set of files to be downloaded. - * @param listener A listener 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, DownloadProgressListener listener) { + @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; } @@ -146,24 +196,101 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * @hide */ @Override - public final int download(DownloadRequest downloadRequest, IDownloadProgressListener listener) + public final int registerStateCallback( + final DownloadRequest downloadRequest, final IDownloadStateCallback callback) throws RemoteException { - return download(downloadRequest, new DownloadProgressListener() { + final int uid = Binder.getCallingUid(); + DeathRecipient deathRecipient = new DeathRecipient() { + @Override + public void binderDied() { + onAppCallbackDied(uid, downloadRequest.getSubscriptionId()); + mDownloadCallbackBinderMap.remove(callback.asBinder()); + mDownloadCallbackDeathRecipients.remove(callback.asBinder()); + } + }; + mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient); + callback.asBinder().linkToDeath(deathRecipient, 0); + + DownloadStateCallback exposedCallback = new DownloadStateCallback() { @Override - public void progress(DownloadRequest request, FileInfo fileInfo, int + public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int - fullDecodedSize) throws RemoteException { - listener.progress(request, fileInfo, currentDownloadSize, fullDownloadSize, - currentDecodedSize, fullDecodedSize); + fullDecodedSize) { + try { + 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, IDownloadProgressListener)} 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}. @@ -179,13 +306,13 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * 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 { @@ -197,7 +324,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * * 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. @@ -217,7 +344,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * 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. */ @@ -231,7 +358,7 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { * 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 @@ -244,4 +371,12 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { @Override public void dispose(int subscriptionId) throws RemoteException { } + + /** + * Indicates that the app identified by the given UID and subscription ID has died. + * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}. + * @param subscriptionId The subscription ID the app is using. + */ + public void onAppCallbackDied(int uid, int subscriptionId) { + } } diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index 843e0482ee1b..f8f370a5fe8d 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -18,13 +18,14 @@ package android.telephony.mbms.vendor; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Intent; 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; @@ -32,6 +33,8 @@ import android.telephony.mbms.StreamingServiceInfo; import java.util.List; /** + * Base class for MBMS streaming services. The middleware should return an instance of this + * object from its {@link android.app.Service#onBind(Intent)} method. * @hide */ @SystemApi @@ -41,16 +44,16 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * * 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; } @@ -60,7 +63,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * @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() { @@ -70,20 +73,20 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { } }, 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); } @@ -92,7 +95,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { @Override public void onMiddlewareReady() { try { - callback.middlewareReady(); + callback.onMiddlewareReady(); } catch (RemoteException e) { onAppCallbackDied(uid, subscriptionId); } @@ -104,7 +107,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * 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. @@ -115,11 +118,11 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * @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; } @@ -127,14 +130,14 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * 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 { @@ -147,8 +150,8 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * @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 @@ -159,19 +162,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { 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); } @@ -180,25 +183,25 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { @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); } @@ -225,32 +228,19 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * 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)}. - * - * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * {@link IStreamingServiceCallback#onStreamStateUpdated(int, int)}. * - * @param subscriptionId The subscription id to use. - * @param serviceId The ID of the streaming service that the app wishes to stop. - */ - @Override - public void stopStreaming(int subscriptionId, String serviceId) - throws RemoteException { - } - - /** - * 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 + * 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} * * @param subscriptionId The subscription id to use. - * @param serviceId The ID of the streaming service that the app wishes to dispose of. + * @param serviceId The ID of the streaming service that the app wishes to stop. */ @Override - public void disposeStream(int subscriptionId, String serviceId) + public void stopStreaming(int subscriptionId, String serviceId) throws RemoteException { } @@ -258,7 +248,7 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * 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/VendorIntents.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java index 367c995c6f7d..8fb27b2953e6 100644 --- a/telephony/java/android/telephony/mbms/vendor/VendorIntents.java +++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java @@ -16,29 +16,32 @@ package android.telephony.mbms.vendor; +import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; 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; import java.util.List; /** + * Contains constants and utility methods for MBMS Download middleware apps to communicate with + * frontend apps. * @hide - * TODO: future systemapi */ -public class VendorIntents { +@SystemApi +public class VendorUtils { /** * The MBMS middleware should send this when a download of single file has completed or * failed. Mandatory extras are - * {@link android.telephony.MbmsDownloadManager#EXTRA_RESULT} - * {@link android.telephony.MbmsDownloadManager#EXTRA_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} */ @@ -49,7 +52,7 @@ public class VendorIntents { * The MBMS middleware should send this when it wishes to request {@code content://} URIs to * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory * extras are - * {@link #EXTRA_REQUEST} + * {@link #EXTRA_SERVICE_ID} * * Optional extras are * {@link #EXTRA_FD_COUNT} (0 if not present) @@ -118,48 +121,35 @@ public class VendorIntents { "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. */ public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI"; /** - * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by + * Extra containing a String representing a service ID, used by * file-descriptor requests and cleanup requests to specify which service they want to * request temp files or clean up temp files for, respectively. */ - public static final String EXTRA_SERVICE_INFO = - "android.telephony.mbms.extra.SERVICE_INFO"; + public static final String EXTRA_SERVICE_ID = + "android.telephony.mbms.extra.SERVICE_ID"; /** * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that * the various intents from the middleware should be targeted towards. - * @param uid The uid of the frontend app. + * @param packageName The package name of the app. * @return The component name of the receiver that the middleware should send its intents to, * or null if the app didn't declare it in the manifest. */ - public static ComponentName getAppReceiverFromUid(Context context, int uid) { - String[] packageNames = context.getPackageManager().getPackagesForUid(uid); - if (packageNames == null) { - return null; - } - - for (String packageName : packageNames) { - ComponentName candidate = new ComponentName(packageName, - MbmsDownloadReceiver.class.getCanonicalName()); - Intent queryIntent = new Intent(); - queryIntent.setComponent(candidate); - List<ResolveInfo> receivers = - context.getPackageManager().queryBroadcastReceivers(queryIntent, 0); - if (receivers != null && receivers.size() > 0) { - return candidate; - } + public static ComponentName getAppReceiverFromPackageName(Context context, String packageName) { + ComponentName candidate = new ComponentName(packageName, + MbmsDownloadReceiver.class.getCanonicalName()); + Intent queryIntent = new Intent(); + queryIntent.setComponent(candidate); + List<ResolveInfo> receivers = + context.getPackageManager().queryBroadcastReceivers(queryIntent, 0); + if (receivers != null && receivers.size() > 0) { + return candidate; } return null; } diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index e7b22bdfadcc..cf4c47bf541f 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -524,6 +524,7 @@ public class ImsConfig { * Defines IMS feature value. */ public static class FeatureValueConstants { + public static final int ERROR = -1; public static final int OFF = 0; public static final int ON = 1; } diff --git a/telephony/java/com/android/internal/telephony/ExponentialBackoff.java b/telephony/java/com/android/internal/telephony/ExponentialBackoff.java new file mode 100644 index 000000000000..80958c077d6a --- /dev/null +++ b/telephony/java/com/android/internal/telephony/ExponentialBackoff.java @@ -0,0 +1,84 @@ +/* + * 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.telephony; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.Looper; + +/** The implementation of exponential backoff with jitter applied. */ +public class ExponentialBackoff { + private int mRetryCounter; + private long mStartDelayMs; + private long mMaximumDelayMs; + private long mCurrentDelayMs; + private int mMultiplier; + private Runnable mRunnable; + private Handler mHandler; + + public ExponentialBackoff( + long initialDelayMs, + long maximumDelayMs, + int multiplier, + @NonNull Looper looper, + @NonNull Runnable runnable) { + this(initialDelayMs, maximumDelayMs, multiplier, new Handler(looper), runnable); + } + + public ExponentialBackoff( + long initialDelayMs, + long maximumDelayMs, + int multiplier, + @NonNull Handler handler, + @NonNull Runnable runnable) { + mRetryCounter = 0; + mStartDelayMs = initialDelayMs; + mMaximumDelayMs = maximumDelayMs; + mMultiplier = multiplier; + mHandler = handler; + mRunnable = runnable; + } + + /** Starts the backoff, the runnable will be executed after {@link #mStartDelayMs}. */ + public void start() { + mRetryCounter = 0; + mCurrentDelayMs = mStartDelayMs; + mHandler.removeCallbacks(mRunnable); + mHandler.postDelayed(mRunnable, mCurrentDelayMs); + } + + /** Stops the backoff, all pending messages will be removed from the message queue. */ + public void stop() { + mRetryCounter = 0; + mHandler.removeCallbacks(mRunnable); + } + + /** Should call when the retry action has failed and we want to retry after a longer delay. */ + public void notifyFailed() { + mRetryCounter++; + long temp = Math.min( + mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter))); + mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp); + mHandler.removeCallbacks(mRunnable); + mHandler.postDelayed(mRunnable, mCurrentDelayMs); + } + + /** Returns the delay for the most recently posted message. */ + public long getCurrentDelay() { + return mCurrentDelayMs; + } +} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index ab7c5e722c24..0dd846e31841 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1080,6 +1080,15 @@ interface ITelephony { boolean isImsRegistered(); /** + * Get IMS Registration Status on a particular subid. + * + * @param subId user preferred subId. + * + * @return {@code true} if the IMS status is registered. + */ + boolean isImsRegisteredForSubscriber(int subId); + + /** * Returns the Status of Wi-Fi Calling */ boolean isWifiCallingAvailable(); diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java index 2fbf7ed5648c..bd8c83e63055 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 @@ public class GsmSmsAddress extends SmsAddress { // 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 d4098d94ba6c..1ca19e01d6c8 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 @@ public class SmsMessage extends SmsMessageBase { } 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/test-runner/Android.mk b/test-runner/Android.mk index 0752661b3a68..a642fdfb58a0 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -70,11 +70,10 @@ LOCAL_MODULE := android.test.mock.sdk LOCAL_SOURCE_FILES_ALL_GENERATED := true -include $(BUILD_STATIC_JAVA_LIBRARY) - # Make sure to run droiddoc first to generate the stub source files. -$(full_classes_compiled_jar) : $(android_test_mock_gen_stamp) -$(full_classes_jack) : $(android_test_mock_gen_stamp) +LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp) + +include $(BUILD_STATIC_JAVA_LIBRARY) # Archive a copy of the classes.jar in SDK build. $(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.jar) diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 3af0adc36bde..677585cc0c0f 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -42,6 +42,7 @@ LOCAL_JNI_SHARED_LIBRARIES := libframeworksnettestsjni \ libui \ libunwind \ libutils \ + libvndksupport \ libcrypto \ libhidl-gen-utils \ libhidlbase \ @@ -52,6 +53,7 @@ LOCAL_JNI_SHARED_LIBRARIES := libframeworksnettestsjni \ libtinyxml2 \ libvintf \ libhwbinder \ + libunwindstack \ android.hidl.token@1.0 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java index bfbb8cc541d7..5008a4188905 100644 --- a/tests/net/java/android/net/apf/ApfTest.java +++ b/tests/net/java/android/net/apf/ApfTest.java @@ -614,9 +614,10 @@ public class ApfTest extends AndroidTestCase { private final long mFixedTimeMs = SystemClock.elapsedRealtime(); public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter, - boolean ieee802_3Filter, IpConnectivityLog log) throws Exception { + boolean ieee802_3Filter, int[] ethTypeBlackList, + IpConnectivityLog log) throws Exception { super(new ApfCapabilities(2, 1700, ARPHRD_ETHER), NetworkInterface.getByName("lo"), - ipManagerCallback, multicastFilter, ieee802_3Filter, log); + ipManagerCallback, multicastFilter, ieee802_3Filter, ethTypeBlackList, log); } // Pretend an RA packet has been received and show it to ApfFilter. @@ -744,9 +745,10 @@ public class ApfTest extends AndroidTestCase { LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); + final int[] ethTypeBlackList = {}; ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, - ALLOW_802_3_FRAMES, mLog); + ALLOW_802_3_FRAMES, ethTypeBlackList, mLog); apfFilter.setLinkProperties(lp); byte[] program = ipManagerCallback.getApfProgram(); @@ -796,9 +798,10 @@ public class ApfTest extends AndroidTestCase { @SmallTest public void testApfFilterIPv6() throws Exception { + final int[] ethTypeBlackList = {}; MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, - ALLOW_802_3_FRAMES, mLog); + ALLOW_802_3_FRAMES, ethTypeBlackList, mLog); byte[] program = ipManagerCallback.getApfProgram(); // Verify empty IPv6 packet is passed @@ -833,6 +836,7 @@ public class ApfTest extends AndroidTestCase { final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255}; final byte[] multicastIpv4Addr = {(byte)224,0,0,1}; final byte[] multicastIpv6Addr = {(byte)0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,(byte)0xfb}; + final int[] ethTypeBlackList = {}; MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); LinkAddress link = new LinkAddress(InetAddress.getByAddress(unicastIpv4Addr), 24); @@ -840,7 +844,7 @@ public class ApfTest extends AndroidTestCase { lp.addLinkAddress(link); ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); apfFilter.setLinkProperties(lp); byte[] program = ipManagerCallback.getApfProgram(); @@ -903,7 +907,7 @@ public class ApfTest extends AndroidTestCase { ipManagerCallback.resetApfProgramWait(); apfFilter.shutdown(); apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); apfFilter.setLinkProperties(lp); program = ipManagerCallback.getApfProgram(); assertDrop(program, mcastv4packet.array()); @@ -924,9 +928,10 @@ public class ApfTest extends AndroidTestCase { LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); + final int[] ethTypeBlackList = {}; ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, - ALLOW_802_3_FRAMES, mLog); + ALLOW_802_3_FRAMES, ethTypeBlackList, mLog); apfFilter.setLinkProperties(lp); byte[] program = ipManagerCallback.getApfProgram(); @@ -948,7 +953,7 @@ public class ApfTest extends AndroidTestCase { ipManagerCallback.resetApfProgramWait(); apfFilter.shutdown(); apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); apfFilter.setLinkProperties(lp); program = ipManagerCallback.getApfProgram(); @@ -968,6 +973,70 @@ public class ApfTest extends AndroidTestCase { apfFilter.shutdown(); } + @SmallTest + public void testApfFilterEthTypeBL() throws Exception { + MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); + LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); + LinkProperties lp = new LinkProperties(); + lp.addLinkAddress(link); + final int[] emptyBlackList = {}; + final int[] ipv4BlackList = {ETH_P_IP}; + final int[] ipv4Ipv6BlackList = {ETH_P_IP, ETH_P_IPV6}; + + ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, + ALLOW_802_3_FRAMES, emptyBlackList, mLog); + apfFilter.setLinkProperties(lp); + + byte[] program = ipManagerCallback.getApfProgram(); + + // Verify empty packet of 100 zero bytes is passed + // Note that eth-type = 0 makes it an IEEE802.3 frame + ByteBuffer packet = ByteBuffer.wrap(new byte[100]); + assertPass(program, packet.array()); + + // Verify empty packet with IPv4 is passed + packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); + assertPass(program, packet.array()); + + // Verify empty IPv6 packet is passed + packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6); + assertPass(program, packet.array()); + + // Now add IPv4 to the black list + ipManagerCallback.resetApfProgramWait(); + apfFilter.shutdown(); + apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, + ALLOW_802_3_FRAMES, ipv4BlackList, mLog); + apfFilter.setLinkProperties(lp); + program = ipManagerCallback.getApfProgram(); + + // Verify that IPv4 frame will be dropped + packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); + assertDrop(program, packet.array()); + + // Verify that IPv6 frame will pass + packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6); + assertPass(program, packet.array()); + + // Now let us have both IPv4 and IPv6 in the black list + ipManagerCallback.resetApfProgramWait(); + apfFilter.shutdown(); + apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, + ALLOW_802_3_FRAMES, ipv4Ipv6BlackList, mLog); + apfFilter.setLinkProperties(lp); + program = ipManagerCallback.getApfProgram(); + + // Verify that IPv4 frame will be dropped + packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP); + assertDrop(program, packet.array()); + + // Verify that IPv6 frame will be dropped + packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6); + assertDrop(program, packet.array()); + + apfFilter.shutdown(); + } + private byte[] getProgram(MockIpManagerCallback cb, ApfFilter filter, LinkProperties lp) { cb.resetApfProgramWait(); filter.setLinkProperties(lp); @@ -991,9 +1060,10 @@ public class ApfTest extends AndroidTestCase { @SmallTest public void testApfFilterArp() throws Exception { + final int[] ethTypeBlackList = {}; MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, ALLOW_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); // Verify initially ARP request filter is off, and GARP filter is on. verifyArpFilter(ipManagerCallback.getApfProgram(), PASS); @@ -1114,8 +1184,9 @@ public class ApfTest extends AndroidTestCase { @SmallTest public void testApfFilterRa() throws Exception { MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback(); + final int[] ethTypeBlackList = {}; TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, DROP_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); byte[] program = ipManagerCallback.getApfProgram(); final int ROUTER_LIFETIME = 1000; @@ -1256,9 +1327,10 @@ public class ApfTest extends AndroidTestCase { public void testRaParsing() throws Exception { final int maxRandomPacketSize = 512; final Random r = new Random(); + final int[] ethTypeBlackList = {}; MockIpManagerCallback cb = new MockIpManagerCallback(); TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); for (int i = 0; i < 1000; i++) { byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; r.nextBytes(packet); @@ -1275,9 +1347,10 @@ public class ApfTest extends AndroidTestCase { public void testRaProcessing() throws Exception { final int maxRandomPacketSize = 512; final Random r = new Random(); + final int[] ethTypeBlackList = {}; MockIpManagerCallback cb = new MockIpManagerCallback(); TestApfFilter apfFilter = new TestApfFilter(cb, DROP_MULTICAST, - DROP_802_3_FRAMES, mLog); + DROP_802_3_FRAMES, ethTypeBlackList, mLog); for (int i = 0; i < 1000; i++) { byte[] packet = new byte[r.nextInt(maxRandomPacketSize + 1)]; r.nextBytes(packet); diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java index 541f91adf747..22d88fb70697 100644 --- a/tests/net/java/android/net/ip/IpManagerTest.java +++ b/tests/net/java/android/net/ip/IpManagerTest.java @@ -180,7 +180,8 @@ public class IpManagerTest { // 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/netlink/ConntrackMessageTest.java b/tests/net/java/android/net/netlink/ConntrackMessageTest.java new file mode 100644 index 000000000000..3aab9426bc3f --- /dev/null +++ b/tests/net/java/android/net/netlink/ConntrackMessageTest.java @@ -0,0 +1,131 @@ +/* + * 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.netlink; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assume.assumeTrue; + +import android.system.OsConstants; +import libcore.util.HexEncoding; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import org.junit.runner.RunWith; +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackMessageTest { + private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN); + + // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443) + public static final String CT_V4UPDATE_TCP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A82BD1" + // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209 + "0800 0200 17D30D1A" + // nla_type=CTA_IP_V4_DST, ip=23.211.13.26 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=6 + "0600 0200 AD2D 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00069780"; // nla_value = 432000 (big endian) + public static final byte[] CT_V4UPDATE_TCP_BYTES = + HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443) + public static final String CT_V4UPDATE_UDP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 6460A792" + // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146 + "0800 0200 D83AC50A" + // nla_type=CTA_IP_V4_DST, ip=216.58.197.10 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 11 000000" + // nla_type=CTA_PROTO_NUM, proto=17 + "0600 0200 90CD 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "000000B4"; // nla_value = 180 (big endian) + public static final byte[] CT_V4UPDATE_UDP_BYTES = + HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testConntrackIPv4TcpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_TCP, + (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333, + (Inet4Address) InetAddress.getByName("23.211.13.26"), 443, + 432000); + assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp); + } + + @Test + public void testConntrackIPv4UdpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_UDP, + (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069, + (Inet4Address) InetAddress.getByName("216.58.197.10"), 443, + 180); + assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp); + } +} diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java index f77608f95b3e..0a5a6aae6e9c 100644 --- a/tests/net/java/android/net/nsd/NsdManagerTest.java +++ b/tests/net/java/android/net/nsd/NsdManagerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static com.android.internal.util.TestUtils.waitForIdleHandler; import android.os.HandlerThread; import android.os.Handler; @@ -38,6 +39,7 @@ import android.support.test.runner.AndroidJUnit4; import android.os.Message; import android.os.Messenger; import com.android.internal.util.AsyncChannel; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -56,7 +58,9 @@ public class NsdManagerTest { @Mock INsdManager mService; MockServiceHandler mServiceHandler; - long mTimeoutMs = 100; // non-final so that tests can adjust the value. + NsdManager mManager; + + long mTimeoutMs = 200; // non-final so that tests can adjust the value. @Before public void setUp() throws Exception { @@ -64,11 +68,23 @@ public class NsdManagerTest { mServiceHandler = spy(MockServiceHandler.create(mContext)); when(mService.getMessenger()).thenReturn(new Messenger(mServiceHandler)); + + mManager = makeManager(); + } + + @After + public void tearDown() throws Exception { + mServiceHandler.waitForIdle(mTimeoutMs); + mServiceHandler.chan.disconnect(); + mServiceHandler.stop(); + if (mManager != null) { + mManager.disconnect(); + } } @Test public void testResolveService() { - NsdManager manager = makeManager(); + NsdManager manager = mManager; NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type"); @@ -88,7 +104,7 @@ public class NsdManagerTest { @Test public void testParallelResolveService() { - NsdManager manager = makeManager(); + NsdManager manager = mManager; NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); NsdServiceInfo reply = new NsdServiceInfo("resolved_name", "resolved_type"); @@ -111,7 +127,7 @@ public class NsdManagerTest { @Test public void testRegisterService() { - NsdManager manager = makeManager(); + NsdManager manager = mManager; NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type"); NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type"); @@ -170,7 +186,7 @@ public class NsdManagerTest { @Test public void testDiscoverService() { - NsdManager manager = makeManager(); + NsdManager manager = mManager; NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type"); NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type"); @@ -248,7 +264,7 @@ public class NsdManagerTest { @Test public void testInvalidCalls() { - NsdManager manager = new NsdManager(mContext, mService); + NsdManager manager = mManager; NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class); NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class); @@ -318,9 +334,10 @@ public class NsdManagerTest { } 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; } @@ -331,27 +348,43 @@ public class NsdManagerTest { // 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; } - @Override - public void handleMessage(Message msg) { + 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) { + 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 static MockServiceHandler create(Context context) { + void stop() { + getLooper().quitSafely(); + } + + 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/TestUtils.java b/tests/net/java/com/android/internal/util/TestUtils.java index c9fa340dc8b5..6db01d343756 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 @@ public final class TestUtils { * 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 f6481cf59140..a8147380386f 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -64,6 +64,7 @@ import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; import android.net.NetworkRequest; import android.net.NetworkSpecifier; +import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.StringNetworkSpecifier; import android.net.metrics.IpConnectivityLog; @@ -88,6 +89,7 @@ import android.test.AndroidTestCase; import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.SmallTest; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.LogPrinter; @@ -109,7 +111,10 @@ import org.mockito.Spy; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -304,6 +309,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { private String mRedirectUrl; MockNetworkAgent(int transport) { + this(transport, new LinkProperties()); + } + + MockNetworkAgent(int transport, LinkProperties linkProperties) { final int type = transportToLegacyType(transport); final String typeName = ConnectivityManager.getNetworkTypeName(type); mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock"); @@ -329,7 +338,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { mHandlerThread.start(); mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, - new LinkProperties(), mScore, new NetworkMisc()) { + linkProperties, mScore, new NetworkMisc()) { @Override public void unwanted() { mDisconnected.open(); } @@ -674,7 +683,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { 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 @@ -3338,6 +3348,68 @@ public class ConnectivityServiceTest extends AndroidTestCase { assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported); } + @SmallTest + public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() { + final NetworkRequest networkRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_WIFI).build(); + final TestNetworkCallback networkCallback = new TestNetworkCallback(); + mCm.registerNetworkCallback(networkRequest, networkCallback); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan0"); + LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24"); + RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null, + NetworkUtils.numericToInetAddress("192.168.12.1"), lp.getInterfaceName()); + lp.addLinkAddress(myIpv4Address); + lp.addRoute(myIpv4DefaultRoute); + + // Verify direct routes are added when network agent is first registered in + // ConnectivityService. + MockNetworkAgent networkAgent = new MockNetworkAgent(TRANSPORT_WIFI, lp); + networkAgent.connect(true); + networkCallback.expectCallback(CallbackState.AVAILABLE, networkAgent); + networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent); + CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, + networkAgent); + networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent); + networkCallback.assertNoCallback(); + checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address), + Arrays.asList(myIpv4DefaultRoute)); + checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()), + Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute)); + + // Verify direct routes are added during subsequent link properties updates. + LinkProperties newLp = new LinkProperties(lp); + LinkAddress myIpv6Address1 = new LinkAddress("fe80::cafe/64"); + LinkAddress myIpv6Address2 = new LinkAddress("2001:db8::2/64"); + newLp.addLinkAddress(myIpv6Address1); + newLp.addLinkAddress(myIpv6Address2); + networkAgent.sendLinkProperties(newLp); + cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent); + networkCallback.assertNoCallback(); + checkDirectlyConnectedRoutes(cbi.arg, + Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2), + Arrays.asList(myIpv4DefaultRoute)); + mCm.unregisterNetworkCallback(networkCallback); + } + + private void checkDirectlyConnectedRoutes(Object callbackObj, + Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) { + assertTrue(callbackObj instanceof LinkProperties); + LinkProperties lp = (LinkProperties) callbackObj; + + Set<RouteInfo> expectedRoutes = new ArraySet<>(); + expectedRoutes.addAll(otherRoutes); + for (LinkAddress address : linkAddresses) { + RouteInfo localRoute = new RouteInfo(address, null, lp.getInterfaceName()); + // Duplicates in linkAddresses are considered failures + assertTrue(expectedRoutes.add(localRoute)); + } + List<RouteInfo> observedRoutes = lp.getRoutes(); + assertEquals(expectedRoutes.size(), observedRoutes.size()); + assertTrue(observedRoutes.containsAll(expectedRoutes)); + } + private static <T> void assertEmpty(T[] ts) { int length = ts.length; assertEquals("expected empty array, but length was " + length, 0, length); diff --git a/tests/net/java/com/android/server/NsdServiceTest.java b/tests/net/java/com/android/server/NsdServiceTest.java index 68cb251cf87e..2e49c98ca9b6 100644 --- a/tests/net/java/com/android/server/NsdServiceTest.java +++ b/tests/net/java/com/android/server/NsdServiceTest.java @@ -95,6 +95,9 @@ public class NsdServiceTest { client2.disconnect(); verify(mDaemon, timeout(mTimeoutMs).times(1)).stop(); + + client1.disconnect(); + client2.disconnect(); } @Test @@ -131,6 +134,8 @@ public class NsdServiceTest { // checks that request are cleaned verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4"); + + client.disconnect(); } NsdService makeService() { diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index d11565abb917..f72a1c638ed5 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.IpConnectivityLogClas 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 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testDefaultNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DefaultNetworkEvent.class), @@ -201,9 +215,14 @@ public class IpConnectivityEventBuilderTest extends TestCase { " time_ms: 1", " transports: 0", " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", " network_id <", " network_id: 102", " >", + " no_default_network_duration_ms: 0", " previous_network_id <", " network_id: 101", " >", @@ -218,7 +237,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testDhcpClientEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpClientEvent.class), @@ -244,7 +263,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testDhcpErrorEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpErrorEvent.class), @@ -269,7 +288,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testIpManagerEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpManagerEvent.class), @@ -295,7 +314,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testIpReachabilityEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpReachabilityEvent.class), @@ -319,7 +338,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(NetworkEvent.class), @@ -348,7 +367,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testValidationProbeEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ValidationProbeEvent.class), @@ -375,7 +394,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testApfProgramEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfProgramEvent.class), @@ -409,7 +428,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testApfStatsSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfStats.class), @@ -442,6 +461,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { " program_updates_all: 7", " program_updates_allowing_multicast: 3", " received_ras: 10", + " total_packet_dropped: 0", + " total_packet_processed: 0", " zero_lifetime_ras: 1", " >", ">", @@ -450,7 +471,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { verifySerialization(want, ev); } - @SmallTest + @Test public void testRaEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(RaEvent.class), @@ -483,11 +504,49 @@ public class IpConnectivityEventBuilderTest extends TestCase { 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.unroutedWakeups = 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", + " non_application_wakeups: 1", + " root_wakeups: 2", + " system_wakeups: 3", + " total_wakeups: 14", + " unrouted_wakeups: 3", + " >", + ">", + "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 e01469b1825e..ede5988cdc6d 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 @@ public class IpConnectivityMetricsTest { 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 <", @@ -256,9 +265,14 @@ public class IpConnectivityMetricsTest { " time_ms: 300", " transports: 0", " default_network_event <", + " default_network_duration_ms: 0", + " final_score: 0", + " initial_score: 0", + " ip_support: 0", " network_id <", " network_id: 102", " >", + " no_default_network_duration_ms: 0", " previous_network_id <", " network_id: 101", " >", @@ -308,6 +322,8 @@ public class IpConnectivityMetricsTest { " program_updates_all: 7", " program_updates_allowing_multicast: 3", " received_ras: 10", + " total_packet_dropped: 0", + " total_packet_processed: 0", " zero_lifetime_ras: 1", " >", ">", @@ -367,6 +383,10 @@ public class IpConnectivityMetricsTest { " event_types: 1", " event_types: 1", " event_types: 2", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", " latencies_ms: 3456", " latencies_ms: 45", " latencies_ms: 638", @@ -384,12 +404,48 @@ public class IpConnectivityMetricsTest { " dns_lookup_batch <", " event_types: 1", " event_types: 2", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", " latencies_ms: 56", " latencies_ms: 34", " return_codes: 0", " 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", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 1", + " total_wakeups: 3", + " unrouted_wakeups: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 1", + " duration_sec: 0", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 2", + " total_wakeups: 4", + " unrouted_wakeups: 1", + " >", + ">", "version: 2\n"); verifySerialization(want, getdump("flush")); @@ -410,6 +466,11 @@ public class IpConnectivityMetricsTest { 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 new file mode 100644 index 000000000000..e3f46a40e2b1 --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java @@ -0,0 +1,226 @@ +/* + * 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.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.InterfaceConfiguration; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.INetworkManagementService; +import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.ConnectivityService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class Nat464XlatTest { + + static final String BASE_IFACE = "test0"; + static final String STACKED_IFACE = "v4-test0"; + static final LinkAddress ADDR = new LinkAddress("192.0.2.5/29"); + + @Mock ConnectivityService mConnectivity; + @Mock INetworkManagementService mNms; + @Mock InterfaceConfiguration mConfig; + @Mock NetworkAgentInfo mNai; + + TestLooper mLooper; + Handler mHandler; + + Nat464Xlat makeNat464Xlat() { + return new Nat464Xlat(mNms, mNai); + } + + @Before + public void setUp() throws Exception { + mLooper = new TestLooper(); + mHandler = new Handler(mLooper.getLooper()); + + MockitoAnnotations.initMocks(this); + + mNai.linkProperties = new LinkProperties(); + mNai.linkProperties.setInterfaceName(BASE_IFACE); + mNai.networkInfo = new NetworkInfo(null); + mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI); + when(mNai.connService()).thenReturn(mConnectivity); + when(mNai.handler()).thenReturn(mHandler); + + when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig); + when(mConfig.getLinkAddress()).thenReturn(ADDR); + } + + @Test + public void testNormalStartAndStop() throws Exception { + Nat464Xlat nat = makeNat464Xlat(); + ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); + + // ConnectivityService starts clat. + nat.start(); + + verify(mNms).registerObserver(eq(nat)); + verify(mNms).startClatd(eq(BASE_IFACE)); + + // Stacked interface up notification arrives. + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + 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)); + assertRunning(nat); + + // ConnectivityService stops clat (Network disconnects, IPv4 addr appears, ...). + 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); + mLooper.dispatchNext(); + + verify(mNms).unregisterObserver(eq(nat)); + verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture()); + assertTrue(c.getValue().getStackedLinks().isEmpty()); + assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE)); + assertIdle(nat); + + verifyNoMoreInteractions(mNms, mConnectivity); + } + + @Test + public void testClatdCrashWhileRunning() throws Exception { + Nat464Xlat nat = makeNat464Xlat(); + ArgumentCaptor<LinkProperties> c = ArgumentCaptor.forClass(LinkProperties.class); + + // ConnectivityService starts clat. + nat.start(); + + verify(mNms).registerObserver(eq(nat)); + verify(mNms).startClatd(eq(BASE_IFACE)); + + // Stacked interface up notification arrives. + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + 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)); + assertRunning(nat); + + // Stacked interface removed notification arrives (clatd crashed, ...). + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + + 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)); + assertIdle(nat); + + // ConnectivityService stops clat: no-op. + nat.stop(); + + verifyNoMoreInteractions(mNms, mConnectivity); + } + + @Test + public void testStopBeforeClatdStarts() throws Exception { + Nat464Xlat nat = makeNat464Xlat(); + + // ConnectivityService starts clat. + nat.start(); + + verify(mNms).registerObserver(eq(nat)); + verify(mNms).startClatd(eq(BASE_IFACE)); + + // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) + nat.stop(); + + verify(mNms).unregisterObserver(eq(nat)); + verify(mNms).stopClatd(eq(BASE_IFACE)); + assertIdle(nat); + + // In-flight interface up notification arrives: no-op + nat.interfaceLinkStateChanged(STACKED_IFACE, true); + mLooper.dispatchNext(); + + + // Interface removed notification arrives after stopClatd() takes effect: no-op. + nat.interfaceRemoved(STACKED_IFACE); + mLooper.dispatchNext(); + + assertIdle(nat); + + verifyNoMoreInteractions(mNms, mConnectivity); + } + + @Test + public void testStopAndClatdNeverStarts() throws Exception { + Nat464Xlat nat = makeNat464Xlat(); + + // ConnectivityService starts clat. + nat.start(); + + verify(mNms).registerObserver(eq(nat)); + verify(mNms).startClatd(eq(BASE_IFACE)); + + // ConnectivityService immediately stops clat (Network disconnects, IPv4 addr appears, ...) + nat.stop(); + + verify(mNms).unregisterObserver(eq(nat)); + verify(mNms).stopClatd(eq(BASE_IFACE)); + assertIdle(nat); + + verifyNoMoreInteractions(mNms, mConnectivity); + } + + static void assertIdle(Nat464Xlat nat) { + assertTrue("Nat464Xlat was not IDLE", !nat.isStarted()); + } + + static void assertRunning(Nat464Xlat nat) { + assertTrue("Nat464Xlat was not RUNNING", nat.isRunning()); + } +} diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index f98ab3d06921..2b105e5c92ee 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 @@ package com.android.server.connectivity; 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.support.test.runner.AndroidJUnit4; 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.ArrayList; 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 @@ public class NetdEventListenerServiceTest { } @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", + " non_application_wakeups: 0", + " root_wakeups: 0", + " system_wakeups: 2", + " total_wakeups: 5", + " unrouted_wakeups: 0", + " >", + ">", + "events <", + " if_name: \"\"", + " link_layer: 4", + " network_id: 0", + " time_ms: 0", + " transports: 0", + " wakeup_stats <", + " application_wakeups: 2", + " duration_sec: 0", + " non_application_wakeups: 1", + " root_wakeups: 2", + " system_wakeups: 3", + " total_wakeups: 10", + " unrouted_wakeups: 2", + " >", + ">", + "version: 2\n"); + assertEquals(want, got); + } + + @Test public void testDnsLogging() throws Exception { asyncDump(100); @@ -111,6 +227,10 @@ public class NetdEventListenerServiceTest { " event_types: 1", " event_types: 2", " event_types: 2", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", " latencies_ms: 3456", " latencies_ms: 267", " latencies_ms: 1230", @@ -142,6 +262,10 @@ public class NetdEventListenerServiceTest { " event_types: 2", " event_types: 1", " event_types: 1", + " getaddrinfo_error_count: 0", + " getaddrinfo_query_count: 0", + " gethostbyname_error_count: 0", + " gethostbyname_query_count: 0", " latencies_ms: 56", " latencies_ms: 78", " latencies_ms: 14", @@ -289,6 +413,11 @@ public class NetdEventListenerServiceTest { 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")); @@ -321,4 +450,15 @@ public class NetdEventListenerServiceTest { } 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 000000000000..27a897d175a2 --- /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/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java index d7739518ef2d..2199a134d657 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -32,12 +32,13 @@ import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; @@ -68,6 +69,7 @@ import java.util.Set; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.runner.RunWith; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -144,8 +146,8 @@ public class OffloadControllerTest { return offload; } - // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available. - // @Test + @Test + @Ignore("Restore when FakeSettingsProvider.clearSettingsProvider() is available.") public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception { setupFunctioningHardwareInterface(); when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1); @@ -165,8 +167,8 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); } - // TODO: Restore when FakeSettingsProvider.clearSettingsProvider() is available. - // @Test + @Test + @Ignore("Restore when FakeSettingsProvider.clearSettingsProvider() is available.") public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception { setupFunctioningHardwareInterface(); when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0); @@ -440,6 +442,9 @@ public class OffloadControllerTest { ethernetStats.txBytes = 100000; when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); offload.setUpstreamLinkProperties(null); + // Expect that we first clear the HAL's upstream parameters. + inOrder.verify(mHardware, times(1)).setUpstreamParameters( + eq(""), eq("0.0.0.0"), eq("0.0.0.0"), eq(null)); // Expect that we fetch stats from the previous upstream. inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface)); @@ -449,8 +454,6 @@ public class OffloadControllerTest { waitForIdle(); // There is no current upstream, so no stats are fetched. inOrder.verify(mHardware, never()).getForwardedStats(any()); - inOrder.verify(mHardware, times(1)).setUpstreamParameters( - eq(null), eq(null), eq(null), eq(null)); inOrder.verifyNoMoreInteractions(); assertEquals(2, stats.size()); @@ -625,6 +628,73 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); } + @Test + public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception { + setupFunctioningHardwareInterface(); + enableOffload(); + + final OffloadController offload = makeOffloadController(); + offload.start(); + + // Pretend to set a few different upstreams (only the interface name + // matters for this test; we're ignoring IP and route information). + final LinkProperties upstreamLp = new LinkProperties(); + for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { + upstreamLp.setInterfaceName(ifname); + offload.setUpstreamLinkProperties(upstreamLp); + } + + // Clear invocation history, especially the getForwardedStats() calls + // that happen with setUpstreamParameters(). + clearInvocations(mHardware); + + OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); + callback.onStoppedUnsupported(); + + // Verify forwarded stats behaviour. + verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); + verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); + verifyNoMoreInteractions(mHardware); + verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); + verifyNoMoreInteractions(mNMService); + } + + @Test + public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters() + throws Exception { + setupFunctioningHardwareInterface(); + enableOffload(); + + final OffloadController offload = makeOffloadController(); + offload.start(); + + // Pretend to set a few different upstreams (only the interface name + // matters for this test; we're ignoring IP and route information). + final LinkProperties upstreamLp = new LinkProperties(); + for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { + upstreamLp.setInterfaceName(ifname); + offload.setUpstreamLinkProperties(upstreamLp); + } + + // Clear invocation history, especially the getForwardedStats() calls + // that happen with setUpstreamParameters(). + clearInvocations(mHardware); + + OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); + callback.onSupportAvailable(); + + // Verify forwarded stats behaviour. + verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); + verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); + verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); + verifyNoMoreInteractions(mNMService); + + // TODO: verify local prefixes and downstreams are also pushed to the HAL. + verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any()); + verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong()); + verifyNoMoreInteractions(mHardware); + } + private static void assertArrayListContains(ArrayList<String> list, String... elems) { for (String element : elems) { assertTrue(list.contains(element)); diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp new file mode 100644 index 000000000000..e26c9c3fdf63 --- /dev/null +++ b/tools/aapt/Android.bp @@ -0,0 +1,115 @@ +// +// Copyright (C) 2014 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. +// + +// ========================================================== +// Setup some common variables for the different build +// targets here. +// ========================================================== + +cc_defaults { + name: "aapt_defaults", + + static_libs: [ + "libandroidfw", + "libpng", + "libutils", + "liblog", + "libcutils", + "libexpat", + "libziparchive", + "libbase", + "libz", + ], + group_static_libs: true, + + cflags: [ + "-Wall", + "-Werror", + ], + + target: { + windows: { + enabled: true, + }, + }, + + // This tool is prebuilt if we're doing an app-only build. + product_variables: { + pdk: { + enabled: false, + }, + unbundled_build: { + enabled: false, + }, + }, +} + +// ========================================================== +// Build the host static library: libaapt +// ========================================================== +cc_library_host_static { + name: "libaapt", + defaults: ["aapt_defaults"], + target: { + darwin: { + cflags: ["-D_DARWIN_UNLIMITED_STREAMS"], + }, + }, + cflags: [ + "-Wno-format-y2k", + "-DSTATIC_ANDROIDFW_FOR_TOOLS", + ], + + srcs: [ + "AaptAssets.cpp", + "AaptConfig.cpp", + "AaptUtil.cpp", + "AaptXml.cpp", + "ApkBuilder.cpp", + "Command.cpp", + "CrunchCache.cpp", + "FileFinder.cpp", + "Images.cpp", + "Package.cpp", + "pseudolocalize.cpp", + "Resource.cpp", + "ResourceFilter.cpp", + "ResourceIdCache.cpp", + "ResourceTable.cpp", + "SourcePos.cpp", + "StringPool.cpp", + "WorkQueue.cpp", + "XMLNode.cpp", + "ZipEntry.cpp", + "ZipFile.cpp", + ], +} + +// ========================================================== +// Build the host tests: libaapt_tests +// ========================================================== +cc_test_host { + name: "libaapt_tests", + defaults: ["aapt_defaults"], + srcs: [ + "tests/AaptConfig_test.cpp", + "tests/AaptGroupEntry_test.cpp", + "tests/Pseudolocales_test.cpp", + "tests/ResourceFilter_test.cpp", + "tests/ResourceTable_test.cpp", + ], + static_libs: ["libaapt"], +} diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index 04f46d9b27fc..7bcf631da652 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -14,7 +14,6 @@ # limitations under the License. # -# This tool is prebuilt if we're doing an app-only build. ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) # ========================================================== @@ -23,37 +22,6 @@ ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) # ========================================================== LOCAL_PATH:= $(call my-dir) -aaptMain := Main.cpp -aaptSources := \ - AaptAssets.cpp \ - AaptConfig.cpp \ - AaptUtil.cpp \ - AaptXml.cpp \ - ApkBuilder.cpp \ - Command.cpp \ - CrunchCache.cpp \ - FileFinder.cpp \ - Images.cpp \ - Package.cpp \ - pseudolocalize.cpp \ - Resource.cpp \ - ResourceFilter.cpp \ - ResourceIdCache.cpp \ - ResourceTable.cpp \ - SourcePos.cpp \ - StringPool.cpp \ - WorkQueue.cpp \ - XMLNode.cpp \ - ZipEntry.cpp \ - ZipFile.cpp - -aaptTests := \ - tests/AaptConfig_test.cpp \ - tests/AaptGroupEntry_test.cpp \ - tests/Pseudolocales_test.cpp \ - tests/ResourceFilter_test.cpp \ - tests/ResourceTable_test.cpp - aaptHostStaticLibs := \ libandroidfw \ libpng \ @@ -62,35 +30,10 @@ aaptHostStaticLibs := \ libcutils \ libexpat \ libziparchive \ - libbase - -aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER_FROM_FILE)\" -aaptCFlags += -Wall -Werror + libbase \ + libz -aaptHostLdLibs_linux := -lrt -ldl -lpthread - -# Statically link libz for MinGW (Win SDK under Linux), -# and dynamically link for all others. -aaptHostStaticLibs_windows := libz -aaptHostLdLibs_linux += -lz -aaptHostLdLibs_darwin := -lz - - -# ========================================================== -# Build the host static library: libaapt -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := libaapt -LOCAL_MODULE_HOST_OS := darwin linux windows -LOCAL_CFLAGS := -Wno-format-y2k -DSTATIC_ANDROIDFW_FOR_TOOLS $(aaptCFlags) -LOCAL_CPPFLAGS := $(aaptCppFlags) -LOCAL_CFLAGS_darwin := -D_DARWIN_UNLIMITED_STREAMS -LOCAL_SRC_FILES := $(aaptSources) -LOCAL_STATIC_LIBRARIES := $(aaptHostStaticLibs) -LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows) - -include $(BUILD_HOST_STATIC_LIBRARY) +aaptCFlags := -Wall -Werror # ========================================================== # Build the host executable: aapt @@ -99,33 +42,10 @@ include $(CLEAR_VARS) LOCAL_MODULE := aapt LOCAL_MODULE_HOST_OS := darwin linux windows -LOCAL_CFLAGS := $(aaptCFlags) -LOCAL_CPPFLAGS := $(aaptCppFlags) -LOCAL_LDLIBS_darwin := $(aaptHostLdLibs_darwin) -LOCAL_LDLIBS_linux := $(aaptHostLdLibs_linux) -LOCAL_SRC_FILES := $(aaptMain) +LOCAL_CFLAGS := -DAAPT_VERSION=\"$(BUILD_NUMBER_FROM_FILE)\" $(aaptCFlags) +LOCAL_SRC_FILES := Main.cpp LOCAL_STATIC_LIBRARIES := libaapt $(aaptHostStaticLibs) -LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows) include $(BUILD_HOST_EXECUTABLE) - -# ========================================================== -# Build the host tests: libaapt_tests -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := libaapt_tests -LOCAL_CFLAGS := $(aaptCFlags) -LOCAL_CPPFLAGS := $(aaptCppFlags) -LOCAL_LDLIBS_darwin := $(aaptHostLdLibs_darwin) -LOCAL_LDLIBS_linux := $(aaptHostLdLibs_linux) -LOCAL_SRC_FILES := $(aaptTests) -LOCAL_C_INCLUDES := $(LOCAL_PATH) -LOCAL_STATIC_LIBRARIES := libaapt $(aaptHostStaticLibs) -LOCAL_STATIC_LIBRARIES_windows := $(aaptHostStaticLibs_windows) - -include $(BUILD_HOST_NATIVE_TEST) - - endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index ba731801e507..5e8580255197 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -29,24 +29,6 @@ using namespace android; -#ifndef AAPT_VERSION - #define AAPT_VERSION "" -#endif - -/* - * Show version info. All the cool kids do it. - */ -int doVersion(Bundle* bundle) -{ - if (bundle->getFileSpecCount() != 0) { - printf("(ignoring extra arguments)\n"); - } - printf("Android Asset Packaging Tool, v0.2-" AAPT_VERSION "\n"); - - return 0; -} - - /* * Open the file read only. The call fails if the file doesn't exist. * diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 417b7ae087e1..d714687ee914 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -20,6 +20,23 @@ using namespace android; static const char* gProgName = "aapt"; +#ifndef AAPT_VERSION + #define AAPT_VERSION "" +#endif + +/* + * Show version info. All the cool kids do it. + */ +int doVersion(Bundle* bundle) +{ + if (bundle->getFileSpecCount() != 0) { + printf("(ignoring extra arguments)\n"); + } + printf("Android Asset Packaging Tool, v0.2-" AAPT_VERSION "\n"); + + return 0; +} + /* * When running under Cygwin on Windows, this will convert slash-based * paths into back-slash-based ones. Otherwise the ApptAssets file comparisons diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index eff82837dd8a..353c3e0652fb 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -24,7 +24,7 @@ toolSources = [ ] cc_defaults { - name: "aapt_defaults", + name: "aapt2_defaults", cflags: [ "-Wall", "-Werror", @@ -39,14 +39,9 @@ cc_defaults { windows: { enabled: true, cflags: ["-Wno-maybe-uninitialized"], - static_libs: ["libz"], }, darwin: { cflags: ["-D_DARWIN_UNLIMITED_STREAMS"], - host_ldlibs: ["-lz"], - }, - linux: { - host_ldlibs: ["-lz"], }, }, static_libs: [ @@ -59,6 +54,7 @@ cc_defaults { "libpng", "libbase", "libprotobuf-cpp-lite", + "libz", ], group_static_libs: true, } @@ -141,7 +137,7 @@ cc_library_host_static { proto: { export_proto_headers: true, }, - defaults: ["aapt_defaults"], + defaults: ["aapt2_defaults"], } // ========================================================== @@ -151,7 +147,7 @@ cc_library_host_shared { name: "libaapt2_jni", srcs: toolSources + ["jni/aapt2_jni.cpp"], static_libs: ["libaapt2"], - defaults: ["aapt_defaults"], + defaults: ["aapt2_defaults"], } // ========================================================== @@ -161,7 +157,7 @@ cc_test_host { name: "aapt2_tests", srcs: ["test/Common.cpp", "**/*_test.cpp"], static_libs: ["libaapt2", "libgmock"], - defaults: ["aapt_defaults"], + defaults: ["aapt2_defaults"], } // ========================================================== @@ -171,5 +167,5 @@ cc_binary_host { name: "aapt2", srcs: ["Main.cpp"] + toolSources, static_libs: ["libaapt2"], - defaults: ["aapt_defaults"], + defaults: ["aapt2_defaults"], } diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 0711749d0378..bfebedef2a1e 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -257,9 +257,11 @@ class XmlFlattenerVisitor : public xml::Visitor { // Process plain strings to make sure they get properly escaped. StringPiece raw_value = xml_attr->value; - util::StringBuilder str_builder; + + util::StringBuilder str_builder(true /*preserve_spaces*/); + str_builder.Append(xml_attr->value); + if (!options_.keep_raw_values) { - str_builder.Append(xml_attr->value); raw_value = str_builder.ToString(); } diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index f1e903f2151e..a57e3178accd 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -23,7 +23,13 @@ #include "util/BigBuffer.h" #include "util/Util.h" -using android::StringPiece16; +using ::aapt::test::StrEq; +using ::android::StringPiece16; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::Ne; +using ::testing::NotNull; namespace aapt { @@ -72,163 +78,138 @@ class XmlFlattenerTest : public ::testing::Test { }; TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:test="http://com.test" - attr="hey"> - <Layout test:hello="hi" /> - <Layout>Some text\\</Layout> - </View>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:test="http://com.test" attr="hey"> + <Layout test:hello="hi" /> + <Layout>Some text\\</Layout> + </View>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); - - ASSERT_EQ(android::ResXMLTree::START_NAMESPACE, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE)); size_t len; - const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); - - const char16_t* namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); - - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); - - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - const char16_t* tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); - - ASSERT_EQ(1u, tree.getAttributeCount()); - ASSERT_EQ(nullptr, tree.getAttributeNamespace(0, &len)); - const char16_t* attr_name = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(u"attr"), StringPiece16(attr_name, len)); - - EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test")); - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"View")); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.getAttributeCount(), Eq(1u)); + EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull()); + EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr")); - ASSERT_EQ(1u, tree.getAttributeCount()); - const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len); - EXPECT_EQ(StringPiece16(u"http://com.test"), StringPiece16(attr_namespace, len)); + const StringPiece16 kAttr(u"attr"); + EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0)); - attr_name = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(u"hello"), StringPiece16(attr_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); + ASSERT_THAT(tree.getAttributeCount(), Eq(1u)); + EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test")); + EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello")); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); - ASSERT_EQ(0u, tree.getAttributeCount()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); - ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); - const char16_t* text = tree.getText(&len); - EXPECT_EQ(StringPiece16(u"Some text\\"), StringPiece16(text, len)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); + ASSERT_THAT(tree.getAttributeCount(), Eq(0u)); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\")); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); - ASSERT_EQ(android::ResXMLTree::END_NAMESPACE, tree.next()); - namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"View")); - namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE)); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test")); - ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT)); } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:tools="http://schemas.android.com/tools" - xmlns:foo="http://schemas.android.com/foo" - foo:bar="Foo" - tools:ignore="MissingTranslation"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:tools="http://schemas.android.com/tools" + xmlns:foo="http://schemas.android.com/foo" + foo:bar="Foo" + tools:ignore="MissingTranslation"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); - - ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE)); size_t len; - const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo"); - - const char16_t* namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespace_uri, len), - u"http://schemas.android.com/foo"); - - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); - EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), - android::NAME_NOT_FOUND); - EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); + EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), + Eq(android::NAME_NOT_FOUND)); + EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0)); } TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@id/id" - class="str" - style="@id/id"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/id" + class="str" + style="@id/id"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } - EXPECT_EQ(tree.indexOfClass(), 0); - EXPECT_EQ(tree.indexOfStyle(), 1); + EXPECT_THAT(tree.indexOfClass(), Eq(0)); + EXPECT_THAT(tree.indexOfStyle(), Eq(1)); } // The device ResXMLParser in libandroidfw differentiates between empty namespace and null // namespace. TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"android\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); + EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0)); } TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kPackage = u"package"; ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); - ASSERT_GE(idx, 0); + ASSERT_THAT(idx, Ge(0)); size_t len; - EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull()); } TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { @@ -236,11 +217,11 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { context_->SetPackageId(0x80); context_->SetNameManglerPolicy({"com.app.test.feature"}); - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@id/foo" - app:foo="@id/foo" />)EOF"); + app:foo="@id/foo" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -253,59 +234,57 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(android::ResXMLTree::BAD_DOCUMENT, tree.getEventType()); - ASSERT_NE(android::ResXMLTree::END_DOCUMENT, tree.getEventType()); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } ssize_t idx; idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id"); - ASSERT_GE(idx, 0); - EXPECT_EQ(idx, tree.indexOfID()); - EXPECT_EQ(ResourceId(0x010100d0), ResourceId(tree.getAttributeNameResID(idx))); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.indexOfID(), Eq(idx)); + EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u)); idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo"); - ASSERT_GE(idx, 0); - EXPECT_EQ(ResourceId(0x80010000), ResourceId(tree.getAttributeNameResID(idx))); - EXPECT_EQ(android::Res_value::TYPE_REFERENCE, tree.getAttributeDataType(idx)); - EXPECT_EQ(ResourceId(0x80020000), tree.getAttributeData(idx)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u)); + EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000))); } TEST_F(XmlFlattenerTest, ProcessEscapedStrings) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom( - R"EOF(<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); + R"(<element value="\?hello" pattern="\\d{5}" other=""">\\d{5}</element>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kValue = u"value"; const StringPiece16 kPattern = u"pattern"; + const StringPiece16 kOther = u"other"; size_t len; ssize_t idx; - const char16_t* str16; idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size()); - ASSERT_GE(idx, 0); - str16 = tree.getAttributeStringValue(idx, &len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"?hello"), StringPiece16(str16, len)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello")); idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size()); - ASSERT_GE(idx, 0); - str16 = tree.getAttributeStringValue(idx, &len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); - - ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); - str16 = tree.getText(&len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}")); + + idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size()); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\"")); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}")); } } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 53c66a628568..f0ac467bdd18 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -293,6 +293,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["instrumentation"]["meta-data"] = meta_data_action; manifest_action["original-package"]; + manifest_action["overlay"]; manifest_action["protected-broadcast"]; manifest_action["uses-permission"]; manifest_action["uses-permission-sdk-23"]; diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 05851485e216..01b2d143de50 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -145,6 +145,12 @@ void PrintTo(const Maybe<T>& value, std::ostream* out) { namespace test { +MATCHER_P(StrEq, a, + std::string(negation ? "isn't" : "is") + " equal to " + + ::testing::PrintToString(android::StringPiece16(a))) { + return android::StringPiece16(arg) == a; +} + MATCHER_P(ValueEq, a, std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { return arg.Equals(&a); diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index b43f8e87fd68..9a82418e0a5a 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -281,16 +281,12 @@ inline Maybe<T> make_nothing() { return Maybe<T>(); } -/** - * Define the == operator between Maybe<T> and Maybe<U> only if the operator T - * == U is defined. - * That way the compiler will show an error at the callsite when comparing two - * Maybe<> objects - * whose inner types can't be compared. - */ +// Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined. +// That way the compiler will show an error at the callsite when comparing two Maybe<> objects +// whose inner types can't be compared. template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, + const Maybe<U>& b) { if (a && b) { return a.value() == b.value(); } else if (!a && !b) { @@ -299,18 +295,22 @@ typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( return false; } -/** - * Same as operator== but negated. - */ template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, + const U& b) { + return a ? a.value() == b : false; +} + +// Same as operator== but negated. +template <typename T, typename U> +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(const Maybe<T>& a, + const Maybe<U>& b) { return !(a == b); } template <typename T, typename U> -typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(const Maybe<T>& a, + const Maybe<U>& b) { if (a && b) { return a.value() < b.value(); } else if (!a && !b) { diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index cf2232254ec4..aefc24b82691 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -312,6 +312,9 @@ static Maybe<std::string> ParseUnicodeCodepoint(const char** start, return result_utf8; } +StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) { +} + StringBuilder& StringBuilder::Append(const StringPiece& str) { if (!error_.empty()) { return *this; @@ -368,14 +371,12 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } last_char_was_escape_ = false; start = current + 1; - } else if (*current == '"') { + } else if (!preserve_spaces_ && *current == '"') { if (!quote_ && trailing_space_) { - // We found an opening quote, and we have - // trailing space, so we should append that + // We found an opening quote, and we have trailing space, so we should append that // space now. if (trailing_space_) { - // We had trailing whitespace, so - // replace with a single space. + // We had trailing whitespace, so replace with a single space. if (!str_.empty()) { str_ += ' '; } @@ -385,7 +386,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { quote_ = !quote_; str_.append(start, current - start); start = current + 1; - } else if (*current == '\'' && !quote_) { + } else if (!preserve_spaces_ && *current == '\'' && !quote_) { // This should be escaped. error_ = "unescaped apostrophe"; return *this; @@ -402,7 +403,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { str_.append(start, current - start); start = current + 1; last_char_was_escape_ = true; - } else if (!quote_) { + } else if (!preserve_spaces_ && !quote_) { // This is not quoted text, so look for whitespace. if (isspace(*current)) { // We found whitespace, see if we have seen some diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 386f74b0301a..b9ada7704a26 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -166,6 +166,8 @@ bool VerifyJavaStringFormat(const android::StringPiece& str); class StringBuilder { public: + explicit StringBuilder(bool preserve_spaces = false); + StringBuilder& Append(const android::StringPiece& str); const std::string& ToString() const; const std::string& Error() const; @@ -179,6 +181,7 @@ class StringBuilder { explicit operator bool() const; private: + bool preserve_spaces_; std::string str_; size_t utf16_len_ = 0; bool quote_ = false; diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index e49aee5d50ed..5cced3e9acab 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -20,16 +20,17 @@ #include "test/Test.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::SizeIs; namespace aapt { TEST(UtilTest, TrimOnlyWhitespace) { - const std::string full = "\n "; - - StringPiece trimmed = util::TrimWhitespace(full); + const StringPiece trimmed = util::TrimWhitespace("\n "); EXPECT_TRUE(trimmed.empty()); - EXPECT_EQ(0u, trimmed.size()); + EXPECT_THAT(trimmed, SizeIs(0u)); } TEST(UtilTest, StringEndsWith) { @@ -41,85 +42,74 @@ TEST(UtilTest, StringStartsWith) { } TEST(UtilTest, StringBuilderSplitEscapeSequence) { - EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder() - .Append("this is a new\\") - .Append("nline.") - .ToString()); + EXPECT_THAT(util::StringBuilder().Append("this is a new\\").Append("nline.").ToString(), + Eq("this is a new\nline.")); } TEST(UtilTest, StringBuilderWhitespaceRemoval) { - EXPECT_EQ(StringPiece("hey guys this is so cool"), - util::StringBuilder() - .Append(" hey guys ") - .Append(" this is so cool ") - .ToString()); - - EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"), - util::StringBuilder() - .Append(" \" wow, so many \t ") - .Append("spaces. \"what? ") - .ToString()); - - EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder() - .Append(" where \t ") - .Append(" \nis the " - " pie?") - .ToString()); + EXPECT_THAT(util::StringBuilder().Append(" hey guys ").Append(" this is so cool ").ToString(), + Eq("hey guys this is so cool")); + EXPECT_THAT( + util::StringBuilder().Append(" \" wow, so many \t ").Append("spaces. \"what? ").ToString(), + Eq(" wow, so many \t spaces. what?")); + EXPECT_THAT(util::StringBuilder().Append(" where \t ").Append(" \nis the pie?").ToString(), + Eq("where is the pie?")); } TEST(UtilTest, StringBuilderEscaping) { - EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"), - util::StringBuilder() - .Append(" hey guys\\n ") - .Append(" this \\t is so\\\\ cool ") - .ToString()); - - EXPECT_EQ(StringPiece("@?#\\\'"), - util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString()); + EXPECT_THAT(util::StringBuilder() + .Append(" hey guys\\n ") + .Append(" this \\t is so\\\\ cool ") + .ToString(), + Eq("hey guys\n this \t is so\\ cool")); + EXPECT_THAT(util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString(), Eq("@?#\\\'")); } TEST(UtilTest, StringBuilderMisplacedQuote) { - util::StringBuilder builder{}; + util::StringBuilder builder; EXPECT_FALSE(builder.Append("they're coming!")); } TEST(UtilTest, StringBuilderUnicodeCodes) { - EXPECT_EQ(std::string("\u00AF\u0AF0 woah"), - util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString()); - + EXPECT_THAT(util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString(), + Eq("\u00AF\u0AF0 woah")); EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo")); } +TEST(UtilTest, StringBuilderPreserveSpaces) { + EXPECT_THAT(util::StringBuilder(true /*preserve_spaces*/).Append("\"").ToString(), Eq("\"")); +} + TEST(UtilTest, TokenizeInput) { auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|'); auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece("this")); + ASSERT_THAT(*iter, Eq("this")); ++iter; - ASSERT_EQ(*iter, StringPiece(" is")); + ASSERT_THAT(*iter, Eq(" is")); ++iter; - ASSERT_EQ(*iter, StringPiece("the")); + ASSERT_THAT(*iter, Eq("the")); ++iter; - ASSERT_EQ(*iter, StringPiece("end")); + ASSERT_THAT(*iter, Eq("end")); ++iter; - ASSERT_EQ(tokenizer.end(), iter); + ASSERT_THAT(iter, Eq(tokenizer.end())); } TEST(UtilTest, TokenizeEmptyString) { auto tokenizer = util::Tokenize(StringPiece(""), '|'); auto iter = tokenizer.begin(); - ASSERT_NE(tokenizer.end(), iter); - ASSERT_EQ(StringPiece(), *iter); + ASSERT_THAT(iter, Ne(tokenizer.end())); + ASSERT_THAT(*iter, Eq(StringPiece())); ++iter; - ASSERT_EQ(tokenizer.end(), iter); + ASSERT_THAT(iter, Eq(tokenizer.end())); } TEST(UtilTest, TokenizeAtEnd) { auto tokenizer = util::Tokenize(StringPiece("one."), '.'); auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece("one")); + ASSERT_THAT(*iter, Eq("one")); ++iter; - ASSERT_NE(iter, tokenizer.end()); - ASSERT_EQ(*iter, StringPiece()); + ASSERT_THAT(iter, Ne(tokenizer.end())); + ASSERT_THAT(*iter, Eq(StringPiece())); } TEST(UtilTest, IsJavaClassName) { diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index fb18ea316ca6..031801e1dd2f 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -28,17 +28,16 @@ constexpr const char* kXmlPreamble = TEST(XmlDomTest, Inflate) { std::stringstream in(kXmlPreamble); - in << R"EOF( - <Layout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView android:id="@+id/id" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - </Layout> - )EOF"; - - const Source source = {"test.xml"}; + in << R"( + <Layout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView android:id="@+id/id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </Layout>)"; + + const Source source("test.xml"); StdErrDiagnostics diag; std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); ASSERT_NE(doc, nullptr); @@ -51,8 +50,8 @@ TEST(XmlDomTest, Inflate) { // Escaping is handled after parsing of the values for resource-specific values. TEST(XmlDomTest, ForwardEscapes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)"); xml::Element* el = xml::FindRootElement(doc->root.get()); ASSERT_NE(nullptr, el); @@ -65,10 +64,20 @@ TEST(XmlDomTest, ForwardEscapes) { ASSERT_NE(nullptr, attr); EXPECT_EQ("\\?hello", attr->value); - ASSERT_EQ(1u, el->children.size()); xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); ASSERT_NE(nullptr, text); EXPECT_EQ("\\\\d{5}", text->text); } +TEST(XmlDomTest, XmlEscapeSequencesAreParsed) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); + + xml::Element* el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + + xml::Attribute* attr = el->FindAttribute({}, "value"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("\"", attr->value); +} + } // namespace aapt diff --git a/tools/bit/Android.bp b/tools/bit/Android.bp new file mode 100644 index 000000000000..258e9b517f6d --- /dev/null +++ b/tools/bit/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2015 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. +// + +// ========================================================== +// Build the host executable: bit +// ========================================================== +cc_binary_host { + name: "bit", + + srcs: [ + "aapt.cpp", + "adb.cpp", + "command.cpp", + "main.cpp", + "make.cpp", + "print.cpp", + "util.cpp", + ], + + static_libs: [ + "libexpat", + "libinstrumentation", + "libjsoncpp", + ], + + shared_libs: ["libprotobuf-cpp-full"], +} diff --git a/tools/bit/Android.mk b/tools/bit/Android.mk deleted file mode 100644 index 57f46d490f24..000000000000 --- a/tools/bit/Android.mk +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (C) 2015 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. -# -LOCAL_PATH:= $(call my-dir) - -# ========================================================== -# Build the host executable: protoc-gen-javastream -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := bit - -LOCAL_MODULE_HOST_OS := linux darwin - -LOCAL_SRC_FILES := \ - aapt.cpp \ - adb.cpp \ - command.cpp \ - main.cpp \ - make.cpp \ - print.cpp \ - util.cpp - -LOCAL_STATIC_LIBRARIES := \ - libexpat \ - libinstrumentation \ - libjsoncpp - -LOCAL_SHARED_LIBRARIES := \ - libprotobuf-cpp-full - -include $(BUILD_HOST_EXECUTABLE) diff --git a/tools/bit/adb.h b/tools/bit/adb.h index dca80c853b45..f0774db933ba 100644 --- a/tools/bit/adb.h +++ b/tools/bit/adb.h @@ -17,7 +17,7 @@ #ifndef ADB_H #define ADB_H -#include "instrumentation_data.pb.h" +#include "proto/instrumentation_data.pb.h" #include <string> diff --git a/tools/incident_report/Android.bp b/tools/incident_report/Android.bp new file mode 100644 index 000000000000..6f21605a99a4 --- /dev/null +++ b/tools/incident_report/Android.bp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2015 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. +// + +// ========================================================== +// Build the host executable: incident_report +// ========================================================== +cc_binary_host { + name: "incident_report", + + srcs: [ + "generic_message.cpp", + "main.cpp", + "printer.cpp", + ], + + shared_libs: [ + "libplatformprotos", + "libprotobuf-cpp-full", + ], + + cflags: ["-Wno-unused-parameter"], + + // b/34740546, work around clang-tidy segmentation fault. + tidy_checks: ["-modernize*"], +} diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk deleted file mode 100644 index e57a95949e5a..000000000000 --- a/tools/incident_report/Android.mk +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright (C) 2015 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. -# -LOCAL_PATH:= $(call my-dir) - -# ========================================================== -# Build the host executable: protoc-gen-javastream -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := incident_report - -LOCAL_C_INCLUDES := \ - external/protobuf/src - -LOCAL_SRC_FILES := \ - generic_message.cpp \ - main.cpp \ - printer.cpp - -LOCAL_SHARED_LIBRARIES := \ - libplatformprotos \ - libprotobuf-cpp-full - -# b/34740546, work around clang-tidy segmentation fault. -LOCAL_TIDY_CHECKS := -modernize* - -LOCAL_C_FLAGS := \ - -Wno-unused-parameter -include $(BUILD_HOST_EXECUTABLE) - - diff --git a/tools/incident_section_gen/Android.bp b/tools/incident_section_gen/Android.bp new file mode 100644 index 000000000000..7f8151f196ae --- /dev/null +++ b/tools/incident_section_gen/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2015 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. +// + +// ========================================================== +// Build the host executable: incident-section-gen +// ========================================================== +cc_binary_host { + name: "incident-section-gen", + // b/34740546, work around clang-tidy segmentation fault. + tidy_checks: ["-modernize*"], + cflags: [ + "-g", + "-O0", + ], + srcs: ["main.cpp"], + shared_libs: [ + "libplatformprotos", + "libprotobuf-cpp-full", + ], +} diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk deleted file mode 100644 index 05490266576a..000000000000 --- a/tools/incident_section_gen/Android.mk +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright (C) 2015 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. -# -LOCAL_PATH:= $(call my-dir) - -# ========================================================== -# Build the host executable: protoc-gen-javastream -# ========================================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := incident-section-gen -# b/34740546, work around clang-tidy segmentation fault. -LOCAL_TIDY_CHECKS := -modernize* -LOCAL_CFLAGS += -g -O0 -LOCAL_C_INCLUDES := \ - external/protobuf/src -LOCAL_SRC_FILES := \ - main.cpp -LOCAL_LDFLAGS := -ldl -LOCAL_SHARED_LIBRARIES := \ - libplatformprotos \ - libprotobuf-cpp-full - -include $(BUILD_HOST_EXECUTABLE) - diff --git a/tools/obbtool/Android.bp b/tools/obbtool/Android.bp new file mode 100644 index 000000000000..f87965860ce1 --- /dev/null +++ b/tools/obbtool/Android.bp @@ -0,0 +1,51 @@ +// +// Copyright 2010 The Android Open Source Project +// +// Opaque Binary Blob (OBB) Tool +// + +cc_binary_host { + name: "obbtool", + + srcs: ["Main.cpp"], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-mismatched-tags", + ], + + static_libs: [ + "libandroidfw", + "libutils", + "libcutils", + "liblog", + ], + + // This tool is prebuilt if we're doing an app-only build. + product_variables: { + unbundled_build: { + enabled: false, + }, + }, +} + +//#################################################### +cc_binary_host { + name: "pbkdf2gen", + + cflags: [ + "-Wall", + "-Werror", + "-Wno-mismatched-tags", + ], + srcs: ["pbkdf2gen.cpp"], + static_libs: ["libcrypto"], + + // This tool is prebuilt if we're doing an app-only build. + product_variables: { + unbundled_build: { + enabled: false, + }, + }, +} diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk deleted file mode 100644 index 6dc306e85bc6..000000000000 --- a/tools/obbtool/Android.mk +++ /dev/null @@ -1,47 +0,0 @@ -# -# Copyright 2010 The Android Open Source Project -# -# Opaque Binary Blob (OBB) Tool -# - -# This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS),) - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - Main.cpp - -LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags - -#LOCAL_C_INCLUDES += - -LOCAL_STATIC_LIBRARIES := \ - libandroidfw \ - libutils \ - libcutils \ - liblog - -ifeq ($(HOST_OS),linux) -LOCAL_LDLIBS += -ldl -lpthread -endif - -LOCAL_MODULE := obbtool - -include $(BUILD_HOST_EXECUTABLE) - -##################################################### -include $(CLEAR_VARS) - -LOCAL_MODULE := pbkdf2gen -LOCAL_MODULE_TAGS := optional -LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags -LOCAL_SRC_FILES := pbkdf2gen.cpp -LOCAL_LDLIBS += -ldl -LOCAL_STATIC_LIBRARIES := libcrypto - -include $(BUILD_HOST_EXECUTABLE) - -####################################################### -endif # TARGET_BUILD_APPS diff --git a/tools/split-select/Android.bp b/tools/split-select/Android.bp new file mode 100644 index 000000000000..ee822b7d7fa7 --- /dev/null +++ b/tools/split-select/Android.bp @@ -0,0 +1,108 @@ +// +// Copyright (C) 2014 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. +// + +// ========================================================== +// Setup some common variables for the different build +// targets here. +// ========================================================== + +cc_defaults { + name: "split-select_defaults", + + cflags: [ + "-Wall", + "-Werror", + ], + include_dirs: ["frameworks/base/tools"], + static_libs: [ + "libaapt", + "libandroidfw", + "libpng", + "libutils", + "liblog", + "libcutils", + "libexpat", + "libziparchive", + "libbase", + "libz", + ], + group_static_libs: true, + + target: { + windows: { + enabled: true, + }, + }, + + // This tool is prebuilt if we're doing an app-only build. + product_variables: { + pdk: { + enabled: false, + }, + unbundled_build: { + enabled: false, + }, + }, +} + +// ========================================================== +// Build the host static library: libsplit-select +// ========================================================== +cc_library_host_static { + name: "libsplit-select", + defaults: ["split-select_defaults"], + + srcs: [ + "Abi.cpp", + "Grouper.cpp", + "Rule.cpp", + "RuleGenerator.cpp", + "SplitDescription.cpp", + "SplitSelector.cpp", + ], + cflags: ["-D_DARWIN_UNLIMITED_STREAMS"], + +} + +// ========================================================== +// Build the host tests: libsplit-select_tests +// ========================================================== +cc_test_host { + name: "libsplit-select_tests", + defaults: ["split-select_defaults"], + + srcs: [ + "Grouper_test.cpp", + "Rule_test.cpp", + "RuleGenerator_test.cpp", + "SplitSelector_test.cpp", + "TestRules.cpp", + ], + + static_libs: ["libsplit-select"], + +} + +// ========================================================== +// Build the host executable: split-select +// ========================================================== +cc_binary_host { + name: "split-select", + defaults: ["split-select_defaults"], + srcs: ["Main.cpp"], + + static_libs: ["libsplit-select"], +} diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk deleted file mode 100644 index 4a1511eae43a..000000000000 --- a/tools/split-select/Android.mk +++ /dev/null @@ -1,119 +0,0 @@ -# -# Copyright (C) 2014 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) - -# ========================================================== -# Setup some common variables for the different build -# targets here. -# ========================================================== -LOCAL_PATH:= $(call my-dir) - -main := Main.cpp -sources := \ - Abi.cpp \ - Grouper.cpp \ - Rule.cpp \ - RuleGenerator.cpp \ - SplitDescription.cpp \ - SplitSelector.cpp - -testSources := \ - Grouper_test.cpp \ - Rule_test.cpp \ - RuleGenerator_test.cpp \ - SplitSelector_test.cpp \ - TestRules.cpp - -cIncludes := \ - external/zlib \ - frameworks/base/tools - -hostStaticLibs := \ - libaapt \ - libandroidfw \ - libpng \ - libutils \ - liblog \ - libcutils \ - libexpat \ - libziparchive \ - libbase - -cFlags := -Wall -Werror - -hostLdLibs_linux := -lrt -ldl -lpthread - -# Statically link libz for MinGW (Win SDK under Linux), -# and dynamically link for all others. -hostStaticLibs_windows := libz -hostLdLibs_darwin := -lz -hostLdLibs_linux += -lz - - -# ========================================================== -# Build the host static library: libsplit-select -# ========================================================== -include $(CLEAR_VARS) -LOCAL_MODULE := libsplit-select -LOCAL_MODULE_HOST_OS := darwin linux windows - -LOCAL_SRC_FILES := $(sources) -LOCAL_STATIC_LIBRARIES := $(hostStaticLibs) -LOCAL_C_INCLUDES := $(cIncludes) -LOCAL_CFLAGS := $(cFlags) -D_DARWIN_UNLIMITED_STREAMS - -include $(BUILD_HOST_STATIC_LIBRARY) - - -# ========================================================== -# Build the host tests: libsplit-select_tests -# ========================================================== -include $(CLEAR_VARS) -LOCAL_MODULE := libsplit-select_tests -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(testSources) - -LOCAL_C_INCLUDES := $(cIncludes) -LOCAL_STATIC_LIBRARIES := libsplit-select $(hostStaticLibs) -LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) -LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin) -LOCAL_LDLIBS_linux := $(hostLdLibs_linux) -LOCAL_CFLAGS := $(cFlags) - -include $(BUILD_HOST_NATIVE_TEST) - -# ========================================================== -# Build the host executable: split-select -# ========================================================== -include $(CLEAR_VARS) -LOCAL_MODULE := split-select -LOCAL_MODULE_HOST_OS := darwin linux windows - -LOCAL_SRC_FILES := $(main) - -LOCAL_C_INCLUDES := $(cIncludes) -LOCAL_STATIC_LIBRARIES := libsplit-select $(hostStaticLibs) -LOCAL_STATIC_LIBRARIES_windows := $(hostStaticLibs_windows) -LOCAL_LDLIBS_darwin := $(hostLdLibs_darwin) -LOCAL_LDLIBS_linux := $(hostLdLibs_linux) -LOCAL_CFLAGS := $(cFlags) - -include $(BUILD_HOST_EXECUTABLE) - -endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp new file mode 100644 index 000000000000..24068e9ffe92 --- /dev/null +++ b/tools/streaming_proto/Android.bp @@ -0,0 +1,29 @@ +// +// Copyright (C) 2015 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. +// + +// ========================================================== +// Build the host executable: protoc-gen-javastream +// ========================================================== +cc_binary_host { + name: "protoc-gen-javastream", + srcs: [ + "Errors.cpp", + "string_utils.cpp", + "main.cpp", + ], + + shared_libs: ["libprotoc"], +} diff --git a/tools/streaming_proto/Android.mk b/tools/streaming_proto/Android.mk index 5a54fd10415d..ebb77a197883 100644 --- a/tools/streaming_proto/Android.mk +++ b/tools/streaming_proto/Android.mk @@ -16,19 +16,6 @@ LOCAL_PATH:= $(call my-dir) # ========================================================== -# Build the host executable: protoc-gen-javastream -# ========================================================== -include $(CLEAR_VARS) -LOCAL_MODULE := protoc-gen-javastream -LOCAL_SRC_FILES := \ - Errors.cpp \ - string_utils.cpp \ - main.cpp -LOCAL_SHARED_LIBRARIES := \ - libprotoc -include $(BUILD_HOST_EXECUTABLE) - -# ========================================================== # Build the java test # ========================================================== include $(CLEAR_VARS) diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp new file mode 100644 index 000000000000..6fb278c83f0a --- /dev/null +++ b/tools/validatekeymaps/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright 2010 The Android Open Source Project +// +// Keymap validation tool. +// + +cc_binary_host { + name: "validatekeymaps", + + srcs: ["Main.cpp"], + + cflags: [ + "-Wall", + "-Werror", + ], + + static_libs: [ + "libinput", + "libutils", + "libcutils", + "liblog", + ], + + // This tool is prebuilt if we're doing an app-only build. + product_variables: { + unbundled_build: { + enabled: false, + }, + }, +} diff --git a/tools/validatekeymaps/Android.mk b/tools/validatekeymaps/Android.mk deleted file mode 100644 index 9af721de97db..000000000000 --- a/tools/validatekeymaps/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright 2010 The Android Open Source Project -# -# Keymap validation tool. -# - -# This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS),) - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - Main.cpp - -LOCAL_CFLAGS := -Wall -Werror - -LOCAL_STATIC_LIBRARIES := \ - libinput \ - libutils \ - libcutils \ - liblog - -ifeq ($(HOST_OS),linux) -LOCAL_LDLIBS += -ldl -lpthread -endif - -LOCAL_MODULE := validatekeymaps -LOCAL_MODULE_TAGS := optional - -include $(BUILD_HOST_EXECUTABLE) - -endif # TARGET_BUILD_APPS |