Merge "Fix a NPE with outlineProvider="none""
diff --git a/Android.bp b/Android.bp
index dba49ce..9ba4004 100644
--- a/Android.bp
+++ b/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
subdirs = [
+ "core/jni",
"libs/*",
"native/android",
"native/graphics/jni",
diff --git a/Android.mk b/Android.mk
index 623ceba..02caa0a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -103,6 +103,8 @@
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
+ core/java/android/app/timezone/ICallback.aidl \
+ core/java/android/app/timezone/IRulesManager.aidl \
core/java/android/app/usage/IUsageStatsManager.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
core/java/android/bluetooth/IBluetoothA2dp.aidl \
@@ -432,10 +434,10 @@
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/IMbmsDownloadManagerListener.aidl \
- telephony/java/android/telephony/mbms/IMbmsStreamingManagerListener.aidl \
- telephony/java/android/telephony/mbms/IDownloadListener.aidl \
- telephony/java/android/telephony/mbms/IStreamingServiceListener.aidl \
+ telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl \
+ telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl \
+ telephony/java/android/telephony/mbms/IDownloadCallback.aidl \
+ telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl \
telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl \
telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl \
telephony/java/com/android/ims/internal/IImsCallSession.aidl \
diff --git a/api/current.txt b/api/current.txt
index 8894f97..3ba7f8f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7503,7 +7503,7 @@
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
- method public void setPeriodicAdvertisingEnable(boolean);
+ method public void setPeriodicAdvertisingEnabled(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7673,8 +7673,10 @@
field public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
field public static final int DATA_COMPLETE = 0; // 0x0
field public static final int DATA_TRUNCATED = 2; // 0x2
+ field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
field public static final int PHY_UNUSED = 0; // 0x0
field public static final int SID_NOT_PRESENT = 255; // 0xff
+ field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
}
public final class ScanSettings implements android.os.Parcelable {
@@ -8320,7 +8322,6 @@
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
- field public static final java.lang.String IPSEC_SERVICE = "ipsec";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -23872,66 +23873,6 @@
field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
}
- public final class IpSecAlgorithm implements android.os.Parcelable {
- ctor public IpSecAlgorithm(java.lang.String, byte[]);
- ctor public IpSecAlgorithm(java.lang.String, byte[], int);
- method public int describeContents();
- method public byte[] getKey();
- method public java.lang.String getName();
- method public int getTruncationLengthBits();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
- field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
- field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
- field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
- field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
- field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
- field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
- }
-
- public final class IpSecManager {
- method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
- method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
- }
-
- public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
- }
-
- public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
- method public void close();
- method public int getSpi();
- }
-
- public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
- method public int getSpi();
- }
-
- public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close() throws java.io.IOException;
- method public int getPort();
- method public java.io.FileDescriptor getSocket();
- }
-
- public final class IpSecTransform implements java.lang.AutoCloseable {
- method public void close();
- field public static final int DIRECTION_IN = 0; // 0x0
- field public static final int DIRECTION_OUT = 1; // 0x1
- }
-
- public static class IpSecTransform.Builder {
- ctor public IpSecTransform.Builder(android.content.Context);
- method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
- method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
- method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
- }
-
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -33842,6 +33783,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -33849,6 +33792,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -33856,6 +33800,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -37589,7 +37534,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
@@ -37673,9 +37619,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -38175,7 +38125,6 @@
method public android.os.PersistableBundle getCarrierConfig();
method public deprecated android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
- method public boolean getDataEnabled();
method public int getDataNetworkType();
method public int getDataState();
method public deprecated java.lang.String getDeviceId();
@@ -38207,6 +38156,7 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getVisualVoicemailPackageName();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -38219,6 +38169,8 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String, int);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
+ method public boolean isDataEnabled();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -38229,7 +38181,6 @@
method public void listen(android.telephony.PhoneStateListener, int);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -38340,9 +38291,6 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSmsFilterSettings> CREATOR;
- field public static final java.lang.String DEFAULT_CLIENT_PREFIX = "//VVM";
- field public static final int DEFAULT_DESTINATION_PORT = -1; // 0xffffffff
- field public static final java.util.List<java.lang.String> DEFAULT_ORIGINATING_NUMBERS;
field public static final int DESTINATION_PORT_ANY = -1; // 0xffffffff
field public static final int DESTINATION_PORT_DATA_SMS = -2; // 0xfffffffe
field public final java.lang.String clientPrefix;
@@ -49761,15 +49709,15 @@
ctor public DexClassLoader(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader);
}
- public final class DexFile {
- ctor public DexFile(java.io.File) throws java.io.IOException;
- ctor public DexFile(java.lang.String) throws java.io.IOException;
+ public final deprecated class DexFile {
+ ctor public deprecated DexFile(java.io.File) throws java.io.IOException;
+ ctor public deprecated DexFile(java.lang.String) throws java.io.IOException;
method public void close() throws java.io.IOException;
method public java.util.Enumeration<java.lang.String> entries();
method public java.lang.String getName();
method public static boolean isDexOptNeeded(java.lang.String) throws java.io.FileNotFoundException, java.io.IOException;
method public java.lang.Class loadClass(java.lang.String, java.lang.ClassLoader);
- method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
+ method public static deprecated dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
}
public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
diff --git a/api/system-current.txt b/api/system-current.txt
index 3a3ba96..8623df7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7808,7 +7808,7 @@
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
- method public void setPeriodicAdvertisingEnable(boolean);
+ method public void setPeriodicAdvertisingEnabled(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7991,8 +7991,10 @@
field public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
field public static final int DATA_COMPLETE = 0; // 0x0
field public static final int DATA_TRUNCATED = 2; // 0x2
+ field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
field public static final int PHY_UNUSED = 0; // 0x0
field public static final int SID_NOT_PRESENT = 255; // 0xff
+ field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
}
public final class ScanSettings implements android.os.Parcelable {
@@ -8655,7 +8657,6 @@
field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
- field public static final java.lang.String IPSEC_SERVICE = "ipsec";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -25710,68 +25711,6 @@
field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
}
- public final class IpSecAlgorithm implements android.os.Parcelable {
- ctor public IpSecAlgorithm(java.lang.String, byte[]);
- ctor public IpSecAlgorithm(java.lang.String, byte[], int);
- method public int describeContents();
- method public byte[] getKey();
- method public java.lang.String getName();
- method public int getTruncationLengthBits();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
- field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
- field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
- field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
- field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
- field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
- field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
- }
-
- public final class IpSecManager {
- method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
- method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
- }
-
- public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
- }
-
- public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
- method public void close();
- method public int getSpi();
- }
-
- public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
- method public int getSpi();
- }
-
- public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close() throws java.io.IOException;
- method public int getPort();
- method public java.io.FileDescriptor getSocket();
- }
-
- public final class IpSecTransform implements java.lang.AutoCloseable {
- method public void close();
- field public static final int DIRECTION_IN = 0; // 0x0
- field public static final int DIRECTION_OUT = 1; // 0x1
- }
-
- public static class IpSecTransform.Builder {
- ctor public IpSecTransform.Builder(android.content.Context);
- method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
- method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
- method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setNattKeepalive(int);
- method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
- method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network);
- }
-
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -36686,6 +36625,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -36693,6 +36634,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -36700,6 +36642,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -40762,7 +40705,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
@@ -40846,9 +40790,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -41388,8 +41336,8 @@
method public int getCurrentPhoneType();
method public int getCurrentPhoneType(int);
method public int getDataActivity();
- method public boolean getDataEnabled();
- method public boolean getDataEnabled(int);
+ method public deprecated boolean getDataEnabled();
+ method public deprecated boolean getDataEnabled(int);
method public int getDataNetworkType();
method public int getDataState();
method public deprecated java.lang.String getDeviceId();
@@ -41422,6 +41370,7 @@
method public int getSimState(int);
method public java.lang.String getSubscriberId();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
+ method public java.lang.String getVisualVoicemailPackageName();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -41436,7 +41385,9 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String, int);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
method public boolean isDataConnectivityPossible();
+ method public boolean isDataEnabled();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isIdle();
method public boolean isNetworkRoaming();
@@ -41454,7 +41405,6 @@
method public boolean needsOtaServiceProvisioning();
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method public void setDataEnabled(boolean);
method public void setDataEnabled(int, boolean);
@@ -41586,9 +41536,6 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSmsFilterSettings> CREATOR;
- field public static final java.lang.String DEFAULT_CLIENT_PREFIX = "//VVM";
- field public static final int DEFAULT_DESTINATION_PORT = -1; // 0xffffffff
- field public static final java.util.List<java.lang.String> DEFAULT_ORIGINATING_NUMBERS;
field public static final int DESTINATION_PORT_ANY = -1; // 0xffffffff
field public static final int DESTINATION_PORT_DATA_SMS = -2; // 0xfffffffe
field public final java.lang.String clientPrefix;
@@ -53385,15 +53332,15 @@
ctor public DexClassLoader(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader);
}
- public final class DexFile {
- ctor public DexFile(java.io.File) throws java.io.IOException;
- ctor public DexFile(java.lang.String) throws java.io.IOException;
+ public final deprecated class DexFile {
+ ctor public deprecated DexFile(java.io.File) throws java.io.IOException;
+ ctor public deprecated DexFile(java.lang.String) throws java.io.IOException;
method public void close() throws java.io.IOException;
method public java.util.Enumeration<java.lang.String> entries();
method public java.lang.String getName();
method public static boolean isDexOptNeeded(java.lang.String) throws java.io.FileNotFoundException, java.io.IOException;
method public java.lang.Class loadClass(java.lang.String, java.lang.ClassLoader);
- method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
+ method public static deprecated dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
}
public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
diff --git a/api/test-current.txt b/api/test-current.txt
index 88b3c2f..2fc87ada 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -7512,7 +7512,7 @@
method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
- method public void setPeriodicAdvertisingEnable(boolean);
+ method public void setPeriodicAdvertisingEnabled(boolean);
method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
@@ -7682,8 +7682,10 @@
field public static final android.os.Parcelable.Creator<android.bluetooth.le.ScanResult> CREATOR;
field public static final int DATA_COMPLETE = 0; // 0x0
field public static final int DATA_TRUNCATED = 2; // 0x2
+ field public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0; // 0x0
field public static final int PHY_UNUSED = 0; // 0x0
field public static final int SID_NOT_PRESENT = 255; // 0xff
+ field public static final int TX_POWER_NOT_PRESENT = 127; // 0x7f
}
public final class ScanSettings implements android.os.Parcelable {
@@ -8331,7 +8333,6 @@
field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
field public static final java.lang.String INPUT_SERVICE = "input";
- field public static final java.lang.String IPSEC_SERVICE = "ipsec";
field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -23946,66 +23947,6 @@
field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
}
- public final class IpSecAlgorithm implements android.os.Parcelable {
- ctor public IpSecAlgorithm(java.lang.String, byte[]);
- ctor public IpSecAlgorithm(java.lang.String, byte[], int);
- method public int describeContents();
- method public byte[] getKey();
- method public java.lang.String getName();
- method public int getTruncationLengthBits();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
- field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
- field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
- field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
- field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
- field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
- field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
- }
-
- public final class IpSecManager {
- method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
- method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
- method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
- method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
- }
-
- public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
- }
-
- public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
- method public void close();
- method public int getSpi();
- }
-
- public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
- method public int getSpi();
- }
-
- public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
- method public void close() throws java.io.IOException;
- method public int getPort();
- method public java.io.FileDescriptor getSocket();
- }
-
- public final class IpSecTransform implements java.lang.AutoCloseable {
- method public void close();
- field public static final int DIRECTION_IN = 0; // 0x0
- field public static final int DIRECTION_OUT = 1; // 0x1
- }
-
- public static class IpSecTransform.Builder {
- ctor public IpSecTransform.Builder(android.content.Context);
- method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
- method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
- method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
- method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
- }
-
public class LinkAddress implements android.os.Parcelable {
method public int describeContents();
method public java.net.InetAddress getAddress();
@@ -33924,6 +33865,8 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
+ field public static final java.lang.String ARCHIVED = "archived";
+ field public static final java.lang.String BACKED_UP = "backed_up";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -33931,6 +33874,7 @@
field public static final java.lang.String DIR_TYPE = "vnd.android.cursor.dir/voicemails";
field public static final java.lang.String DURATION = "duration";
field public static final java.lang.String HAS_CONTENT = "has_content";
+ field public static final java.lang.String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
field public static final java.lang.String IS_READ = "is_read";
field public static final java.lang.String ITEM_TYPE = "vnd.android.cursor.item/voicemail";
field public static final java.lang.String LAST_MODIFIED = "last_modified";
@@ -33938,6 +33882,7 @@
field public static final java.lang.String NUMBER = "number";
field public static final java.lang.String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
field public static final java.lang.String PHONE_ACCOUNT_ID = "subscription_id";
+ field public static final java.lang.String RESTORED = "restored";
field public static final java.lang.String SOURCE_DATA = "source_data";
field public static final java.lang.String SOURCE_PACKAGE = "source_package";
field public static final java.lang.String TRANSCRIPTION = "transcription";
@@ -37688,7 +37633,8 @@
field public static final java.lang.String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final java.lang.String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
field public static final java.lang.String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool";
- field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final deprecated java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
+ field public static final java.lang.String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array";
field public static final java.lang.String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool";
field public static final java.lang.String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool";
field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
@@ -37772,9 +37718,13 @@
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
field public static final java.lang.String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = "vvm_cellular_data_required_bool";
+ field public static final java.lang.String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
field public static final java.lang.String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string";
+ field public static final java.lang.String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array";
+ field public static final java.lang.String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
field public static final java.lang.String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int";
field public static final java.lang.String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
+ field public static final java.lang.String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final java.lang.String KEY_VVM_TYPE_STRING = "vvm_type_string";
field public static final java.lang.String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -38274,7 +38224,6 @@
method public android.os.PersistableBundle getCarrierConfig();
method public deprecated android.telephony.CellLocation getCellLocation();
method public int getDataActivity();
- method public boolean getDataEnabled();
method public int getDataNetworkType();
method public int getDataState();
method public deprecated java.lang.String getDeviceId();
@@ -38306,6 +38255,7 @@
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getVisualVoicemailPackageName();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
method public int getVoiceNetworkType();
@@ -38318,6 +38268,8 @@
method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(java.lang.String, int);
method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String);
method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String);
+ method public boolean isConcurrentVoiceAndDataAllowed();
+ method public boolean isDataEnabled();
method public boolean isHearingAidCompatibilitySupported();
method public boolean isNetworkRoaming();
method public boolean isSmsCapable();
@@ -38328,7 +38280,6 @@
method public void listen(android.telephony.PhoneStateListener, int);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
- method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
method public void setDataEnabled(boolean);
method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
method public boolean setOperatorBrandOverride(java.lang.String);
@@ -38439,9 +38390,6 @@
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.VisualVoicemailSmsFilterSettings> CREATOR;
- field public static final java.lang.String DEFAULT_CLIENT_PREFIX = "//VVM";
- field public static final int DEFAULT_DESTINATION_PORT = -1; // 0xffffffff
- field public static final java.util.List<java.lang.String> DEFAULT_ORIGINATING_NUMBERS;
field public static final int DESTINATION_PORT_ANY = -1; // 0xffffffff
field public static final int DESTINATION_PORT_DATA_SMS = -2; // 0xfffffffe
field public final java.lang.String clientPrefix;
@@ -49870,15 +49818,15 @@
ctor public DexClassLoader(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader);
}
- public final class DexFile {
- ctor public DexFile(java.io.File) throws java.io.IOException;
- ctor public DexFile(java.lang.String) throws java.io.IOException;
+ public final deprecated class DexFile {
+ ctor public deprecated DexFile(java.io.File) throws java.io.IOException;
+ ctor public deprecated DexFile(java.lang.String) throws java.io.IOException;
method public void close() throws java.io.IOException;
method public java.util.Enumeration<java.lang.String> entries();
method public java.lang.String getName();
method public static boolean isDexOptNeeded(java.lang.String) throws java.io.FileNotFoundException, java.io.IOException;
method public java.lang.Class loadClass(java.lang.String, java.lang.ClassLoader);
- method public static dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
+ method public static deprecated dalvik.system.DexFile loadDex(java.lang.String, java.lang.String, int) throws java.io.IOException;
}
public final class InMemoryDexClassLoader extends dalvik.system.BaseDexClassLoader {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ce019cac..1e5ea26 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -26,6 +26,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.job.IJobScheduler;
import android.app.job.JobScheduler;
+import android.app.timezone.RulesManager;
import android.app.trust.TrustManager;
import android.app.usage.IUsageStatsManager;
import android.app.usage.NetworkStatsManager;
@@ -786,6 +787,13 @@
return new ContextHubManager(ctx.getOuterContext(),
ctx.mMainThread.getHandler().getLooper());
}});
+
+ registerService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, RulesManager.class,
+ new CachedServiceFetcher<RulesManager>() {
+ @Override
+ public RulesManager createService(ContextImpl ctx) {
+ return new RulesManager(ctx.getOuterContext());
+ }});
}
/**
diff --git a/core/java/android/app/timezone/Callback.java b/core/java/android/app/timezone/Callback.java
new file mode 100644
index 0000000..b51e5ba
--- /dev/null
+++ b/core/java/android/app/timezone/Callback.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 android.app.timezone;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback interface for receiving information about an async time zone operation.
+ * The methods will be called on your application's main thread.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public abstract class Callback {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_INSTALL_BAD_DISTRO_STRUCTURE,
+ ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION, ERROR_INSTALL_RULES_TOO_OLD,
+ ERROR_INSTALL_VALIDATION_ERROR})
+ public @interface AsyncResultCode {}
+
+ /**
+ * Indicates that an operation succeeded.
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Indicates an install / uninstall did not fully succeed for an unknown reason.
+ */
+ public static final int ERROR_UNKNOWN_FAILURE = 1;
+
+ /**
+ * Indicates an install failed because of a structural issue with the provided distro,
+ * e.g. it wasn't in the right format or the contents were structured incorrectly.
+ */
+ public static final int ERROR_INSTALL_BAD_DISTRO_STRUCTURE = 2;
+
+ /**
+ * Indicates an install failed because of a versioning issue with the provided distro,
+ * e.g. it was created for a different version of Android.
+ */
+ public static final int ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION = 3;
+
+ /**
+ * Indicates an install failed because the rules provided are too old for the device,
+ * e.g. the Android device shipped with a newer rules version.
+ */
+ public static final int ERROR_INSTALL_RULES_TOO_OLD = 4;
+
+ /**
+ * Indicates an install failed because the distro contents failed validation.
+ */
+ public static final int ERROR_INSTALL_VALIDATION_ERROR = 5;
+
+ /**
+ * Reports the result of an async time zone operation.
+ */
+ public abstract void onFinished(@AsyncResultCode int status);
+}
diff --git a/core/java/android/app/timezone/DistroFormatVersion.java b/core/java/android/app/timezone/DistroFormatVersion.java
new file mode 100644
index 0000000..e879e8f
--- /dev/null
+++ b/core/java/android/app/timezone/DistroFormatVersion.java
@@ -0,0 +1,120 @@
+/*
+ * 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.app.timezone;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Versioning information about a distro's format or a device's supported format.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ * <dt>majorVersion</dt>
+ * <dd>the major distro format version. Major versions differences are not compatible - e.g.
+ * 2 is not compatible with 1 or 3.</dd>
+ * <dt>minorVersion</dt>
+ * <dd>the minor distro format version. Minor versions should be backwards compatible iff the
+ * major versions match exactly, i.e. version 2.2 will be compatible with 2.1 devices but not
+ * 2.3 devices.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class DistroFormatVersion implements Parcelable {
+
+ private final int mMajorVersion;
+ private final int mMinorVersion;
+
+ public DistroFormatVersion(int majorVersion, int minorVersion) {
+ mMajorVersion = Utils.validateVersion("major", majorVersion);
+ mMinorVersion = Utils.validateVersion("minor", minorVersion);
+ }
+
+ public static final Creator<DistroFormatVersion> CREATOR = new Creator<DistroFormatVersion>() {
+ public DistroFormatVersion createFromParcel(Parcel in) {
+ int majorVersion = in.readInt();
+ int minorVersion = in.readInt();
+ return new DistroFormatVersion(majorVersion, minorVersion);
+ }
+
+ public DistroFormatVersion[] newArray(int size) {
+ return new DistroFormatVersion[size];
+ }
+ };
+
+ public int getMajorVersion() {
+ return mMajorVersion;
+ }
+
+ public int getMinorVersion() {
+ return mMinorVersion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mMajorVersion);
+ out.writeInt(mMinorVersion);
+ }
+
+ /**
+ * If this object describes a device's supported version and the parameter describes a distro's
+ * version, this method returns whether the device would accept the distro.
+ */
+ public boolean supports(DistroFormatVersion distroFormatVersion) {
+ return mMajorVersion == distroFormatVersion.mMajorVersion
+ && mMinorVersion <= distroFormatVersion.mMinorVersion;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DistroFormatVersion that = (DistroFormatVersion) o;
+
+ if (mMajorVersion != that.mMajorVersion) {
+ return false;
+ }
+ return mMinorVersion == that.mMinorVersion;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mMajorVersion;
+ result = 31 * result + mMinorVersion;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "DistroFormatVersion{"
+ + "mMajorVersion=" + mMajorVersion
+ + ", mMinorVersion=" + mMinorVersion
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timezone/DistroRulesVersion.java b/core/java/android/app/timezone/DistroRulesVersion.java
new file mode 100644
index 0000000..5503ce1
--- /dev/null
+++ b/core/java/android/app/timezone/DistroRulesVersion.java
@@ -0,0 +1,128 @@
+/*
+ * 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.app.timezone;
+
+import static android.app.timezone.Utils.validateRulesVersion;
+import static android.app.timezone.Utils.validateVersion;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Versioning information about a set of time zone rules.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ * <dt>rulesVersion</dt>
+ * <dd>the IANA rules version. e.g. "2017a"</dd>
+ * <dt>revision</dt>
+ * <dd>the revision for the rules. Allows there to be several revisions for a given IANA rules
+ * release. Numerically higher is newer.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class DistroRulesVersion implements Parcelable {
+
+ private final String mRulesVersion;
+ private final int mRevision;
+
+ public DistroRulesVersion(String rulesVersion, int revision) {
+ mRulesVersion = validateRulesVersion("rulesVersion", rulesVersion);
+ mRevision = validateVersion("revision", revision);
+ }
+
+ public static final Creator<DistroRulesVersion> CREATOR = new Creator<DistroRulesVersion>() {
+ public DistroRulesVersion createFromParcel(Parcel in) {
+ String rulesVersion = in.readString();
+ int revision = in.readInt();
+ return new DistroRulesVersion(rulesVersion, revision);
+ }
+
+ public DistroRulesVersion[] newArray(int size) {
+ return new DistroRulesVersion[size];
+ }
+ };
+
+ public String getRulesVersion() {
+ return mRulesVersion;
+ }
+
+ public int getRevision() {
+ return mRevision;
+ }
+
+ /**
+ * Returns true if this DistroRulesVersion is older than the one supplied. It returns false if
+ * it is the same or newer. This method compares the {@code rulesVersion} and the
+ * {@code revision}.
+ */
+ public boolean isOlderThan(DistroRulesVersion distroRulesVersion) {
+ int rulesComparison = mRulesVersion.compareTo(distroRulesVersion.mRulesVersion);
+ if (rulesComparison < 0) {
+ return true;
+ }
+ if (rulesComparison > 0) {
+ return false;
+ }
+ return mRevision < distroRulesVersion.mRevision;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mRulesVersion);
+ out.writeInt(mRevision);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DistroRulesVersion that = (DistroRulesVersion) o;
+
+ if (mRevision != that.mRevision) {
+ return false;
+ }
+ return mRulesVersion.equals(that.mRulesVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mRulesVersion.hashCode();
+ result = 31 * result + mRevision;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "DistroRulesVersion{"
+ + "mRulesVersion='" + mRulesVersion + '\''
+ + ", mRevision='" + mRevision + '\''
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timezone/ICallback.aidl b/core/java/android/app/timezone/ICallback.aidl
new file mode 100644
index 0000000..519ef1a
--- /dev/null
+++ b/core/java/android/app/timezone/ICallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.app.timezone;
+
+/**
+ * Callback interface for a timezone updater to receive information about the success or failure of
+ * an installation/uninstallation attempt.
+ *
+ * {@hide}
+ */
+oneway interface ICallback {
+ void onFinished(int error);
+}
\ No newline at end of file
diff --git a/core/java/android/app/timezone/IRulesManager.aidl b/core/java/android/app/timezone/IRulesManager.aidl
new file mode 100644
index 0000000..40f3fd2
--- /dev/null
+++ b/core/java/android/app/timezone/IRulesManager.aidl
@@ -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 android.app.timezone;
+
+import android.app.timezone.ICallback;
+import android.app.timezone.RulesState;
+import android.os.ParcelFileDescriptor;
+
+ /**
+ * Interface to the TimeZone Rules Manager Service.
+ *
+ * <p>This interface is only intended for system apps to call. They should use the
+ * {@link android.app.timezone.RulesManager} class rather than going through this
+ * Binder interface directly. See {@link android.app.timezone.RulesManager} for more complete
+ * documentation.
+ *
+ * {@hide}
+ */
+interface IRulesManager {
+
+ /**
+ * Returns information about the current time zone rules state such as the IANA version of
+ * the system and any currently installed distro. This method is intended to allow clients to
+ * determine if the current state can be improved; for example by passing the information to a
+ * server that may provide a new distro for download.
+ */
+ RulesState getRulesState();
+
+ /**
+ * Requests installation of the supplied distro. The distro must have been checked for integrity
+ * by the caller or have been received via a trusted mechanism.
+ *
+ * @param distroFileDescriptor the file descriptor for the distro
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link ICallback} to receive callbacks related to the
+ * installation
+ * @return zero if the installation will be attempted; nonzero on error
+ */
+ int requestInstall(in ParcelFileDescriptor distroFileDescriptor, in byte[] checkToken,
+ ICallback callback);
+
+ /**
+ * Requests uninstallation of the currently installed distro (leaving the device with no
+ * distro installed).
+ *
+ * @param checkToken an optional token provided if the uninstall was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link ICallback} to receive callbacks related to the
+ * uninstall
+ * @return zero if the uninstallation will be attempted; nonzero on error
+ */
+ int requestUninstall(in byte[] checkToken, ICallback callback);
+
+ /**
+ * Requests the system does not modify the currently installed time zone distro, if any. This
+ * method records the fact that a time zone check operation triggered by the system is now
+ * complete and there was nothing to do. The token passed should be the one presented when the
+ * check was triggered.
+ *
+ * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
+ * should be careful not to pass false if the failure is unlikely to resolve by itself.
+ *
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param success true if the check was successful, false if it was not successful but may
+ * succeed if it is retried
+ */
+ void requestNothing(in byte[] token, boolean success);
+}
diff --git a/core/java/android/app/timezone/RulesManager.java b/core/java/android/app/timezone/RulesManager.java
new file mode 100644
index 0000000..649d894
--- /dev/null
+++ b/core/java/android/app/timezone/RulesManager.java
@@ -0,0 +1,212 @@
+/*
+ * 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.app.timezone;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * The interface through which a time zone update application interacts with the Android system
+ * to handle time zone rule updates.
+ *
+ * <p>This interface is intended for use with the default APK-based time zone rules update
+ * application but it can also be used by OEMs if that mechanism is turned off using configuration.
+ * All callers must possess the {@link android.Manifest.permission#UPDATE_TIME_ZONE_RULES} system
+ * permission.
+ *
+ * <p>When using the default mechanism, when properly configured the Android system will send a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with a
+ * {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} extra to the time zone rules updater application
+ * when it detects that it or the OEM's APK containing time zone rules data has been modified. The
+ * updater application is then responsible for calling one of
+ * {@link #requestInstall(ParcelFileDescriptor, byte[], Callback)},
+ * {@link #requestUninstall(byte[], Callback)} or
+ * {@link #requestNothing(byte[], boolean)}, indicating, respectively, whether a new time zone rules
+ * distro should be installed, the current distro should be uninstalled, or there is nothing to do
+ * (or that the correct operation could not be determined due to an error). In each case the updater
+ * must pass the {@link RulesUpdaterContract#EXTRA_CHECK_TOKEN} value it received from the intent
+ * back so the system in the {@code checkToken} parameter.
+ *
+ * <p>If OEMs want to handle their own time zone rules updates, perhaps via a server-side component
+ * rather than an APK, then they should disable the default triggering mechanism in config and are
+ * responsible for triggering their own update checks / installs / uninstalls. In this case the
+ * "check token" parameter can be left null and there is never any need to call
+ * {@link #requestNothing(byte[], boolean)}.
+ *
+ * <p>OEMs should not mix the default mechanism and their own as this could lead to conflicts and
+ * unnecessary checks being triggered.
+ *
+ * <p>Applications obtain this using {@link android.app.Activity#getSystemService(String)} with
+ * {@link Context#TIME_ZONE_RULES_MANAGER_SERVICE}.
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesManager {
+ private static final String TAG = "timezone.RulesManager";
+ private static final boolean DEBUG = false;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SUCCESS, ERROR_UNKNOWN_FAILURE, ERROR_OPERATION_IN_PROGRESS})
+ public @interface ResultCode {}
+
+ /**
+ * Indicates that an operation succeeded.
+ */
+ public static final int SUCCESS = 0;
+
+ /**
+ * Indicates that an install/uninstall cannot be initiated because there is one already in
+ * progress.
+ */
+ public static final int ERROR_OPERATION_IN_PROGRESS = 1;
+
+ /**
+ * Indicates an install / uninstall did not fully succeed for an unknown reason.
+ */
+ public static final int ERROR_UNKNOWN_FAILURE = 2;
+
+ private final Context mContext;
+ private final IRulesManager mIRulesManager;
+
+ public RulesManager(Context context) {
+ mContext = context;
+ mIRulesManager = IRulesManager.Stub.asInterface(
+ ServiceManager.getService(Context.TIME_ZONE_RULES_MANAGER_SERVICE));
+ }
+
+ /**
+ * Returns information about the current time zone rules state such as the IANA version of
+ * the system and any currently installed distro. This method is intended to allow clients to
+ * determine if the current state can be improved; for example by passing the information to a
+ * server that may provide a new distro for download.
+ */
+ public RulesState getRulesState() {
+ try {
+ logDebug("sIRulesManager.getRulesState()");
+ RulesState rulesState = mIRulesManager.getRulesState();
+ logDebug("sIRulesManager.getRulesState() returned " + rulesState);
+ return rulesState;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests installation of the supplied distro. The distro must have been checked for integrity
+ * by the caller or have been received via a trusted mechanism.
+ *
+ * @param distroFileDescriptor the file descriptor for the distro
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link Callback} to receive callbacks related to the installation
+ * @return {@link #SUCCESS} if the installation will be attempted
+ */
+ @ResultCode
+ public int requestInstall(
+ ParcelFileDescriptor distroFileDescriptor, byte[] checkToken, Callback callback)
+ throws IOException {
+
+ ICallback iCallback = new CallbackWrapper(mContext, callback);
+ try {
+ logDebug("sIRulesManager.requestInstall()");
+ return mIRulesManager.requestInstall(distroFileDescriptor, checkToken, iCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Requests uninstallation of the currently installed distro (leaving the device with no
+ * distro installed).
+ *
+ * @param checkToken an optional token provided if the uninstall was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param callback the {@link Callback} to receive callbacks related to the uninstall
+ * @return {@link #SUCCESS} if the uninstallation will be attempted
+ */
+ @ResultCode
+ public int requestUninstall(byte[] checkToken, Callback callback) {
+ ICallback iCallback = new CallbackWrapper(mContext, callback);
+ try {
+ logDebug("sIRulesManager.requestUninstall()");
+ return mIRulesManager.requestUninstall(checkToken, iCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /*
+ * We wrap incoming binder calls with a private class implementation that
+ * redirects them into main-thread actions. This serializes the backup
+ * progress callbacks nicely within the usual main-thread lifecycle pattern.
+ */
+ private class CallbackWrapper extends ICallback.Stub {
+ final Handler mHandler;
+ final Callback mCallback;
+
+ CallbackWrapper(Context context, Callback callback) {
+ mCallback = callback;
+ mHandler = new Handler(context.getMainLooper());
+ }
+
+ // Binder calls into this object just enqueue on the main-thread handler
+ @Override
+ public void onFinished(int status) {
+ logDebug("mCallback.onFinished(status), status=" + status);
+ mHandler.post(() -> mCallback.onFinished(status));
+ }
+ }
+
+ /**
+ * Requests the system does not modify the currently installed time zone distro, if any. This
+ * method records the fact that a time zone check operation triggered by the system is now
+ * complete and there was nothing to do. The token passed should be the one presented when the
+ * check was triggered.
+ *
+ * <p>Note: Passing {@code success == false} may result in more checks being triggered. Clients
+ * should be careful not to pass false if the failure is unlikely to resolve by itself.
+ *
+ * @param checkToken an optional token provided if the install was triggered in response to a
+ * {@link RulesUpdaterContract#ACTION_TRIGGER_RULES_UPDATE_CHECK} intent
+ * @param succeeded true if the check was successful, false if it was not successful but may
+ * succeed if it is retried
+ */
+ public void requestNothing(byte[] checkToken, boolean succeeded) {
+ try {
+ logDebug("sIRulesManager.requestNothing() with token=" + Arrays.toString(checkToken));
+ mIRulesManager.requestNothing(checkToken, succeeded);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ static void logDebug(String msg) {
+ if (DEBUG) {
+ Log.v(TAG, msg);
+ }
+ }
+}
diff --git a/core/java/android/app/timezone/RulesState.aidl b/core/java/android/app/timezone/RulesState.aidl
new file mode 100644
index 0000000..f789120
--- /dev/null
+++ b/core/java/android/app/timezone/RulesState.aidl
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+parcelable RulesState;
\ No newline at end of file
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
new file mode 100644
index 0000000..33f4e80
--- /dev/null
+++ b/core/java/android/app/timezone/RulesState.java
@@ -0,0 +1,319 @@
+/*
+ * 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.app.timezone;
+
+import static android.app.timezone.Utils.validateConditionalNull;
+import static android.app.timezone.Utils.validateNotNull;
+import static android.app.timezone.Utils.validateRulesVersion;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Description of the state of time zone rules on a device.
+ *
+ * <p>The following properties are included:
+ * <dl>
+ * <dt>systemRulesVersion</dt>
+ * <dd>the IANA rules version that shipped with the OS. Always present. e.g. "2017a".</dd>
+ * <dt>distroFormatVersionSupported</dt>
+ * <dd>the distro format version supported by this device. Always present.</dd>
+ * <dt>operationInProgress</dt>
+ * <dd>{@code true} if there is an install / uninstall operation currently happening.</dd>
+ * <dt>stagedOperationType</dt>
+ * <dd>one of {@link #STAGED_OPERATION_UNKNOWN}, {@link #STAGED_OPERATION_NONE},
+ * {@link #STAGED_OPERATION_UNINSTALL} and {@link #STAGED_OPERATION_INSTALL} indicating whether
+ * there is a currently staged time zone distro operation. {@link #STAGED_OPERATION_UNKNOWN} is
+ * used when {@link #isOperationInProgress()} is {@code true}. Staged operations currently
+ * require a reboot to become active.</dd>
+ * <dt>stagedDistroRulesVersion</dt>
+ * <dd>[present if distroStagedState == STAGED_STATE_INSTALL], the rules version of the distro
+ * currently staged for installation.</dd>
+ * <dt>distroStatus</dt>
+ * <dd>{@link #DISTRO_STATUS_INSTALLED} if there is a time zone distro installed and active,
+ * {@link #DISTRO_STATUS_NONE} if there is no active installed distro.
+ * {@link #DISTRO_STATUS_UNKNOWN} is used when {@link #isOperationInProgress()} is {@code true}.
+ * </dd>
+ * <dt>installedDistroRulesVersion</dt>
+ * <dd>[present if distroStatus == {@link #DISTRO_STATUS_INSTALLED}], the rules version of the
+ * installed and active distro.</dd>
+ * </dl>
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesState implements Parcelable {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ STAGED_OPERATION_UNKNOWN,
+ STAGED_OPERATION_NONE,
+ STAGED_OPERATION_UNINSTALL,
+ STAGED_OPERATION_INSTALL })
+ private @interface StagedOperationType {}
+
+ /** Staged state could not be determined. */
+ public static final int STAGED_OPERATION_UNKNOWN = 0;
+ /** Nothing is staged. */
+ public static final int STAGED_OPERATION_NONE = 1;
+ /** An uninstall is staged. */
+ public static final int STAGED_OPERATION_UNINSTALL = 2;
+ /** An install is staged. */
+ public static final int STAGED_OPERATION_INSTALL = 3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DISTRO_STATUS_UNKNOWN,
+ DISTRO_STATUS_NONE,
+ DISTRO_STATUS_INSTALLED })
+ private @interface DistroStatus {}
+
+ /** The current distro status could not be determined. */
+ public static final int DISTRO_STATUS_UNKNOWN = 0;
+ /** There is no active installed time zone distro. */
+ public static final int DISTRO_STATUS_NONE = 1;
+ /** The is an active, installed time zone distro. */
+ public static final int DISTRO_STATUS_INSTALLED = 2;
+
+ private static final byte BYTE_FALSE = 0;
+ private static final byte BYTE_TRUE = 1;
+
+ private final String mSystemRulesVersion;
+ private final DistroFormatVersion mDistroFormatVersionSupported;
+ private final boolean mOperationInProgress;
+ @StagedOperationType private final int mStagedOperationType;
+ @Nullable private final DistroRulesVersion mStagedDistroRulesVersion;
+ @DistroStatus private final int mDistroStatus;
+ @Nullable private final DistroRulesVersion mInstalledDistroRulesVersion;
+
+ public RulesState(String systemRulesVersion, DistroFormatVersion distroFormatVersionSupported,
+ boolean operationInProgress,
+ @StagedOperationType int stagedOperationType,
+ @Nullable DistroRulesVersion stagedDistroRulesVersion,
+ @DistroStatus int distroStatus,
+ @Nullable DistroRulesVersion installedDistroRulesVersion) {
+ this.mSystemRulesVersion = validateRulesVersion("systemRulesVersion", systemRulesVersion);
+ this.mDistroFormatVersionSupported =
+ validateNotNull("distroFormatVersionSupported", distroFormatVersionSupported);
+ this.mOperationInProgress = operationInProgress;
+
+ if (operationInProgress && stagedOperationType != STAGED_OPERATION_UNKNOWN) {
+ throw new IllegalArgumentException(
+ "stagedOperationType != STAGED_OPERATION_UNKNOWN");
+ }
+ this.mStagedOperationType = validateStagedOperation(stagedOperationType);
+ this.mStagedDistroRulesVersion = validateConditionalNull(
+ mStagedOperationType == STAGED_OPERATION_INSTALL /* requireNotNull */,
+ "stagedDistroRulesVersion", stagedDistroRulesVersion);
+
+ if (operationInProgress && distroStatus != DISTRO_STATUS_UNKNOWN) {
+ throw new IllegalArgumentException("distroInstalled != DISTRO_STATUS_UNKNOWN");
+ }
+ this.mDistroStatus = validateDistroStatus(distroStatus);
+ this.mInstalledDistroRulesVersion = validateConditionalNull(
+ mDistroStatus == DISTRO_STATUS_INSTALLED/* requireNotNull */,
+ "installedDistroRulesVersion", installedDistroRulesVersion);
+ }
+
+ public String getSystemRulesVersion() {
+ return mSystemRulesVersion;
+ }
+
+ public boolean isOperationInProgress() {
+ return mOperationInProgress;
+ }
+
+ public @StagedOperationType int getStagedOperationType() {
+ return mStagedOperationType;
+ }
+
+ /**
+ * Returns the staged rules version when {@link #getStagedOperationType()} is
+ * {@link #STAGED_OPERATION_INSTALL}.
+ */
+ public @Nullable DistroRulesVersion getStagedDistroRulesVersion() {
+ return mStagedDistroRulesVersion;
+ }
+
+ public @DistroStatus int getDistroStatus() {
+ return mDistroStatus;
+ }
+
+ /**
+ * Returns the installed rules version when {@link #getDistroStatus()} is
+ * {@link #DISTRO_STATUS_INSTALLED}.
+ */
+ public @Nullable DistroRulesVersion getInstalledDistroRulesVersion() {
+ return mInstalledDistroRulesVersion;
+ }
+
+ /**
+ * Returns true if a distro in the specified format is supported on this device.
+ */
+ public boolean isDistroFormatVersionSupported(DistroFormatVersion distroFormatVersion) {
+ return mDistroFormatVersionSupported.supports(distroFormatVersion);
+ }
+
+ /**
+ * Returns true if the distro IANA rules version supplied is newer or the same as the version in
+ * the system image data files.
+ */
+ public boolean isSystemVersionOlderThan(DistroRulesVersion distroRulesVersion) {
+ return mSystemRulesVersion.compareTo(distroRulesVersion.getRulesVersion()) < 0;
+ }
+
+ public boolean isDistroInstalled() {
+ return mDistroStatus == DISTRO_STATUS_INSTALLED;
+ }
+
+ /**
+ * Returns true if the rules version supplied is newer than the one currently installed. If
+ * there is no installed distro this method throws IllegalStateException.
+ */
+ public boolean isInstalledDistroOlderThan(DistroRulesVersion distroRulesVersion) {
+ if (mOperationInProgress) {
+ throw new IllegalStateException("Distro state not known: operation in progress.");
+ }
+ if (!isDistroInstalled()) {
+ throw new IllegalStateException("No distro installed.");
+ }
+ return mInstalledDistroRulesVersion.isOlderThan(distroRulesVersion);
+ }
+
+ public static final Parcelable.Creator<RulesState> CREATOR =
+ new Parcelable.Creator<RulesState>() {
+ public RulesState createFromParcel(Parcel in) {
+ return RulesState.createFromParcel(in);
+ }
+
+ public RulesState[] newArray(int size) {
+ return new RulesState[size];
+ }
+ };
+
+ private static RulesState createFromParcel(Parcel in) {
+ String systemRulesVersion = in.readString();
+ DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
+ boolean operationInProgress = in.readByte() == BYTE_TRUE;
+ int distroStagedState = in.readByte();
+ DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null);
+ int installedDistroStatus = in.readByte();
+ DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
+ return new RulesState(systemRulesVersion, distroFormatVersionSupported, operationInProgress,
+ distroStagedState, stagedDistroRulesVersion,
+ installedDistroStatus, installedDistroRulesVersion);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mSystemRulesVersion);
+ out.writeParcelable(mDistroFormatVersionSupported, 0);
+ out.writeByte(mOperationInProgress ? BYTE_TRUE : BYTE_FALSE);
+ out.writeByte((byte) mStagedOperationType);
+ out.writeParcelable(mStagedDistroRulesVersion, 0);
+ out.writeByte((byte) mDistroStatus);
+ out.writeParcelable(mInstalledDistroRulesVersion, 0);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ RulesState that = (RulesState) o;
+
+ if (mOperationInProgress != that.mOperationInProgress) {
+ return false;
+ }
+ if (mStagedOperationType != that.mStagedOperationType) {
+ return false;
+ }
+ if (mDistroStatus != that.mDistroStatus) {
+ return false;
+ }
+ if (!mSystemRulesVersion.equals(that.mSystemRulesVersion)) {
+ return false;
+ }
+ if (!mDistroFormatVersionSupported.equals(that.mDistroFormatVersionSupported)) {
+ return false;
+ }
+ if (mStagedDistroRulesVersion != null ? !mStagedDistroRulesVersion
+ .equals(that.mStagedDistroRulesVersion) : that.mStagedDistroRulesVersion != null) {
+ return false;
+ }
+ return mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
+ .equals(that.mInstalledDistroRulesVersion)
+ : that.mInstalledDistroRulesVersion == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mSystemRulesVersion.hashCode();
+ result = 31 * result + mDistroFormatVersionSupported.hashCode();
+ result = 31 * result + (mOperationInProgress ? 1 : 0);
+ result = 31 * result + mStagedOperationType;
+ result = 31 * result + (mStagedDistroRulesVersion != null ? mStagedDistroRulesVersion
+ .hashCode()
+ : 0);
+ result = 31 * result + mDistroStatus;
+ result = 31 * result + (mInstalledDistroRulesVersion != null ? mInstalledDistroRulesVersion
+ .hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RulesState{"
+ + "mSystemRulesVersion='" + mSystemRulesVersion + '\''
+ + ", mDistroFormatVersionSupported=" + mDistroFormatVersionSupported
+ + ", mOperationInProgress=" + mOperationInProgress
+ + ", mStagedOperationType=" + mStagedOperationType
+ + ", mStagedDistroRulesVersion=" + mStagedDistroRulesVersion
+ + ", mDistroStatus=" + mDistroStatus
+ + ", mInstalledDistroRulesVersion=" + mInstalledDistroRulesVersion
+ + '}';
+ }
+
+ private static int validateStagedOperation(int stagedOperationType) {
+ if (stagedOperationType < STAGED_OPERATION_UNKNOWN
+ || stagedOperationType > STAGED_OPERATION_INSTALL) {
+ throw new IllegalArgumentException("Unknown operation type=" + stagedOperationType);
+ }
+ return stagedOperationType;
+ }
+
+ private static int validateDistroStatus(int distroStatus) {
+ if (distroStatus < DISTRO_STATUS_UNKNOWN || distroStatus > DISTRO_STATUS_INSTALLED) {
+ throw new IllegalArgumentException("Unknown distro status=" + distroStatus);
+ }
+ return distroStatus;
+ }
+}
diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java
new file mode 100644
index 0000000..4e77818
--- /dev/null
+++ b/core/java/android/app/timezone/RulesUpdaterContract.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.app.timezone;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Constants related to the contract between the Android system and the privileged time zone updater
+ * application.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class RulesUpdaterContract {
+
+ /**
+ * The system permission possessed by the Android system that allows it to trigger time zone
+ * update checks. The updater should be configured to require this permission when registering
+ * for {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intents.
+ */
+ public static final String TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION =
+ android.Manifest.permission.TRIGGER_TIME_ZONE_RULES_CHECK;
+
+ /**
+ * The system permission possessed by the time zone rules updater app that allows it to update
+ * device time zone rules. The Android system requires this permission for calls made to
+ * {@link RulesManager}.
+ */
+ public static final String UPDATE_TIME_ZONE_RULES_PERMISSION =
+ android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+
+ /**
+ * The action of the intent that the Android system will broadcast. The intent will be targeted
+ * at the configured updater application's package meaning the term "broadcast" only loosely
+ * applies.
+ */
+ public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
+ "android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
+
+ /**
+ * The extra containing the {@code byte[]} that should be passed to
+ * {@link RulesManager#requestInstall(ParcelFileDescriptor, byte[], Callback)},
+ * {@link RulesManager#requestUninstall(byte[], Callback)} and
+ * {@link RulesManager#requestNothing(byte[], boolean)} methods when the
+ * {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
+ */
+ public static final String EXTRA_CHECK_TOKEN =
+ "android.intent.extra.timezone.CHECK_TOKEN";
+
+ /**
+ * Creates an intent that would trigger a time zone rules update check.
+ */
+ public static Intent createUpdaterIntent(String updaterPackageName) {
+ Intent intent = new Intent(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK);
+ intent.setPackage(updaterPackageName);
+ intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ return intent;
+ }
+
+ /**
+ * Broadcasts an {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent with the
+ * {@link #EXTRA_CHECK_TOKEN} that triggers an update check, including the required receiver
+ * permission.
+ */
+ public static void sendBroadcast(Context context, String updaterAppPackageName,
+ byte[] checkTokenBytes) {
+ Intent intent = createUpdaterIntent(updaterAppPackageName);
+ intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
+ context.sendBroadcast(intent, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+ }
+}
diff --git a/core/java/android/app/timezone/Utils.java b/core/java/android/app/timezone/Utils.java
new file mode 100644
index 0000000..8dd3fb7
--- /dev/null
+++ b/core/java/android/app/timezone/Utils.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.timezone;
+
+/**
+ * Shared code for android.app.timezone classes.
+ */
+final class Utils {
+ private Utils() {}
+
+ static int validateVersion(String type, int version) {
+ if (version < 0 || version > 999) {
+ throw new IllegalArgumentException("Invalid " + type + " version=" + version);
+ }
+ return version;
+ }
+
+ static String validateRulesVersion(String type, String rulesVersion) {
+ validateNotNull(type, rulesVersion);
+
+ if (rulesVersion.isEmpty()) {
+ throw new IllegalArgumentException(type + " must not be empty");
+ }
+ return rulesVersion;
+ }
+
+ /** Validates that {@code object} is not null. Always returns {@code object}. */
+ static <T> T validateNotNull(String type, T object) {
+ if (object == null) {
+ throw new NullPointerException(type + " == null");
+ }
+ return object;
+ }
+
+ /**
+ * If {@code requireNotNull} is {@code true} calls {@link #validateNotNull(String, Object)},
+ * and {@link #validateNull(String, Object)} otherwise. Returns {@code object}.
+ */
+ static <T> T validateConditionalNull(boolean requireNotNull, String type, T object) {
+ if (requireNotNull) {
+ return validateNotNull(type, object);
+ } else {
+ return validateNull(type, object);
+ }
+ }
+
+ /** Validates that {@code object} is null. Always returns null. */
+ static <T> T validateNull(String type, T object) {
+ if (object != null) {
+ throw new IllegalArgumentException(type + " != null");
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 735d84e..ff52f27 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1032,28 +1032,6 @@
}
/**
- * enable or disable Bluetooth HCI snoop log.
- *
- * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission
- *
- * @return true to indicate configure HCI log successfully, or false on
- * immediate error
- * @hide
- */
- public boolean configHciSnoopLog(boolean enable) {
- try {
- mServiceLock.readLock().lock();
- if (mService != null) return mService.configHciSnoopLog(enable);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- } finally {
- mServiceLock.readLock().unlock();
- }
- return false;
- }
-
- /**
* Factory reset bluetooth settings.
*
* <p>Requires the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e0b03d2..f158f5f 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1714,7 +1714,7 @@
* an d{@link BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect
* if {@code autoConnect} is set to true.
* @param handler The handler to use for the callback. If {@code null}, callbacks will happen
- * on the service's main thread.
+ * on an un-specified background thread.
* @throws NullPointerException if callback is null
*/
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
@@ -1723,9 +1723,6 @@
if (callback == null)
throw new NullPointerException("callback is null");
- if (handler == null)
- handler = new Handler(Looper.getMainLooper());
-
// TODO(Bluetooth) check whether platform support BLE
// Do the check here or in GattServer?
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 7806e31..3135b30 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -156,11 +156,13 @@
}
mClientIf = clientIf;
if (status != GATT_SUCCESS) {
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE,
- BluetoothProfile.STATE_DISCONNECTED);
+ if (mCallback != null) {
+ mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
}
});
@@ -189,10 +191,12 @@
return;
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+ if (mCallback != null) {
+ mCallback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+ }
}
});
}
@@ -209,10 +213,12 @@
return;
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+ if (mCallback != null) {
+ mCallback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+ }
}
});
}
@@ -232,10 +238,13 @@
int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
BluetoothProfile.STATE_DISCONNECTED;
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState);
+ if (mCallback != null) {
+ mCallback.onConnectionStateChange(BluetoothGatt.this, status,
+ profileState);
+ }
}
});
@@ -291,10 +300,12 @@
}
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onServicesDiscovered(BluetoothGatt.this, status);
+ if (mCallback != null) {
+ mCallback.onServicesDiscovered(BluetoothGatt.this, status);
+ }
}
});
}
@@ -341,10 +352,13 @@
if (status == 0) characteristic.setValue(value);
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status);
+ if (mCallback != null) {
+ mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic,
+ status);
+ }
}
});
}
@@ -387,10 +401,13 @@
mAuthRetryState = AUTH_RETRY_STATE_IDLE;
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
+ if (mCallback != null) {
+ mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
+ status);
+ }
}
});
}
@@ -413,10 +430,12 @@
characteristic.setValue(value);
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
+ if (mCallback != null) {
+ mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
+ }
}
});
}
@@ -458,10 +477,12 @@
mAuthRetryState = AUTH_RETRY_STATE_IDLE;
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+ if (mCallback != null) {
+ mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+ }
}
});
}
@@ -502,10 +523,12 @@
mAuthRetryState = AUTH_RETRY_STATE_IDLE;
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+ if (mCallback != null) {
+ mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+ }
}
});
}
@@ -526,10 +549,12 @@
mDeviceBusy = false;
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
+ if (mCallback != null) {
+ mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
+ }
}
});
}
@@ -545,10 +570,12 @@
if (!address.equals(mDevice.getAddress())) {
return;
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+ if (mCallback != null) {
+ mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+ }
}
});
}
@@ -565,10 +592,12 @@
return;
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
+ if (mCallback != null) {
+ mCallback.onMtuChanged(BluetoothGatt.this, mtu, status);
+ }
}
});
}
@@ -587,11 +616,13 @@
return;
}
- mHandler.post(new Runnable() {
+ runOrQueueCallback(new Runnable() {
@Override
public void run() {
- mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
- timeout, status);
+ if (mCallback != null) {
+ mCallback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+ timeout, status);
+ }
}
});
}
@@ -672,6 +703,22 @@
}
/**
+ * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
+ * immediately if no Handler was provided.
+ */
+ private void runOrQueueCallback(final Runnable cb) {
+ if (mHandler == null) {
+ try {
+ cb.run();
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception in callback", ex);
+ }
+ } else {
+ mHandler.post(cb);
+ }
+ }
+
+ /**
* Register an application callback to start using GATT.
*
* <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index b337817..43c5ae4 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -97,7 +97,6 @@
ParcelFileDescriptor connectSocket(in BluetoothDevice device, int type, in ParcelUuid uuid, int port, int flag);
ParcelFileDescriptor createSocketChannel(int type, in String serviceName, in ParcelUuid uuid, int port, int flag);
- boolean configHciSnoopLog(boolean enable);
boolean factoryReset();
boolean isMultiAdvertisementSupported();
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 3021be1..1bc211c 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -172,7 +172,7 @@
*
* @param enable whether the periodic advertising should be enabled (true), or disabled (false).
*/
- public void setPeriodicAdvertisingEnable(boolean enable) {
+ public void setPeriodicAdvertisingEnabled(boolean enable) {
try {
gatt.setPeriodicAdvertisingEnable(this.advertiserId, enable);
} catch (RemoteException e) {
diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
index 2c46e85..c3c16a4 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetCallback.java
@@ -135,7 +135,7 @@
int status) {}
/**
- * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnable}
+ * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnabled}
* indicating result of the operation.
*
* @param advertisingSet The advertising set.
diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
index 31d8f48..71c4484 100644
--- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java
+++ b/core/java/android/bluetooth/le/AdvertisingSetParameters.java
@@ -242,7 +242,7 @@
* non-connectable.
* Legacy advertisements can be both connectable and scannable. Non-legacy
* advertisements can be only scannable or only connectable.
- * @param connectable Controls whether the advertisment type will be
+ * @param connectable Controls whether the advertisement type will be
* connectable (true) or non-connectable (false).
*/
public Builder setConnectable(boolean connectable) {
@@ -254,7 +254,7 @@
* Set whether the advertisement type should be scannable.
* Legacy advertisements can be both connectable and scannable. Non-legacy
* advertisements can be only scannable or only connectable.
- * @param scannable Controls whether the advertisment type will be
+ * @param scannable Controls whether the advertisement type will be
* scannable (true) or non-scannable (false).
*/
public Builder setScannable(boolean scannable) {
@@ -386,6 +386,7 @@
/**
* Build the {@link AdvertisingSetParameters} object.
+ * @throws IllegalStateException if invalid combination of parameters is used.
*/
public AdvertisingSetParameters build() {
if (isLegacy) {
@@ -394,22 +395,22 @@
}
if (connectable == true && scannable == false) {
- throw new IllegalArgumentException(
+ throw new IllegalStateException(
"Legacy advertisement can't be connectable and non-scannable");
}
if (includeTxPower) {
- throw new IllegalArgumentException(
+ throw new IllegalStateException(
"Legacy advertising can't include TX power level in header");
}
} else {
if (connectable && scannable) {
- throw new IllegalArgumentException(
+ throw new IllegalStateException(
"Advertising can't be both connectable and scannable");
}
if (isAnonymous && connectable) {
- throw new IllegalArgumentException(
+ throw new IllegalStateException(
"Advertising can't be both connectable and anonymous");
}
}
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
index 5b2fa40..e552398 100644
--- a/core/java/android/bluetooth/le/ScanResult.java
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -52,6 +52,16 @@
public static final int SID_NOT_PRESENT = 0xFF;
/**
+ * TX power is not present in the packet.
+ */
+ public static final int TX_POWER_NOT_PRESENT = 0x7F;
+
+ /**
+ * Periodic advertising interval is not present in the packet.
+ */
+ public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00;
+
+ /**
* Mask for checking whether event type represents legacy advertisement.
*/
private static final int ET_LEGACY_MASK = 0x10;
@@ -265,15 +275,16 @@
/**
* Returns the transmit power in dBm.
- * Valid range is [-127, 126]. A value of 127 indicates that the
- * advertisement did not indicate TX power.
+ * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT}
+ * indicates that the TX power is not present.
*/
public int getTxPower() { return mTxPower; }
/**
* Returns the periodic advertising interval in units of 1.25ms.
- * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of 0 means
- * periodic advertising is not used for this scan result.
+ * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of
+ * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic
+ * advertising interval is not present.
*/
public int getPeriodicAdvertisingInterval() {
return mPeriodicAdvertisingInterval;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fed36b0..b2f2cb7 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2656,6 +2656,7 @@
SENSOR_SERVICE,
STORAGE_SERVICE,
WALLPAPER_SERVICE,
+ TIME_ZONE_RULES_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
CONNECTIVITY_SERVICE,
@@ -2761,9 +2762,6 @@
* <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
* <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
* handling management of network connections.
- * <dt> {@link #IPSEC_SERVICE} ("ipsec")
- * <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
- * sockets and networks.
* <dt> {@link #WIFI_SERVICE} ("wifi")
* <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
* connectivity. On releases before NYC, it should only be obtained from an application
@@ -3098,6 +3096,7 @@
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
+ * @hide
* @see #getSystemService
*/
public static final String IPSEC_SERVICE = "ipsec";
@@ -3668,6 +3667,15 @@
public static final String GATEKEEPER_SERVICE = "android.service.gatekeeper.IGateKeeperService";
/**
+ * Use with {@link #getSystemService} to retrieve an
+ * {@link android.app.timezone.ITimeZoneRulesManager}.
+ * @hide
+ *
+ * @see #getSystemService
+ */
+ public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index 14bb923..f079647 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -755,8 +755,9 @@
if (!(obj instanceof BandConfig))
return false;
BandConfig other = (BandConfig) obj;
- if (mDescriptor != other.getDescriptor())
- return false;
+ BandDescriptor otherDesc = other.getDescriptor();
+ if ((mDescriptor == null) != (otherDesc == null)) return false;
+ if (mDescriptor != null && !mDescriptor.equals(otherDesc)) return false;
return true;
}
}
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index ce7894f..48b095d 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -24,6 +24,8 @@
/**
* IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
* RFC 4301.
+ *
+ * @hide
*/
public final class IpSecAlgorithm implements Parcelable {
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 375b7ee..114e46e 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -37,6 +37,8 @@
* <p>An IpSecManager may be obtained by calling {@link
* android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
* android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ *
+ * @hide
*/
public final class IpSecManager {
private static final String TAG = "IpSecManager";
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 801e98c..639d1f2 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -45,6 +45,8 @@
*
* <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
* of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ *
+ * @hide
*/
public final class IpSecTransform implements AutoCloseable {
private static final String TAG = "IpSecTransform";
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index b56437e..0b1569c 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -20,6 +20,7 @@
import android.util.Log;
import com.android.internal.os.RoSystemProperties;
+import com.android.org.conscrypt.Conscrypt;
import com.android.org.conscrypt.OpenSSLContextImpl;
import com.android.org.conscrypt.OpenSSLSocketImpl;
import com.android.org.conscrypt.SSLClientSessionCache;
@@ -212,7 +213,7 @@
private SSLSocketFactory makeSocketFactory(
KeyManager[] keyManagers, TrustManager[] trustManagers) {
try {
- OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
+ OpenSSLContextImpl sslContext = (OpenSSLContextImpl) Conscrypt.newPreferredSSLContextSpi();
sslContext.engineInit(keyManagers, trustManagers, null);
sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache);
return sslContext.engineGetSocketFactory();
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 83c4691..3fd9f19 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -372,6 +372,8 @@
((DiscoveryListener) listener).onServiceLost((NsdServiceInfo) message.obj);
break;
case STOP_DISCOVERY_FAILED:
+ // TODO: failure to stop discovery should be internal and retried internally, as
+ // the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
removeListener(message.arg2);
((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
message.arg1);
@@ -393,6 +395,8 @@
((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
break;
case UNREGISTER_SERVICE_SUCCEEDED:
+ // TODO: do not unregister listener until service is unregistered, or provide
+ // alternative way for unregistering ?
removeListener(message.arg2);
((RegistrationListener) listener).onServiceUnregistered(ns);
break;
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index 94fd5b0..4ba1144 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -209,10 +209,11 @@
public native final IHwBinder readStrongBinder();
// Handle is stored as part of the blob.
- public native final HwBlob readBuffer();
+ public native final HwBlob readBuffer(long expectedSize);
public native final HwBlob readEmbeddedBuffer(
- long parentHandle, long offset, boolean nullable);
+ long expectedSize, long parentHandle, long offset,
+ boolean nullable);
public native final void writeBuffer(HwBlob blob);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 4f4152c..2a8ae93 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -728,9 +728,11 @@
int timeTotal = -1;
int uncryptTime = -1;
int sourceVersion = -1;
- int temperature_start = -1;
- int temperature_end = -1;
- int temperature_max = -1;
+ int temperatureStart = -1;
+ int temperatureEnd = -1;
+ int temperatureMax = -1;
+ int errorCode = -1;
+ int causeCode = -1;
while ((line = in.readLine()) != null) {
// Here is an example of lines in last_install:
@@ -777,11 +779,15 @@
bytesStashedInMiB = (bytesStashedInMiB == -1) ? scaled :
bytesStashedInMiB + scaled;
} else if (line.startsWith("temperature_start")) {
- temperature_start = scaled;
+ temperatureStart = scaled;
} else if (line.startsWith("temperature_end")) {
- temperature_end = scaled;
+ temperatureEnd = scaled;
} else if (line.startsWith("temperature_max")) {
- temperature_max = scaled;
+ temperatureMax = scaled;
+ } else if (line.startsWith("error")) {
+ errorCode = scaled;
+ } else if (line.startsWith("cause")) {
+ causeCode = scaled;
}
}
@@ -801,14 +807,20 @@
if (bytesStashedInMiB != -1) {
MetricsLogger.histogram(context, "ota_stashed_in_MiBs", bytesStashedInMiB);
}
- if (temperature_start != -1) {
- MetricsLogger.histogram(context, "ota_temperature_start", temperature_start);
+ if (temperatureStart != -1) {
+ MetricsLogger.histogram(context, "ota_temperature_start", temperatureStart);
}
- if (temperature_end != -1) {
- MetricsLogger.histogram(context, "ota_temperature_end", temperature_end);
+ if (temperatureEnd != -1) {
+ MetricsLogger.histogram(context, "ota_temperature_end", temperatureEnd);
}
- if (temperature_max != -1) {
- MetricsLogger.histogram(context, "ota_temperature_max", temperature_max);
+ if (temperatureMax != -1) {
+ MetricsLogger.histogram(context, "ota_temperature_max", temperatureMax);
+ }
+ if (errorCode != -1) {
+ MetricsLogger.histogram(context, "ota_non_ab_error_code", errorCode);
+ }
+ if (causeCode != -1) {
+ MetricsLogger.histogram(context, "ota_non_ab_cause_code", causeCode);
}
} catch (IOException e) {
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 1ef3916..65b33e5 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -16,27 +16,25 @@
package android.os;
-import java.util.ArrayList;
+import java.util.Map;
import android.util.Log;
-/** @hide */
+/**
+ * Java API for libvintf.
+ * @hide
+ */
public class VintfObject {
- private static final String LOG_TAG = "VintfObject";
+ /// ---------- OTA
/**
- * Slurps all device information (both manifests)
- * and report it.
+ * Slurps all device information (both manifests and both matrices)
+ * and report them.
* If any error in getting one of the manifests, it is not included in
* the list.
*/
- public static String[] report() {
- ArrayList<String> ret = new ArrayList<>();
- put(ret, getDeviceManifest(), "device manifest");
- put(ret, getFrameworkManifest(), "framework manifest");
- return ret.toArray(new String[0]);
- }
+ public static native String[] report();
/**
* Verify that the given metadata for an OTA package is compatible with
@@ -50,15 +48,26 @@
*/
public static native int verify(String[] packageInfo);
- // return null if any error, otherwise XML string.
- private static native String getDeviceManifest();
- private static native String getFrameworkManifest();
+ /// ---------- CTS Device Info
- private static void put(ArrayList<String> list, String content, String message) {
- if (content == null || content.length() == 0) {
- Log.e(LOG_TAG, "Cannot get;" + message + "; check native logs for details.");
- return;
- }
- list.add(content);
- }
+ /**
+ * @return a list of HAL names and versions that is supported by this
+ * device as stated in device and framework manifests. For example,
+ * ["android.hidl.manager@1.0", "android.hardware.camera.device@1.0",
+ * "android.hardware.camera.device@3.2"]. There are no duplicates.
+ */
+ public static native String[] getHalNamesAndVersions();
+
+ /**
+ * @return the BOARD_SEPOLICY_VERS build flag available in device manifest.
+ */
+ public static native String getSepolicyVersion();
+
+ /**
+ * @return a list of VNDK snapshots supported by the framework, as
+ * specified in framework manifest. For example,
+ * [("25.0.5", ["libjpeg.so", "libbase.so"]),
+ * ("25.1.3", ["libjpeg.so", "libbase.so"])]
+ */
+ public static native Map<String, String[]> getVndkSnapshots();
}
diff --git a/core/java/android/os/VintfRuntimeInfo.java b/core/java/android/os/VintfRuntimeInfo.java
new file mode 100644
index 0000000..29698b9
--- /dev/null
+++ b/core/java/android/os/VintfRuntimeInfo.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.os;
+
+/**
+ * Java API for ::android::vintf::RuntimeInfo. Methods return null / 0 on any error.
+ *
+ * @hide
+ */
+public class VintfRuntimeInfo {
+
+ private VintfRuntimeInfo() {}
+
+ /**
+ * @return /sys/fs/selinux/policyvers, via security_policyvers() native call
+ */
+ public static native long getKernelSepolicyVersion();
+ /**
+ * @return content of /proc/cpuinfo
+ */
+ public static native String getCpuInfo();
+ /**
+ * @return os name extracted from uname() native call
+ */
+ public static native String getOsName();
+ /**
+ * @return node name extracted from uname() native call
+ */
+ public static native String getNodeName();
+ /**
+ * @return os release extracted from uname() native call
+ */
+ public static native String getOsRelease();
+ /**
+ * @return os version extracted from uname() native call
+ */
+ public static native String getOsVersion();
+ /**
+ * @return hardware id extracted from uname() native call
+ */
+ public static native String getHardwareId();
+ /**
+ * @return kernel version extracted from uname() native call. Format is
+ * {@code x.y.z}.
+ */
+ public static native String getKernelVersion();
+ /**
+ * @return libavb version in OS. Format is {@code x.y}.
+ */
+ public static native String getBootAvbVersion();
+ /**
+ * @return libavb version in bootloader. Format is {@code x.y}.
+ */
+ public static native String getBootVbmetaAvbVersion();
+
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3f3f859..35a3c0c 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4720,13 +4720,6 @@
public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
/**
- * bluetooth HCI snoop log configuration
- * @hide
- */
- public static final String BLUETOOTH_HCI_LOG =
- "bluetooth_hci_log";
-
- /**
* @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/
@Deprecated
diff --git a/core/java/android/provider/TimeZoneRulesDataContract.java b/core/java/android/provider/TimeZoneRulesDataContract.java
new file mode 100644
index 0000000..19e914b
--- /dev/null
+++ b/core/java/android/provider/TimeZoneRulesDataContract.java
@@ -0,0 +1,114 @@
+/*
+ * 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.provider;
+
+import android.net.Uri;
+
+/**
+ * A set of constants for implementing a time zone data content provider, which is used by the time
+ * zone updater application.
+ *
+ * @hide
+ */
+// TODO(nfuller): Expose necessary APIs for OEMs with @SystemApi. http://b/31008728
+public final class TimeZoneRulesDataContract {
+
+ private TimeZoneRulesDataContract() {}
+
+ /**
+ * The authority that <em>must</em> be used for the time zone data content provider.
+ * To be accepted by the time zone updater application it <em>must</em> be exposed by the
+ * package specified in the config_timeZoneRulesDataPackage config value.
+ */
+ public static final String AUTHORITY = "com.android.timezone";
+
+ /** A content:// style uri to the authority for the time zone data content provider */
+ private static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * The content:// style URI for determining what type of update is available.
+ *
+ * <p>The URI can be queried using
+ * {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)};
+ * the result will be a cursor with a single row. If the {@link #COLUMN_OPERATION}
+ * column is {@link #OPERATION_INSTALL} then see {@link #DATA_URI} for how to obtain the
+ * binary data.
+ */
+ public static final Uri OPERATION_URI = Uri.withAppendedPath(AUTHORITY_URI, "operation");
+
+ /**
+ * The {@code String} column of the {@link #OPERATION_URI} that provides an int specifying the
+ * type of operation to perform. See {@link #OPERATION_NO_OP}, {@link #OPERATION_UNINSTALL} and
+ * {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_OPERATION = "operation";
+
+ /**
+ * An operation type used when the time zone rules on device should be left as they are.
+ * This is not expected to be used in normal operation but a safe result in the event of an
+ * error that cannot be recovered from.
+ */
+ public static final String OPERATION_NO_OP = "NOOP";
+
+ /**
+ * An operation type used when the current time zone rules on device should be uninstalled,
+ * returning to the values held in the system partition.
+ */
+ public static final String OPERATION_UNINSTALL = "UNINSTALL";
+
+ /**
+ * An operation type used when the current time zone rules on device should be replaced by
+ * a new set obtained via the {@link android.content.ContentProvider#openFile(Uri, String)}
+ * method.
+ */
+ public static final String OPERATION_INSTALL = "INSTALL";
+
+ /**
+ * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the major
+ * version of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_DISTRO_MAJOR_VERSION = "distro_major_version";
+
+ /**
+ * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the minor
+ * version of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_DISTRO_MINOR_VERSION = "distro_minor_version";
+
+ /**
+ * The {@code nullable String} column of the {@link #OPERATION_URI} that describes the IANA
+ * rules version of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_RULES_VERSION = "rules_version";
+
+ /**
+ * The {@code nullable int} column of the {@link #OPERATION_URI} that describes the revision
+ * number of the distro to be installed.
+ * Only non-null if {@link #COLUMN_OPERATION} contains {@link #OPERATION_INSTALL}.
+ */
+ public static final String COLUMN_REVISION = "revision";
+
+ /**
+ * The content:// style URI for obtaining time zone bundle data.
+ *
+ * <p>Use {@link android.content.ContentProvider#openFile(Uri, String)} with "r" mode.
+ */
+ public static final Uri DATA_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
+}
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index e48a0d0..a8b094e 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -284,8 +284,6 @@
* not.
*
* <P>Type: INTEGER (boolean)</P>
- *
- * @hide
*/
public static final String BACKED_UP = "backed_up";
@@ -294,8 +292,6 @@
* restored, 0 if not.
*
* <P>Type: INTEGER (boolean)</P>
- *
- * @hide
*/
public static final String RESTORED = "restored";
@@ -305,19 +301,19 @@
* if not.
*
* <P>Type: INTEGER (boolean)</P>
- *
- * @hide
*/
public static final String ARCHIVED = "archived";
/**
* Flag to indicate the voicemail is a OMTP voicemail handled by the {@link
* android.telephony.VisualVoicemailService}. The UI should only show OMTP voicemails from
- * the current visual voicemail package.
+ * the current visual voicemail package. For example, the selection could be
+ * {@code WHERE (IS_OMTP_VOICEMAIL == 0) OR ( IS_OMTP_VOICEMAIL == 1 AND SOURCE_PACKAGE ==
+ * "current.vvm.package")}
*
* <P>Type: INTEGER (boolean)</P>
*
- * @hide
+ * @see android.telephony.TelephonyManager#getVisualVoicemailPackageName
*/
public static final String IS_OMTP_VOICEMAIL = "is_omtp_voicemail";
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index d857bf7..951aa8d 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -89,8 +89,9 @@
/**
* Exception class used to capture a stack trace in {@link #wtf}.
+ * @hide
*/
- private static class TerribleFailure extends Exception {
+ public static class TerribleFailure extends Exception {
TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2e9cbf2..eef1cf5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1920,7 +1920,7 @@
Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER,
mPendingEvents.size());
- Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, p);
+ Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
msg.setAsynchronous(true);
mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
return DISPATCH_IN_PROGRESS;
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
new file mode 100644
index 0000000..7d5f8ee
--- /dev/null
+++ b/core/jni/Android.bp
@@ -0,0 +1,277 @@
+cc_library_shared {
+ name: "libandroid_runtime",
+
+ cflags: [
+ "-Wno-unused-parameter",
+ "-Wno-non-virtual-dtor",
+ "-Wno-maybe-uninitialized",
+ "-Wno-parentheses",
+
+ "-DHWUI_NEW_OPS",
+
+ "-DGL_GLEXT_PROTOTYPES",
+ "-DEGL_EGLEXT_PROTOTYPES",
+
+ "-DU_USING_ICU_NAMESPACE=0",
+
+ "-Wall",
+ "-Werror",
+ "-Wno-error=deprecated-declarations",
+ "-Wunused",
+ "-Wunreachable-code",
+
+ // necessary for Clang as the GL bindings need to turn
+ // off a GCC warning that Clang doesn't know.
+ "-Wno-unknown-pragmas",
+ ],
+
+ cppflags: ["-Wno-conversion-null"],
+
+ srcs: [
+ "AndroidRuntime.cpp",
+ "com_android_internal_content_NativeLibraryHelper.cpp",
+ "com_google_android_gles_jni_EGLImpl.cpp",
+ "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
+ "android_app_Activity.cpp",
+ "android_app_ApplicationLoaders.cpp",
+ "android_app_NativeActivity.cpp",
+ "android_app_admin_SecurityLog.cpp",
+ "android_opengl_EGL14.cpp",
+ "android_opengl_EGLExt.cpp",
+ "android_opengl_GLES10.cpp",
+ "android_opengl_GLES10Ext.cpp",
+ "android_opengl_GLES11.cpp",
+ "android_opengl_GLES11Ext.cpp",
+ "android_opengl_GLES20.cpp",
+ "android_opengl_GLES30.cpp",
+ "android_opengl_GLES31.cpp",
+ "android_opengl_GLES31Ext.cpp",
+ "android_opengl_GLES32.cpp",
+ "android_database_CursorWindow.cpp",
+ "android_database_SQLiteCommon.cpp",
+ "android_database_SQLiteConnection.cpp",
+ "android_database_SQLiteGlobal.cpp",
+ "android_database_SQLiteDebug.cpp",
+ "android_graphics_drawable_AnimatedVectorDrawable.cpp",
+ "android_graphics_drawable_VectorDrawable.cpp",
+ "android_view_DisplayEventReceiver.cpp",
+ "android_view_DisplayListCanvas.cpp",
+ "android_view_GraphicBuffer.cpp",
+ "android_view_HardwareLayer.cpp",
+ "android_view_InputChannel.cpp",
+ "android_view_InputDevice.cpp",
+ "android_view_InputEventReceiver.cpp",
+ "android_view_InputEventSender.cpp",
+ "android_view_InputQueue.cpp",
+ "android_view_KeyCharacterMap.cpp",
+ "android_view_KeyEvent.cpp",
+ "android_view_MotionEvent.cpp",
+ "android_view_PointerIcon.cpp",
+ "android_view_RenderNode.cpp",
+ "android_view_RenderNodeAnimator.cpp",
+ "android_view_Surface.cpp",
+ "android_view_SurfaceControl.cpp",
+ "android_view_SurfaceSession.cpp",
+ "android_view_TextureView.cpp",
+ "android_view_ThreadedRenderer.cpp",
+ "android_view_VelocityTracker.cpp",
+ "android_text_AndroidCharacter.cpp",
+ "android_text_AndroidBidi.cpp",
+ "android_text_StaticLayout.cpp",
+ "android_os_Debug.cpp",
+ "android_os_GraphicsEnvironment.cpp",
+ "android_os_HwBinder.cpp",
+ "android_os_HwBlob.cpp",
+ "android_os_HwParcel.cpp",
+ "android_os_HwRemoteBinder.cpp",
+ "android_os_MemoryFile.cpp",
+ "android_os_MessageQueue.cpp",
+ "android_os_Parcel.cpp",
+ "android_os_SELinux.cpp",
+ "android_os_seccomp.cpp",
+ "android_os_SystemClock.cpp",
+ "android_os_SystemProperties.cpp",
+ "android_os_Trace.cpp",
+ "android_os_UEventObserver.cpp",
+ "android_os_VintfObject.cpp",
+ "android_os_VintfRuntimeInfo.cpp",
+ "android_net_LocalSocketImpl.cpp",
+ "android_net_NetUtils.cpp",
+ "android_net_TrafficStats.cpp",
+ "android_nio_utils.cpp",
+ "android_util_AssetManager.cpp",
+ "android_util_Binder.cpp",
+ "android_util_EventLog.cpp",
+ "android_util_MemoryIntArray.cpp",
+ "android_util_Log.cpp",
+ "android_util_PathParser.cpp",
+ "android_util_Process.cpp",
+ "android_util_StringBlock.cpp",
+ "android_util_XmlBlock.cpp",
+ "android_util_jar_StrictJarFile.cpp",
+ "android_graphics_Canvas.cpp",
+ "android_graphics_Picture.cpp",
+ "android/graphics/Bitmap.cpp",
+ "android/graphics/BitmapFactory.cpp",
+ "android/graphics/Camera.cpp",
+ "android/graphics/CanvasProperty.cpp",
+ "android/graphics/ColorFilter.cpp",
+ "android/graphics/DrawFilter.cpp",
+ "android/graphics/FontFamily.cpp",
+ "android/graphics/CreateJavaOutputStreamAdaptor.cpp",
+ "android/graphics/Graphics.cpp",
+ "android/graphics/HarfBuzzNGFaceSkia.cpp",
+ "android/graphics/Interpolator.cpp",
+ "android/graphics/MaskFilter.cpp",
+ "android/graphics/Matrix.cpp",
+ "android/graphics/Movie.cpp",
+ "android/graphics/NinePatch.cpp",
+ "android/graphics/NinePatchPeeker.cpp",
+ "android/graphics/Paint.cpp",
+ "android/graphics/Path.cpp",
+ "android/graphics/PathMeasure.cpp",
+ "android/graphics/PathEffect.cpp",
+ "android/graphics/Picture.cpp",
+ "android/graphics/PorterDuff.cpp",
+ "android/graphics/BitmapRegionDecoder.cpp",
+ "android/graphics/Rasterizer.cpp",
+ "android/graphics/Region.cpp",
+ "android/graphics/Shader.cpp",
+ "android/graphics/SurfaceTexture.cpp",
+ "android/graphics/Typeface.cpp",
+ "android/graphics/Utils.cpp",
+ "android/graphics/Xfermode.cpp",
+ "android/graphics/YuvToJpegEncoder.cpp",
+ "android/graphics/pdf/PdfDocument.cpp",
+ "android/graphics/pdf/PdfEditor.cpp",
+ "android/graphics/pdf/PdfRenderer.cpp",
+ "android_media_AudioRecord.cpp",
+ "android_media_AudioSystem.cpp",
+ "android_media_AudioTrack.cpp",
+ "android_media_DeviceCallback.cpp",
+ "android_media_JetPlayer.cpp",
+ "android_media_RemoteDisplay.cpp",
+ "android_media_ToneGenerator.cpp",
+ "android_hardware_Camera.cpp",
+ "android_hardware_camera2_CameraMetadata.cpp",
+ "android_hardware_camera2_legacy_LegacyCameraDevice.cpp",
+ "android_hardware_camera2_legacy_PerfMeasurement.cpp",
+ "android_hardware_camera2_DngCreator.cpp",
+ "android_hardware_Radio.cpp",
+ "android_hardware_SensorManager.cpp",
+ "android_hardware_SerialPort.cpp",
+ "android_hardware_SoundTrigger.cpp",
+ "android_hardware_UsbDevice.cpp",
+ "android_hardware_UsbDeviceConnection.cpp",
+ "android_hardware_UsbRequest.cpp",
+ "android_hardware_location_ContextHubService.cpp",
+ "android_hardware_location_ActivityRecognitionHardware.cpp",
+ "android_util_FileObserver.cpp",
+ "android/opengl/poly_clip.cpp", // TODO: .arm
+ "android/opengl/util.cpp",
+ "android_server_NetworkManagementSocketTagger.cpp",
+ "android_server_Watchdog.cpp",
+ "android_ddm_DdmHandleNativeHeap.cpp",
+ "android_backup_BackupDataInput.cpp",
+ "android_backup_BackupDataOutput.cpp",
+ "android_backup_FileBackupHelperBase.cpp",
+ "android_backup_BackupHelperDispatcher.cpp",
+ "android_app_backup_FullBackup.cpp",
+ "android_content_res_ObbScanner.cpp",
+ "android_content_res_Configuration.cpp",
+ "android_animation_PropertyValuesHolder.cpp",
+ "com_android_internal_net_NetworkStatsFactory.cpp",
+ "com_android_internal_os_PathClassLoaderFactory.cpp",
+ "com_android_internal_os_Zygote.cpp",
+ "com_android_internal_util_VirtualRefBasePtr.cpp",
+ "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
+ "hwbinder/EphemeralStorage.cpp",
+ "fd_utils.cpp",
+ ],
+
+ include_dirs: [
+ // we need to access the private Bionic header
+ // <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
+ "bionic/libc/private",
+
+ "frameworks/native/opengl/libs",
+
+ "external/skia/include/private",
+ "external/skia/src/core",
+ "external/skia/src/effects",
+ "external/skia/src/images",
+ "frameworks/base/media/jni",
+ "libcore/include",
+ ],
+
+ static_libs: [
+ "libseccomp_policy",
+ "libselinux",
+ "libcrypto",
+ ],
+
+ shared_libs: [
+ "libmemtrack",
+ "libandroidfw",
+ "libbase",
+ "libexpat",
+ "libnativehelper",
+ "liblog",
+ "libcutils",
+ "libdebuggerd_client",
+ "libutils",
+ "libbinder",
+ "libnetutils",
+ "libui",
+ "libgui",
+ "libinput",
+ "libinputflinger",
+ "libcamera_client",
+ "libcamera_metadata",
+ "libskia",
+ "libsqlite",
+ "libEGL",
+ "libGLESv1_CM",
+ "libGLESv2",
+ "libvulkan",
+ "libETC1",
+ "libhardware",
+ "libhardware_legacy",
+ "libselinux",
+ "libsonivox",
+ "libcrypto",
+ "libssl",
+ "libicuuc",
+ "libicui18n",
+ "libmedia",
+ "libaudioclient",
+ "libjpeg",
+ "libusbhost",
+ "libharfbuzz_ng",
+ "libz",
+ "libaudioutils",
+ "libpdfium",
+ "libimg_utils",
+ "libnetd_client",
+ "libradio",
+ "libsoundtrigger",
+ "libminikin",
+ "libprocessgroup",
+ "libnativebridge",
+ "libradio_metadata",
+ "libnativeloader",
+ "libmemunreachable",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "libvintf",
+
+ "libhwui",
+ "libdl",
+ ],
+
+ local_include_dirs: ["android/graphics"],
+ export_include_dirs: ["include"],
+ // AndroidRuntime.h depends on nativehelper/jni.h
+ export_shared_lib_headers: ["libnativehelper"],
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
deleted file mode 100644
index 0834625..0000000
--- a/core/jni/Android.mk
+++ /dev/null
@@ -1,308 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS += -DHAVE_CONFIG_H -DKHTML_NO_EXCEPTIONS -DGKWQ_NO_JAVA
-LOCAL_CFLAGS += -DNO_SUPPORT_JS_BINDING -DQT_NO_WHEELEVENT -DKHTML_NO_XBL
-LOCAL_CFLAGS += -U__APPLE__
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_CFLAGS += -Wno-non-virtual-dtor
-LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses
-LOCAL_CFLAGS += -DHWUI_NEW_OPS
-LOCAL_CPPFLAGS += -Wno-conversion-null
-
-ifeq ($(TARGET_ARCH), arm)
- LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))"
-else
- LOCAL_CFLAGS += -DPACKED=""
-endif
-
-ifneq ($(ENABLE_CPUSETS),)
- LOCAL_CFLAGS += -DENABLE_CPUSETS
-endif
-
-LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
-
-LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
-
-LOCAL_SRC_FILES:= \
- AndroidRuntime.cpp \
- com_android_internal_content_NativeLibraryHelper.cpp \
- com_google_android_gles_jni_EGLImpl.cpp \
- com_google_android_gles_jni_GLImpl.cpp.arm \
- android_app_Activity.cpp \
- android_app_ApplicationLoaders.cpp \
- android_app_NativeActivity.cpp \
- android_app_admin_SecurityLog.cpp \
- android_opengl_EGL14.cpp \
- android_opengl_EGLExt.cpp \
- android_opengl_GLES10.cpp \
- android_opengl_GLES10Ext.cpp \
- android_opengl_GLES11.cpp \
- android_opengl_GLES11Ext.cpp \
- android_opengl_GLES20.cpp \
- android_opengl_GLES30.cpp \
- android_opengl_GLES31.cpp \
- android_opengl_GLES31Ext.cpp \
- android_opengl_GLES32.cpp \
- android_database_CursorWindow.cpp \
- android_database_SQLiteCommon.cpp \
- android_database_SQLiteConnection.cpp \
- android_database_SQLiteGlobal.cpp \
- android_database_SQLiteDebug.cpp \
- android_graphics_drawable_AnimatedVectorDrawable.cpp \
- android_graphics_drawable_VectorDrawable.cpp \
- android_view_DisplayEventReceiver.cpp \
- android_view_DisplayListCanvas.cpp \
- android_view_GraphicBuffer.cpp \
- android_view_HardwareLayer.cpp \
- android_view_InputChannel.cpp \
- android_view_InputDevice.cpp \
- android_view_InputEventReceiver.cpp \
- android_view_InputEventSender.cpp \
- android_view_InputQueue.cpp \
- android_view_KeyCharacterMap.cpp \
- android_view_KeyEvent.cpp \
- android_view_MotionEvent.cpp \
- android_view_PointerIcon.cpp \
- android_view_RenderNode.cpp \
- android_view_RenderNodeAnimator.cpp \
- android_view_Surface.cpp \
- android_view_SurfaceControl.cpp \
- android_view_SurfaceSession.cpp \
- android_view_TextureView.cpp \
- android_view_ThreadedRenderer.cpp \
- android_view_VelocityTracker.cpp \
- android_text_AndroidCharacter.cpp \
- android_text_AndroidBidi.cpp \
- android_text_StaticLayout.cpp \
- android_os_Debug.cpp \
- android_os_GraphicsEnvironment.cpp \
- android_os_HwBinder.cpp \
- android_os_HwBlob.cpp \
- android_os_HwParcel.cpp \
- android_os_HwRemoteBinder.cpp \
- android_os_MemoryFile.cpp \
- android_os_MessageQueue.cpp \
- android_os_Parcel.cpp \
- android_os_SELinux.cpp \
- android_os_seccomp.cpp \
- android_os_SystemClock.cpp \
- android_os_SystemProperties.cpp \
- android_os_Trace.cpp \
- android_os_UEventObserver.cpp \
- android_os_VintfObject.cpp \
- android_net_LocalSocketImpl.cpp \
- android_net_NetUtils.cpp \
- android_net_TrafficStats.cpp \
- android_nio_utils.cpp \
- android_util_AssetManager.cpp \
- android_util_Binder.cpp \
- android_util_EventLog.cpp \
- android_util_MemoryIntArray.cpp \
- android_util_Log.cpp \
- android_util_PathParser.cpp \
- android_util_Process.cpp \
- android_util_StringBlock.cpp \
- android_util_XmlBlock.cpp \
- android_util_jar_StrictJarFile.cpp \
- android_graphics_Canvas.cpp \
- android_graphics_Picture.cpp \
- android/graphics/Bitmap.cpp \
- android/graphics/BitmapFactory.cpp \
- android/graphics/Camera.cpp \
- android/graphics/CanvasProperty.cpp \
- android/graphics/ColorFilter.cpp \
- android/graphics/DrawFilter.cpp \
- android/graphics/FontFamily.cpp \
- android/graphics/CreateJavaOutputStreamAdaptor.cpp \
- android/graphics/Graphics.cpp \
- android/graphics/HarfBuzzNGFaceSkia.cpp \
- android/graphics/Interpolator.cpp \
- android/graphics/MaskFilter.cpp \
- android/graphics/Matrix.cpp \
- android/graphics/Movie.cpp \
- android/graphics/NinePatch.cpp \
- android/graphics/NinePatchPeeker.cpp \
- android/graphics/Paint.cpp \
- android/graphics/Path.cpp \
- android/graphics/PathMeasure.cpp \
- android/graphics/PathEffect.cpp \
- android/graphics/Picture.cpp \
- android/graphics/PorterDuff.cpp \
- android/graphics/BitmapRegionDecoder.cpp \
- android/graphics/Rasterizer.cpp \
- android/graphics/Region.cpp \
- android/graphics/Shader.cpp \
- android/graphics/SurfaceTexture.cpp \
- android/graphics/Typeface.cpp \
- android/graphics/Utils.cpp \
- android/graphics/Xfermode.cpp \
- android/graphics/YuvToJpegEncoder.cpp \
- android/graphics/pdf/PdfDocument.cpp \
- android/graphics/pdf/PdfEditor.cpp \
- android/graphics/pdf/PdfRenderer.cpp \
- android_media_AudioRecord.cpp \
- android_media_AudioSystem.cpp \
- android_media_AudioTrack.cpp \
- android_media_DeviceCallback.cpp \
- android_media_JetPlayer.cpp \
- android_media_RemoteDisplay.cpp \
- android_media_ToneGenerator.cpp \
- android_hardware_Camera.cpp \
- android_hardware_camera2_CameraMetadata.cpp \
- android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
- android_hardware_camera2_legacy_PerfMeasurement.cpp \
- android_hardware_camera2_DngCreator.cpp \
- android_hardware_Radio.cpp \
- android_hardware_SensorManager.cpp \
- android_hardware_SerialPort.cpp \
- android_hardware_SoundTrigger.cpp \
- android_hardware_UsbDevice.cpp \
- android_hardware_UsbDeviceConnection.cpp \
- android_hardware_UsbRequest.cpp \
- android_hardware_location_ContextHubService.cpp \
- android_hardware_location_ActivityRecognitionHardware.cpp \
- android_util_FileObserver.cpp \
- android/opengl/poly_clip.cpp.arm \
- android/opengl/util.cpp \
- android_server_NetworkManagementSocketTagger.cpp \
- android_server_Watchdog.cpp \
- android_ddm_DdmHandleNativeHeap.cpp \
- android_backup_BackupDataInput.cpp \
- android_backup_BackupDataOutput.cpp \
- android_backup_FileBackupHelperBase.cpp \
- android_backup_BackupHelperDispatcher.cpp \
- android_app_backup_FullBackup.cpp \
- android_content_res_ObbScanner.cpp \
- android_content_res_Configuration.cpp \
- android_animation_PropertyValuesHolder.cpp \
- com_android_internal_net_NetworkStatsFactory.cpp \
- com_android_internal_os_PathClassLoaderFactory.cpp \
- com_android_internal_os_Zygote.cpp \
- com_android_internal_util_VirtualRefBasePtr.cpp \
- com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \
- hwbinder/EphemeralStorage.cpp \
- fd_utils.cpp \
-
-LOCAL_C_INCLUDES += \
- $(LOCAL_PATH)/include \
- $(JNI_H_INCLUDE) \
- $(LOCAL_PATH)/android/graphics \
- $(LOCAL_PATH)/../../libs/hwui \
- $(LOCAL_PATH)/../../../native/opengl/libs \
- $(LOCAL_PATH)/../../../native/vulkan/include \
- $(call include-path-for, bluedroid) \
- $(call include-path-for, libhardware)/hardware \
- $(call include-path-for, libhardware_legacy)/hardware_legacy \
- $(TOP)/frameworks/base/media/jni \
- $(TOP)/system/core/base/include \
- $(TOP)/system/core/include \
- $(TOP)/system/media/camera/include \
- $(TOP)/system/netd/include \
- external/pdfium/core/include/fpdfapi \
- external/pdfium/fpdfsdk/include \
- external/pdfium/public \
- external/pdfium \
- external/skia/include/private \
- external/skia/src/core \
- external/skia/src/effects \
- external/skia/src/images \
- external/sqlite/dist \
- external/sqlite/android \
- external/expat/lib \
- external/tremor/Tremor \
- external/harfbuzz_ng/src \
- libcore/include \
- $(call include-path-for, audio-utils) \
- frameworks/minikin/include \
- external/freetype/include
-# TODO: clean up Minikin so it doesn't need the freetype include
-
-LOCAL_STATIC_LIBRARIES := \
- libseccomp_policy \
- libselinux \
- libcrypto \
-
-LOCAL_SHARED_LIBRARIES := \
- libmemtrack \
- libandroidfw \
- libbase \
- libexpat \
- libnativehelper \
- liblog \
- libcutils \
- libdebuggerd_client \
- libutils \
- libbinder \
- libnetutils \
- libui \
- libgui \
- libinput \
- libinputflinger \
- libcamera_client \
- libcamera_metadata \
- libskia \
- libsqlite \
- libEGL \
- libGLESv1_CM \
- libGLESv2 \
- libvulkan \
- libETC1 \
- libhardware \
- libhardware_legacy \
- libselinux \
- libsonivox \
- libcrypto \
- libssl \
- libicuuc \
- libicui18n \
- libmedia \
- libaudioclient \
- libjpeg \
- libusbhost \
- libharfbuzz_ng \
- libz \
- libaudioutils \
- libpdfium \
- libimg_utils \
- libnetd_client \
- libradio \
- libsoundtrigger \
- libminikin \
- libprocessgroup \
- libnativebridge \
- libradio_metadata \
- libnativeloader \
- libmemunreachable \
- libhidlbase \
- libhidltransport \
- libhwbinder \
- libvintf \
-
-LOCAL_SHARED_LIBRARIES += \
- libhwui \
- libdl
-
-# we need to access the private Bionic header
-# <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
-LOCAL_C_INCLUDES += bionic/libc/private
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-
-# AndroidRuntime.h depends on nativehelper/jni.h
-LOCAL_EXPORT_C_INCLUDE_DIRS += libnativehelper/include
-
-LOCAL_MODULE:= libandroid_runtime
-
-# -Wno-unknown-pragmas: necessary for Clang as the GL bindings need to turn
-# off a GCC warning that Clang doesn't know.
-LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code \
- -Wno-unknown-pragmas
-
-# -Wno-c++11-extensions: Clang warns about Skia using the C++11 override keyword, but this project
-# is not being compiled with that level. Remove once this has changed.
-LOCAL_CLANG_CFLAGS += -Wno-c++11-extensions
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 898c27e..2b5c0d6 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -165,6 +165,7 @@
extern int register_android_os_Parcel(JNIEnv* env);
extern int register_android_os_SELinux(JNIEnv* env);
extern int register_android_os_VintfObject(JNIEnv *env);
+extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
extern int register_android_os_seccomp(JNIEnv* env);
extern int register_android_os_SystemProperties(JNIEnv *env);
extern int register_android_os_SystemClock(JNIEnv* env);
@@ -529,7 +530,7 @@
/*
* Reads a "property" into "buffer". If the property is non-empty, it
* is treated as a dex2oat compiler option that should be
- * passed as a quoted option, e.g. "-Ximage-compiler-option --compiler-filter=verify-none".
+ * passed as a quoted option, e.g. "-Ximage-compiler-option --compiler-filter=assume-verified".
*
* The "compilerArg" is a prefix for the option such as "--compiler-filter=".
*
@@ -605,6 +606,7 @@
{
JavaVMInitArgs initArgs;
char propBuf[PROPERTY_VALUE_MAX];
+ char stackTraceDirBuf[sizeof("-Xstacktracedir:")-1 + PROPERTY_VALUE_MAX];
char stackTraceFileBuf[sizeof("-Xstacktracefile:")-1 + PROPERTY_VALUE_MAX];
char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX];
char heapstartsizeOptsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
@@ -682,7 +684,12 @@
executionMode = kEMJitCompiler;
}
- parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");
+ // If dalvik.vm.stack-trace-dir is set, it enables the "new" stack trace
+ // dump scheme and a new file is created for each stack dump. If it isn't set,
+ // the old scheme is enabled.
+ if (!parseRuntimeOption("dalvik.vm.stack-trace-dir", stackTraceDirBuf, "-Xstacktracedir:")) {
+ parseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");
+ }
strcpy(jniOptsBuf, "-Xjniopts:");
if (parseRuntimeOption("dalvik.vm.jniopts", jniOptsBuf, "-Xjniopts:")) {
@@ -777,7 +784,7 @@
"-Xmx", "-Ximage-compiler-option");
if (skip_compilation) {
addOption("-Ximage-compiler-option");
- addOption("--compiler-filter=verify-none");
+ addOption("--compiler-filter=assume-verified");
} else {
parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
"--compiler-filter=", "-Ximage-compiler-option");
@@ -808,7 +815,7 @@
"-Xmx", "-Xcompiler-option");
if (skip_compilation) {
addOption("-Xcompiler-option");
- addOption("--compiler-filter=verify-none");
+ addOption("--compiler-filter=assume-verified");
// We skip compilation when a minimal runtime is brought up for decryption. In that case
// /data is temporarily backed by a tmpfs, which is usually small.
@@ -1308,6 +1315,7 @@
REG_JNI(register_android_os_HwParcel),
REG_JNI(register_android_os_HwRemoteBinder),
REG_JNI(register_android_os_VintfObject),
+ REG_JNI(register_android_os_VintfRuntimeInfo),
REG_JNI(register_android_nio_utils),
REG_JNI(register_android_graphics_Canvas),
REG_JNI(register_android_graphics_Graphics),
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 4b279f63..44a3555 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -22,7 +22,7 @@
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
-#include "activity_recognition.h"
+#include <hardware/activity_recognition.h>
// keep base connection data from the HAL
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index fbccfd5..baeda96 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "context_hub.h"
+#include <hardware/context_hub.h>
#define LOG_NDEBUG 0
#define LOG_TAG "ContextHubService"
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 678041f..b21ea828 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -574,7 +574,7 @@
size_t parentHandle;
const hidl_string *s;
- status_t err = parcel->readBuffer(&parentHandle,
+ status_t err = parcel->readBuffer(sizeof(*s), &parentHandle,
reinterpret_cast<const void**>(&s));
if (err != OK) {
@@ -583,7 +583,7 @@
}
err = ::android::hardware::readEmbeddedFromParcel(
- const_cast<hidl_string *>(s),
+ const_cast<hidl_string &>(*s),
*parcel, parentHandle, 0 /* parentOffset */);
if (err != OK) {
@@ -602,7 +602,7 @@
size_t parentHandle; \
\
const hidl_vec<Type> *vec; \
- status_t err = parcel->readBuffer(&parentHandle, \
+ status_t err = parcel->readBuffer(sizeof(*vec), &parentHandle, \
reinterpret_cast<const void**>(&vec)); \
\
if (err != OK) { \
@@ -613,7 +613,7 @@
size_t childHandle; \
\
err = ::android::hardware::readEmbeddedFromParcel( \
- const_cast<hidl_vec<Type> *>(vec), \
+ const_cast<hidl_vec<Type> &>(*vec), \
*parcel, \
parentHandle, \
0 /* parentOffset */, \
@@ -645,7 +645,7 @@
size_t parentHandle;
const hidl_vec<bool> *vec;
- status_t err = parcel->readBuffer(&parentHandle,
+ status_t err = parcel->readBuffer(sizeof(*vec), &parentHandle,
reinterpret_cast<const void**>(&vec));
if (err != OK) {
@@ -656,7 +656,7 @@
size_t childHandle;
err = ::android::hardware::readEmbeddedFromParcel(
- const_cast<hidl_vec<bool> *>(vec),
+ const_cast<hidl_vec<bool> &>(*vec),
*parcel,
parentHandle,
0 /* parentOffset */,
@@ -709,7 +709,7 @@
size_t parentHandle;
const string_vec *vec;
- status_t err = parcel->readBuffer(&parentHandle,
+ status_t err = parcel->readBuffer(sizeof(*vec), &parentHandle,
reinterpret_cast<const void **>(&vec));
if (err != OK) {
@@ -719,16 +719,15 @@
size_t childHandle;
err = ::android::hardware::readEmbeddedFromParcel(
- const_cast<string_vec *>(vec),
+ const_cast<string_vec &>(*vec),
*parcel, parentHandle, 0 /* parentOffset */, &childHandle);
for (size_t i = 0; (err == OK) && (i < vec->size()); ++i) {
err = android::hardware::readEmbeddedFromParcel(
- const_cast<hidl_vec<hidl_string> *>(vec),
+ const_cast<hidl_string &>((*vec)[i]),
*parcel,
childHandle,
- i * sizeof(hidl_string),
- nullptr /* childHandle */);
+ i * sizeof(hidl_string) /* parentOffset */);
}
if (err != OK) {
@@ -810,13 +809,20 @@
return JHwRemoteBinder::NewObject(env, binder);
}
-static jobject JHwParcel_native_readBuffer(JNIEnv *env, jobject thiz) {
+static jobject JHwParcel_native_readBuffer(JNIEnv *env, jobject thiz,
+ jlong expectedSize) {
hardware::Parcel *parcel =
JHwParcel::GetNativeContext(env, thiz)->getParcel();
size_t handle;
const void *ptr;
- status_t status = parcel->readBuffer(&handle, &ptr);
+
+ if (expectedSize < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return nullptr;
+ }
+
+ status_t status = parcel->readBuffer(expectedSize, &handle, &ptr);
if (status != OK) {
jniThrowException(env, "java/util/NoSuchElementException", NULL);
@@ -827,8 +833,8 @@
}
static jobject JHwParcel_native_readEmbeddedBuffer(
- JNIEnv *env, jobject thiz, jlong parentHandle, jlong offset,
- jboolean nullable) {
+ JNIEnv *env, jobject thiz, jlong expectedSize,
+ jlong parentHandle, jlong offset, jboolean nullable) {
hardware::Parcel *parcel =
JHwParcel::GetNativeContext(env, thiz)->getParcel();
@@ -836,8 +842,13 @@
const void *ptr;
status_t status =
- parcel->readNullableEmbeddedBuffer(&childHandle, parentHandle, offset,
- &ptr);
+ parcel->readNullableEmbeddedBuffer(expectedSize,
+ &childHandle, parentHandle, offset, &ptr);
+
+ if (expectedSize < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return nullptr;
+ }
if (status != OK) {
jniThrowException(env, "java/util/NoSuchElementException", NULL);
@@ -952,10 +963,10 @@
{ "send", "()V", (void *)JHwParcel_native_send },
- { "readBuffer", "()L" PACKAGE_PATH "/HwBlob;",
+ { "readBuffer", "(J)L" PACKAGE_PATH "/HwBlob;",
(void *)JHwParcel_native_readBuffer },
- { "readEmbeddedBuffer", "(JJZ)L" PACKAGE_PATH "/HwBlob;",
+ { "readEmbeddedBuffer", "(JJJZ)L" PACKAGE_PATH "/HwBlob;",
(void *)JHwParcel_native_readEmbeddedBuffer },
{ "writeBuffer", "(L" PACKAGE_PATH "/HwBlob;)V",
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 9491a1e..fa9379e 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -16,41 +16,84 @@
#define LOG_TAG "VintfObject"
//#define LOG_NDEBUG 0
+#include <android-base/logging.h>
+
+#include <vector>
+#include <string>
#include <JNIHelp.h>
#include <vintf/VintfObject.h>
+#include <vintf/parse_string.h>
#include <vintf/parse_xml.h>
#include "core_jni_helpers.h"
+static jclass gString;
+static jclass gHashMapClazz;
+static jmethodID gHashMapInit;
+static jmethodID gHashMapPut;
+
namespace android {
using vintf::HalManifest;
-using vintf::RuntimeInfo;
+using vintf::SchemaType;
using vintf::VintfObject;
+using vintf::XmlConverter;
+using vintf::Vndk;
using vintf::gHalManifestConverter;
+using vintf::gCompatibilityMatrixConverter;
+using vintf::to_string;
-static jstring android_os_VintfObject_getDeviceManifest(JNIEnv* env, jclass clazz)
-{
- const HalManifest *manifest = VintfObject::GetDeviceHalManifest();
- if (manifest == nullptr) {
- return nullptr;
+template<typename V>
+static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
+ size_t i;
+ typename V::const_iterator it;
+ jobjectArray ret = env->NewObjectArray(v.size(), gString, NULL /* init element */);
+ for (i = 0, it = v.begin(); it != v.end(); ++i, ++it) {
+ env->SetObjectArrayElement(ret, i, env->NewStringUTF(it->c_str()));
}
- std::string xml = gHalManifestConverter(*manifest);
- return env->NewStringUTF(xml.c_str());
+ return ret;
}
-static jstring android_os_VintfObject_getFrameworkManifest(JNIEnv* env, jclass clazz)
-{
- const HalManifest *manifest = VintfObject::GetFrameworkHalManifest();
- if (manifest == nullptr) {
- return nullptr;
+template<typename T>
+static void tryAddSchema(const T* object, const XmlConverter<T>& converter,
+ const std::string& description,
+ std::vector<std::string>* cStrings) {
+ if (object == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << "Cannot get " << description;
+ } else {
+ cStrings->push_back(converter(*object));
}
- std::string xml = gHalManifestConverter(*manifest);
- return env->NewStringUTF(xml.c_str());
}
-static jint android_os_VintfObject_verify(JNIEnv *env, jclass clazz, jobjectArray packageInfo) {
+static void tryAddHalNamesAndVersions(const HalManifest *manifest,
+ const std::string& description,
+ std::set<std::string> *output) {
+ if (manifest == nullptr) {
+ LOG(WARNING) << __FUNCTION__ << "Cannot get " << description;
+ } else {
+ auto names = manifest->getHalNamesAndVersions();
+ output->insert(names.begin(), names.end());
+ }
+}
+
+static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass)
+{
+ std::vector<std::string> cStrings;
+
+ tryAddSchema(VintfObject::GetDeviceHalManifest(), gHalManifestConverter,
+ "device manifest", &cStrings);
+ tryAddSchema(VintfObject::GetFrameworkHalManifest(), gHalManifestConverter,
+ "framework manifest", &cStrings);
+ tryAddSchema(VintfObject::GetDeviceCompatibilityMatrix(), gCompatibilityMatrixConverter,
+ "device compatibility matrix", &cStrings);
+ tryAddSchema(VintfObject::GetFrameworkCompatibilityMatrix(), gCompatibilityMatrixConverter,
+ "framework compatibility matrix", &cStrings);
+
+ return toJavaStringArray(env, cStrings);
+}
+
+static jint android_os_VintfObject_verify(JNIEnv* env, jclass, jobjectArray packageInfo) {
size_t count = env->GetArrayLength(packageInfo);
std::vector<std::string> cPackageInfo{count};
for (size_t i = 0; i < count; ++i) {
@@ -63,18 +106,61 @@
return status;
}
+static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, jclass) {
+ std::set<std::string> halNames;
+ tryAddHalNamesAndVersions(VintfObject::GetDeviceHalManifest(),
+ "device manifest", &halNames);
+ tryAddHalNamesAndVersions(VintfObject::GetFrameworkHalManifest(),
+ "framework manifest", &halNames);
+ return toJavaStringArray(env, halNames);
+}
+
+static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) {
+ const HalManifest *manifest = VintfObject::GetDeviceHalManifest();
+ if (manifest == nullptr || manifest->type() != SchemaType::DEVICE) {
+ LOG(WARNING) << __FUNCTION__ << "Cannot get device manifest";
+ return nullptr;
+ }
+ std::string cString = to_string(manifest->sepolicyVersion());
+ return env->NewStringUTF(cString.c_str());
+}
+
+static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) {
+ const HalManifest *manifest = VintfObject::GetFrameworkHalManifest();
+ if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) {
+ LOG(WARNING) << __FUNCTION__ << "Cannot get framework manifest";
+ return nullptr;
+ }
+ jobject jMap = env->NewObject(gHashMapClazz, gHashMapInit);
+ for (const Vndk &vndk : manifest->vndks()) {
+ std::string key = to_string(vndk.versionRange());
+ env->CallObjectMethod(jMap, gHashMapPut,
+ env->NewStringUTF(key.c_str()), toJavaStringArray(env, vndk.libraries()));
+ }
+ return jMap;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gVintfObjectMethods[] = {
- {"getDeviceManifest", "()Ljava/lang/String;", (void*)android_os_VintfObject_getDeviceManifest},
- {"getFrameworkManifest", "()Ljava/lang/String;", (void*)android_os_VintfObject_getFrameworkManifest},
- {"verify", "([Ljava/lang/String;)I", (void*)android_os_VintfObject_verify},
+ {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
+ {"verify", "([Ljava/lang/String;)I", (void*)android_os_VintfObject_verify},
+ {"getHalNamesAndVersions", "()[Ljava/lang/String;", (void*)android_os_VintfObject_getHalNamesAndVersions},
+ {"getSepolicyVersion", "()Ljava/lang/String;", (void*)android_os_VintfObject_getSepolicyVersion},
+ {"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
};
const char* const kVintfObjectPathName = "android/os/VintfObject";
int register_android_os_VintfObject(JNIEnv* env)
{
+
+ gString = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/String"));
+ gHashMapClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/util/HashMap"));
+ gHashMapInit = GetMethodIDOrDie(env, gHashMapClazz, "<init>", "()V");
+ gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz,
+ "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+
return RegisterMethodsOrDie(env, kVintfObjectPathName, gVintfObjectMethods,
NELEM(gVintfObjectMethods));
}
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
new file mode 100644
index 0000000..ecb6854
--- /dev/null
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VintfRuntimeInfo"
+//#define LOG_NDEBUG 0
+
+#include <JNIHelp.h>
+#include <vintf/VintfObject.h>
+#include <vintf/parse_string.h>
+#include <vintf/parse_xml.h>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+using vintf::RuntimeInfo;
+using vintf::VintfObject;
+
+#define MAP_STRING_METHOD(javaMethod, cppString) \
+ static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) \
+ { \
+ const RuntimeInfo *info = VintfObject::GetRuntimeInfo(); \
+ if (info == nullptr) return nullptr; \
+ return env->NewStringUTF((cppString).c_str()); \
+ } \
+
+MAP_STRING_METHOD(getCpuInfo, info->cpuInfo());
+MAP_STRING_METHOD(getOsName, info->osName());
+MAP_STRING_METHOD(getNodeName, info->nodeName());
+MAP_STRING_METHOD(getOsRelease, info->osRelease());
+MAP_STRING_METHOD(getOsVersion, info->osVersion());
+MAP_STRING_METHOD(getHardwareId, info->hardwareId());
+MAP_STRING_METHOD(getKernelVersion, vintf::to_string(info->kernelVersion()));
+MAP_STRING_METHOD(getBootAvbVersion, vintf::to_string(info->bootAvbVersion()));
+MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()));
+
+
+static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz)
+{
+ const RuntimeInfo *info = VintfObject::GetRuntimeInfo();
+ if (info == nullptr) return 0;
+ return static_cast<jlong>(info->kernelSepolicyVersion());
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gVintfRuntimeInfoMethods[] = {
+ {"getKernelSepolicyVersion", "()J", (void*)android_os_VintfRuntimeInfo_getKernelSepolicyVersion},
+ {"getCpuInfo", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getCpuInfo},
+ {"getOsName", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getOsName},
+ {"getNodeName", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getNodeName},
+ {"getOsRelease", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getOsRelease},
+ {"getOsVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getOsVersion},
+ {"getHardwareId", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getHardwareId},
+ {"getKernelVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getKernelVersion},
+ {"getBootAvbVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getBootAvbVersion},
+ {"getBootVbmetaAvbVersion", "()Ljava/lang/String;", (void*)android_os_VintfRuntimeInfo_getBootVbmetaAvbVersion},
+};
+
+const char* const kVintfRuntimeInfoPathName = "android/os/VintfRuntimeInfo";
+
+int register_android_os_VintfRuntimeInfo(JNIEnv* env)
+{
+ return RegisterMethodsOrDie(env, kVintfRuntimeInfoPathName, gVintfRuntimeInfoMethods, NELEM(gVintfRuntimeInfoMethods));
+}
+
+};
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index b95258b..6000fb5 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -685,11 +685,14 @@
// Grant CAP_WAKE_ALARM to the Bluetooth process.
// Additionally, allow bluetooth to open packet sockets so it can start the DHCP client.
+ // Grant CAP_SYS_NICE to allow Bluetooth to set RT priority for
+ // audio-related threads.
// TODO: consider making such functionality an RPC to netd.
if (multiuser_get_app_id(uid) == AID_BLUETOOTH) {
capabilities |= (1LL << CAP_WAKE_ALARM);
capabilities |= (1LL << CAP_NET_RAW);
capabilities |= (1LL << CAP_NET_BIND_SERVICE);
+ capabilities |= (1LL << CAP_SYS_NICE);
}
// Grant CAP_BLOCK_SUSPEND to processes that belong to GID "wakelock"
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f5f67d06..c2f27a9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -788,16 +788,6 @@
android:description="@string/permdesc_callPhone"
android:protectionLevel="dangerous" />
- <!-- Allows an application to manage its own calls, but rely on the system to route focus to the
- currently active call.
- <p>Protection level: dangerous
- -->
- <permission android:name="android.permission.MANAGE_OWN_CALLS"
- android:permissionGroup="android.permission-group.PHONE"
- android:label="@string/permlab_manageOwnCalls"
- android:description="@string/permdesc_manageOwnCalls"
- android:protectionLevel="dangerous" />
-
<!-- Allows an application to access the IMS call service: making and
modifying a call
<p>Protection level: signature|privileged
@@ -875,6 +865,17 @@
android:description="@string/permdesc_processOutgoingCalls"
android:protectionLevel="dangerous" />
+ <!-- Allows a calling application which manages it own calls through the self-managed
+ {@link android.telecom.ConnectionService} APIs. See
+ {@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED for more information on the
+ self-managed ConnectionService APIs.
+ <p>Protection level: normal
+ -->
+ <permission android:name="android.permission.MANAGE_OWN_CALLS"
+ android:label="@string/permlab_manageOwnCalls"
+ android:description="@string/permdesc_manageOwnCalls"
+ android:protectionLevel="normal" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device microphone -->
<!-- ====================================================================== -->
@@ -2093,6 +2094,22 @@
<permission android:name="android.permission.UPDATE_CONFIG"
android:protectionLevel="signature|privileged" />
+ <!-- Allows a time zone rule updater application to request
+ the system installs / uninstalls timezone rules.
+ <p>An application requesting this permission is responsible for
+ verifying the source and integrity of the update before passing
+ it off to the installer components.
+ @hide -->
+ <permission android:name="android.permission.UPDATE_TIME_ZONE_RULES"
+ android:protectionLevel="signature|privileged" />
+
+ <!-- Must be required by a time zone rule updater application,
+ to ensure that only the system can trigger it.
+ @hide -->
+ <permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.TRIGGER_TIME_ZONE_RULES_CHECK"/>
+
<!-- Allows the system to reset throttling in shortcut manager.
@hide -->
<permission android:name="android.permission.RESET_SHORTCUT_MANAGER_THROTTLING"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 589aa07..22a1a36 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1282,6 +1282,49 @@
<!-- True if WallpaperService is enabled -->
<bool name="config_enableWallpaperService">true</bool>
+ <!-- Enables the TimeZoneRuleManager service. This is the master switch for the updateable time
+ zone update mechanism. -->
+ <bool name="config_enableUpdateableTimeZoneRules">false</bool>
+
+ <!-- Enables APK-based time zone update triggering. Set this to false when updates are triggered
+ via external events and not by APK updates. For example, if an updater checks with a server
+ on a regular schedule.
+ [This is only used if config_enableUpdateableTimeZoneRules is true.] -->
+ <bool name="config_timeZoneRulesUpdateTrackingEnabled">false</bool>
+
+ <!-- The package of the time zone rules updater application. Expected to be the same
+ for all Android devices that support APK-based time zone rule updates.
+ A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+ will be sent to the updater app if the system server detects an update to the updater or
+ data app packages.
+ The package referenced here must have the android.permission.UPDATE_TIME_ZONE_RULES
+ permission.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <string name="config_timeZoneRulesUpdaterPackage" translateable="false"></string>
+
+ <!-- The package of the time zone rules data application. Expected to be configured
+ by OEMs to reference their own priv-app APK package.
+ A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+ will be sent to the updater app if the system server detects an update to the updater or
+ data app packages.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <string name="config_timeZoneRulesDataPackage" translateable="false"></string>
+
+ <!-- The allowed time in milliseconds between an update check intent being broadcast and the
+ response being considered overdue. Reliability triggers will not fire in this time.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <!-- 5 minutes -->
+ <integer name="config_timeZoneRulesCheckTimeMillisAllowed">300000</integer>
+
+ <!-- The number of times a time zone update check is allowed to fail before the system will stop
+ reacting to reliability triggers.
+ [This is only used if config_enableUpdateableTimeZoneRules and
+ config_timeZoneRulesUpdateTrackingEnabled are true.] -->
+ <integer name="config_timeZoneRulesCheckRetryCount">5</integer>
+
<!-- Whether to enable network location overlay which allows network
location provider to be replaced by an app at run-time. When disabled,
only the config_networkLocationProviderPackageName package will be
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 99dc9b4..ea89cc1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,12 @@
<java-symbol type="bool" name="split_action_bar_is_narrow" />
<java-symbol type="bool" name="config_useVolumeKeySounds" />
<java-symbol type="bool" name="config_enableWallpaperService" />
+ <java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
+ <java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
+ <java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
+ <java-symbol type="string" name="config_timeZoneRulesDataPackage" />
+ <java-symbol type="integer" name="config_timeZoneRulesCheckTimeMillisAllowed" />
+ <java-symbol type="integer" name="config_timeZoneRulesCheckRetryCount" />
<java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
<java-symbol type="bool" name="config_enableScreenshotChord" />
<java-symbol type="bool" name="config_bluetooth_default_profiles" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java
index 411a3f8..37b2a50 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java
@@ -72,8 +72,6 @@
getAddress();
} else if ("getBondedDevices".equals(command)) {
getBondedDevices();
- } else if ("enableBtSnoop".equals(command)) {
- enableBtSnoop();
} else {
finish(null);
}
@@ -116,12 +114,6 @@
finish(mSuccessResult);
}
- public void enableBtSnoop() {
- Assert.assertTrue("failed to enable snoop log",
- getBluetoothAdapter().configHciSnoopLog(true));
- finish(mSuccessResult);
- }
-
public void finish(Bundle result) {
if (result == null) {
result = new Bundle();
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
new file mode 100644
index 0000000..9bbcd3d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link DistroFormatVersion}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class DistroFormatVersionTest {
+
+ @Test
+ public void equalsAndHashCode() {
+ DistroFormatVersion one = new DistroFormatVersion(1, 2);
+ assertEqualsContract(one, one);
+
+ DistroFormatVersion two = new DistroFormatVersion(1, 2);
+ assertEqualsContract(one, two);
+
+ DistroFormatVersion three = new DistroFormatVersion(2, 1);
+ assertFalse(one.equals(three));
+ }
+
+ @Test
+ public void parcelable() {
+ DistroFormatVersion version = new DistroFormatVersion(2, 3);
+
+ Parcel parcel = Parcel.obtain();
+ version.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ DistroFormatVersion newVersion = DistroFormatVersion.CREATOR.createFromParcel(parcel);
+
+ assertEquals(version, newVersion);
+ }
+
+ @Test
+ public void supportsVersion() {
+ DistroFormatVersion deviceVersion = new DistroFormatVersion(2, 2);
+ assertTrue(deviceVersion.supports(deviceVersion));
+
+ DistroFormatVersion sameVersion = new DistroFormatVersion(2, 2);
+ assertTrue(deviceVersion.supports(sameVersion));
+
+ // Minor versions are backwards compatible.
+ DistroFormatVersion sameMajorNewerMinor = new DistroFormatVersion(2, 3);
+ assertTrue(deviceVersion.supports(sameMajorNewerMinor));
+ DistroFormatVersion sameMajorOlderMinor = new DistroFormatVersion(2, 1);
+ assertFalse(deviceVersion.supports(sameMajorOlderMinor));
+
+ // Major versions are not backwards compatible.
+ DistroFormatVersion newerMajor = new DistroFormatVersion(1, 2);
+ assertFalse(deviceVersion.supports(newerMajor));
+ DistroFormatVersion olderMajor = new DistroFormatVersion(3, 2);
+ assertFalse(deviceVersion.supports(olderMajor));
+ }
+
+ private static void assertEqualsContract(DistroFormatVersion one, DistroFormatVersion two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
new file mode 100644
index 0000000..2fbc9a1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link DistroRulesVersion}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class DistroRulesVersionTest {
+
+ @Test
+ public void equalsAndHashCode() {
+ DistroRulesVersion one = new DistroRulesVersion("2016a", 2);
+ assertEqualsContract(one, one);
+
+ DistroRulesVersion two = new DistroRulesVersion("2016a", 2);
+ assertEqualsContract(one, two);
+
+ DistroRulesVersion three = new DistroRulesVersion("2016b", 1);
+ assertFalse(one.equals(three));
+ }
+
+ @Test
+ public void parcelable() {
+ DistroRulesVersion version = new DistroRulesVersion("2016a", 2);
+
+ Parcel parcel = Parcel.obtain();
+ version.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ DistroRulesVersion newVersion = DistroRulesVersion.CREATOR.createFromParcel(parcel);
+
+ assertEquals(version, newVersion);
+ }
+
+ @Test
+ public void isOlderThan() {
+ DistroRulesVersion deviceVersion = new DistroRulesVersion("2016b", 2);
+ assertFalse(deviceVersion.isOlderThan(deviceVersion));
+
+ DistroRulesVersion sameVersion = new DistroRulesVersion("2016b", 2);
+ assertFalse(deviceVersion.isOlderThan(sameVersion));
+
+ DistroRulesVersion sameRulesNewerRevision = new DistroRulesVersion("2016b", 3);
+ assertTrue(deviceVersion.isOlderThan(sameRulesNewerRevision));
+
+ DistroRulesVersion sameRulesOlderRevision = new DistroRulesVersion("2016b", 1);
+ assertFalse(deviceVersion.isOlderThan(sameRulesOlderRevision));
+
+ DistroRulesVersion newerRules = new DistroRulesVersion("2016c", 2);
+ assertTrue(deviceVersion.isOlderThan(newerRules));
+
+ DistroRulesVersion olderRules = new DistroRulesVersion("2016a", 2);
+ assertFalse(deviceVersion.isOlderThan(olderRules));
+ }
+
+ private static void assertEqualsContract(DistroRulesVersion one, DistroRulesVersion two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
new file mode 100644
index 0000000..a9357c9
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.app.timezone;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link RulesState}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class RulesStateTest {
+
+ @Test
+ public void equalsAndHashCode() {
+ RulesState one = new RulesState(
+ "2016a", formatVersion(1, 2), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertEqualsContract(one, one);
+
+ RulesState two = new RulesState(
+ "2016a", formatVersion(1, 2), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertEqualsContract(one, two);
+
+ RulesState differentSystemRules = new RulesState(
+ "2016b", formatVersion(1, 2), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentSystemRules));
+
+ RulesState differentFormatVersion = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentFormatVersion));
+
+ RulesState differentOperationInProgress = new RulesState(
+ "2016a", formatVersion(1, 1), true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ assertFalse(one.equals(differentOperationInProgress));
+
+ RulesState differentStagedOperation = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentStagedOperation));
+
+ RulesState differentStagedInstallVersion = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 4),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 2));
+ assertFalse(one.equals(differentStagedInstallVersion));
+
+ RulesState differentInstalled = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ assertFalse(one.equals(differentInstalled));
+
+ RulesState differentInstalledVersion = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016a", 3),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+ assertFalse(one.equals(differentInstalledVersion));
+ }
+
+ @Test
+ public void parcelable() {
+ RulesState rulesState1 = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, rulesVersion("2016b", 2),
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+ checkParcelableRoundTrip(rulesState1);
+
+ RulesState rulesStateWithNulls = new RulesState(
+ "2016a", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ checkParcelableRoundTrip(rulesStateWithNulls);
+
+ RulesState rulesStateWithUnknowns = new RulesState(
+ "2016a", formatVersion(1, 1), true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ checkParcelableRoundTrip(rulesStateWithNulls);
+ }
+
+ private static void checkParcelableRoundTrip(RulesState rulesState) {
+ Parcel parcel = Parcel.obtain();
+ rulesState.writeToParcel(parcel, 0 /* flags */);
+ parcel.setDataPosition(0);
+
+ RulesState newVersion = RulesState.CREATOR.createFromParcel(parcel);
+
+ assertEquals(rulesState, newVersion);
+ }
+
+ @Test
+ public void isSystemVersionOlderThan() {
+ RulesState rulesState = new RulesState(
+ "2016b", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, rulesVersion("2016b", 3));
+ assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016a", 1)));
+ assertFalse(rulesState.isSystemVersionOlderThan(rulesVersion("2016b", 1)));
+ assertTrue(rulesState.isSystemVersionOlderThan(rulesVersion("2016c", 1)));
+ }
+
+ @Test
+ public void isInstalledDistroOlderThan() {
+ RulesState operationInProgress = new RulesState(
+ "2016b", formatVersion(1, 1), true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* installedDistroRulesVersion */);
+ try {
+ operationInProgress.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ RulesState nothingInstalled = new RulesState(
+ "2016b", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ try {
+ nothingInstalled.isInstalledDistroOlderThan(rulesVersion("2016b", 1));
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ DistroRulesVersion installedVersion = rulesVersion("2016b", 3);
+ RulesState rulesStateWithInstalledVersion = new RulesState(
+ "2016b", formatVersion(1, 1), false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedVersion);
+
+ DistroRulesVersion olderRules = rulesVersion("2016a", 1);
+ assertEquals(installedVersion.isOlderThan(olderRules),
+ rulesStateWithInstalledVersion.isInstalledDistroOlderThan(olderRules));
+
+ DistroRulesVersion sameRules = rulesVersion("2016b", 1);
+ assertEquals(installedVersion.isOlderThan(sameRules),
+ rulesStateWithInstalledVersion.isInstalledDistroOlderThan(sameRules));
+
+ DistroRulesVersion newerRules = rulesVersion("2016c", 1);
+ assertEquals(installedVersion.isOlderThan(newerRules),
+ rulesStateWithInstalledVersion.isInstalledDistroOlderThan(newerRules));
+ }
+
+ private static void assertEqualsContract(RulesState one, RulesState two) {
+ assertEquals(one, two);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+
+ private static DistroRulesVersion rulesVersion(String rulesVersion, int revision) {
+ return new DistroRulesVersion(rulesVersion, revision);
+ }
+
+ private static DistroFormatVersion formatVersion(int majorVersion, int minorVersion) {
+ return new DistroFormatVersion(majorVersion, minorVersion);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
new file mode 100644
index 0000000..e7a839c
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.timezone;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+import android.content.Context;
+import android.content.Intent;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+/**
+ * Tests for {@link RulesUpdaterContract}.
+ */
+// TODO(nfuller) Move to CTS once this class is part of the SystemApi. http://b/31008728
+public class RulesUpdaterContractTest {
+
+ @Test
+ public void createUpdaterIntent() throws Exception {
+ String packageName = "foobar";
+ Intent intent = RulesUpdaterContract.createUpdaterIntent(packageName);
+
+ assertEquals(RulesUpdaterContract.ACTION_TRIGGER_RULES_UPDATE_CHECK, intent.getAction());
+ assertEquals(packageName, intent.getPackage());
+ assertEquals(Intent.FLAG_INCLUDE_STOPPED_PACKAGES, intent.getFlags());
+ }
+
+ @Test
+ public void sendBroadcast() throws Exception {
+ String packageName = "foobar";
+ byte[] tokenBytes = new byte[] { 1, 2, 3, 4, 5 };
+
+ Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(packageName);
+ expectedIntent.putExtra(RulesUpdaterContract.EXTRA_CHECK_TOKEN, tokenBytes);
+
+ Context mockContext = mock(Context.class);
+
+ RulesUpdaterContract.sendBroadcast(mockContext, packageName, tokenBytes);
+
+ verify(mockContext).sendBroadcast(
+ filterEquals(expectedIntent),
+ eq(RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION));
+ }
+
+ /**
+ * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
+ * check the parameter against the intent supplied.
+ */
+ private static Intent filterEquals(final Intent expected) {
+ final Matcher<Intent> m = new BaseMatcher<Intent>() {
+ @Override
+ public boolean matches(Object actual) {
+ return actual != null && expected.filterEquals((Intent) actual);
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expected.toString());
+ }
+ };
+ return argThat(m);
+ }
+}
diff --git a/core/tests/coretests/src/android/os/VintfObjectTest.java b/core/tests/coretests/src/android/os/VintfObjectTest.java
index aaaf55c..821ee80 100644
--- a/core/tests/coretests/src/android/os/VintfObjectTest.java
+++ b/core/tests/coretests/src/android/os/VintfObjectTest.java
@@ -26,5 +26,8 @@
// From /system/manifest.xml
assertTrue(String.join("", xmls).contains(
"<manifest version=\"1.0\" type=\"framework\">"));
+ // From /system/compatibility-matrix.xml
+ assertTrue(String.join("", xmls).contains(
+ "<compatibility-matrix version=\"1.0\" type=\"framework\">"));
}
}
diff --git a/keystore/tests/Android.mk b/keystore/tests/Android.mk
deleted file mode 100644
index a740b13..0000000
--- a/keystore/tests/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_JAVA_LIBRARIES := android.test.runner bouncycastle conscrypt
-LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := KeyStoreTests
-
-include $(BUILD_PACKAGE)
diff --git a/keystore/tests/AndroidManifest.xml b/keystore/tests/AndroidManifest.xml
deleted file mode 100644
index 415442f..0000000
--- a/keystore/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.security.tests"
- android:sharedUserId="android.uid.system">
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.security.tests"
- android:label="KeyStore Tests">
- </instrumentation>
-</manifest>
diff --git a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java b/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
deleted file mode 100644
index bc8dd13..0000000
--- a/keystore/tests/src/android/security/KeyPairGeneratorSpecTest.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2012 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.security;
-
-import android.test.AndroidTestCase;
-
-import java.math.BigInteger;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-public class KeyPairGeneratorSpecTest extends AndroidTestCase {
- private static final String TEST_ALIAS_1 = "test1";
-
- private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
- private static final long NOW_MILLIS = System.currentTimeMillis();
-
- private static final BigInteger SERIAL_1 = BigInteger.ONE;
-
- /* We have to round this off because X509v3 doesn't store milliseconds. */
- private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
- @SuppressWarnings("deprecation")
- private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
- public void testConstructor_Success() throws Exception {
- KeyPairGeneratorSpec spec =
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1,
- SERIAL_1, NOW, NOW_PLUS_10_YEARS, 0);
-
- assertEquals("Context should be the one specified", getContext(), spec.getContext());
-
- assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
-
- assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
-
- assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
-
- assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
-
- assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
-
- assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
- }
-
- public void testBuilder_Success() throws Exception {
- KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("RSA")
- .setKeySize(1024)
- .setSubject(TEST_DN_1)
- .setSerialNumber(SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build();
-
- assertEquals("Context should be the one specified", getContext(), spec.getContext());
-
- assertEquals("Alias should be the one specified", TEST_ALIAS_1, spec.getKeystoreAlias());
-
- assertEquals("Key algorithm should be the one specified", "RSA", spec.getKeyType());
-
- assertEquals("Key size should be the one specified", 1024, spec.getKeySize());
-
- assertEquals("subjectDN should be the one specified", TEST_DN_1, spec.getSubjectDN());
-
- assertEquals("startDate should be the one specified", NOW, spec.getStartDate());
-
- assertEquals("endDate should be the one specified", NOW_PLUS_10_YEARS, spec.getEndDate());
-
- assertEquals("encryption flag should be on", KeyStore.FLAG_ENCRYPTED, spec.getFlags());
- }
-
- public void testConstructor_NullContext_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(null, TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when context is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullKeystoreAlias_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), null, "RSA", 1024, null, TEST_DN_1, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when keystoreAlias is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullSubjectDN_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, null, SERIAL_1, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when subjectDN is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullSerial_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, null, NOW,
- NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when startDate is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullStartDate_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
- null, NOW_PLUS_10_YEARS, 0);
- fail("Should throw IllegalArgumentException when startDate is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_NullEndDate_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
- NOW, null, 0);
- fail("Should throw IllegalArgumentException when keystoreAlias is null");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testConstructor_EndBeforeStart_Failure() throws Exception {
- try {
- new KeyPairGeneratorSpec(getContext(), TEST_ALIAS_1, "RSA", 1024, null, TEST_DN_1, SERIAL_1,
- NOW_PLUS_10_YEARS, NOW, 0);
- fail("Should throw IllegalArgumentException when end is before start");
- } catch (IllegalArgumentException success) {
- }
- }
-}
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
deleted file mode 100644
index 319cf32..0000000
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ /dev/null
@@ -1,974 +0,0 @@
-/*
- * Copyright (C) 2009 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.security;
-
-import android.app.Activity;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.Process;
-import android.security.keymaster.ExportResult;
-import android.security.keymaster.KeyCharacteristics;
-import android.security.keymaster.KeymasterArguments;
-import android.security.keymaster.KeymasterBlob;
-import android.security.keymaster.KeymasterDefs;
-import android.security.keymaster.OperationResult;
-import android.test.ActivityUnitTestCase;
-import android.test.AssertionFailedError;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-import com.android.org.conscrypt.NativeConstants;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.security.spec.RSAKeyGenParameterSpec;
-
-/**
- * Junit / Instrumentation test case for KeyStore class
- *
- * Running the test suite:
- *
- * runtest keystore-unit
- *
- * Or this individual test case:
- *
- * runtest --path frameworks/base/keystore/tests/src/android/security/KeyStoreTest.java
- */
-@MediumTest
-public class KeyStoreTest extends ActivityUnitTestCase<Activity> {
- private static final String TEST_PASSWD = "12345678";
- private static final String TEST_PASSWD2 = "87654321";
- private static final String TEST_KEYNAME = "test-key";
- private static final String TEST_KEYNAME1 = "test-key.1";
- private static final String TEST_KEYNAME2 = "test-key\02";
- private static final byte[] TEST_KEYVALUE = "test value".getBytes(StandardCharsets.UTF_8);
-
- // "Hello, World" in Chinese
- private static final String TEST_I18N_KEY = "\u4F60\u597D, \u4E16\u754C";
- private static final byte[] TEST_I18N_VALUE = TEST_I18N_KEY.getBytes(StandardCharsets.UTF_8);
-
- // Test vector data for signatures
- private static final int RSA_KEY_SIZE = 1024;
- private static final byte[] TEST_DATA = new byte[RSA_KEY_SIZE / 8];
- static {
- for (int i = 0; i < TEST_DATA.length; i++) {
- TEST_DATA[i] = (byte) i;
- }
- }
-
- private KeyStore mKeyStore = null;
-
- public KeyStoreTest() {
- super(Activity.class);
- }
-
- private static final byte[] PRIVKEY_BYTES = hexToBytes(
- "308204BE020100300D06092A864886F70D0101010500048204A8308204A4020100028201" +
- "0100E0473E8AB8F2284FEB9E742FF9748FA118ED98633C92F52AEB7A2EBE0D3BE60329BE" +
- "766AD10EB6A515D0D2CFD9BEA7930F0C306537899F7958CD3E85B01F8818524D312584A9" +
- "4B251E3625B54141EDBFEE198808E1BB97FC7CB49B9EAAAF68E9C98D7D0EDC53BBC0FA00" +
- "34356D6305FBBCC3C7001405386ABBC873CB0F3EF7425F3D33DF7B315AE036D2A0B66AFD" +
- "47503B169BF36E3B5162515B715FDA83DEAF2C58AEB9ABFB3097C3CC9DD9DBE5EF296C17" +
- "6139028E8A671E63056D45F40188D2C4133490845DE52C2534E9C6B2478C07BDAE928823" +
- "B62D066C7770F9F63F3DBA247F530844747BE7AAA85D853B8BD244ACEC3DE3C89AB46453" +
- "AB4D24C3AC6902030100010282010037784776A5F17698F5AC960DFB83A1B67564E648BD" +
- "0597CF8AB8087186F2669C27A9ECBDD480F0197A80D07309E6C6A96F925331E57F8B4AC6" +
- "F4D45EDA45A23269C09FC428C07A4E6EDF738A15DEC97FABD2F2BB47A14F20EA72FCFE4C" +
- "36E01ADA77BD137CD8D4DA10BB162E94A4662971F175F985FA188F056CB97EE2816F43AB" +
- "9D3747612486CDA8C16196C30818A995EC85D38467791267B3BF21F273710A6925862576" +
- "841C5B6712C12D4BD20A2F3299ADB7C135DA5E9515ABDA76E7CAF2A3BE80551D073B78BF" +
- "1162C48AD2B7F4743A0238EE4D252F7D5E7E6533CCAE64CCB39360075A2FD1E034EC3AE5" +
- "CE9C408CCBF0E25E4114021687B3DD4754AE8102818100F541884BC3737B2922D4119EF4" +
- "5E2DEE2CD4CBB75F45505A157AA5009F99C73A2DF0724AC46024306332EA898177634546" +
- "5DC6DF1E0A6F140AFF3B7396E6A8994AC5DAA96873472FE37749D14EB3E075E629DBEB35" +
- "83338A6F3649D0A2654A7A42FD9AB6BFA4AC4D481D390BB229B064BDC311CC1BE1B63189" +
- "DA7C40CDECF2B102818100EA1A742DDB881CEDB7288C87E38D868DD7A409D15A43F445D5" +
- "377A0B5731DDBFCA2DAF28A8E13CD5C0AFCEC3347D74A39E235A3CD9633F274DE2B94F92" +
- "DF43833911D9E9F1CF58F27DE2E08FF45964C720D3EC2139DC7CAFC912953CDECB2F355A" +
- "2E2C35A50FAD754CB3B23166424BA3B6E3112A2B898C38C5C15EDB238693390281805182" +
- "8F1EC6FD996029901BAF1D7E337BA5F0AF27E984EAD895ACE62BD7DF4EE45A224089F2CC" +
- "151AF3CD173FCE0474BCB04F386A2CDCC0E0036BA2419F54579262D47100BE931984A3EF" +
- "A05BECF141574DC079B3A95C4A83E6C43F3214D6DF32D512DE198085E531E616B83FD7DD" +
- "9D1F4E2607C3333D07C55D107D1D3893587102818100DB4FB50F50DE8EDB53FF34C80931" +
- "88A0512867DA2CCA04897759E587C244010DAF8664D59E8083D16C164789301F67A9F078" +
- "060D834A2ADBD367575B68A8A842C2B02A89B3F31FCCEC8A22FE395795C5C6C7422B4E5D" +
- "74A1E9A8F30E7759B9FC2D639C1F15673E84E93A5EF1506F4315383C38D45CBD1B14048F" +
- "4721DC82326102818100D8114593AF415FB612DBF1923710D54D07486205A76A3B431949" +
- "68C0DFF1F11EF0F61A4A337D5FD3741BBC9640E447B8B6B6C47C3AC1204357D3B0C55BA9" +
- "286BDA73F629296F5FA9146D8976357D3C751E75148696A40B74685C82CE30902D639D72" +
- "4FF24D5E2E9407EE34EDED2E3B4DF65AA9BCFEB6DF28D07BA6903F165768");
-
- private static final byte[] AES256_BYTES = hexToBytes(
- "0CC175B9C0F1B6A831C399E269772661CEC520EA51EA0A47E87295FA3245A605");
-
- private static byte[] hexToBytes(String s) {
- int len = s.length();
- byte[] data = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(
- s.charAt(i + 1), 16));
- }
- return data;
- }
-
- @Override
- protected void setUp() throws Exception {
- mKeyStore = KeyStore.getInstance();
- if (mKeyStore.state() != KeyStore.State.UNINITIALIZED) {
- mKeyStore.reset();
- }
- assertEquals("KeyStore should be in an uninitialized state",
- KeyStore.State.UNINITIALIZED, mKeyStore.state());
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- mKeyStore.reset();
- super.tearDown();
- }
-
- public void testState() throws Exception {
- assertEquals(KeyStore.State.UNINITIALIZED, mKeyStore.state());
- }
-
- public void testPassword() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
- }
-
- public void testGet() throws Exception {
- assertNull(mKeyStore.get(TEST_KEYNAME));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertNull(mKeyStore.get(TEST_KEYNAME));
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- }
-
- public void testPut() throws Exception {
- assertNull(mKeyStore.get(TEST_KEYNAME));
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- }
-
- public void testPut_grantedUid_Wifi() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testPut_ungrantedUid_Bluetooth() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- public void testI18n() throws Exception {
- assertFalse(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_I18N_KEY));
- mKeyStore.onUserPasswordChanged(TEST_I18N_KEY);
- assertTrue(mKeyStore.put(TEST_I18N_KEY, TEST_I18N_VALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_I18N_KEY));
- }
-
- public void testDelete() throws Exception {
- assertFalse(mKeyStore.delete(TEST_KEYNAME));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.delete(TEST_KEYNAME));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- assertTrue(mKeyStore.delete(TEST_KEYNAME));
- assertNull(mKeyStore.get(TEST_KEYNAME));
- }
-
- public void testDelete_grantedUid_Wifi() throws Exception {
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.delete(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testDelete_ungrantedUid_Bluetooth() throws Exception {
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.delete(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- public void testContains() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testContains_grantedUid_Wifi() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.WIFI_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testContains_grantedUid_Bluetooth() throws Exception {
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertFalse(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- public void testList() throws Exception {
- String[] emptyResult = mKeyStore.list(TEST_KEYNAME);
- assertNotNull(emptyResult);
- assertEquals(0, emptyResult.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-
- String[] results = mKeyStore.list(TEST_KEYNAME);
- assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
- TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
- new HashSet(Arrays.asList(results)));
- }
-
- public void testList_ungrantedUid_Bluetooth() throws Exception {
- String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
- assertEquals(0, results1.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
-
- String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.BLUETOOTH_UID);
- assertEquals(0, results2.length);
- }
-
- public void testList_grantedUid_Wifi() throws Exception {
- String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
- assertNotNull(results1);
- assertEquals(0, results1.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED);
-
- String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.WIFI_UID);
- assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
- TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
- new HashSet(Arrays.asList(results2)));
- }
-
- public void testList_grantedUid_Vpn() throws Exception {
- String[] results1 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
- assertNotNull(results1);
- assertEquals(0, results1.length);
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.put(TEST_KEYNAME1, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
- mKeyStore.put(TEST_KEYNAME2, TEST_KEYVALUE, Process.VPN_UID, KeyStore.FLAG_ENCRYPTED);
-
- String[] results2 = mKeyStore.list(TEST_KEYNAME, Process.VPN_UID);
- assertEquals(new HashSet(Arrays.asList(TEST_KEYNAME1.substring(TEST_KEYNAME.length()),
- TEST_KEYNAME2.substring(TEST_KEYNAME.length()))),
- new HashSet(Arrays.asList(results2)));
- }
-
- public void testLock() throws Exception {
- assertFalse(mKeyStore.lock());
-
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
-
- assertTrue(mKeyStore.lock());
- assertEquals(KeyStore.State.LOCKED, mKeyStore.state());
- }
-
- public void testUnlock() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertEquals(KeyStore.State.UNLOCKED, mKeyStore.state());
- mKeyStore.lock();
-
- assertFalse(mKeyStore.unlock(TEST_PASSWD2));
- assertTrue(mKeyStore.unlock(TEST_PASSWD));
- }
-
- public void testIsEmpty() throws Exception {
- assertTrue(mKeyStore.isEmpty());
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- assertTrue(mKeyStore.isEmpty());
- mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED);
- assertFalse(mKeyStore.isEmpty());
- mKeyStore.reset();
- assertTrue(mKeyStore.isEmpty());
- }
-
- public void testGenerate_NotInitialized_Fail() throws Exception {
- assertFalse("Should fail when keystore is not initialized",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- }
-
- public void testGenerate_Locked_Fail() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- mKeyStore.lock();
- assertFalse("Should fail when keystore is locked",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- }
-
- public void testGenerate_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key when unlocked",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testGenerate_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key when unlocked",
- mKeyStore.generate(TEST_KEYNAME, Process.WIFI_UID, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testGenerate_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.generate(TEST_KEYNAME, Process.BLUETOOTH_UID,
- NativeConstants.EVP_PKEY_RSA, RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testImport_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testImport_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, Process.WIFI_UID, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testImport_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.importKey(TEST_KEYNAME, PRIVKEY_BYTES, Process.BLUETOOTH_UID,
- KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testImport_Failure_BadEncoding() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertFalse("Invalid DER-encoded key should not be imported", mKeyStore.importKey(
- TEST_KEYNAME, TEST_DATA, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- }
-
- public void testSign_Success() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
-
- assertNotNull("Signature should not be null", signature);
- }
-
- public void testVerify_Success() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- final byte[] signature = mKeyStore.sign(TEST_KEYNAME, TEST_DATA);
-
- assertNotNull("Signature should not be null", signature);
-
- assertTrue("Signature should verify with same data",
- mKeyStore.verify(TEST_KEYNAME, TEST_DATA, signature));
- }
-
- public void testSign_NotInitialized_Failure() throws Exception {
- assertNull("Should not be able to sign without first initializing the keystore",
- mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
- }
-
- public void testSign_NotGenerated_Failure() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
-
- assertNull("Should not be able to sign without first generating keys",
- mKeyStore.sign(TEST_KEYNAME, TEST_DATA));
- }
-
- public void testGrant_Generated_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testGrant_Imported_Success() throws Exception {
- assertTrue("Password should work for keystore", mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should be able to grant key to other user", mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testGrant_NoKey_Failure() throws Exception {
- assertTrue("Should be able to unlock keystore for test",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse("Should not be able to grant without first initializing the keystore",
- mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testGrant_NotInitialized_Failure() throws Exception {
- assertFalse("Should not be able to grant without first initializing the keystore",
- mKeyStore.grant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_Generated_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_Imported_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key for testcase", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_NotInitialized_Failure() throws Exception {
- assertFalse("Should fail to ungrant key when keystore not initialized",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_NoGrant_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertFalse("Should not be able to revoke not existent grant",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_DoubleUngrant_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
-
- assertFalse("Should fail to ungrant key to other user second time",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testUngrant_DoubleGrantUngrant_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to generate key for testcase",
- mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue("Should be able to grant key to other user",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to grant key to other user a second time",
- mKeyStore.grant(TEST_KEYNAME, 0));
-
- assertTrue("Should be able to ungrant key to other user",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
-
- assertFalse("Should fail to ungrant key to other user second time",
- mKeyStore.ungrant(TEST_KEYNAME, 0));
- }
-
- public void testDuplicate_grantedUid_Wifi_Success() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
-
- // source doesn't exist
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, -1, TEST_KEYNAME1, Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
-
- // Copy from current UID to granted UID
- assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME1));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME1, Process.WIFI_UID));
-
- // Copy from granted UID to same granted UID
- assertTrue(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2,
- Process.WIFI_UID));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME1, Process.WIFI_UID));
- assertTrue(mKeyStore.contains(TEST_KEYNAME2, Process.WIFI_UID));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME1, Process.WIFI_UID, TEST_KEYNAME2,
- Process.WIFI_UID));
-
- assertTrue(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME1));
- assertTrue(mKeyStore.contains(TEST_KEYNAME2));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, -1));
- }
-
- public void testDuplicate_ungrantedUid_Bluetooth_Failure() throws Exception {
- assertTrue(mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
-
- assertTrue(mKeyStore.generate(TEST_KEYNAME, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA,
- RSA_KEY_SIZE, KeyStore.FLAG_ENCRYPTED, null));
-
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
-
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, -1, TEST_KEYNAME2, Process.BLUETOOTH_UID));
- assertFalse(mKeyStore.duplicate(TEST_KEYNAME, Process.BLUETOOTH_UID, TEST_KEYNAME2,
- Process.BLUETOOTH_UID));
-
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME, Process.BLUETOOTH_UID));
- }
-
- /**
- * The amount of time to allow before and after expected time for variance
- * in timing tests.
- */
- private static final long SLOP_TIME_MILLIS = 15000L;
-
- public void testGetmtime_Success() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- long now = System.currentTimeMillis();
- long actual = mKeyStore.getmtime(TEST_KEYNAME);
-
- long expectedAfter = now - SLOP_TIME_MILLIS;
- long expectedBefore = now + SLOP_TIME_MILLIS;
-
- assertLessThan("Time should be close to current time", expectedBefore, actual);
- assertGreaterThan("Time should be close to current time", expectedAfter, actual);
- }
-
- private static void assertLessThan(String explanation, long expectedBefore, long actual) {
- if (actual >= expectedBefore) {
- throw new AssertionFailedError(explanation + ": actual=" + actual
- + ", expected before: " + expectedBefore);
- }
- }
-
- private static void assertGreaterThan(String explanation, long expectedAfter, long actual) {
- if (actual <= expectedAfter) {
- throw new AssertionFailedError(explanation + ": actual=" + actual
- + ", expected after: " + expectedAfter);
- }
- }
-
- public void testGetmtime_NonExist_Failure() throws Exception {
- assertTrue("Password should work for keystore",
- mKeyStore.onUserPasswordChanged(TEST_PASSWD));
-
- assertTrue("Should be able to import key when unlocked", mKeyStore.importKey(TEST_KEYNAME,
- PRIVKEY_BYTES, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertEquals("-1 should be returned for non-existent key",
- -1L, mKeyStore.getmtime(TEST_KEYNAME2));
- }
-
- private KeyCharacteristics generateRsaKey(String name) throws Exception {
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result);
- return outCharacteristics;
- }
-
- public void testGenerateKey() throws Exception {
- generateRsaKey("test");
- mKeyStore.delete("test");
- }
-
- public void testGenerateRsaWithEntropy() throws Exception {
- byte[] entropy = new byte[] {1,2,3,4,5};
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int result = mKeyStore.generateKey(name, args, entropy, 0, outCharacteristics);
- assertEquals("generateKey should succeed", KeyStore.NO_ERROR, result);
- }
-
- public void testGenerateAndDelete() throws Exception {
- generateRsaKey("test");
- assertTrue("delete should succeed", mKeyStore.delete("test"));
- }
-
- public void testGetKeyCharacteristicsSuccess() throws Exception {
- mKeyStore.onUserPasswordChanged(TEST_PASSWD);
- String name = "test";
- KeyCharacteristics gen = generateRsaKey(name);
- KeyCharacteristics call = new KeyCharacteristics();
- int result = mKeyStore.getKeyCharacteristics(name, null, null, call);
- assertEquals("getKeyCharacteristics should succeed", KeyStore.NO_ERROR, result);
- mKeyStore.delete("test");
- }
-
- public void testAppId() throws Exception {
- String name = "test";
- byte[] id = new byte[] {0x01, 0x02, 0x03};
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 2048);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- args.addBytes(KeymasterDefs.KM_TAG_APPLICATION_ID, id);
- args.addUnsignedLong(KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, RSAKeyGenParameterSpec.F4);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int result = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("generateRsaKey should succeed", KeyStore.NO_ERROR, result);
- assertEquals("getKeyCharacteristics should fail without application ID",
- KeymasterDefs.KM_ERROR_INVALID_KEY_BLOB,
- mKeyStore.getKeyCharacteristics(name, null, null, outCharacteristics));
- assertEquals("getKeyCharacteristics should succeed with application ID",
- KeyStore.NO_ERROR,
- mKeyStore.getKeyCharacteristics(name, new KeymasterBlob(id), null,
- outCharacteristics));
- }
-
-
- public void testExportRsa() throws Exception {
- String name = "test";
- generateRsaKey(name);
- ExportResult result = mKeyStore.exportKey(name, KeymasterDefs.KM_KEY_FORMAT_X509, null,
- null);
- assertEquals("Export success", KeyStore.NO_ERROR, result.resultCode);
- // TODO: Verify we have an RSA public key that's well formed.
- }
-
- public void testAesGcmEncryptSuccess() throws Exception {
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-
- args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 128);
- OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null);
- IBinder token = result.token;
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
- assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
- assertEquals("Finish should succeed", KeyStore.NO_ERROR,
- mKeyStore.finish(token, null, null).resultCode);
- // TODO: Assert that an AEAD tag was returned by finish
- }
-
- public void testBadToken() throws Exception {
- IBinder token = new Binder();
- OperationResult result = mKeyStore.update(token, null, new byte[] {0x01});
- assertEquals("Update with invalid token should fail",
- KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, result.resultCode);
- }
-
- private int importAesKey(String name, byte[] key, int size, int mode) {
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, mode);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, size);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
- return mKeyStore.importKey(name, args, KeymasterDefs.KM_KEY_FORMAT_RAW, key, 0,
- new KeyCharacteristics());
- }
- private byte[] doOperation(String name, int purpose, byte[] in, KeymasterArguments beginArgs) {
- OperationResult result = mKeyStore.begin(name, purpose,
- true, beginArgs, null);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- IBinder token = result.token;
- result = mKeyStore.update(token, null, in);
- assertEquals("Update should succeed", KeyStore.NO_ERROR, result.resultCode);
- assertEquals("All data should be consumed", in.length, result.inputConsumed);
- assertEquals("Finish should succeed", KeyStore.NO_ERROR,
- mKeyStore.finish(token, null, null).resultCode);
- return result.output;
- }
-
- public void testImportAes() throws Exception {
- int result = importAesKey("aes", AES256_BYTES, 256, KeymasterDefs.KM_MODE_ECB);
- assertEquals("import should succeed", KeyStore.NO_ERROR, result);
- mKeyStore.delete("aes");
- }
-
- public void testAes256Ecb() throws Exception {
- byte[] key =
- hexToBytes("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
- String name = "aes";
- assertEquals(KeyStore.NO_ERROR, importAesKey(name, key, 256, KeymasterDefs.KM_MODE_ECB));
- byte[][] testVectors = new byte[][] {
- hexToBytes("6bc1bee22e409f96e93d7e117393172a"),
- hexToBytes("ae2d8a571e03ac9c9eb76fac45af8e51"),
- hexToBytes("30c81c46a35ce411e5fbc1191a0a52ef"),
- hexToBytes("f69f2445df4f9b17ad2b417be66c3710")};
- byte[][] cipherVectors = new byte[][] {
- hexToBytes("f3eed1bdb5d2a03c064b5a7e3db181f8"),
- hexToBytes("591ccb10d410ed26dc5ba74a31362870"),
- hexToBytes("b6ed21b99ca6f4f9f153e7b1beafed1d"),
- hexToBytes("23304b7a39f9f3ff067d8d8f9e24ecc7")};
- KeymasterArguments beginArgs = new KeymasterArguments();
- beginArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- beginArgs.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- beginArgs.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- for (int i = 0; i < testVectors.length; i++) {
- byte[] cipherText = doOperation(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, testVectors[i],
- beginArgs);
- MoreAsserts.assertEquals(cipherVectors[i], cipherText);
- }
- for (int i = 0; i < testVectors.length; i++) {
- byte[] plainText = doOperation(name, KeymasterDefs.KM_PURPOSE_DECRYPT,
- cipherVectors[i], beginArgs);
- MoreAsserts.assertEquals(testVectors[i], plainText);
- }
- }
-
- // This is a very implementation specific test and should be thrown out eventually, however it
- // is nice for now to test that keystore is properly pruning operations.
- public void testOperationPruning() throws Exception {
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
- args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
-
- args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
- OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- IBinder first = result.token;
- // Implementation detail: softkeymaster supports 16 concurrent operations
- for (int i = 0; i < 16; i++) {
- result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT, true, args, null);
- assertEquals("Begin should succeed", KeyStore.NO_ERROR, result.resultCode);
- }
- // At this point the first operation should be pruned.
- assertEquals("Operation should be pruned", KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE,
- mKeyStore.update(first, null, new byte[] {0x01}).resultCode);
- }
-
- public void testAuthNeeded() throws Exception {
- String name = "test";
- KeymasterArguments args = new KeymasterArguments();
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
- args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addEnum(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
- args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addEnum(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
- args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
-
- KeyCharacteristics outCharacteristics = new KeyCharacteristics();
- int rc = mKeyStore.generateKey(name, args, null, 0, outCharacteristics);
- assertEquals("Generate should succeed", KeyStore.NO_ERROR, rc);
- OperationResult result = mKeyStore.begin(name, KeymasterDefs.KM_PURPOSE_ENCRYPT,
- true, args, null);
- assertEquals("Begin should expect authorization", KeyStore.OP_AUTH_NEEDED,
- result.resultCode);
- IBinder token = result.token;
- result = mKeyStore.update(token, null, new byte[] {0x01, 0x02, 0x03, 0x04});
- assertEquals("Update should require authorization",
- KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED, result.resultCode);
- }
-
- public void testPasswordRemovalEncryptedEntry() throws Exception {
- mKeyStore.onUserPasswordChanged("test");
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_ENCRYPTED));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- mKeyStore.onUserPasswordChanged("");
- // Removing the password should have deleted all entries using FLAG_ENCRYPTED
- assertNull(mKeyStore.get(TEST_KEYNAME));
- assertFalse(mKeyStore.contains(TEST_KEYNAME));
- }
-
- public void testPasswordRemovalUnencryptedEntry() throws Exception {
- mKeyStore.onUserPasswordChanged("test");
- assertTrue(mKeyStore.put(TEST_KEYNAME, TEST_KEYVALUE, KeyStore.UID_SELF,
- KeyStore.FLAG_NONE));
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- mKeyStore.onUserPasswordChanged("");
- // Removing the password should not delete unencrypted entries.
- assertTrue(mKeyStore.contains(TEST_KEYNAME));
- assertTrue(Arrays.equals(TEST_KEYVALUE, mKeyStore.get(TEST_KEYNAME)));
- }
-}
diff --git a/keystore/tests/src/android/security/SystemKeyStoreTest.java b/keystore/tests/src/android/security/SystemKeyStoreTest.java
deleted file mode 100644
index ecf7cbc..0000000
--- a/keystore/tests/src/android/security/SystemKeyStoreTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2010 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.security;
-
-import android.app.Activity;
-import android.security.SystemKeyStore;
-import android.test.ActivityUnitTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-/**
- * Junit / Instrumentation test case for KeyStore class
- *
- * Running the test suite:
- *
- * runtest keystore-unit
- *
- * Or this individual test case:
- *
- * runtest --path frameworks/base/keystore/tests/src/android/security/SystemKeyStoreTest.java
- */
-@MediumTest
-public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> {
-
- private static final String keyName = "TestKey";
- private static final String keyName2 = "TestKey2";
- private SystemKeyStore mSysKeyStore = null;
-
- public SystemKeyStoreTest() {
- super(Activity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- mSysKeyStore = SystemKeyStore.getInstance();
- try {
- mSysKeyStore.deleteKey(keyName);
- mSysKeyStore.deleteKey(keyName2);
- } catch (Exception e) { }
- super.setUp();
- }
-
- @Override
- protected void tearDown() throws Exception {
- try {
- mSysKeyStore.deleteKey(keyName);
- mSysKeyStore.deleteKey(keyName2);
- } catch (Exception e) { }
- super.tearDown();
- }
-
- public void testBasicAccess() throws Exception {
- try {
- byte[] newKey = mSysKeyStore.generateNewKey(128, "AES", keyName);
- assertNotNull(newKey);
- byte[] recKey = mSysKeyStore.retrieveKey(keyName);
- assertEquals(newKey.length, recKey.length);
- for (int i = 0; i < newKey.length; i++) {
- assertEquals(newKey[i], recKey[i]);
- }
- mSysKeyStore.deleteKey(keyName);
- byte[] nullKey = mSysKeyStore.retrieveKey(keyName);
- assertNull(nullKey);
-
- String newKeyStr = mSysKeyStore.generateNewKeyHexString(128, "AES", keyName2);
- assertNotNull(newKeyStr);
- String recKeyStr = mSysKeyStore.retrieveKeyHexString(keyName2);
- assertEquals(newKeyStr, recKeyStr);
-
- mSysKeyStore.deleteKey(keyName2);
- String nullKey2 = mSysKeyStore.retrieveKeyHexString(keyName2);
- assertNull(nullKey2);
- } catch (Exception e) {
- fail();
- }
- }
-}
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
deleted file mode 100644
index 1af0b7d..0000000
--- a/keystore/tests/src/android/security/keystore/AndroidKeyPairGeneratorTest.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2012 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.security.keystore;
-
-import android.security.Credentials;
-import android.security.KeyPairGeneratorSpec;
-import android.security.KeyStore;
-import android.security.keymaster.ExportResult;
-import android.security.keymaster.KeymasterDefs;
-import android.test.AndroidTestCase;
-
-import java.io.ByteArrayInputStream;
-import java.math.BigInteger;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.AlgorithmParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
-public class AndroidKeyPairGeneratorTest extends AndroidTestCase {
- private android.security.KeyStore mAndroidKeyStore;
-
- private java.security.KeyPairGenerator mGenerator;
-
- private static final String TEST_ALIAS_1 = "test1";
-
- private static final String TEST_ALIAS_2 = "test2";
-
- private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
- private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
-
- private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
-
- private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
-
- private static final long NOW_MILLIS = System.currentTimeMillis();
-
- /* We have to round this off because X509v3 doesn't store milliseconds. */
- private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
- @SuppressWarnings("deprecation")
- private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
- @Override
- protected void setUp() throws Exception {
- mAndroidKeyStore = android.security.KeyStore.getInstance();
-
- assertTrue(mAndroidKeyStore.reset());
-
- assertFalse(mAndroidKeyStore.isUnlocked());
-
- mGenerator = java.security.KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
- }
-
- private void setupPassword() {
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- String[] aliases = mAndroidKeyStore.list("");
- assertNotNull(aliases);
- assertEquals(0, aliases.length);
- }
-
- public void testKeyPairGenerator_Initialize_Params_Encrypted_Success() throws Exception {
- setupPassword();
-
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build());
- }
-
- public void testKeyPairGenerator_Initialize_KeySize_Encrypted_Failure() throws Exception {
- setupPassword();
-
- try {
- mGenerator.initialize(1024);
- fail("KeyPairGenerator should not support setting the key size");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyPairGenerator_Initialize_KeySizeAndSecureRandom_Encrypted_Failure()
- throws Exception {
- setupPassword();
-
- try {
- mGenerator.initialize(1024, new SecureRandom());
- fail("KeyPairGenerator should not support setting the key size");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyPairGenerator_Initialize_ParamsAndSecureRandom_Encrypted_Failure()
- throws Exception {
- setupPassword();
-
- mGenerator.initialize(
- new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("RSA")
- .setKeySize(1024)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build(),
- new SecureRandom());
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_Encrypted_Success() throws Exception {
- setupPassword();
-
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_EC_Unencrypted_Success() throws Exception {
- KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
- generator.initialize(new KeyGenParameterSpec.Builder(
- TEST_ALIAS_1,
- KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
- .setCertificateSubject(TEST_DN_1)
- .setCertificateSerialNumber(TEST_SERIAL_1)
- .setCertificateNotBefore(NOW)
- .setCertificateNotAfter(NOW_PLUS_10_YEARS)
- .setDigests(KeyProperties.DIGEST_SHA256)
- .build());
-
- final KeyPair pair = generator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_Legacy_GenerateKeyPair_EC_Unencrypted_Success()
- throws Exception {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("EC")
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 256, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_EC_P521_Unencrypted_Success() throws Exception {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeyType("EC")
- .setKeySize(521)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "EC", 521, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_RSA_Unencrypted_Success() throws Exception {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_RSA_WithParams_Unencrypted_Success()
- throws Exception {
- AlgorithmParameterSpec spec = new RSAKeyGenParameterSpec(1024, BigInteger.valueOf(3L));
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setKeySize(1024)
- .setAlgorithmParameterSpec(spec)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
-
- final KeyPair pair = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair);
-
- assertKeyPairCorrect(pair, TEST_ALIAS_1, "RSA", 1024, spec, TEST_DN_1, TEST_SERIAL_1, NOW,
- NOW_PLUS_10_YEARS);
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_Replaced_Success() throws Exception {
- // Generate the first key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
- final KeyPair pair1 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair1);
- assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
- NOW, NOW_PLUS_10_YEARS);
- }
-
- // Replace the original key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_2)
- .setSubject(TEST_DN_2)
- .setSerialNumber(TEST_SERIAL_2)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
- final KeyPair pair2 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair2);
- assertKeyPairCorrect(pair2, TEST_ALIAS_2, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
- NOW, NOW_PLUS_10_YEARS);
- }
- }
-
- public void testKeyPairGenerator_GenerateKeyPair_Replaced_UnencryptedToEncrypted_Success()
- throws Exception {
- // Generate the first key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_1)
- .setSerialNumber(TEST_SERIAL_1)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .build());
- final KeyPair pair1 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair1);
- assertKeyPairCorrect(pair1, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_1, TEST_SERIAL_1,
- NOW, NOW_PLUS_10_YEARS);
- }
-
- // Attempt to replace previous key
- {
- mGenerator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
- .setAlias(TEST_ALIAS_1)
- .setSubject(TEST_DN_2)
- .setSerialNumber(TEST_SERIAL_2)
- .setStartDate(NOW)
- .setEndDate(NOW_PLUS_10_YEARS)
- .setEncryptionRequired()
- .build());
- try {
- mGenerator.generateKeyPair();
- fail("Should not be able to generate encrypted key while not initialized");
- } catch (IllegalStateException expected) {
- }
-
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- final KeyPair pair2 = mGenerator.generateKeyPair();
- assertNotNull("The KeyPair returned should not be null", pair2);
- assertKeyPairCorrect(pair2, TEST_ALIAS_1, "RSA", 2048, null, TEST_DN_2, TEST_SERIAL_2,
- NOW, NOW_PLUS_10_YEARS);
- }
- }
-
- private void assertKeyPairCorrect(KeyPair pair, String alias, String keyType, int keySize,
- AlgorithmParameterSpec spec, X500Principal dn, BigInteger serial, Date start, Date end)
- throws Exception {
- final PublicKey pubKey = pair.getPublic();
- assertNotNull("The PublicKey for the KeyPair should be not null", pubKey);
- assertEquals(keyType, pubKey.getAlgorithm());
-
- if ("EC".equalsIgnoreCase(keyType)) {
- assertEquals("Curve should be what was specified during initialization", keySize,
- ((ECPublicKey) pubKey).getParams().getCurve().getField().getFieldSize());
- } else if ("RSA".equalsIgnoreCase(keyType)) {
- RSAPublicKey rsaPubKey = (RSAPublicKey) pubKey;
- assertEquals("Modulus size should be what is specified during initialization",
- (keySize + 7) & ~7, (rsaPubKey.getModulus().bitLength() + 7) & ~7);
- if (spec != null) {
- RSAKeyGenParameterSpec params = (RSAKeyGenParameterSpec) spec;
- assertEquals((keySize + 7) & ~7, (params.getKeysize() + 7) & ~7);
- assertEquals(params.getPublicExponent(), rsaPubKey.getPublicExponent());
- }
- }
-
- final PrivateKey privKey = pair.getPrivate();
- assertNotNull("The PrivateKey for the KeyPair should be not null", privKey);
- assertEquals(keyType, privKey.getAlgorithm());
-
- if ("EC".equalsIgnoreCase(keyType)) {
- assertTrue("EC private key must be instanceof ECKey: " + privKey.getClass().getName(),
- privKey instanceof ECKey);
- assertEquals("Private and public key must have the same EC parameters",
- ((ECKey) pubKey).getParams(), ((ECKey) privKey).getParams());
- } else if ("RSA".equalsIgnoreCase(keyType)) {
- assertTrue("RSA private key must be instance of RSAKey: "
- + privKey.getClass().getName(),
- privKey instanceof RSAKey);
- assertEquals("Private and public key must have the same RSA modulus",
- ((RSAKey) pubKey).getModulus(), ((RSAKey) privKey).getModulus());
- }
-
- final byte[] userCertBytes = mAndroidKeyStore.get(Credentials.USER_CERTIFICATE + alias);
- assertNotNull("The user certificate should exist for the generated entry", userCertBytes);
-
- final CertificateFactory cf = CertificateFactory.getInstance("X.509");
- final Certificate userCert =
- cf.generateCertificate(new ByteArrayInputStream(userCertBytes));
-
- assertTrue("Certificate should be in X.509 format", userCert instanceof X509Certificate);
-
- final X509Certificate x509userCert = (X509Certificate) userCert;
-
- assertEquals(
- "Public key used to sign certificate should have the same algorithm as in KeyPair",
- pubKey.getAlgorithm(), x509userCert.getPublicKey().getAlgorithm());
-
- assertEquals("PublicKey used to sign certificate should match one returned in KeyPair",
- pubKey,
- AndroidKeyStoreProvider.getAndroidKeyStorePublicKey(
- Credentials.USER_PRIVATE_KEY + alias,
- KeyStore.UID_SELF,
- x509userCert.getPublicKey().getAlgorithm(),
- x509userCert.getPublicKey().getEncoded()));
-
- assertEquals("The Subject DN should be the one passed into the params", dn,
- x509userCert.getSubjectDN());
-
- assertEquals("The Issuer DN should be the same as the Subject DN", dn,
- x509userCert.getIssuerDN());
-
- assertEquals("The Serial should be the one passed into the params", serial,
- x509userCert.getSerialNumber());
-
- assertDateEquals("The notBefore date should be the one passed into the params", start,
- x509userCert.getNotBefore());
-
- assertDateEquals("The notAfter date should be the one passed into the params", end,
- x509userCert.getNotAfter());
-
- // Assert that the cert's signature verifies using the public key from generated KeyPair
- x509userCert.verify(pubKey);
- // Assert that the cert's signature verifies using the public key from the cert itself.
- x509userCert.verify(x509userCert.getPublicKey());
-
- final byte[] caCerts = mAndroidKeyStore.get(Credentials.CA_CERTIFICATE + alias);
- assertNull("A list of CA certificates should not exist for the generated entry", caCerts);
-
- ExportResult exportResult = mAndroidKeyStore.exportKey(
- Credentials.USER_PRIVATE_KEY + alias, KeymasterDefs.KM_KEY_FORMAT_X509, null, null);
- assertEquals(KeyStore.NO_ERROR, exportResult.resultCode);
- final byte[] pubKeyBytes = exportResult.exportData;
- assertNotNull("The keystore should return the public key for the generated key",
- pubKeyBytes);
- assertTrue("Public key X.509 format should be as expected",
- Arrays.equals(pubKey.getEncoded(), pubKeyBytes));
- }
-
- private static void assertDateEquals(String message, Date date1, Date date2) throws Exception {
- SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");
-
- String result1 = formatter.format(date1);
- String result2 = formatter.format(date2);
-
- assertEquals(message, result1, result2);
- }
-}
diff --git a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java b/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
deleted file mode 100644
index aa718dc..0000000
--- a/keystore/tests/src/android/security/keystore/AndroidKeyStoreTest.java
+++ /dev/null
@@ -1,2210 +0,0 @@
-/*
- * Copyright (C) 2012 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.security.keystore;
-
-import com.android.org.bouncycastle.x509.X509V3CertificateGenerator;
-
-import com.android.org.conscrypt.NativeConstants;
-
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.security.KeyStoreParameter;
-import android.test.AndroidTestCase;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.math.BigInteger;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyStore.Entry;
-import java.security.KeyStore.PrivateKeyEntry;
-import java.security.KeyStore.TrustedCertificateEntry;
-import java.security.KeyStoreException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.x500.X500Principal;
-
-public class AndroidKeyStoreTest extends AndroidTestCase {
- private android.security.KeyStore mAndroidKeyStore;
-
- private java.security.KeyStore mKeyStore;
-
- private static final String TEST_ALIAS_1 = "test1";
-
- private static final String TEST_ALIAS_2 = "test2";
-
- private static final String TEST_ALIAS_3 = "test3";
-
- private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
-
- private static final X500Principal TEST_DN_2 = new X500Principal("CN=test2");
-
- private static final BigInteger TEST_SERIAL_1 = BigInteger.ONE;
-
- private static final BigInteger TEST_SERIAL_2 = BigInteger.valueOf(2L);
-
- private static final long NOW_MILLIS = System.currentTimeMillis();
-
- /* We have to round this off because X509v3 doesn't store milliseconds. */
- private static final Date NOW = new Date(NOW_MILLIS - (NOW_MILLIS % 1000L));
-
- @SuppressWarnings("deprecation")
- private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
-
- /*
- * The keys and certificates below are generated with:
- *
- * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
- * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
- * mkdir -p demoCA/newcerts
- * touch demoCA/index.txt
- * echo "01" > demoCA/serial
- * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
- */
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_RSA_CA_1 = {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
- (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
- (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53,
- (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43,
- (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d,
- (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61,
- (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
- (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12,
- (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69,
- (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74,
- (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73,
- (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32,
- (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36,
- (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17,
- (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31,
- (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34,
- (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41,
- (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d,
- (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69,
- (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77,
- (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41,
- (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64,
- (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20,
- (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30,
- (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
- (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
- (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
- (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72,
- (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7,
- (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c,
- (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64,
- (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42,
- (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb,
- (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01,
- (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03,
- (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7,
- (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d,
- (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00,
- (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9,
- (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41,
- (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60,
- (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56,
- (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f,
- (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92,
- (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62,
- (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6,
- (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb,
- (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f,
- (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51,
- (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
- (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30,
- (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e,
- (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05,
- (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9,
- (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d,
- (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51,
- (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d,
- (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80,
- (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f,
- (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73,
- (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97,
- (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4,
- (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
- (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
- (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
- (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
- (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
- (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
- (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
- (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09,
- (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e,
- (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05,
- (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30,
- (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
- (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
- (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
- (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a,
- (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2,
- (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda,
- (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe,
- (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b,
- (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19,
- (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3,
- (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85,
- (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5,
- (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80,
- (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd,
- (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38,
- (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2,
- (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50,
- (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f,
- (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12,
- (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14,
- (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb,
- (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74,
- (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0,
- (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e,
- (byte) 0xf1, (byte) 0x61
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_RSA_KEY_1 = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
- (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
- (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
- (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
- (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
- (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
- (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
- (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
- (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
- (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
- (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
- (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
- (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
- (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
- (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
- (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
- (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
- (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
- (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
- (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
- (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
- (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
- (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
- (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
- (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
- (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
- (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
- (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
- (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
- (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
- (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
- (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
- (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
- (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
- (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
- (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
- (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
- (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
- (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
- (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
- (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
- (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
- (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
- (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
- (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
- (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
- (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
- (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
- (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
- (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
- (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
- (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
- (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
- (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
- (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
- (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
- (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
- (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
- (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
- (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
- (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
- (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
- (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
- (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
- (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
- (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
- (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
- (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
- (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
- (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
- (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
- (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
- (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
- (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
- (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
- (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
- (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
- (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
- (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
- (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
- (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
- (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
- (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
- (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
- (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
- (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
- (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
- (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
- (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
- (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
- (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
- (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
- (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
- (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
- (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
- (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
- (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
- (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
- (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
- (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
- (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
- (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
- (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
- (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
- (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
- (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_RSA_USER_1 = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x95, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xfe, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
- (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
- (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
- (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
- (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
- (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
- (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
- (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
- (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
- (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
- (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30, (byte) 0x1e,
- (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32, (byte) 0x30, (byte) 0x38,
- (byte) 0x31, (byte) 0x34, (byte) 0x32, (byte) 0x33, (byte) 0x32, (byte) 0x35,
- (byte) 0x34, (byte) 0x38, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32,
- (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x32, (byte) 0x32,
- (byte) 0x33, (byte) 0x32, (byte) 0x35, (byte) 0x34, (byte) 0x38, (byte) 0x5a,
- (byte) 0x30, (byte) 0x55, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13,
- (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08,
- (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31, (byte) 0x1b,
- (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e, (byte) 0x64,
- (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20, (byte) 0x54,
- (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43, (byte) 0x61,
- (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x31, (byte) 0x1c, (byte) 0x30,
- (byte) 0x1a, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03,
- (byte) 0x13, (byte) 0x13, (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76,
- (byte) 0x65, (byte) 0x72, (byte) 0x31, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
- (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
- (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
- (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
- (byte) 0x81, (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6,
- (byte) 0x5b, (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c,
- (byte) 0x66, (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86,
- (byte) 0x8a, (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3,
- (byte) 0x02, (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08,
- (byte) 0xf3, (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04,
- (byte) 0x6d, (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f,
- (byte) 0x67, (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c,
- (byte) 0xcb, (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30,
- (byte) 0xe2, (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5,
- (byte) 0x79, (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b,
- (byte) 0xce, (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb,
- (byte) 0x08, (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff,
- (byte) 0x3b, (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9,
- (byte) 0xc4, (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29,
- (byte) 0x0d, (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b,
- (byte) 0x23, (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78,
- (byte) 0x08, (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5,
- (byte) 0xf1, (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19,
- (byte) 0xb4, (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03,
- (byte) 0x16, (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce,
- (byte) 0x9e, (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03,
- (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x7b, (byte) 0x30,
- (byte) 0x79, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00,
- (byte) 0x30, (byte) 0x2c, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86,
- (byte) 0x48, (byte) 0x01, (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01,
- (byte) 0x0d, (byte) 0x04, (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f,
- (byte) 0x70, (byte) 0x65, (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c,
- (byte) 0x20, (byte) 0x47, (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72,
- (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43,
- (byte) 0x65, (byte) 0x72, (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69,
- (byte) 0x63, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04,
- (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x32, (byte) 0xa1, (byte) 0x1e,
- (byte) 0x6b, (byte) 0x69, (byte) 0x04, (byte) 0xfe, (byte) 0xb3, (byte) 0xcd,
- (byte) 0xf8, (byte) 0xbb, (byte) 0x14, (byte) 0xcd, (byte) 0xff, (byte) 0xd4,
- (byte) 0x16, (byte) 0xc3, (byte) 0xab, (byte) 0x44, (byte) 0x2f, (byte) 0x30,
- (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23,
- (byte) 0x04, (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14,
- (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60,
- (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c,
- (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e,
- (byte) 0x5d, (byte) 0x51, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
- (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03,
- (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x46, (byte) 0x42, (byte) 0xef,
- (byte) 0x56, (byte) 0x89, (byte) 0x78, (byte) 0x90, (byte) 0x38, (byte) 0x24,
- (byte) 0x9f, (byte) 0x8c, (byte) 0x7a, (byte) 0xce, (byte) 0x7a, (byte) 0xa5,
- (byte) 0xb5, (byte) 0x1e, (byte) 0x74, (byte) 0x96, (byte) 0x34, (byte) 0x49,
- (byte) 0x8b, (byte) 0xed, (byte) 0x44, (byte) 0xb3, (byte) 0xc9, (byte) 0x05,
- (byte) 0xd7, (byte) 0x48, (byte) 0x55, (byte) 0x52, (byte) 0x59, (byte) 0x15,
- (byte) 0x0b, (byte) 0xaa, (byte) 0x16, (byte) 0x86, (byte) 0xd2, (byte) 0x8e,
- (byte) 0x16, (byte) 0x99, (byte) 0xe8, (byte) 0x5f, (byte) 0x11, (byte) 0x71,
- (byte) 0x42, (byte) 0x55, (byte) 0xd1, (byte) 0xc4, (byte) 0x6f, (byte) 0x2e,
- (byte) 0xa9, (byte) 0x64, (byte) 0x6f, (byte) 0xd8, (byte) 0xfd, (byte) 0x43,
- (byte) 0x13, (byte) 0x24, (byte) 0xaa, (byte) 0x67, (byte) 0xe6, (byte) 0xf5,
- (byte) 0xca, (byte) 0x80, (byte) 0x5e, (byte) 0x3a, (byte) 0x3e, (byte) 0xcc,
- (byte) 0x4f, (byte) 0xba, (byte) 0x87, (byte) 0xe6, (byte) 0xae, (byte) 0xbf,
- (byte) 0x8f, (byte) 0xd5, (byte) 0x28, (byte) 0x38, (byte) 0x58, (byte) 0x30,
- (byte) 0x24, (byte) 0xf6, (byte) 0x53, (byte) 0x5b, (byte) 0x41, (byte) 0x53,
- (byte) 0xe6, (byte) 0x45, (byte) 0xbc, (byte) 0xbe, (byte) 0xe6, (byte) 0xbb,
- (byte) 0x5d, (byte) 0xd8, (byte) 0xa7, (byte) 0xf9, (byte) 0x64, (byte) 0x99,
- (byte) 0x04, (byte) 0x43, (byte) 0x75, (byte) 0xd7, (byte) 0x2d, (byte) 0x32,
- (byte) 0x0a, (byte) 0x94, (byte) 0xaf, (byte) 0x06, (byte) 0x34, (byte) 0xae,
- (byte) 0x46, (byte) 0xbd, (byte) 0xda, (byte) 0x00, (byte) 0x0e, (byte) 0x25,
- (byte) 0xc2, (byte) 0xf7, (byte) 0xc9, (byte) 0xc3, (byte) 0x65, (byte) 0xd2,
- (byte) 0x08, (byte) 0x41, (byte) 0x0a, (byte) 0xf3, (byte) 0x72
- };
-
- /*
- * The keys and certificates below are generated with:
- *
- * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
- * openssl ecparam -name prime256v1 -out ecparam.pem
- * openssl req -newkey ec:ecparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
- * mkdir -p demoCA/newcerts
- * touch demoCA/index.txt
- * echo "01" > demoCA/serial
- * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
- */
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_EC_CA_1 = {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x58, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xc1, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0xb2,
- (byte) 0x8c, (byte) 0x04, (byte) 0x95, (byte) 0xeb, (byte) 0x10, (byte) 0xcb,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31,
- (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
- (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
- (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
- (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
- (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
- (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
- (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
- (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
- (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
- (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d,
- (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37,
- (byte) 0x31, (byte) 0x36, (byte) 0x32, (byte) 0x38, (byte) 0x32, (byte) 0x38,
- (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30,
- (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x32,
- (byte) 0x38, (byte) 0x32, (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x45,
- (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41,
- (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a,
- (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53,
- (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21,
- (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74,
- (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20,
- (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74,
- (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20,
- (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x30, (byte) 0x81, (byte) 0x9f,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x8d,
- (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89, (byte) 0x02, (byte) 0x81,
- (byte) 0x81, (byte) 0x00, (byte) 0xb5, (byte) 0xf6, (byte) 0x08, (byte) 0x0f,
- (byte) 0xc4, (byte) 0x4d, (byte) 0xe4, (byte) 0x0d, (byte) 0x34, (byte) 0x1d,
- (byte) 0xe2, (byte) 0x23, (byte) 0x18, (byte) 0x63, (byte) 0x03, (byte) 0xf7,
- (byte) 0x14, (byte) 0x0e, (byte) 0x98, (byte) 0xcd, (byte) 0x45, (byte) 0x1f,
- (byte) 0xfe, (byte) 0xfb, (byte) 0x09, (byte) 0x3f, (byte) 0x5d, (byte) 0x36,
- (byte) 0x3b, (byte) 0x0f, (byte) 0xf9, (byte) 0x5e, (byte) 0x86, (byte) 0x56,
- (byte) 0x64, (byte) 0xd7, (byte) 0x3f, (byte) 0xae, (byte) 0x33, (byte) 0x09,
- (byte) 0xd3, (byte) 0xdd, (byte) 0x06, (byte) 0x17, (byte) 0x26, (byte) 0xdc,
- (byte) 0xa2, (byte) 0x8c, (byte) 0x3c, (byte) 0x65, (byte) 0xed, (byte) 0x03,
- (byte) 0x82, (byte) 0x78, (byte) 0x9b, (byte) 0xee, (byte) 0xe3, (byte) 0x98,
- (byte) 0x58, (byte) 0xe1, (byte) 0xf1, (byte) 0xa0, (byte) 0x85, (byte) 0xae,
- (byte) 0x63, (byte) 0x84, (byte) 0x41, (byte) 0x46, (byte) 0xa7, (byte) 0x4f,
- (byte) 0xdc, (byte) 0xbb, (byte) 0x1c, (byte) 0x6e, (byte) 0xec, (byte) 0x7b,
- (byte) 0xd5, (byte) 0xab, (byte) 0x3d, (byte) 0x6a, (byte) 0x05, (byte) 0x58,
- (byte) 0x0f, (byte) 0x9b, (byte) 0x6a, (byte) 0x67, (byte) 0x4b, (byte) 0xe9,
- (byte) 0x2a, (byte) 0x6d, (byte) 0x96, (byte) 0x11, (byte) 0x53, (byte) 0x95,
- (byte) 0x78, (byte) 0xaa, (byte) 0xd1, (byte) 0x91, (byte) 0x4a, (byte) 0xf8,
- (byte) 0x54, (byte) 0x52, (byte) 0x6d, (byte) 0xb9, (byte) 0xca, (byte) 0x74,
- (byte) 0x81, (byte) 0xf8, (byte) 0x99, (byte) 0x64, (byte) 0xd1, (byte) 0x4f,
- (byte) 0x01, (byte) 0x38, (byte) 0x4f, (byte) 0x08, (byte) 0x5c, (byte) 0x31,
- (byte) 0xcb, (byte) 0x7c, (byte) 0x5c, (byte) 0x78, (byte) 0x5d, (byte) 0x47,
- (byte) 0xd9, (byte) 0xf0, (byte) 0x1a, (byte) 0xeb, (byte) 0x02, (byte) 0x03,
- (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3, (byte) 0x50, (byte) 0x30,
- (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14,
- (byte) 0x5f, (byte) 0x5b, (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa,
- (byte) 0xa1, (byte) 0x9f, (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1,
- (byte) 0xbc, (byte) 0x20, (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4,
- (byte) 0xfa, (byte) 0xe3, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18, (byte) 0x30,
- (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b, (byte) 0x5e,
- (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f, (byte) 0x9e,
- (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20, (byte) 0x72,
- (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3, (byte) 0x30,
- (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
- (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01,
- (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
- (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
- (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81,
- (byte) 0x81, (byte) 0x00, (byte) 0xa1, (byte) 0x4a, (byte) 0xe6, (byte) 0xfc,
- (byte) 0x7f, (byte) 0x17, (byte) 0xaa, (byte) 0x65, (byte) 0x4a, (byte) 0x34,
- (byte) 0xde, (byte) 0x69, (byte) 0x67, (byte) 0x54, (byte) 0x4d, (byte) 0xa2,
- (byte) 0xc2, (byte) 0x98, (byte) 0x02, (byte) 0x43, (byte) 0x6a, (byte) 0x0e,
- (byte) 0x0b, (byte) 0x7f, (byte) 0xa4, (byte) 0x46, (byte) 0xaf, (byte) 0xa4,
- (byte) 0x65, (byte) 0xa0, (byte) 0xdb, (byte) 0xf1, (byte) 0x5b, (byte) 0xd5,
- (byte) 0x09, (byte) 0xbc, (byte) 0xee, (byte) 0x37, (byte) 0x51, (byte) 0x19,
- (byte) 0x36, (byte) 0xc0, (byte) 0x90, (byte) 0xd3, (byte) 0x5f, (byte) 0xf3,
- (byte) 0x4f, (byte) 0xb9, (byte) 0x08, (byte) 0x45, (byte) 0x0e, (byte) 0x01,
- (byte) 0x8a, (byte) 0x95, (byte) 0xef, (byte) 0x92, (byte) 0x95, (byte) 0x33,
- (byte) 0x78, (byte) 0xdd, (byte) 0x90, (byte) 0xbb, (byte) 0xf3, (byte) 0x06,
- (byte) 0x75, (byte) 0xd0, (byte) 0x66, (byte) 0xe6, (byte) 0xd0, (byte) 0x18,
- (byte) 0x6e, (byte) 0xeb, (byte) 0x1c, (byte) 0x52, (byte) 0xc3, (byte) 0x2e,
- (byte) 0x57, (byte) 0x7d, (byte) 0xa9, (byte) 0x03, (byte) 0xdb, (byte) 0xf4,
- (byte) 0x57, (byte) 0x5f, (byte) 0x6c, (byte) 0x7e, (byte) 0x00, (byte) 0x0d,
- (byte) 0x8f, (byte) 0xe8, (byte) 0x91, (byte) 0xf7, (byte) 0xae, (byte) 0x24,
- (byte) 0x35, (byte) 0x07, (byte) 0xb5, (byte) 0x48, (byte) 0x2d, (byte) 0x36,
- (byte) 0x30, (byte) 0x5d, (byte) 0xe9, (byte) 0x49, (byte) 0x2d, (byte) 0xd1,
- (byte) 0x5d, (byte) 0xc5, (byte) 0xf4, (byte) 0x33, (byte) 0x77, (byte) 0x3c,
- (byte) 0x71, (byte) 0xad, (byte) 0x90, (byte) 0x65, (byte) 0xa9, (byte) 0xc1,
- (byte) 0x0b, (byte) 0x5c, (byte) 0x62, (byte) 0x55, (byte) 0x50, (byte) 0x6f,
- (byte) 0x9b, (byte) 0xc9, (byte) 0x0d, (byte) 0xee
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_EC_KEY_1 = new byte[] {
- (byte) 0x30, (byte) 0x81, (byte) 0x87, (byte) 0x02, (byte) 0x01, (byte) 0x00,
- (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
- (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d,
- (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x04, (byte) 0x6d, (byte) 0x30,
- (byte) 0x6b, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x20,
- (byte) 0x3a, (byte) 0x8a, (byte) 0x02, (byte) 0xdc, (byte) 0xde, (byte) 0x70,
- (byte) 0x84, (byte) 0x45, (byte) 0x34, (byte) 0xaf, (byte) 0xbd, (byte) 0xd5,
- (byte) 0x02, (byte) 0x17, (byte) 0x69, (byte) 0x90, (byte) 0x65, (byte) 0x1e,
- (byte) 0x87, (byte) 0xf1, (byte) 0x3d, (byte) 0x17, (byte) 0xb6, (byte) 0xf4,
- (byte) 0x31, (byte) 0x94, (byte) 0x86, (byte) 0x76, (byte) 0x55, (byte) 0xf7,
- (byte) 0xcc, (byte) 0xba, (byte) 0xa1, (byte) 0x44, (byte) 0x03, (byte) 0x42,
- (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7, (byte) 0x9b,
- (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33, (byte) 0x14,
- (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3, (byte) 0xcd,
- (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d, (byte) 0xf3,
- (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f, (byte) 0x79,
- (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3, (byte) 0xd1,
- (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf, (byte) 0x50,
- (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22, (byte) 0xe6,
- (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68, (byte) 0x3b,
- (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77, (byte) 0x5e,
- (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2, (byte) 0x38
- };
-
- /**
- * Generated from above and converted with:
- *
- * openssl x509 -outform d -in usercert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
- */
- private static final byte[] FAKE_EC_USER_1 = new byte[] {
- (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x51, (byte) 0x30, (byte) 0x82,
- (byte) 0x01, (byte) 0xba, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
- (byte) 0x02, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x0d,
- (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86,
- (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05,
- (byte) 0x00, (byte) 0x30, (byte) 0x45, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
- (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13,
- (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d,
- (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74,
- (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18,
- (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e,
- (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64,
- (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50,
- (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64,
- (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x33,
- (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x37, (byte) 0x31, (byte) 0x36,
- (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x38, (byte) 0x5a, (byte) 0x17,
- (byte) 0x0d, (byte) 0x32, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
- (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x33, (byte) 0x30, (byte) 0x30,
- (byte) 0x38, (byte) 0x5a, (byte) 0x30, (byte) 0x62, (byte) 0x31, (byte) 0x0b,
- (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
- (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55, (byte) 0x31,
- (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55,
- (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53, (byte) 0x6f,
- (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74, (byte) 0x61,
- (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30, (byte) 0x1f,
- (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x0c,
- (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65, (byte) 0x72,
- (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57, (byte) 0x69,
- (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73, (byte) 0x20,
- (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c, (byte) 0x74,
- (byte) 0x64, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c, (byte) 0x12,
- (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, (byte) 0x72,
- (byte) 0x2e, (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
- (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d,
- (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07,
- (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02,
- (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
- (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03,
- (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xd9, (byte) 0xcf, (byte) 0xe7,
- (byte) 0x9b, (byte) 0x23, (byte) 0xc8, (byte) 0xa3, (byte) 0xb8, (byte) 0x33,
- (byte) 0x14, (byte) 0xa4, (byte) 0x4d, (byte) 0x75, (byte) 0x90, (byte) 0xf3,
- (byte) 0xcd, (byte) 0x43, (byte) 0xe5, (byte) 0x1b, (byte) 0x05, (byte) 0x1d,
- (byte) 0xf3, (byte) 0xd0, (byte) 0xa3, (byte) 0xb7, (byte) 0x32, (byte) 0x5f,
- (byte) 0x79, (byte) 0xdc, (byte) 0x88, (byte) 0xb8, (byte) 0x4d, (byte) 0xb3,
- (byte) 0xd1, (byte) 0x6d, (byte) 0xf7, (byte) 0x75, (byte) 0xf3, (byte) 0xbf,
- (byte) 0x50, (byte) 0xa1, (byte) 0xbc, (byte) 0x03, (byte) 0x64, (byte) 0x22,
- (byte) 0xe6, (byte) 0x1a, (byte) 0xa1, (byte) 0xe1, (byte) 0x06, (byte) 0x68,
- (byte) 0x3b, (byte) 0xbc, (byte) 0x9f, (byte) 0xd3, (byte) 0xae, (byte) 0x77,
- (byte) 0x5e, (byte) 0x88, (byte) 0x0c, (byte) 0x5e, (byte) 0x0c, (byte) 0xb2,
- (byte) 0x38, (byte) 0xa3, (byte) 0x7b, (byte) 0x30, (byte) 0x79, (byte) 0x30,
- (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13,
- (byte) 0x04, (byte) 0x02, (byte) 0x30, (byte) 0x00, (byte) 0x30, (byte) 0x2c,
- (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01,
- (byte) 0x86, (byte) 0xf8, (byte) 0x42, (byte) 0x01, (byte) 0x0d, (byte) 0x04,
- (byte) 0x1f, (byte) 0x16, (byte) 0x1d, (byte) 0x4f, (byte) 0x70, (byte) 0x65,
- (byte) 0x6e, (byte) 0x53, (byte) 0x53, (byte) 0x4c, (byte) 0x20, (byte) 0x47,
- (byte) 0x65, (byte) 0x6e, (byte) 0x65, (byte) 0x72, (byte) 0x61, (byte) 0x74,
- (byte) 0x65, (byte) 0x64, (byte) 0x20, (byte) 0x43, (byte) 0x65, (byte) 0x72,
- (byte) 0x74, (byte) 0x69, (byte) 0x66, (byte) 0x69, (byte) 0x63, (byte) 0x61,
- (byte) 0x74, (byte) 0x65, (byte) 0x30, (byte) 0x1d, (byte) 0x06, (byte) 0x03,
- (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16, (byte) 0x04,
- (byte) 0x14, (byte) 0xd5, (byte) 0xc4, (byte) 0x72, (byte) 0xbd, (byte) 0xd2,
- (byte) 0x4e, (byte) 0x90, (byte) 0x1b, (byte) 0x14, (byte) 0x32, (byte) 0xdb,
- (byte) 0x03, (byte) 0xae, (byte) 0xfa, (byte) 0x27, (byte) 0x7d, (byte) 0x8d,
- (byte) 0xe4, (byte) 0x80, (byte) 0x58, (byte) 0x30, (byte) 0x1f, (byte) 0x06,
- (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04, (byte) 0x18,
- (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x5f, (byte) 0x5b,
- (byte) 0x5e, (byte) 0xac, (byte) 0x29, (byte) 0xfa, (byte) 0xa1, (byte) 0x9f,
- (byte) 0x9e, (byte) 0xad, (byte) 0x46, (byte) 0xe1, (byte) 0xbc, (byte) 0x20,
- (byte) 0x72, (byte) 0xcf, (byte) 0x4a, (byte) 0xd4, (byte) 0xfa, (byte) 0xe3,
- (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
- (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
- (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81,
- (byte) 0x00, (byte) 0x43, (byte) 0x99, (byte) 0x9f, (byte) 0x67, (byte) 0x08,
- (byte) 0x43, (byte) 0xd5, (byte) 0x6b, (byte) 0x6f, (byte) 0xd7, (byte) 0x05,
- (byte) 0xd6, (byte) 0x75, (byte) 0x34, (byte) 0x30, (byte) 0xca, (byte) 0x20,
- (byte) 0x47, (byte) 0x61, (byte) 0xa1, (byte) 0x89, (byte) 0xb6, (byte) 0xf1,
- (byte) 0x49, (byte) 0x7b, (byte) 0xd9, (byte) 0xb9, (byte) 0xe8, (byte) 0x1e,
- (byte) 0x29, (byte) 0x74, (byte) 0x0a, (byte) 0x67, (byte) 0xc0, (byte) 0x7d,
- (byte) 0xb8, (byte) 0xe6, (byte) 0x39, (byte) 0xa8, (byte) 0x5e, (byte) 0xc3,
- (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x6a, (byte) 0x1f, (byte) 0x1d,
- (byte) 0xfc, (byte) 0x11, (byte) 0x59, (byte) 0x0b, (byte) 0xb9, (byte) 0xad,
- (byte) 0x3a, (byte) 0x4e, (byte) 0x50, (byte) 0x0a, (byte) 0x61, (byte) 0xdb,
- (byte) 0x75, (byte) 0x6b, (byte) 0xe5, (byte) 0x3f, (byte) 0x8d, (byte) 0xde,
- (byte) 0x28, (byte) 0x68, (byte) 0xb1, (byte) 0x29, (byte) 0x9a, (byte) 0x18,
- (byte) 0x8a, (byte) 0xfc, (byte) 0x3f, (byte) 0x13, (byte) 0x93, (byte) 0x29,
- (byte) 0xed, (byte) 0x22, (byte) 0x7c, (byte) 0xb4, (byte) 0x50, (byte) 0xd5,
- (byte) 0x4d, (byte) 0x32, (byte) 0x4d, (byte) 0x42, (byte) 0x2b, (byte) 0x29,
- (byte) 0x97, (byte) 0x86, (byte) 0xc0, (byte) 0x01, (byte) 0x00, (byte) 0x25,
- (byte) 0xf6, (byte) 0xd3, (byte) 0x2a, (byte) 0xd8, (byte) 0xda, (byte) 0x13,
- (byte) 0x94, (byte) 0x12, (byte) 0x78, (byte) 0x14, (byte) 0x0b, (byte) 0x51,
- (byte) 0xc0, (byte) 0x45, (byte) 0xb4, (byte) 0x02, (byte) 0x37, (byte) 0x98,
- (byte) 0x42, (byte) 0x3c, (byte) 0xcb, (byte) 0x2e, (byte) 0xe4, (byte) 0x38,
- (byte) 0x69, (byte) 0x1b, (byte) 0x72, (byte) 0xf0, (byte) 0xaa, (byte) 0x89,
- (byte) 0x7e, (byte) 0xde, (byte) 0xb2
- };
-
- /**
- * The amount of time to allow before and after expected time for variance
- * in timing tests.
- */
- private static final long SLOP_TIME_MILLIS = 15000L;
-
- @Override
- protected void setUp() throws Exception {
- mAndroidKeyStore = android.security.KeyStore.getInstance();
-
- assertTrue(mAndroidKeyStore.reset());
- assertFalse(mAndroidKeyStore.isUnlocked());
-
- mKeyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
- }
-
- private void setupPassword() {
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- assertEquals(0, mAndroidKeyStore.list("").length);
- }
-
- private void assertAliases(final String[] expectedAliases) throws KeyStoreException {
- final Enumeration<String> aliases = mKeyStore.aliases();
- int count = 0;
-
- final Set<String> expectedSet = new HashSet<String>();
- expectedSet.addAll(Arrays.asList(expectedAliases));
-
- while (aliases.hasMoreElements()) {
- count++;
- final String alias = aliases.nextElement();
- assertTrue("The alias should be in the expected set", expectedSet.contains(alias));
- expectedSet.remove(alias);
- }
- assertTrue("The expected set and actual set should be exactly equal", expectedSet.isEmpty());
- assertEquals("There should be the correct number of keystore entries",
- expectedAliases.length, count);
- }
-
- public void testKeyStore_Aliases_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertAliases(new String[] {});
-
- assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
- null));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
- }
-
- public void testKeyStore_Aliases_NotInitialized_Encrypted_Failure() throws Exception {
- setupPassword();
-
- try {
- mKeyStore.aliases();
- fail("KeyStore should throw exception when not initialized");
- } catch (KeyStoreException success) {
- }
- }
-
- public void testKeyStore_ContainsAliases_PrivateAndCA_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertAliases(new String[] {});
-
- assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
- null));
-
- assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
-
- assertFalse("Should not contain unadded certificate alias",
- mKeyStore.containsAlias(TEST_ALIAS_3));
- }
-
- public void testKeyStore_ContainsAliases_CAOnly_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
- }
-
- public void testKeyStore_ContainsAliases_NonExistent_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_1));
- }
-
- public void testKeyStore_DeleteEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // TEST_ALIAS_1
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // TEST_ALIAS_2
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // TEST_ALIAS_3
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_3, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
-
- mKeyStore.deleteEntry(TEST_ALIAS_1);
-
- assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
-
- mKeyStore.deleteEntry(TEST_ALIAS_3);
-
- assertAliases(new String[] { TEST_ALIAS_2 });
-
- mKeyStore.deleteEntry(TEST_ALIAS_2);
-
- assertAliases(new String[] { });
- }
-
- public void testKeyStore_DeleteEntry_EmptyStore_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // Should not throw when a non-existent entry is requested for delete.
- mKeyStore.deleteEntry(TEST_ALIAS_1);
- }
-
- public void testKeyStore_DeleteEntry_NonExistent_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // TEST_ALIAS_1
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // Should not throw when a non-existent entry is requested for delete.
- mKeyStore.deleteEntry(TEST_ALIAS_2);
- }
-
- public void testKeyStore_GetCertificate_Single_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- assertNull("Certificate should not exist in keystore",
- mKeyStore.getCertificate(TEST_ALIAS_2));
-
- Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
-
- assertNotNull("Retrieved certificate should not be null", retrieved);
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertEquals("Actual and retrieved certificates should be the same", actual, retrieved);
- }
-
- public void testKeyStore_GetCertificate_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("Certificate should not exist in keystore",
- mKeyStore.getCertificate(TEST_ALIAS_1));
- }
-
- public void testKeyStore_GetCertificateAlias_CAEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Encrypted_Success()
- throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- // Insert TrustedCertificateEntry with CA name
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- // Insert PrivateKeyEntry that uses the same CA
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertEquals("Stored certificate alias should be found", TEST_ALIAS_2,
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_NonExist_Empty_Encrypted_Failure()
- throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- assertNull("Stored certificate alias should not be found",
- mKeyStore.getCertificateAlias(actual));
- }
-
- public void testKeyStore_GetCertificateAlias_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory f = CertificateFactory.getInstance("X.509");
- Certificate userCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- assertNull("Stored certificate alias should be found",
- mKeyStore.getCertificateAlias(userCert));
- }
-
- public void testKeyStore_GetCertificateChain_SingleLength_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- Certificate[] expected = new Certificate[2];
- expected[0] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expected[1] = cf.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1);
-
- assertNotNull("Returned certificate chain should not be null", actual);
- assertEquals("Returned certificate chain should be correct size", expected.length,
- actual.length);
- assertEquals("First certificate should be user certificate", expected[0], actual[0]);
- assertEquals("Second certificate should be CA certificate", expected[1], actual[1]);
-
- // Negative test when keystore is populated.
- assertNull("Stored certificate alias should not be found",
- mKeyStore.getCertificateChain(TEST_ALIAS_2));
- }
-
- public void testKeyStore_GetCertificateChain_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("Stored certificate alias should not be found",
- mKeyStore.getCertificateChain(TEST_ALIAS_1));
- }
-
- public void testKeyStore_GetCreationDate_PrivateKeyEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Date now = new Date();
- Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-
- Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
- Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
- assertTrue("Time should be close to current time", actual.before(expectedBefore));
- assertTrue("Time should be close to current time", actual.after(expectedAfter));
- }
-
- public void testKeyStore_GetCreationDate_PrivateKeyEntry_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Date now = new Date();
- Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
-
- Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
- Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
- assertTrue("Time should be close to current time", actual.before(expectedBefore));
- assertTrue("Time should be close to current time", actual.after(expectedAfter));
- }
-
- public void testKeyStore_GetCreationDate_CAEntry_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Date now = new Date();
- Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
- assertNotNull("Certificate should be found", actual);
-
- Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
- Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
-
- assertTrue("Time should be close to current time", actual.before(expectedBefore));
- assertTrue("Time should be close to current time", actual.after(expectedAfter));
- }
-
- public void testKeyStore_GetEntry_NullParams_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Entry should exist", entry);
-
- assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_EC_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- FAKE_EC_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_EC_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Entry should exist", entry);
-
- assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
- }
-
- public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- FAKE_RSA_USER_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Entry should exist", entry);
-
- assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- @SuppressWarnings("unchecked")
- private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, String keyType, byte[] key,
- byte[] cert, byte[] ca) throws Exception {
- KeyFactory keyFact = KeyFactory.getInstance(keyType);
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(key));
-
- CertificateFactory certFact = CertificateFactory.getInstance("X.509");
- Certificate expectedCert = certFact.generateCertificate(new ByteArrayInputStream(cert));
-
- final Collection<Certificate> expectedChain;
- if (ca != null) {
- expectedChain = (Collection<Certificate>) certFact
- .generateCertificates(new ByteArrayInputStream(ca));
- } else {
- expectedChain = null;
- }
-
- assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, expectedChain);
- }
-
- private void assertPrivateKeyEntryEquals(PrivateKeyEntry keyEntry, PrivateKey expectedKey,
- Certificate expectedCert, Collection<Certificate> expectedChain) throws Exception {
- if (expectedKey instanceof ECKey) {
- assertEquals("Returned PrivateKey should be what we inserted",
- ((ECKey) expectedKey).getParams().getCurve(),
- ((ECKey) keyEntry.getCertificate().getPublicKey()).getParams().getCurve());
- } else if (expectedKey instanceof RSAKey) {
- assertEquals("Returned PrivateKey should be what we inserted",
- ((RSAKey) expectedKey).getModulus(),
- ((RSAKey) keyEntry.getPrivateKey()).getModulus());
- }
-
- assertEquals("Returned Certificate should be what we inserted", expectedCert,
- keyEntry.getCertificate());
-
- Certificate[] actualChain = keyEntry.getCertificateChain();
-
- assertEquals("First certificate in chain should be user cert", expectedCert, actualChain[0]);
-
- if (expectedChain == null) {
- assertEquals("Certificate chain should not include CAs", 1, actualChain.length);
- } else {
- int i = 1;
- final Iterator<Certificate> it = expectedChain.iterator();
- while (it.hasNext()) {
- assertEquals("CA chain certificate should equal what we put in", it.next(),
- actualChain[i++]);
- }
- }
- }
-
- public void testKeyStore_GetEntry_Nonexistent_NullParams_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("A non-existent entry should return null",
- mKeyStore.getEntry(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetEntry_Nonexistent_NullParams_Unencrypted_Failure() throws Exception {
- mKeyStore.load(null, null);
-
- assertNull("A non-existent entry should return null",
- mKeyStore.getEntry(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetKey_NoPassword_Encrypted_Success() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
- assertNotNull("Key should exist", key);
-
- assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
- assertTrue("Should be a RSAKey", key instanceof RSAKey);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- assertEquals("Inserted key should be same as retrieved key",
- ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
- }
-
- public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_NONE));
-
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
- assertNotNull("Key should exist", key);
-
- assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
- assertTrue("Should be a RSAKey", key instanceof RSAKey);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- assertEquals("Inserted key should be same as retrieved key",
- ((RSAKey) expectedKey).getModulus(), ((RSAKey) key).getModulus());
- }
-
- public void testKeyStore_GetKey_Certificate_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetKey_NonExistent_Encrypted_Failure() throws Exception {
- setupPassword();
-
- mKeyStore.load(null, null);
-
- assertNull("A non-existent entry should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
- }
-
- public void testKeyStore_GetProvider_Encrypted_Success() throws Exception {
- assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName());
- setupPassword();
- assertEquals(AndroidKeyStoreProvider.PROVIDER_NAME, mKeyStore.getProvider().getName());
- }
-
- public void testKeyStore_GetType_Encrypted_Success() throws Exception {
- assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
- setupPassword();
- assertEquals(AndroidKeyStoreSpi.NAME, mKeyStore.getType());
- }
-
- public void testKeyStore_IsCertificateEntry_CA_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should return true for CA certificate",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsCertificateEntry_PrivateKey_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertFalse("Should return false for PrivateKeyEntry",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsCertificateEntry_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertFalse("Should return false for non-existent entry",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsCertificateEntry_NonExist_Unencrypted_Failure() throws Exception {
- mKeyStore.load(null, null);
-
- assertFalse("Should return false for non-existent entry",
- mKeyStore.isCertificateEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsKeyEntry_PrivateKey_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsKeyEntry_CA_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_IsKeyEntry_NonExist_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertFalse("Should return false for non-existent entry",
- mKeyStore.isKeyEntry(TEST_ALIAS_1));
- }
-
- public void testKeyStore_SetCertificate_CA_Encrypted_Success() throws Exception {
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
- final Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- setupPassword();
- mKeyStore.load(null, null);
-
- mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual);
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
-
- assertEquals("Retrieved certificate should be the same as the one inserted", actual,
- retrieved);
- }
-
- public void testKeyStore_SetCertificate_CAExists_Overwrite_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
- final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // TODO have separate FAKE_CA for second test
- mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
-
- assertAliases(new String[] { TEST_ALIAS_1 });
- }
-
- public void testKeyStore_SetCertificate_PrivateKeyExists_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.importKey(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1,
- FAKE_RSA_KEY_1, KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_USER_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
- final Certificate cert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- try {
- mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
- fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate");
- } catch (KeyStoreException success) {
- }
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_EC_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("EC");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_EC_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_EC_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_RSA_Unencrypted_Success() throws Exception {
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_Params_Unencrypted_Failure() throws Exception {
- mKeyStore.load(null, null);
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry entry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- try {
- mKeyStore.setEntry(TEST_ALIAS_1, entry,
- new KeyStoreParameter.Builder(getContext())
- .setEncryptionRequired(true)
- .build());
- fail("Shouldn't be able to insert encrypted entry when KeyStore uninitialized");
- } catch (KeyStoreException expected) {
- }
-
- assertNull(mKeyStore.getEntry(TEST_ALIAS_1, null));
- }
-
- public void
- testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final KeyFactory keyFact = KeyFactory.getInstance("RSA");
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- // Start with PrivateKeyEntry
- {
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // TODO make entirely new test vector for the overwrite
- // Replace with PrivateKeyEntry
- {
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
-
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
- }
-
- public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- // Start with TrustedCertificateEntry
- {
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedCertEntry.getTrustedCertificate(),
- actualCertEntry.getTrustedCertificate());
- }
-
- // Replace with PrivateKeyEntry
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
- }
-
- public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // Start with PrivateKeyEntry
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = caCert;
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // Replace with TrustedCertificateEntry
- {
- TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedCertEntry.getTrustedCertificate(),
- actualCertEntry.getTrustedCertificate());
- }
- }
-
- public
- void
- testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // Start with PrivateKeyEntry
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[2];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- expectedChain[1] = caCert;
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // Replace with PrivateKeyEntry that has no chain
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey expectedKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] expectedChain = new Certificate[1];
- expectedChain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
-
- mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actualPrivEntry = (PrivateKeyEntry) actualEntry;
- assertPrivateKeyEntryEquals(actualPrivEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- null);
- }
- }
-
- public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Encrypted_Success()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- // Insert TrustedCertificateEntry
- {
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualCertEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedCertEntry.getTrustedCertificate(),
- actualCertEntry.getTrustedCertificate());
- }
-
- // Replace with TrustedCertificateEntry of USER
- {
- final Certificate userCert = f
- .generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
-
- TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert);
- mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
- assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
- actualEntry instanceof TrustedCertificateEntry);
- TrustedCertificateEntry actualUserEntry = (TrustedCertificateEntry) actualEntry;
- assertEquals("Stored and retrieved certificates should be the same",
- expectedUserEntry.getTrustedCertificate(),
- actualUserEntry.getTrustedCertificate());
- }
- }
-
- public void testKeyStore_SetKeyEntry_ProtectedKey_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- try {
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain);
- fail("Should fail when a password is specified");
- } catch (KeyStoreException success) {
- }
- }
-
- public void testKeyStore_SetKeyEntry_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
- }
-
- public void testKeyStore_SetKeyEntry_Replaced_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- final CertificateFactory f = CertificateFactory.getInstance("X.509");
-
- final Certificate caCert = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
-
- // Insert initial key
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
-
- // TODO make a separate key
- // Replace key
- {
- KeyFactory keyFact = KeyFactory.getInstance("RSA");
- PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1));
- final Certificate[] chain = new Certificate[2];
- chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
- chain[1] = caCert;
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
-
- Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull("Retrieved entry should exist", actualEntry);
-
- assertTrue("Retrieved entry should be of type PrivateKeyEntry",
- actualEntry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry actual = (PrivateKeyEntry) actualEntry;
-
- assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1,
- FAKE_RSA_CA_1);
- }
- }
-
- @SuppressWarnings("deprecation")
- private static X509Certificate generateCertificate(android.security.KeyStore keyStore,
- String alias, BigInteger serialNumber, X500Principal subjectDN, Date notBefore,
- Date notAfter) throws Exception {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + alias;
-
- KeyPair keyPair = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
- keyStore, privateKeyAlias, KeyStore.UID_SELF);
-
- final X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
- certGen.setPublicKey(keyPair.getPublic());
- certGen.setSerialNumber(serialNumber);
- certGen.setSubjectDN(subjectDN);
- certGen.setIssuerDN(subjectDN);
- certGen.setNotBefore(notBefore);
- certGen.setNotAfter(notAfter);
- certGen.setSignatureAlgorithm("sha1WithRSA");
-
- final X509Certificate cert = certGen.generate(keyPair.getPrivate());
-
- return cert;
- }
-
- public void testKeyStore_SetKeyEntry_ReplacedChain_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- // Create key #1
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
- NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-
- assertTrue(key instanceof PrivateKey);
-
- PrivateKey expectedKey = (PrivateKey) key;
-
- X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
- TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- expectedCert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
- assertTrue(entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
- }
-
- // Replace key #1 with new chain
- {
- Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
-
- assertTrue(key instanceof PrivateKey);
-
- PrivateKey expectedKey = (PrivateKey) key;
-
- X509Certificate expectedCert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
- TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
- mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null,
- new Certificate[] { expectedCert });
-
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
- assertTrue(entry instanceof PrivateKeyEntry);
-
- PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry;
-
- assertPrivateKeyEntryEquals(keyEntry, expectedKey, expectedCert, null);
- }
- }
-
- public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Encrypted_Failure()
- throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- // Create key #1
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
- NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1,
- TEST_SERIAL_1, TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- }
-
- // Create key #2
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_2;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias, KeyStore.UID_SELF,
- NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED, null));
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
- TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_2,
- cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- }
-
- // Replace key #1 with key #2
- {
- Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null);
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_2,
- TEST_SERIAL_2, TEST_DN_2, NOW, NOW_PLUS_10_YEARS);
-
- try {
- mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert });
- fail("Should not allow setting of KeyEntry with wrong PrivaetKey");
- } catch (KeyStoreException success) {
- }
- }
- }
-
- public void testKeyStore_SetKeyEntry_ReplacedChain_UnencryptedToEncrypted_Failure()
- throws Exception {
- mKeyStore.load(null, null);
-
- // Create key #1
- {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore.generate(privateKeyAlias,
- android.security.KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024,
- android.security.KeyStore.FLAG_NONE, null));
-
- X509Certificate cert =
- generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1, TEST_DN_1,
- NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- cert.getEncoded(), android.security.KeyStore.UID_SELF,
- android.security.KeyStore.FLAG_NONE));
- }
-
- // Replace with one that requires encryption
- {
- Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-
- try {
- mKeyStore.setEntry(TEST_ALIAS_1, entry,
- new KeyStoreParameter.Builder(getContext())
- .setEncryptionRequired(true)
- .build());
- fail("Should not allow setting of Entry without unlocked keystore");
- } catch (KeyStoreException success) {
- }
-
- assertTrue(mAndroidKeyStore.onUserPasswordChanged("1111"));
- assertTrue(mAndroidKeyStore.isUnlocked());
-
- mKeyStore.setEntry(TEST_ALIAS_1, entry,
- new KeyStoreParameter.Builder(getContext())
- .setEncryptionRequired(true)
- .build());
- }
- }
-
- public void testKeyStore_Size_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_1, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertEquals("The keystore size should match expected", 1, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_1 });
-
- assertTrue(mAndroidKeyStore.put(Credentials.CA_CERTIFICATE + TEST_ALIAS_2, FAKE_RSA_CA_1,
- KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
-
- assertEquals("The keystore size should match expected", 2, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
-
- assertTrue(mAndroidKeyStore.generate(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3,
- KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024, KeyStore.FLAG_ENCRYPTED,
- null));
-
- assertEquals("The keystore size should match expected", 3, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
-
- assertTrue(mAndroidKeyStore.delete(Credentials.CA_CERTIFICATE + TEST_ALIAS_1));
-
- assertEquals("The keystore size should match expected", 2, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
-
- assertTrue(mAndroidKeyStore.delete(Credentials.USER_PRIVATE_KEY + TEST_ALIAS_3));
-
- assertEquals("The keystore size should match expected", 1, mKeyStore.size());
- assertAliases(new String[] { TEST_ALIAS_2 });
- }
-
- public void testKeyStore_Store_LoadStoreParam_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- try {
- mKeyStore.store(null);
- fail("Should throw UnsupportedOperationException when trying to store");
- } catch (UnsupportedOperationException success) {
- }
- }
-
- public void testKeyStore_Load_InputStreamSupplied_Encrypted_Failure() throws Exception {
- byte[] buf = "FAKE KEYSTORE".getBytes();
- ByteArrayInputStream is = new ByteArrayInputStream(buf);
-
- try {
- mKeyStore.load(is, null);
- fail("Should throw IllegalArgumentException when InputStream is supplied");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyStore_Load_PasswordSupplied_Encrypted_Failure() throws Exception {
- try {
- mKeyStore.load(null, "password".toCharArray());
- fail("Should throw IllegalArgumentException when password is supplied");
- } catch (IllegalArgumentException success) {
- }
- }
-
- public void testKeyStore_Store_OutputStream_Encrypted_Failure() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- OutputStream sink = new ByteArrayOutputStream();
- try {
- mKeyStore.store(sink, null);
- fail("Should throw UnsupportedOperationException when trying to store");
- } catch (UnsupportedOperationException success) {
- }
-
- try {
- mKeyStore.store(sink, "blah".toCharArray());
- fail("Should throw UnsupportedOperationException when trying to store");
- } catch (UnsupportedOperationException success) {
- }
- }
-
- private void setupKey() throws Exception {
- final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + TEST_ALIAS_1;
- assertTrue(mAndroidKeyStore
- .generate(privateKeyAlias, KeyStore.UID_SELF, NativeConstants.EVP_PKEY_RSA, 1024,
- KeyStore.FLAG_ENCRYPTED, null));
-
- X509Certificate cert = generateCertificate(mAndroidKeyStore, TEST_ALIAS_1, TEST_SERIAL_1,
- TEST_DN_1, NOW, NOW_PLUS_10_YEARS);
-
- assertTrue(mAndroidKeyStore.put(Credentials.USER_CERTIFICATE + TEST_ALIAS_1,
- cert.getEncoded(), KeyStore.UID_SELF, KeyStore.FLAG_ENCRYPTED));
- }
-
- public void testKeyStore_KeyOperations_Wrap_Encrypted_Success() throws Exception {
- setupPassword();
- mKeyStore.load(null, null);
-
- setupKey();
-
- // Test key usage
- Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null);
- assertNotNull(e);
- assertTrue(e instanceof PrivateKeyEntry);
-
- PrivateKeyEntry privEntry = (PrivateKeyEntry) e;
- PrivateKey privKey = privEntry.getPrivateKey();
- assertNotNull(privKey);
-
- PublicKey pubKey = privEntry.getCertificate().getPublicKey();
-
- Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
- c.init(Cipher.WRAP_MODE, pubKey);
-
- byte[] expectedKey = new byte[] {
- 0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A
- };
-
- SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES");
-
- byte[] wrappedExpected = c.wrap(expectedSecret);
-
- c.init(Cipher.UNWRAP_MODE, privKey);
- SecretKey actualSecret = (SecretKey) c.unwrap(wrappedExpected, "AES", Cipher.SECRET_KEY);
-
- assertEquals(Arrays.toString(expectedSecret.getEncoded()),
- Arrays.toString(actualSecret.getEncoded()));
- }
-}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
new file mode 100644
index 0000000..558cdc0
--- /dev/null
+++ b/libs/hwui/Android.bp
@@ -0,0 +1,336 @@
+cc_defaults {
+ name: "hwui_defaults",
+ defaults: [
+ "hwui_static_deps",
+
+ //"hwui_bugreport_font_cache_usage",
+ //"hwui_compile_for_perf",
+ "hwui_enable_renderscript",
+
+ // Enables fine-grained GLES error checking
+ // If enabled, every GLES call is wrapped & error checked
+ // Has moderate overhead
+ //"hwui_enable_opengl-validation",
+ ],
+
+ cflags: [
+ "-DEGL_EGLEXT_PROTOTYPES",
+ "-DGL_GLEXT_PROTOTYPES",
+ "-DATRACE_TAG=ATRACE_TAG_VIEW",
+ "-DLOG_TAG=\"OpenGLRenderer\"",
+ "-Wall",
+ "-Wno-unused-parameter",
+ "-Wunreachable-code",
+ "-Werror",
+ "-fvisibility=hidden",
+ "-DHWUI_NEW_OPS",
+
+ // GCC false-positives on this warning, and since we -Werror that's
+ // a problem
+ "-Wno-free-nonheap-object",
+ ],
+
+ include_dirs: [
+ "external/skia/include/private",
+ "external/skia/src/core",
+ ],
+
+ product_variables: {
+ device_uses_hwc2: {
+ cflags: ["-DUSE_HWC2"],
+ },
+ },
+}
+
+cc_defaults {
+ name: "hwui_static_deps",
+ shared_libs: [
+ "liblog",
+ "libcutils",
+ "libutils",
+ "libEGL",
+ "libGLESv2",
+ "libskia",
+ "libui",
+ "libgui",
+ "libprotobuf-cpp-lite",
+ "libharfbuzz_ng",
+ "libft2",
+ "libminikin",
+ "libandroidfw",
+ ],
+}
+
+cc_defaults {
+ name: "hwui_bugreport_font_cache_usage",
+ srcs: ["font/FontCacheHistoryTracker.cpp"],
+ cflags: ["-DBUGREPORT_FONT_CACHE_USAGE"],
+}
+
+cc_defaults {
+ name: "hwui_compile_for_perf",
+ // TODO: Non-arm?
+ cflags: [
+ "-fno-omit-frame-pointer",
+ "-marm",
+ "-mapcs",
+ ],
+}
+
+cc_defaults {
+ name: "hwui_enable_renderscript",
+ cflags: ["-DANDROID_ENABLE_RENDERSCRIPT"],
+ include_dirs: [
+ "frameworks/rs/cpp",
+ "frameworks/rs",
+ ],
+ shared_libs: ["libRScpp"],
+}
+
+cc_defaults {
+ name: "hwui_debug",
+ cflags: ["-include debug/wrap_gles.h"],
+ srcs: [
+ "debug/wrap_gles.cpp",
+ ],
+ include_dirs: ["frameworks/native/opengl/libs/GLES2"],
+}
+
+cc_defaults {
+ name: "hwui_enable_opengl_validation",
+ defaults: ["hwui_debug"],
+ cflags: ["-DDEBUG_OPENGL=3"],
+}
+
+// ------------------------
+// library
+// ------------------------
+
+cc_defaults {
+ name: "libhwui_defaults",
+ defaults: ["hwui_defaults"],
+ srcs: [
+ "font/CacheTexture.cpp",
+ "font/Font.cpp",
+ "hwui/Canvas.cpp",
+ "hwui/MinikinSkia.cpp",
+ "hwui/MinikinUtils.cpp",
+ "hwui/PaintImpl.cpp",
+ "hwui/Typeface.cpp",
+ "renderstate/Blend.cpp",
+ "renderstate/MeshState.cpp",
+ "renderstate/OffscreenBufferPool.cpp",
+ "renderstate/PixelBufferState.cpp",
+ "renderstate/RenderState.cpp",
+ "renderstate/Scissor.cpp",
+ "renderstate/Stencil.cpp",
+ "renderstate/TextureState.cpp",
+ "renderthread/CanvasContext.cpp",
+ "renderthread/DrawFrameTask.cpp",
+ "renderthread/EglManager.cpp",
+ "renderthread/RenderProxy.cpp",
+ "renderthread/RenderTask.cpp",
+ "renderthread/RenderThread.cpp",
+ "renderthread/TimeLord.cpp",
+ "thread/TaskManager.cpp",
+ "utils/Blur.cpp",
+ "utils/GLUtils.cpp",
+ "utils/LinearAllocator.cpp",
+ "utils/NinePatchImpl.cpp",
+ "utils/StringUtils.cpp",
+ "utils/TestWindowContext.cpp",
+ "utils/VectorDrawableUtils.cpp",
+ "AmbientShadow.cpp",
+ "AnimationContext.cpp",
+ "Animator.cpp",
+ "AnimatorManager.cpp",
+ "AssetAtlas.cpp",
+ "BakedOpDispatcher.cpp",
+ "BakedOpRenderer.cpp",
+ "BakedOpState.cpp",
+ "Caches.cpp",
+ "CanvasState.cpp",
+ "ClipArea.cpp",
+ "DamageAccumulator.cpp",
+ "DeferredDisplayList.cpp",
+ "DeferredLayerUpdater.cpp",
+ "DeviceInfo.cpp",
+ "DisplayList.cpp",
+ "DisplayListCanvas.cpp",
+ "Dither.cpp",
+ "Extensions.cpp",
+ "FboCache.cpp",
+ "FontRenderer.cpp",
+ "FrameBuilder.cpp",
+ "FrameInfo.cpp",
+ "FrameInfoVisualizer.cpp",
+ "GammaFontRenderer.cpp",
+ "GlopBuilder.cpp",
+ "GpuMemoryTracker.cpp",
+ "GradientCache.cpp",
+ "Image.cpp",
+ "Interpolator.cpp",
+ "JankTracker.cpp",
+ "Layer.cpp",
+ "LayerBuilder.cpp",
+ "LayerCache.cpp",
+ "LayerRenderer.cpp",
+ "LayerUpdateQueue.cpp",
+ "Matrix.cpp",
+ "OpDumper.cpp",
+ "OpenGLRenderer.cpp",
+ "Patch.cpp",
+ "PatchCache.cpp",
+ "PathCache.cpp",
+ "PathTessellator.cpp",
+ "PathParser.cpp",
+ "PixelBuffer.cpp",
+ "Program.cpp",
+ "ProgramCache.cpp",
+ "Properties.cpp",
+ "PropertyValuesHolder.cpp",
+ "PropertyValuesAnimatorSet.cpp",
+ "Readback.cpp",
+ "RecordingCanvas.cpp",
+ "RenderBufferCache.cpp",
+ "RenderNode.cpp",
+ "RenderProperties.cpp",
+ "ResourceCache.cpp",
+ "ShadowTessellator.cpp",
+ "SkiaCanvas.cpp",
+ "SkiaCanvasProxy.cpp",
+ "SkiaShader.cpp",
+ "Snapshot.cpp",
+ "SpotShadow.cpp",
+ "TessellationCache.cpp",
+ "TextDropShadowCache.cpp",
+ "Texture.cpp",
+ "TextureCache.cpp",
+ "VectorDrawable.cpp",
+ "protos/hwui.proto",
+ ],
+
+ proto: {
+ export_proto_headers: true,
+ },
+
+ export_include_dirs: ["."],
+}
+
+cc_library {
+ name: "libhwui",
+ defaults: ["libhwui_defaults"],
+}
+
+// ------------------------
+// static library null gpu
+// ------------------------
+
+cc_library_static {
+ name: "libhwui_static_null_gpu",
+ defaults: ["libhwui_defaults"],
+ cflags: ["-DHWUI_NULL_GPU"],
+ srcs: [
+ "debug/nullegl.cpp",
+ "debug/nullgles.cpp",
+ ],
+ export_include_dirs: ["."],
+}
+
+cc_defaults {
+ name: "hwui_test_defaults",
+ defaults: ["hwui_defaults"],
+ srcs: [
+ "tests/common/scenes/*.cpp",
+ "tests/common/TestContext.cpp",
+ "tests/common/TestScene.cpp",
+ "tests/common/TestUtils.cpp",
+ ],
+}
+
+// ------------------------
+// unit tests
+// ------------------------
+
+cc_test {
+ name: "hwui_unit_tests",
+ defaults: ["hwui_test_defaults"],
+ static_libs: ["libhwui_static_null_gpu"],
+ shared_libs: ["libmemunreachable"],
+ cflags: ["-DHWUI_NULL_GPU"],
+
+ srcs: [
+ "tests/unit/main.cpp",
+ "tests/unit/BakedOpDispatcherTests.cpp",
+ "tests/unit/BakedOpRendererTests.cpp",
+ "tests/unit/BakedOpStateTests.cpp",
+ "tests/unit/CanvasStateTests.cpp",
+ "tests/unit/ClipAreaTests.cpp",
+ "tests/unit/DamageAccumulatorTests.cpp",
+ "tests/unit/DeviceInfoTests.cpp",
+ "tests/unit/FatVectorTests.cpp",
+ "tests/unit/FontRendererTests.cpp",
+ "tests/unit/FrameBuilderTests.cpp",
+ "tests/unit/GlopBuilderTests.cpp",
+ "tests/unit/GpuMemoryTrackerTests.cpp",
+ "tests/unit/GradientCacheTests.cpp",
+ "tests/unit/LayerUpdateQueueTests.cpp",
+ "tests/unit/LeakCheckTests.cpp",
+ "tests/unit/LinearAllocatorTests.cpp",
+ "tests/unit/MatrixTests.cpp",
+ "tests/unit/OffscreenBufferPoolTests.cpp",
+ "tests/unit/OpDumperTests.cpp",
+ "tests/unit/RecordingCanvasTests.cpp",
+ "tests/unit/RenderNodeTests.cpp",
+ "tests/unit/RenderPropertiesTests.cpp",
+ "tests/unit/SkiaBehaviorTests.cpp",
+ "tests/unit/SkiaCanvasTests.cpp",
+ "tests/unit/SnapshotTests.cpp",
+ "tests/unit/StringUtilsTests.cpp",
+ "tests/unit/TestUtilsTests.cpp",
+ "tests/unit/TextDropShadowCacheTests.cpp",
+ "tests/unit/VectorDrawableTests.cpp",
+ ],
+}
+
+// ------------------------
+// Macro-bench app
+// ------------------------
+
+cc_test {
+ name: "hwuitest",
+ defaults: ["hwui_test_defaults"],
+ gtest: false,
+
+ // set to libhwui_static_null_gpu to skip actual GL commands
+ whole_static_libs: ["libhwui"],
+
+ srcs: [
+ "tests/macrobench/TestSceneRunner.cpp",
+ "tests/macrobench/main.cpp",
+ ],
+}
+
+// ------------------------
+// Micro-bench app
+// ---------------------
+
+cc_benchmark {
+ name: "hwuimicro",
+ defaults: ["hwui_test_defaults"],
+
+ cflags: ["-DHWUI_NULL_GPU"],
+
+ whole_static_libs: ["libhwui_static_null_gpu"],
+
+ srcs: [
+ "tests/microbench/main.cpp",
+ "tests/microbench/DisplayListCanvasBench.cpp",
+ "tests/microbench/FontBench.cpp",
+ "tests/microbench/FrameBuilderBench.cpp",
+ "tests/microbench/LinearAllocatorBench.cpp",
+ "tests/microbench/PathParserBench.cpp",
+ "tests/microbench/ShadowBench.cpp",
+ "tests/microbench/TaskManagerBench.cpp",
+ ],
+}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
deleted file mode 100644
index bf9423c..0000000
--- a/libs/hwui/Android.mk
+++ /dev/null
@@ -1,357 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-
-HWUI_NEW_OPS := true
-BUGREPORT_FONT_CACHE_USAGE := false
-
-# Enables fine-grained GLES error checking
-# If set to true, every GLES call is wrapped & error checked
-# Has moderate overhead
-HWUI_ENABLE_OPENGL_VALIDATION := false
-
-hwui_src_files := \
- font/CacheTexture.cpp \
- font/Font.cpp \
- hwui/Canvas.cpp \
- hwui/MinikinSkia.cpp \
- hwui/MinikinUtils.cpp \
- hwui/PaintImpl.cpp \
- hwui/Typeface.cpp \
- renderstate/Blend.cpp \
- renderstate/MeshState.cpp \
- renderstate/OffscreenBufferPool.cpp \
- renderstate/PixelBufferState.cpp \
- renderstate/RenderState.cpp \
- renderstate/Scissor.cpp \
- renderstate/Stencil.cpp \
- renderstate/TextureState.cpp \
- renderthread/CanvasContext.cpp \
- renderthread/DrawFrameTask.cpp \
- renderthread/EglManager.cpp \
- renderthread/RenderProxy.cpp \
- renderthread/RenderTask.cpp \
- renderthread/RenderThread.cpp \
- renderthread/TimeLord.cpp \
- thread/TaskManager.cpp \
- utils/Blur.cpp \
- utils/GLUtils.cpp \
- utils/LinearAllocator.cpp \
- utils/NinePatchImpl.cpp \
- utils/StringUtils.cpp \
- utils/TestWindowContext.cpp \
- utils/VectorDrawableUtils.cpp \
- AmbientShadow.cpp \
- AnimationContext.cpp \
- Animator.cpp \
- AnimatorManager.cpp \
- AssetAtlas.cpp \
- Caches.cpp \
- CanvasState.cpp \
- ClipArea.cpp \
- DamageAccumulator.cpp \
- DeferredDisplayList.cpp \
- DeferredLayerUpdater.cpp \
- DeviceInfo.cpp \
- DisplayList.cpp \
- DisplayListCanvas.cpp \
- Dither.cpp \
- Extensions.cpp \
- FboCache.cpp \
- FontRenderer.cpp \
- FrameInfo.cpp \
- FrameInfoVisualizer.cpp \
- GammaFontRenderer.cpp \
- GlopBuilder.cpp \
- GpuMemoryTracker.cpp \
- GradientCache.cpp \
- Image.cpp \
- Interpolator.cpp \
- JankTracker.cpp \
- Layer.cpp \
- LayerCache.cpp \
- LayerRenderer.cpp \
- LayerUpdateQueue.cpp \
- Matrix.cpp \
- OpenGLRenderer.cpp \
- Patch.cpp \
- PatchCache.cpp \
- PathCache.cpp \
- PathTessellator.cpp \
- PathParser.cpp \
- PixelBuffer.cpp \
- Program.cpp \
- ProgramCache.cpp \
- Properties.cpp \
- PropertyValuesHolder.cpp \
- PropertyValuesAnimatorSet.cpp \
- Readback.cpp \
- RenderBufferCache.cpp \
- RenderNode.cpp \
- RenderProperties.cpp \
- ResourceCache.cpp \
- ShadowTessellator.cpp \
- SkiaCanvas.cpp \
- SkiaCanvasProxy.cpp \
- SkiaShader.cpp \
- Snapshot.cpp \
- SpotShadow.cpp \
- TessellationCache.cpp \
- TextDropShadowCache.cpp \
- Texture.cpp \
- TextureCache.cpp \
- VectorDrawable.cpp \
- protos/hwui.proto
-
-hwui_test_common_src_files := \
- $(call all-cpp-files-under, tests/common/scenes) \
- tests/common/TestContext.cpp \
- tests/common/TestScene.cpp \
- tests/common/TestUtils.cpp
-
-hwui_cflags := \
- -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \
- -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \
- -Wall -Wno-unused-parameter -Wunreachable-code -Werror
-
-ifeq ($(TARGET_USES_HWC2),true)
- hwui_cflags += -DUSE_HWC2
-endif
-
-# GCC false-positives on this warning, and since we -Werror that's
-# a problem
-hwui_cflags += -Wno-free-nonheap-object
-
-ifeq (true, $(HWUI_NEW_OPS))
- hwui_src_files += \
- BakedOpDispatcher.cpp \
- BakedOpRenderer.cpp \
- BakedOpState.cpp \
- FrameBuilder.cpp \
- LayerBuilder.cpp \
- OpDumper.cpp \
- RecordingCanvas.cpp
-
- hwui_cflags += -DHWUI_NEW_OPS
-
-endif
-
-ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE))
- hwui_src_files += \
- font/FontCacheHistoryTracker.cpp
- hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE
-endif
-
-
-ifndef HWUI_COMPILE_SYMBOLS
- hwui_cflags += -fvisibility=hidden
-endif
-
-ifdef HWUI_COMPILE_FOR_PERF
- # TODO: Non-arm?
- hwui_cflags += -fno-omit-frame-pointer -marm -mapcs
-endif
-
-# This has to be lazy-resolved because it depends on the LOCAL_MODULE_CLASS
-# which varies depending on what is being built
-define hwui_proto_include
-$(call local-generated-sources-dir)/proto/$(LOCAL_PATH)
-endef
-
-hwui_c_includes += \
- external/skia/include/private \
- external/skia/src/core \
- external/harfbuzz_ng/src \
- external/freetype/include
-
-ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
- hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT
- hwui_c_includes += \
- $(call intermediates-dir-for,STATIC_LIBRARIES,TARGET,) \
- frameworks/rs/cpp \
- frameworks/rs
-endif
-
-ifeq (true, $(HWUI_ENABLE_OPENGL_VALIDATION))
- hwui_cflags += -include debug/wrap_gles.h
- hwui_src_files += debug/wrap_gles.cpp
- hwui_c_includes += frameworks/native/opengl/libs/GLES2
- hwui_cflags += -DDEBUG_OPENGL=3
-endif
-
-
-# ------------------------
-# static library
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
-LOCAL_MODULE := libhwui_static
-LOCAL_CFLAGS := $(hwui_cflags)
-LOCAL_SRC_FILES := $(hwui_src_files)
-LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
- $(LOCAL_PATH) \
- $(call hwui_proto_include)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_STATIC_LIBRARY)
-
-# ------------------------
-# static library null gpu
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := STATIC_LIBRARIES
-LOCAL_MODULE := libhwui_static_null_gpu
-LOCAL_CFLAGS := \
- $(hwui_cflags) \
- -DHWUI_NULL_GPU
-LOCAL_SRC_FILES := \
- $(hwui_src_files) \
- debug/nullegl.cpp \
- debug/nullgles.cpp
-LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include)
-LOCAL_EXPORT_C_INCLUDE_DIRS := \
- $(LOCAL_PATH) \
- $(call hwui_proto_include)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_STATIC_LIBRARY)
-
-# ------------------------
-# shared library
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_CLASS := SHARED_LIBRARIES
-LOCAL_MODULE := libhwui
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_SHARED_LIBRARY)
-
-# ------------------------
-# unit tests
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := hwui_unit_tests
-LOCAL_MODULE_TAGS := tests
-LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_SHARED_LIBRARIES := libmemunreachable
-LOCAL_CFLAGS := \
- $(hwui_cflags) \
- -DHWUI_NULL_GPU
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-LOCAL_SRC_FILES += \
- $(hwui_test_common_src_files) \
- tests/unit/main.cpp \
- tests/unit/CanvasStateTests.cpp \
- tests/unit/ClipAreaTests.cpp \
- tests/unit/DamageAccumulatorTests.cpp \
- tests/unit/DeviceInfoTests.cpp \
- tests/unit/FatVectorTests.cpp \
- tests/unit/FontRendererTests.cpp \
- tests/unit/GlopBuilderTests.cpp \
- tests/unit/GpuMemoryTrackerTests.cpp \
- tests/unit/GradientCacheTests.cpp \
- tests/unit/LayerUpdateQueueTests.cpp \
- tests/unit/LinearAllocatorTests.cpp \
- tests/unit/MatrixTests.cpp \
- tests/unit/OffscreenBufferPoolTests.cpp \
- tests/unit/RenderNodeTests.cpp \
- tests/unit/RenderPropertiesTests.cpp \
- tests/unit/SkiaBehaviorTests.cpp \
- tests/unit/SnapshotTests.cpp \
- tests/unit/StringUtilsTests.cpp \
- tests/unit/TestUtilsTests.cpp \
- tests/unit/TextDropShadowCacheTests.cpp \
- tests/unit/VectorDrawableTests.cpp
-
-ifeq (true, $(HWUI_NEW_OPS))
- LOCAL_SRC_FILES += \
- tests/unit/BakedOpDispatcherTests.cpp \
- tests/unit/BakedOpRendererTests.cpp \
- tests/unit/BakedOpStateTests.cpp \
- tests/unit/FrameBuilderTests.cpp \
- tests/unit/LeakCheckTests.cpp \
- tests/unit/OpDumperTests.cpp \
- tests/unit/RecordingCanvasTests.cpp \
- tests/unit/SkiaCanvasTests.cpp
-endif
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_NATIVE_TEST)
-
-# ------------------------
-# Macro-bench app
-# ------------------------
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_MODULE:= hwuitest
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := hwuitest
-LOCAL_MODULE_STEM_64 := hwuitest64
-LOCAL_CFLAGS := $(hwui_cflags)
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-# set to libhwui_static_null_gpu to skip actual GL commands
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static
-
-LOCAL_SRC_FILES += \
- $(hwui_test_common_src_files) \
- tests/macrobench/TestSceneRunner.cpp \
- tests/macrobench/main.cpp
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_EXECUTABLE)
-
-# ------------------------
-# Micro-bench app
-# ---------------------
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_MODULE:= hwuimicro
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := hwuimicro
-LOCAL_MODULE_STEM_64 := hwuimicro64
-LOCAL_CFLAGS := \
- $(hwui_cflags) \
- -DHWUI_NULL_GPU
-
-LOCAL_C_INCLUDES := $(hwui_c_includes)
-
-LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu
-LOCAL_STATIC_LIBRARIES := libgoogle-benchmark
-
-LOCAL_SRC_FILES += \
- $(hwui_test_common_src_files) \
- tests/microbench/main.cpp \
- tests/microbench/DisplayListCanvasBench.cpp \
- tests/microbench/FontBench.cpp \
- tests/microbench/LinearAllocatorBench.cpp \
- tests/microbench/PathParserBench.cpp \
- tests/microbench/ShadowBench.cpp \
- tests/microbench/TaskManagerBench.cpp
-
-ifeq (true, $(HWUI_NEW_OPS))
- LOCAL_SRC_FILES += \
- tests/microbench/FrameBuilderBench.cpp
-endif
-
-include $(LOCAL_PATH)/hwui_static_deps.mk
-include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 6433c86..b95fd63 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -112,6 +112,7 @@
} vertices;
int elementCount;
+ int vertexCount; // only used for meshes (for glDrawRangeElements)
TextureVertex mappedVertices[4];
} mesh;
diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp
index e502725..8fcd1ea 100644
--- a/libs/hwui/GlopBuilder.cpp
+++ b/libs/hwui/GlopBuilder.cpp
@@ -210,6 +210,7 @@
alphaVertex ? kAlphaVertexStride : kVertexStride };
mOutGlop->mesh.elementCount = indices
? vertexBuffer.getIndexCount() : vertexBuffer.getVertexCount();
+ mOutGlop->mesh.vertexCount = vertexBuffer.getVertexCount(); // used for glDrawRangeElements()
return *this;
}
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 2132c2b..9c253c4 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -265,7 +265,6 @@
/ kSlowFrameBucketIntervalMs;
framebucket = std::min(framebucket,
static_cast<uint32_t>(mData->slowFrameCounts.size() - 1));
- framebucket = std::max(framebucket, 0u);
mData->slowFrameCounts[framebucket]++;
}
diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk
deleted file mode 100644
index 8dae273..0000000
--- a/libs/hwui/hwui_static_deps.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-###############################################################################
-#
-#
-# This file contains the shared and static dependencies needed by any target
-# that attempts to statically link HWUI (i.e. libhwui_static build target). This
-# file should be included by any target that lists libhwui_static as a
-# dependency.
-#
-# This is a workaround for the fact that the build system does not add these
-# transitive dependencies when it attempts to link libhwui_static into another
-# library.
-#
-###############################################################################
-
-LOCAL_SHARED_LIBRARIES += \
- liblog \
- libcutils \
- libutils \
- libEGL \
- libGLESv2 \
- libskia \
- libui \
- libgui \
- libprotobuf-cpp-lite \
- libharfbuzz_ng \
- libft2 \
- libminikin \
- libandroidfw
-
-ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
- LOCAL_SHARED_LIBRARIES += libRScpp
-endif
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 5e60064..c4e8e4f 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -364,18 +364,28 @@
const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
while (elementsCount > 0) {
GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
+ GLsizei vertexCount = (drawCount / 6) * 4;
meshState().bindPositionVertexPointer(vertexData, vertices.stride);
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
meshState().bindTexCoordsVertexPointer(
vertexData + kMeshTextureOffset, vertices.stride);
}
- glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
+ if (mCaches->extensions().getMajorGlVersion() >= 3) {
+ glDrawRangeElements(mesh.primitiveMode, 0, vertexCount-1, drawCount, GL_UNSIGNED_SHORT, nullptr);
+ } else {
+ glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr);
+ }
elementsCount -= drawCount;
- vertexData += (drawCount / 6) * 4 * vertices.stride;
+ vertexData += vertexCount * vertices.stride;
}
} else if (indices.bufferObject || indices.indices) {
- glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+ if (mCaches->extensions().getMajorGlVersion() >= 3) {
+ // use glDrawRangeElements to reduce CPU overhead (otherwise the driver has to determine the min/max index values)
+ glDrawRangeElements(mesh.primitiveMode, 0, mesh.vertexCount-1, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+ } else {
+ glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, indices.indices);
+ }
} else {
glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount);
}
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index d2dc440..3f45497 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -30,9 +30,9 @@
#include "SoundPool.h"
#include "SoundPoolThread.h"
#include <media/AudioPolicyHelper.h>
-#include <ndk/NdkMediaCodec.h>
-#include <ndk/NdkMediaExtractor.h>
-#include <ndk/NdkMediaFormat.h>
+#include <media/NdkMediaCodec.h>
+#include <media/NdkMediaExtractor.h>
+#include <media/NdkMediaFormat.h>
namespace android
{
diff --git a/native/android/Android.bp b/native/android/Android.bp
index eacda93..3f9cc21 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -19,3 +19,61 @@
first_version: "9",
unversioned_until: "current",
}
+
+cc_defaults {
+ name: "libandroid_defaults",
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
+
+cc_library_shared {
+ name: "libandroid",
+ defaults: ["libandroid_defaults"],
+
+ srcs: [
+ "asset_manager.cpp",
+ "choreographer.cpp",
+ "configuration.cpp",
+ "input.cpp",
+ "looper.cpp",
+ "native_activity.cpp",
+ "native_window.cpp",
+ "net.c",
+ "obb.cpp",
+ "sensor.cpp",
+ "storage_manager.cpp",
+ "trace.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ "libcutils",
+ "libandroidfw",
+ "libinput",
+ "libutils",
+ "libbinder",
+ "libui",
+ "libgui",
+ "libandroid_runtime",
+ "libnetd_client",
+ ],
+
+ static_libs: ["libstorage"],
+
+ include_dirs: ["bionic/libc/dns/include"],
+}
+
+// Network library.
+cc_library_shared {
+ name: "libandroid_net",
+ defaults: ["libandroid_defaults"],
+ srcs: ["net.c"],
+
+ shared_libs: ["libnetd_client"],
+
+ include_dirs: ["bionic/libc/dns/include"],
+}
diff --git a/native/android/Android.mk b/native/android/Android.mk
deleted file mode 100644
index 1f69df1..0000000
--- a/native/android/Android.mk
+++ /dev/null
@@ -1,66 +0,0 @@
-BASE_PATH := $(call my-dir)
-LOCAL_PATH:= $(call my-dir)
-
-common_cflags := -Wall -Werror -Wunused -Wunreachable-code
-
-include $(CLEAR_VARS)
-
-# our source files
-#
-LOCAL_SRC_FILES:= \
- asset_manager.cpp \
- choreographer.cpp \
- configuration.cpp \
- input.cpp \
- looper.cpp \
- native_activity.cpp \
- native_window.cpp \
- net.c \
- obb.cpp \
- sensor.cpp \
- storage_manager.cpp \
- trace.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
- liblog \
- libcutils \
- libandroidfw \
- libinput \
- libutils \
- libbinder \
- libui \
- libgui \
- libandroid_runtime \
- libnetd_client \
-
-LOCAL_STATIC_LIBRARIES := \
- libstorage
-
-LOCAL_C_INCLUDES += \
- frameworks/base/native/include \
- frameworks/base/core/jni/android \
- bionic/libc/dns/include \
- system/netd/include \
-
-LOCAL_MODULE := libandroid
-
-LOCAL_CFLAGS += $(common_cflags)
-
-include $(BUILD_SHARED_LIBRARY)
-
-# Network library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := libandroid_net
-LOCAL_CFLAGS := $(common_cflags)
-LOCAL_SRC_FILES:= \
- net.c \
-
-LOCAL_SHARED_LIBRARIES := \
- libnetd_client \
-
-LOCAL_C_INCLUDES += \
- frameworks/base/native/include \
- bionic/libc/dns/include \
- system/netd/include \
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java
index 3831cf7..dbfeefd 100644
--- a/obex/javax/obex/ServerSession.java
+++ b/obex/javax/obex/ServerSession.java
@@ -104,7 +104,6 @@
case ObexHelper.OBEX_OPCODE_DISCONNECT:
handleDisconnectRequest();
- done = true;
break;
case ObexHelper.OBEX_OPCODE_GET:
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index e450283..2ef1cf5 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -33,6 +33,7 @@
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver">
<intent-filter>
<action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" />
+ <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_RESET" />
</intent-filter>
</receiver>
<service android:name="com.android.carrierdefaultapp.ProvisionObserver"
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index e1125d9..d5d0b79 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -91,6 +91,10 @@
arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY);
arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY);
break;
+ case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET:
+ configs = b.getStringArray(CarrierConfigManager
+ .KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
+ break;
default:
Rlog.e(TAG, "load carrier config failure with un-configured key: " +
intent.getAction());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index cdfdad4..267dd3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -188,9 +188,16 @@
if (mIcon == null) {
return false;
}
- Drawable drawable = getIcon(mIcon);
+ Drawable drawable;
+ try {
+ drawable = getIcon(mIcon);
+ } catch (OutOfMemoryError e) {
+ Log.w(TAG, "OOM while inflating " + mIcon.icon + " for slot " + mSlot);
+ return false;
+ }
+
if (drawable == null) {
- Log.w(TAG, "No icon for slot " + mSlot);
+ Log.w(TAG, "No icon for slot " + mSlot + "; " + mIcon.icon);
return false;
}
if (withClear) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index a2d1baf..c726189 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -47,6 +47,11 @@
public void onPhoneStateChanged(int phoneState) {
update();
}
+
+ @Override
+ public void onRefreshCarrierInfo() {
+ update();
+ }
};
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index 05ad161..238bf0f 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -2895,6 +2895,7 @@
mAllocationArray[0] = createTyped(rs, t, usage);
if ((usage & USAGE_IO_INPUT) != 0) {
if (numAlloc > MAX_NUMBER_IO_INPUT_ALLOC) {
+ mAllocationArray[0].destroy();
throw new RSIllegalArgumentException("Exceeds the max number of Allocations allowed: " +
MAX_NUMBER_IO_INPUT_ALLOC);
}
diff --git a/rs/java/android/renderscript/ScriptIntrinsicLUT.java b/rs/java/android/renderscript/ScriptIntrinsicLUT.java
index 69ff64a..e90462d 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicLUT.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicLUT.java
@@ -56,6 +56,10 @@
}
+ public void destroy() {
+ mTables.destroy();
+ super.destroy();
+ }
private void validate(int index, int value) {
if (index < 0 || index > 255) {
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 898eb7e..689c979 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -18,7 +18,7 @@
LOCAL_AIDL_INCLUDES += \
system/netd/server/binder
-LOCAL_JAVA_LIBRARIES := services.net telephony-common
+LOCAL_JAVA_LIBRARIES := services.net
LOCAL_STATIC_JAVA_LIBRARIES := tzdata_shared2 tzdata_update2
LOCAL_PROTOC_OPTIMIZE_TYPE := nano
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 5eb4db4..1262d88 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1496,16 +1496,6 @@
if (mGetNameAddressOnly) return;
}
- try {
- boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver,
- Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1);
- if (!mBluetooth.configHciSnoopLog(enableHciSnoopLog)) {
- Slog.e(TAG,"IBluetooth.configHciSnoopLog return false");
- }
- } catch (RemoteException e) {
- Slog.e(TAG,"Unable to call configHciSnoopLog", e);
- }
-
//Register callback object
try {
mBluetooth.registerCallback(mBluetoothCallback);
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index a691af9..55bca64 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -26,6 +26,8 @@
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.os.Binder;
+import android.os.HandlerThread;
+import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.UserHandle;
@@ -37,9 +39,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
@@ -59,10 +63,10 @@
private static final boolean DBG = true;
private final Context mContext;
- private final ContentResolver mContentResolver;
+ private final NsdSettings mNsdSettings;
private final NsdStateMachine mNsdStateMachine;
- private final NativeDaemonConnector mNativeConnector;
- private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
+ private final DaemonConnection mDaemon;
+ private final NativeCallbackReceiver mDaemonCallback;
/**
* Clients receiving asynchronous messages
@@ -108,8 +112,8 @@
false, contentObserver);
}
- NsdStateMachine(String name) {
- super(name);
+ NsdStateMachine(String name, Handler handler) {
+ super(name, handler);
addState(mDefaultState);
addState(mDisabledState, mDefaultState);
addState(mEnabledState, mDefaultState);
@@ -541,36 +545,35 @@
return sb.toString();
}
- private NsdService(Context context) {
- mContext = context;
- mContentResolver = context.getContentResolver();
-
- mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
- MDNS_TAG, 25, null);
-
- mNsdStateMachine = new NsdStateMachine(TAG);
+ @VisibleForTesting
+ NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn) {
+ mContext = ctx;
+ mNsdSettings = settings;
+ mNsdStateMachine = new NsdStateMachine(TAG, handler);
mNsdStateMachine.start();
-
- Thread th = new Thread(mNativeConnector, MDNS_TAG);
- th.start();
+ mDaemonCallback = new NativeCallbackReceiver();
+ mDaemon = fn.get(mDaemonCallback);
}
public static NsdService create(Context context) throws InterruptedException {
- NsdService service = new NsdService(context);
- service.mNativeDaemonConnected.await();
+ NsdSettings settings = NsdSettings.makeDefault(context);
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
+ service.mDaemonCallback.awaitConnection();
return service;
}
public Messenger getMessenger() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
- "NsdService");
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
return new Messenger(mNsdStateMachine.getHandler());
}
public void setEnabled(boolean enable) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
"NsdService");
- Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
+ mNsdSettings.putEnabledStatus(enable);
if (enable) {
mNsdStateMachine.sendMessage(NsdManager.ENABLE);
} else {
@@ -590,8 +593,10 @@
}
private boolean isNsdEnabled() {
- boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
- if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
+ boolean ret = mNsdSettings.isEnabled();
+ if (DBG) {
+ Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
+ }
return ret;
}
@@ -655,14 +660,23 @@
}
class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
- public void onDaemonConnected() {
- mNativeDaemonConnected.countDown();
+ private final CountDownLatch connected = new CountDownLatch(1);
+
+ public void awaitConnection() throws InterruptedException {
+ connected.await();
}
+ @Override
+ public void onDaemonConnected() {
+ connected.countDown();
+ }
+
+ @Override
public boolean onCheckHoldWakeLock(int code) {
return false;
}
+ @Override
public boolean onEvent(int code, String raw, String[] cooked) {
// TODO: NDC translates a message to a callback, we could enhance NDC to
// directly interact with a state machine through messages
@@ -672,132 +686,102 @@
}
}
- private boolean startMDnsDaemon() {
- if (DBG) Slog.d(TAG, "startMDnsDaemon");
- try {
- mNativeConnector.execute("mdnssd", "start-service");
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to start daemon" + e);
- return false;
+ interface DaemonConnectionSupplier {
+ DaemonConnection get(NativeCallbackReceiver callback);
+ }
+
+ @VisibleForTesting
+ public static class DaemonConnection {
+ final NativeDaemonConnector mNativeConnector;
+
+ DaemonConnection(NativeCallbackReceiver callback) {
+ mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
+ new Thread(mNativeConnector, MDNS_TAG).start();
}
- return true;
+
+ public boolean execute(Object... args) {
+ if (DBG) {
+ Slog.d(TAG, "mdnssd " + Arrays.toString(args));
+ }
+ try {
+ mNativeConnector.execute("mdnssd", args);
+ } catch (NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
+ return false;
+ }
+ return true;
+ }
+
+ public boolean execute(Command cmd) {
+ if (DBG) {
+ Slog.d(TAG, cmd.toString());
+ }
+ try {
+ mNativeConnector.execute(cmd);
+ } catch (NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to execute " + cmd, e);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private boolean startMDnsDaemon() {
+ return mDaemon.execute("start-service");
}
private boolean stopMDnsDaemon() {
- if (DBG) Slog.d(TAG, "stopMDnsDaemon");
- try {
- mNativeConnector.execute("mdnssd", "stop-service");
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to start daemon" + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-service");
}
private boolean registerService(int regId, NsdServiceInfo service) {
- if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
- try {
- Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
- service.getServiceType(), service.getPort(),
- Base64.encodeToString(service.getTxtRecord(), Base64.DEFAULT)
- .replace("\n", ""));
-
- mNativeConnector.execute(cmd);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to execute registerService " + e);
- return false;
+ if (DBG) {
+ Slog.d(TAG, "registerService: " + regId + " " + service);
}
- return true;
+ String name = service.getServiceName();
+ String type = service.getServiceType();
+ int port = service.getPort();
+ byte[] textRecord = service.getTxtRecord();
+ String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
+ Command cmd = new Command("mdnssd", "register", regId, name, type, port, record);
+ return mDaemon.execute(cmd);
}
private boolean unregisterService(int regId) {
- if (DBG) Slog.d(TAG, "unregisterService: " + regId);
- try {
- mNativeConnector.execute("mdnssd", "stop-register", regId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to execute unregisterService " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-register", regId);
}
private boolean updateService(int regId, DnsSdTxtRecord t) {
- if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
- try {
- if (t == null) return false;
- mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to updateServices " + e);
+ if (t == null) {
return false;
}
- return true;
+ return mDaemon.execute("update", regId, t.size(), t.getRawData());
}
private boolean discoverServices(int discoveryId, String serviceType) {
- if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
- try {
- mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to discoverServices " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("discover", discoveryId, serviceType);
}
private boolean stopServiceDiscovery(int discoveryId) {
- if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
- try {
- mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-discover", discoveryId);
}
private boolean resolveService(int resolveId, NsdServiceInfo service) {
- if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
- try {
- mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
- service.getServiceType(), "local.");
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to resolveService " + e);
- return false;
- }
- return true;
+ String name = service.getServiceName();
+ String type = service.getServiceType();
+ return mDaemon.execute("resolve", resolveId, name, type, "local.");
}
private boolean stopResolveService(int resolveId) {
- if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
- try {
- mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stop resolve " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-resolve", resolveId);
}
private boolean getAddrInfo(int resolveId, String hostname) {
- if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
- try {
- mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to getAddrInfo " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("getaddrinfo", resolveId, hostname);
}
private boolean stopGetAddrInfo(int resolveId) {
- if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
- try {
- mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
- } catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
- return false;
- }
- return true;
+ return mDaemon.execute("stop-getaddrinfo", resolveId);
}
@Override
@@ -927,4 +911,25 @@
return -1;
}
}
+
+ @VisibleForTesting
+ public interface NsdSettings {
+ boolean isEnabled();
+ void putEnabledStatus(boolean isEnabled);
+
+ static NsdSettings makeDefault(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ return new NsdSettings() {
+ @Override
+ public boolean isEnabled() {
+ return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
+ }
+
+ @Override
+ public void putEnabledStatus(boolean isEnabled) {
+ Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
+ }
+ };
+ }
+ }
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 82e6b42..f45beb6 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -57,9 +57,8 @@
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.IPhoneStateListener;
-import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.PhoneConstantConversions;
import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyIntents;
import com.android.server.am.BatteryStatsService;
@@ -171,7 +170,7 @@
private int[] mDataConnectionNetworkType;
- private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN;
+ private int mOtaspMode = TelephonyManager.OTASP_UNKNOWN;
private ArrayList<List<CellInfo>> mCellInfo = null;
@@ -1496,7 +1495,7 @@
Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY,
- DefaultPhoneNotifier.convertCallState(state).toString());
+ PhoneConstantConversions.convertCallState(state).toString());
if (!TextUtils.isEmpty(incomingNumber)) {
intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
}
@@ -1530,7 +1529,7 @@
// required info.
Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY,
- DefaultPhoneNotifier.convertDataState(state).toString());
+ PhoneConstantConversions.convertDataState(state).toString());
if (!isDataConnectivityPossible) {
intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5ae4d67..c5fc038 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5324,35 +5324,61 @@
return tracesFile;
}
+ public static class DumpStackFileObserver extends FileObserver {
+ // Keep in sync with frameworks/native/cmds/dumpstate/utils.cpp
+ private static final int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
+ static final int TRACE_DUMP_TIMEOUT_SECONDS = TRACE_DUMP_TIMEOUT_MS / 1000;
+
+ private final String mTracesPath;
+ private boolean mClosed;
+
+ public DumpStackFileObserver(String tracesPath) {
+ super(tracesPath, FileObserver.CLOSE_WRITE);
+ mTracesPath = tracesPath;
+ }
+
+ @Override
+ public synchronized void onEvent(int event, String path) {
+ mClosed = true;
+ notify();
+ }
+
+ public void dumpWithTimeout(int pid) {
+ Process.sendSignal(pid, Process.SIGNAL_QUIT);
+ synchronized (this) {
+ try {
+ wait(TRACE_DUMP_TIMEOUT_MS); // Wait for traces file to be closed.
+ } catch (InterruptedException e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ if (!mClosed) {
+ Slog.w(TAG, "Didn't see close of " + mTracesPath + " for pid " + pid +
+ ". Attempting native stack collection.");
+ Debug.dumpNativeBacktraceToFileTimeout(pid, mTracesPath, TRACE_DUMP_TIMEOUT_SECONDS);
+ }
+ mClosed = false;
+ }
+ }
+
private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
// Use a FileObserver to detect when traces finish writing.
// The order of traces is considered important to maintain for legibility.
- FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
- @Override
- public synchronized void onEvent(int event, String path) { notify(); }
- };
-
+ DumpStackFileObserver observer = new DumpStackFileObserver(tracesPath);
try {
observer.startWatching();
// First collect all of the stacks of the most important pids.
if (firstPids != null) {
- try {
- int num = firstPids.size();
- for (int i = 0; i < num; i++) {
- synchronized (observer) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
- + firstPids.get(i));
- final long sime = SystemClock.elapsedRealtime();
- Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
- observer.wait(1000); // Wait for write-close, give up after 1 sec
- if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
- + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
- }
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
+ int num = firstPids.size();
+ for (int i = 0; i < num; i++) {
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for pid "
+ + firstPids.get(i));
+ final long sime = SystemClock.elapsedRealtime();
+ observer.dumpWithTimeout(firstPids.get(i));
+ if (DEBUG_ANR) Slog.d(TAG, "Done with pid " + firstPids.get(i)
+ + " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
}
@@ -5364,7 +5390,8 @@
if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for native pid " + pid);
final long sime = SystemClock.elapsedRealtime();
- Debug.dumpNativeBacktraceToFileTimeout(pid, tracesPath, 10);
+ Debug.dumpNativeBacktraceToFileTimeout(
+ pid, tracesPath, DumpStackFileObserver.TRACE_DUMP_TIMEOUT_SECONDS);
if (DEBUG_ANR) Slog.d(TAG, "Done with native pid " + pid
+ " in " + (SystemClock.elapsedRealtime()-sime) + "ms");
}
@@ -5391,19 +5418,12 @@
ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
if (lastPids.indexOfKey(stats.pid) >= 0) {
numProcs++;
- try {
- synchronized (observer) {
- if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
- + stats.pid);
- final long stime = SystemClock.elapsedRealtime();
- Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
- observer.wait(1000); // Wait for write-close, give up after 1 sec
- if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
- + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
- }
- } catch (InterruptedException e) {
- Slog.wtf(TAG, e);
- }
+ if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid "
+ + stats.pid);
+ final long stime = SystemClock.elapsedRealtime();
+ observer.dumpWithTimeout(stats.pid);
+ if (DEBUG_ANR) Slog.d(TAG, "Done with extra pid " + stats.pid
+ + " in " + (SystemClock.elapsedRealtime()-stime) + "ms");
} else if (DEBUG_ANR) {
Slog.d(TAG, "Skipping next CPU consuming process, not a java proc: "
+ stats.pid);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 71c7fd3..2e74eb9 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -493,8 +493,9 @@
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
runningIntent.setData(Uri.fromParts("package",
appInfo.packageName, null));
- PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
- runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent pi = PendingIntent.getActivityAsUser(ams.mContext, 0,
+ runningIntent, PendingIntent.FLAG_UPDATE_CURRENT, null,
+ UserHandle.of(userId));
notiBuilder.setColor(ams.mContext.getColor(
com.android.internal
.R.color.system_notification_accent_color));
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index e667680..9edc35e 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -219,6 +219,10 @@
return (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ private WifiManager getWifiManager() {
+ return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ }
+
private void updateConfiguration() {
mConfig = new TetheringConfiguration(mContext);
}
@@ -409,16 +413,21 @@
}
private int setWifiTethering(final boolean enable) {
- synchronized (mPublicSync) {
- mWifiTetherRequested = enable;
- final WifiManager wifiManager =
- (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- if ((enable && wifiManager.startSoftAp(null /* use existing wifi config */)) ||
- (!enable && wifiManager.stopSoftAp())) {
- return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ int rval = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPublicSync) {
+ mWifiTetherRequested = enable;
+ final WifiManager mgr = getWifiManager();
+ if ((enable && mgr.startSoftAp(null /* use existing wifi config */)) ||
+ (!enable && mgr.stopSoftAp())) {
+ rval = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ }
}
- return ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
+ return rval;
}
private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
@@ -1334,12 +1343,37 @@
} else {
mForwardedDownstreams.remove(who);
}
+
+ // If this is a Wi-Fi interface, notify WifiManager of the active serving state.
+ if (who.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+ final WifiManager mgr = getWifiManager();
+ final String iface = who.interfaceName();
+ switch (mode) {
+ case IControlsTethering.STATE_TETHERED:
+ mgr.updateInterfaceIpState(iface, WifiManager.IFACE_IP_MODE_TETHERED);
+ break;
+ case IControlsTethering.STATE_LOCAL_ONLY:
+ mgr.updateInterfaceIpState(iface, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+ break;
+ default:
+ Log.wtf(TAG, "Unknown active serving mode: " + mode);
+ break;
+ }
+ }
}
private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) {
mNotifyList.remove(who);
mIPv6TetheringCoordinator.removeActiveDownstream(who);
mForwardedDownstreams.remove(who);
+
+ // If this is a Wi-Fi interface, tell WifiManager of any errors.
+ if (who.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+ if (who.lastError() != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ getWifiManager().updateInterfaceIpState(
+ who.interfaceName(), WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+ }
+ }
}
class InitialState extends State {
@@ -1661,7 +1695,7 @@
public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
int state, int error) {
synchronized (mPublicSync) {
- TetherState tetherState = mTetherStates.get(iface);
+ final TetherState tetherState = mTetherStates.get(iface);
if (tetherState != null && tetherState.stateMachine.equals(who)) {
tetherState.lastState = state;
tetherState.lastError = error;
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 e21349a..d3cfd87 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -117,9 +117,11 @@
setInitialState(mInitialState);
}
- public int interfaceType() {
- return mInterfaceType;
- }
+ public String interfaceName() { return mIfaceName; }
+
+ public int interfaceType() { return mInterfaceType; }
+
+ public int lastError() { return mLastError; }
// configured when we start tethering and unconfig'd on error or conclusion
private boolean configureIfaceIp(boolean enabled) {
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index eead114..6d64b12 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -129,10 +129,11 @@
brightnessMode = mLastBrightnessMode;
}
- if ((color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS ||
- mBrightnessMode != brightnessMode)) {
+ if (!mInitialized || color != mColor || mode != mMode || onMS != mOnMS ||
+ offMS != mOffMS || mBrightnessMode != brightnessMode) {
if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
+ Integer.toHexString(color) + ": brightnessMode=" + brightnessMode);
+ mInitialized = true;
mLastColor = mColor;
mColor = color;
mMode = mode;
@@ -164,6 +165,7 @@
private int mLastColor;
private boolean mVrModeEnabled;
private boolean mUseLowPersistenceForVR;
+ private boolean mInitialized;
}
public LightsService(Context context) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 4e14408..d93d620 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -139,7 +139,7 @@
// (by default is speed-profile) they will be interepreted/JITed. This in itself is
// not a problem as we will end up doing profile guided compilation. However, some
// core apps may be loaded by system server which doesn't JIT and we need to make
- // sure we don't interpret-only
+ // sure we are not interpreting all their code in that process.
int compilationReason = p.coreApp
? PackageManagerService.REASON_CORE_APP
: PackageManagerService.REASON_AB_OTA;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index d696f49..ddb3639 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -52,7 +52,8 @@
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
+import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
+import static dalvik.system.DexFile.getSafeModeCompilerFilter;
import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
/**
@@ -359,13 +360,7 @@
int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
if (vmSafeMode) {
- // For the compilation, it doesn't really matter what we return here because installd
- // will replace the filter with interpret-only anyway.
- // However, we return a non profile guided filter so that we simplify the logic of
- // merging profiles.
- // TODO(calin): safe mode path could be simplified if we pass interpret-only from
- // here rather than letting installd decide on the filter.
- return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
+ return getSafeModeCompilerFilter(targetCompilerFilter);
}
if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
@@ -549,9 +544,13 @@
@Override
protected int adjustDexoptNeeded(int dexoptNeeded) {
- // Ensure compilation, no matter the current state.
- // TODO: The return value is wrong when patchoat is needed.
- return DexFile.DEX2OAT_FROM_SCRATCH;
+ if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
+ // Ensure compilation by pretending a compiler filter change on the
+ // apk/odex location (the reason for the '-'. A positive value means
+ // the 'oat' location).
+ return -DexFile.DEX2OAT_FOR_FILTER;
+ }
+ return dexoptNeeded;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b207b81..2391abd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -92,11 +92,12 @@
import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getFullCompilerFilter;
-import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -7364,20 +7365,7 @@
// are verify-profile but for preopted apps there's no profile.
// Do a hacky check to ensure that if we have no profiles (a reasonable indication
// that before the OTA the app was preopted) the app gets compiled with a non-profile
- // filter (by default interpret-only).
- // Note that at this stage unused apps are already filtered.
- if (isSystemApp(pkg) &&
- DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
- !Environment.getReferenceProfile(pkg.packageName).exists()) {
- compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
- }
-
- // If the OTA updates a system app which was previously preopted to a non-preopted state
- // the app might end up being verified at runtime. That's because by default the apps
- // are verify-profile but for preopted apps there's no profile.
- // Do a hacky check to ensure that if we have no profiles (a reasonable indication
- // that before the OTA the app was preopted) the app gets compiled with a non-profile
- // filter (by default interpret-only).
+ // filter (by default 'quicken').
// Note that at this stage unused apps are already filtered.
if (isSystemApp(pkg) &&
DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 0634dac..f56534d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -123,11 +123,4 @@
return value;
}
-
- /**
- * Return the non-profile-guided filter corresponding to the given filter.
- */
- public static String getNonProfileGuidedCompilerFilter(String filter) {
- return DexFile.getNonProfileGuidedCompilerFilter(filter);
- }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1bfc157..2f000c2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1454,10 +1454,10 @@
pw.println(" -f: force compilation even if not needed");
pw.println(" -m: select compilation mode");
pw.println(" MODE is one of the dex2oat compiler filters:");
- pw.println(" verify-none");
- pw.println(" verify-at-runtime");
- pw.println(" verify-profile");
- pw.println(" interpret-only");
+ pw.println(" assume-verified");
+ pw.println(" extract");
+ pw.println(" verify");
+ pw.println(" quicken");
pw.println(" space-profile");
pw.println(" space");
pw.println(" speed-profile");
diff --git a/services/core/java/com/android/server/timezone/CheckToken.java b/services/core/java/com/android/server/timezone/CheckToken.java
new file mode 100644
index 0000000..5128360
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/CheckToken.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * A deserialized version of the byte[] sent to the time zone update application to identify a
+ * triggered time zone update check. It encodes the optimistic lock ID used to detect
+ * concurrent checks and the minimal package versions that will have been checked.
+ */
+final class CheckToken {
+
+ final int mOptimisticLockId;
+ final PackageVersions mPackageVersions;
+
+ CheckToken(int optimisticLockId, PackageVersions packageVersions) {
+ this.mOptimisticLockId = optimisticLockId;
+
+ if (packageVersions == null) {
+ throw new NullPointerException("packageVersions == null");
+ }
+ this.mPackageVersions = packageVersions;
+ }
+
+ byte[] toByteArray() {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(12 /* (3 * sizeof(int)) */);
+ try (DataOutputStream dos = new DataOutputStream(baos)) {
+ dos.writeInt(mOptimisticLockId);
+ dos.writeInt(mPackageVersions.mUpdateAppVersion);
+ dos.writeInt(mPackageVersions.mDataAppVersion);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to write into a ByteArrayOutputStream", e);
+ }
+ return baos.toByteArray();
+ }
+
+ static CheckToken fromByteArray(byte[] tokenBytes) throws IOException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(tokenBytes);
+ try (DataInputStream dis = new DataInputStream(bais)) {
+ int versionId = dis.readInt();
+ int updateAppVersion = dis.readInt();
+ int dataAppVersion = dis.readInt();
+ return new CheckToken(versionId, new PackageVersions(updateAppVersion, dataAppVersion));
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ CheckToken checkToken = (CheckToken) o;
+
+ if (mOptimisticLockId != checkToken.mOptimisticLockId) {
+ return false;
+ }
+ return mPackageVersions.equals(checkToken.mPackageVersions);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mOptimisticLockId;
+ result = 31 * result + mPackageVersions.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Token{" +
+ "mOptimisticLockId=" + mOptimisticLockId +
+ ", mPackageVersions=" + mPackageVersions +
+ '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/ClockHelper.java b/services/core/java/com/android/server/timezone/ClockHelper.java
new file mode 100644
index 0000000..353728a
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/ClockHelper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface for obtaining a monotonically increasing time value in milliseconds.
+ */
+interface ClockHelper {
+
+ long currentTimestamp();
+}
diff --git a/services/core/java/com/android/server/timezone/ConfigHelper.java b/services/core/java/com/android/server/timezone/ConfigHelper.java
new file mode 100644
index 0000000..f9984fa
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/ConfigHelper.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 com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around device config for use by {@link PackageTracker}; it is not
+ * possible to test various states with the real one because config is fixed in the system image.
+ */
+interface ConfigHelper {
+
+ boolean isTrackingEnabled();
+
+ String getUpdateAppPackageName();
+
+ String getDataAppPackageName();
+
+ int getCheckTimeAllowedMillis();
+
+ int getFailedCheckRetryCount();
+}
diff --git a/services/core/java/com/android/server/timezone/FileDescriptorHelper.java b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
new file mode 100644
index 0000000..c3b1101
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/FileDescriptorHelper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+
+/**
+ * An easy-to-mock interface around use of {@link ParcelFileDescriptor} for use by
+ * {@link RulesManagerService}.
+ */
+interface FileDescriptorHelper {
+
+ byte[] readFully(ParcelFileDescriptor parcelFileDescriptor) throws IOException;
+}
diff --git a/services/core/java/com/android/server/timezone/IntentHelper.java b/services/core/java/com/android/server/timezone/IntentHelper.java
new file mode 100644
index 0000000..0cb9065
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/IntentHelper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around intent sending / receiving for use by {@link PackageTracker};
+ * it is not possible to test various cases with the real one because of the need to simulate
+ * receiving and broadcasting intents.
+ */
+interface IntentHelper {
+
+ void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+
+ void sendTriggerUpdateCheck(CheckToken checkToken);
+
+ void enableReliabilityTriggering();
+
+ void disableReliabilityTriggering();
+
+ interface Listener {
+ void triggerUpdateIfNeeded(boolean packageUpdated);
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
new file mode 100644
index 0000000..3ffbb2d
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Slog;
+
+import java.util.regex.Pattern;
+
+/**
+ * The bona fide implementation of {@link IntentHelper}.
+ */
+final class IntentHelperImpl implements IntentHelper {
+
+ private final static String TAG = "timezone.IntentHelperImpl";
+
+ private final Context mContext;
+ private String mUpdaterAppPackageName;
+
+ private boolean mReliabilityReceiverEnabled;
+ private Receiver mReliabilityReceiver;
+
+ IntentHelperImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void initialize(
+ String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+ mUpdaterAppPackageName = updaterAppPackageName;
+
+ // Register for events of interest.
+
+ // The intent filter that triggers when package update events happen that indicate there may
+ // be work to do.
+ IntentFilter packageIntentFilter = new IntentFilter();
+ // Either of these mean a downgrade?
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageIntentFilter.addDataScheme("package");
+ packageIntentFilter.addDataSchemeSpecificPart(
+ updaterAppPackageName, PatternMatcher.PATTERN_LITERAL);
+ packageIntentFilter.addDataSchemeSpecificPart(
+ dataAppPackageName, PatternMatcher.PATTERN_LITERAL);
+ Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
+ mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
+
+ // TODO(nfuller): Add more exotic intents as needed. e.g.
+ // packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ // Also, disabled...?
+ mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+ }
+
+ /** Sends an intent to trigger an update check. */
+ @Override
+ public void sendTriggerUpdateCheck(CheckToken checkToken) {
+ RulesUpdaterContract.sendBroadcast(
+ mContext, mUpdaterAppPackageName, checkToken.toByteArray());
+ }
+
+ @Override
+ public synchronized void enableReliabilityTriggering() {
+ if (!mReliabilityReceiverEnabled) {
+ // The intent filter that exists to make updates reliable in the event of failures /
+ // reboots.
+ IntentFilter reliabilityIntentFilter = new IntentFilter();
+ reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
+ mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
+ mReliabilityReceiverEnabled = true;
+ }
+ }
+
+ @Override
+ public synchronized void disableReliabilityTriggering() {
+ if (mReliabilityReceiverEnabled) {
+ mContext.unregisterReceiver(mReliabilityReceiver);
+ mReliabilityReceiverEnabled = false;
+ }
+ }
+
+ private static class Receiver extends BroadcastReceiver {
+ private final Listener mListener;
+ private final boolean mPackageUpdated;
+
+ private Receiver(Listener listener, boolean packageUpdated) {
+ mListener = listener;
+ mPackageUpdated = packageUpdated;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Slog.d(TAG, "Received intent: " + intent.toString());
+ mListener.triggerUpdateIfNeeded(mPackageUpdated);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/timezone/PackageManagerHelper.java b/services/core/java/com/android/server/timezone/PackageManagerHelper.java
new file mode 100644
index 0000000..804941a
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageManagerHelper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+/**
+ * An easy-to-mock facade around PackageManager for use by {@link PackageTracker}; it is not
+ * possible to test various cases with the real one because of the need to simulate package versions
+ * and manifest configurations.
+ */
+interface PackageManagerHelper {
+
+ int getInstalledPackageVersion(String packageName)
+ throws PackageManager.NameNotFoundException;
+
+ boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException;
+
+ boolean usesPermission(String packageName, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException;
+
+ boolean contentProviderRegistered(String authority, String requiredPackageName);
+
+ boolean receiverRegistered(Intent intent, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException;
+}
diff --git a/services/core/java/com/android/server/timezone/PackageStatus.java b/services/core/java/com/android/server/timezone/PackageStatus.java
new file mode 100644
index 0000000..6379096
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageStatus.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Information about the status of the time zone update / data packages that are persisted by the
+ * Android system.
+ */
+final class PackageStatus {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ CHECK_STARTED, CHECK_COMPLETED_SUCCESS, CHECK_COMPLETED_FAILURE })
+ @interface CheckStatus {}
+
+ /** A time zone update check has been started but not yet completed. */
+ static final int CHECK_STARTED = 1;
+ /** A time zone update check has been completed and succeeded. */
+ static final int CHECK_COMPLETED_SUCCESS = 2;
+ /** A time zone update check has been completed and failed. */
+ static final int CHECK_COMPLETED_FAILURE = 3;
+
+ @CheckStatus
+ final int mCheckStatus;
+
+ // Non-null
+ final PackageVersions mVersions;
+
+ PackageStatus(@CheckStatus int checkStatus, PackageVersions versions) {
+ this.mCheckStatus = checkStatus;
+ if (checkStatus < 1 || checkStatus > 3) {
+ throw new IllegalArgumentException("Unknown checkStatus " + checkStatus);
+ }
+ if (versions == null) {
+ throw new NullPointerException("versions == null");
+ }
+ this.mVersions = versions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PackageStatus that = (PackageStatus) o;
+
+ if (mCheckStatus != that.mCheckStatus) {
+ return false;
+ }
+ return mVersions.equals(that.mVersions);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mCheckStatus;
+ result = 31 * result + mVersions.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageStatus{" +
+ "mCheckStatus=" + mCheckStatus +
+ ", mVersions=" + mVersions +
+ '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageStatusStorage.java b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
new file mode 100644
index 0000000..31f0e31
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageStatusStorage.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Slog;
+
+import java.io.File;
+
+import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_FAILURE;
+import static com.android.server.timezone.PackageStatus.CHECK_COMPLETED_SUCCESS;
+import static com.android.server.timezone.PackageStatus.CHECK_STARTED;
+
+/**
+ * Storage logic for accessing/mutating the Android system's persistent state related to time zone
+ * update checking. There is expected to be a single instance and all methods synchronized on
+ * {@code this} for thread safety.
+ */
+final class PackageStatusStorage {
+
+ private static final String TAG = "timezone.PackageStatusStorage";
+
+ private static final String DATABASE_NAME = "timezonepackagestatus.db";
+ private static final int DATABASE_VERSION = 1;
+
+ /** The table name. It will have a single row with _id == {@link #SINGLETON_ID} */
+ private static final String TABLE = "status";
+ private static final String COLUMN_ID = "_id";
+
+ /**
+ * Column that stores a monotonically increasing lock ID, used to detect concurrent update
+ * issues without on-line locks. Incremented on every write.
+ */
+ private static final String COLUMN_OPTIMISTIC_LOCK_ID = "optimistic_lock_id";
+
+ /**
+ * Column that stores the current "check status" of the time zone update application packages.
+ */
+ private static final String COLUMN_CHECK_STATUS = "check_status";
+
+ /**
+ * Column that stores the version of the time zone rules update application being checked / last
+ * checked.
+ */
+ private static final String COLUMN_UPDATE_APP_VERSION = "update_app_package_version";
+
+ /**
+ * Column that stores the version of the time zone rules data application being checked / last
+ * checked.
+ */
+ private static final String COLUMN_DATA_APP_VERSION = "data_app_package_version";
+
+ /**
+ * The ID of the one row.
+ */
+ private static final int SINGLETON_ID = 1;
+
+ private static final int UNKNOWN_PACKAGE_VERSION = -1;
+
+ private final DatabaseHelper mDatabaseHelper;
+
+ PackageStatusStorage(Context context) {
+ mDatabaseHelper = new DatabaseHelper(context);
+ }
+
+ void deleteDatabaseForTests() {
+ SQLiteDatabase.deleteDatabase(mDatabaseHelper.getDatabaseFile());
+ }
+
+ /**
+ * Obtain the current check status of the application packages. Returns {@code null} the first
+ * time it is called, or after {@link #resetCheckState()}.
+ */
+ PackageStatus getPackageStatus() {
+ synchronized (this) {
+ try {
+ return getPackageStatusInternal();
+ } catch (IllegalArgumentException e) {
+ // This means that data exists in the table but it was bad.
+ Slog.e(TAG, "Package status invalid, resetting and retrying", e);
+
+ // Reset the storage so it is in a good state again.
+ mDatabaseHelper.recoverFromBadData();
+ return getPackageStatusInternal();
+ }
+ }
+ }
+
+ private PackageStatus getPackageStatusInternal() {
+ String[] columns = {
+ COLUMN_CHECK_STATUS, COLUMN_UPDATE_APP_VERSION, COLUMN_DATA_APP_VERSION
+ };
+ Cursor cursor = mDatabaseHelper.getReadableDatabase()
+ .query(TABLE, columns, COLUMN_ID + " = ?",
+ new String[] { Integer.toString(SINGLETON_ID) },
+ null /* groupBy */, null /* having */, null /* orderBy */);
+ if (cursor.getCount() != 1) {
+ Slog.e(TAG, "Unable to find package status from package status row. Rows returned: "
+ + cursor.getCount());
+ return null;
+ }
+ cursor.moveToFirst();
+
+ // Determine check status.
+ if (cursor.isNull(0)) {
+ // This is normal the first time getPackageStatus() is called, or after
+ // resetCheckState().
+ return null;
+ }
+ int checkStatus = cursor.getInt(0);
+
+ // Determine package version.
+ if (cursor.isNull(1) || cursor.isNull(2)) {
+ Slog.e(TAG, "Package version information unexpectedly null");
+ return null;
+ }
+ PackageVersions packageVersions = new PackageVersions(cursor.getInt(1), cursor.getInt(2));
+
+ return new PackageStatus(checkStatus, packageVersions);
+ }
+
+ /**
+ * Generate a new {@link CheckToken} that can be passed to the time zone rules update
+ * application.
+ */
+ CheckToken generateCheckToken(PackageVersions currentInstalledVersions) {
+ if (currentInstalledVersions == null) {
+ throw new NullPointerException("currentInstalledVersions == null");
+ }
+
+ synchronized (this) {
+ Integer optimisticLockId = getCurrentOptimisticLockId();
+ if (optimisticLockId == null) {
+ Slog.w(TAG, "Unable to find optimistic lock ID from package status row");
+
+ // Recover.
+ optimisticLockId = mDatabaseHelper.recoverFromBadData();
+ }
+
+ int newOptimisticLockId = optimisticLockId + 1;
+ boolean statusRowUpdated = writeStatusRow(
+ optimisticLockId, newOptimisticLockId, CHECK_STARTED, currentInstalledVersions);
+ if (!statusRowUpdated) {
+ Slog.e(TAG, "Unable to update status to CHECK_STARTED in package status row."
+ + " synchronization failure?");
+ return null;
+ }
+ return new CheckToken(newOptimisticLockId, currentInstalledVersions);
+ }
+ }
+
+ /**
+ * Reset the current device state to "unknown".
+ */
+ void resetCheckState() {
+ synchronized(this) {
+ Integer optimisticLockId = getCurrentOptimisticLockId();
+ if (optimisticLockId == null) {
+ Slog.w(TAG, "resetCheckState: Unable to find optimistic lock ID from package"
+ + " status row");
+ // Attempt to recover the storage state.
+ optimisticLockId = mDatabaseHelper.recoverFromBadData();
+ }
+
+ int newOptimisticLockId = optimisticLockId + 1;
+ if (!writeStatusRow(optimisticLockId, newOptimisticLockId,
+ null /* status */, null /* packageVersions */)) {
+ Slog.e(TAG, "resetCheckState: Unable to reset package status row,"
+ + " newOptimisticLockId=" + newOptimisticLockId);
+ }
+ }
+ }
+
+ /**
+ * Update the current device state if possible. Returns true if the update was successful.
+ * {@code false} indicates the storage has been changed since the {@link CheckToken} was
+ * generated and the update was discarded.
+ */
+ boolean markChecked(CheckToken checkToken, boolean succeeded) {
+ synchronized (this) {
+ int optimisticLockId = checkToken.mOptimisticLockId;
+ int newOptimisticLockId = optimisticLockId + 1;
+ int status = succeeded ? CHECK_COMPLETED_SUCCESS : CHECK_COMPLETED_FAILURE;
+ return writeStatusRow(optimisticLockId, newOptimisticLockId,
+ status, checkToken.mPackageVersions);
+ }
+ }
+
+ // Caller should be synchronized(this)
+ private Integer getCurrentOptimisticLockId() {
+ final String[] columns = { COLUMN_OPTIMISTIC_LOCK_ID };
+ final String querySelection = COLUMN_ID + " = ?";
+ final String[] querySelectionArgs = { Integer.toString(SINGLETON_ID) };
+
+ SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
+ try (Cursor cursor = database.query(TABLE, columns, querySelection, querySelectionArgs,
+ null /* groupBy */, null /* having */, null /* orderBy */)) {
+ if (cursor.getCount() != 1) {
+ Slog.w(TAG, cursor.getCount() + " rows returned, expected exactly one.");
+ return null;
+ }
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ }
+ }
+
+ // Caller should be synchronized(this)
+ private boolean writeStatusRow(int optimisticLockId, int newOptimisticLockId, Integer status,
+ PackageVersions packageVersions) {
+ if ((status == null) != (packageVersions == null)) {
+ throw new IllegalArgumentException(
+ "Provide both status and packageVersions, or neither.");
+ }
+
+ SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_OPTIMISTIC_LOCK_ID, newOptimisticLockId);
+ if (status == null) {
+ values.putNull(COLUMN_CHECK_STATUS);
+ values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ } else {
+ values.put(COLUMN_CHECK_STATUS, status);
+ values.put(COLUMN_UPDATE_APP_VERSION, packageVersions.mUpdateAppVersion);
+ values.put(COLUMN_DATA_APP_VERSION, packageVersions.mDataAppVersion);
+ }
+
+ String updateSelection = COLUMN_ID + " = ? AND " + COLUMN_OPTIMISTIC_LOCK_ID + " = ?";
+ String[] updateSelectionArgs = {
+ Integer.toString(SINGLETON_ID), Integer.toString(optimisticLockId)
+ };
+ int count = database.update(TABLE, values, updateSelection, updateSelectionArgs);
+ if (count > 1) {
+ // This has to be because of corruption: there should only ever be one row.
+ Slog.w(TAG, "writeStatusRow: " + count + " rows updated, expected exactly one.");
+ // Reset the table.
+ mDatabaseHelper.recoverFromBadData();
+ }
+
+ // 1 is the success case. 0 rows updated means the row is missing or the optimistic lock ID
+ // was not as expected, this could be because of corruption but is most likely due to an
+ // optimistic lock failure. Callers can decide on a case-by-case basis.
+ return count == 1;
+ }
+
+ /** Only used during tests to force an empty table. */
+ void deleteRowForTests() {
+ mDatabaseHelper.getWritableDatabase().delete(TABLE, null, null);
+ }
+
+ /** Only used during tests to force a known table state. */
+ public void forceCheckStateForTests(int checkStatus, PackageVersions packageVersions) {
+ int optimisticLockId = getCurrentOptimisticLockId();
+ writeStatusRow(optimisticLockId, optimisticLockId, checkStatus, packageVersions);
+ }
+
+ static class DatabaseHelper extends SQLiteOpenHelper {
+
+ private final Context mContext;
+
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE + " (" +
+ "_id INTEGER PRIMARY KEY," +
+ COLUMN_OPTIMISTIC_LOCK_ID + " INTEGER NOT NULL," +
+ COLUMN_CHECK_STATUS + " INTEGER," +
+ COLUMN_UPDATE_APP_VERSION + " INTEGER NOT NULL," +
+ COLUMN_DATA_APP_VERSION + " INTEGER NOT NULL" +
+ ");");
+ insertInitialRowState(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ // no-op: nothing to upgrade
+ }
+
+ /** Recover the initial data row state, returning the new current optimistic lock ID */
+ int recoverFromBadData() {
+ // Delete the table content.
+ SQLiteDatabase writableDatabase = getWritableDatabase();
+ writableDatabase.delete(TABLE, null /* whereClause */, null /* whereArgs */);
+
+ // Insert the initial content.
+ return insertInitialRowState(writableDatabase);
+ }
+
+ /** Insert the initial data row, returning the optimistic lock ID */
+ private static int insertInitialRowState(SQLiteDatabase db) {
+ // Doesn't matter what it is, but we avoid the obvious starting value each time the row
+ // is reset to ensure that old tokens are unlikely to work.
+ final int initialOptimisticLockId = (int) System.currentTimeMillis();
+
+ // Insert the one row.
+ ContentValues values = new ContentValues();
+ values.put(COLUMN_ID, SINGLETON_ID);
+ values.put(COLUMN_OPTIMISTIC_LOCK_ID, initialOptimisticLockId);
+ values.putNull(COLUMN_CHECK_STATUS);
+ values.put(COLUMN_UPDATE_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ values.put(COLUMN_DATA_APP_VERSION, UNKNOWN_PACKAGE_VERSION);
+ long id = db.insert(TABLE, null, values);
+ if (id == -1) {
+ Slog.w(TAG, "insertInitialRow: could not insert initial row, id=" + id);
+ return -1;
+ }
+ return initialOptimisticLockId;
+ }
+
+ File getDatabaseFile() {
+ return mContext.getDatabasePath(DATABASE_NAME);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageTracker.java b/services/core/java/com/android/server/timezone/PackageTracker.java
new file mode 100644
index 0000000..8abf7df
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageTracker.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.TimeZoneRulesDataContract;
+import android.util.Slog;
+
+/**
+ * Monitors the installed applications associated with time zone updates. If the app packages are
+ * updated it indicates there <em>might</em> be a time zone rules update to apply so a targeted
+ * broadcast intent is used to trigger the time zone updater app.
+ *
+ * <p>The "update triggering" behavior of this component can be disabled via device configuration.
+ *
+ * <p>The package tracker listens for package updates of the time zone "updater app" and "data app".
+ * It also listens for "reliability" triggers. Reliability triggers are there to ensure that the
+ * package tracker handles failures reliably and are "idle maintenance" events or something similar.
+ * Reliability triggers can cause a time zone update check to take place if the current state is
+ * unclear. For example, it can be unclear after boot or after a failure. If there are repeated
+ * failures reliability updates are halted until the next boot.
+ *
+ * <p>This component keeps persistent track of the most recent app packages checked to avoid
+ * unnecessary expense from broadcasting intents (which will cause other app processes to spawn).
+ * The current status is also stored to detect whether the most recently-generated check is
+ * complete successfully. For example, if the device was interrupted while doing a check and never
+ * acknowledged a check then a check will be retried the next time a "reliability trigger" event
+ * happens.
+ */
+// Also made non-final so it can be mocked.
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class PackageTracker implements IntentHelper.Listener {
+ private static final String TAG = "timezone.PackageTracker";
+
+ private final PackageManagerHelper mPackageManagerHelper;
+ private final IntentHelper mIntentHelper;
+ private final ConfigHelper mConfigHelper;
+ private final PackageStatusStorage mPackageStatusStorage;
+ private final ClockHelper mClockHelper;
+
+ // False if tracking is disabled.
+ private boolean mTrackingEnabled;
+
+ // These fields may be null if package tracking is disabled.
+ private String mUpdateAppPackageName;
+ private String mDataAppPackageName;
+
+ // The time a triggered check is allowed to take before it is considered overdue.
+ private int mCheckTimeAllowedMillis;
+ // The number of failed checks in a row before reliability checks should stop happening.
+ private long mFailedCheckRetryCount;
+
+ // Reliability check state: If a check was triggered but not acknowledged within
+ // mCheckTimeAllowedMillis then another one can be triggered.
+ private Long mLastTriggerTimestamp = null;
+
+ // Reliability check state: Whether any checks have been triggered at all.
+ private boolean mCheckTriggered;
+
+ // Reliability check state: A count of how many failures have occurred consecutively.
+ private int mCheckFailureCount;
+
+ /** Creates the {@link PackageTracker} for normal use. */
+ static PackageTracker create(Context context) {
+ PackageTrackerHelperImpl helperImpl = new PackageTrackerHelperImpl(context);
+ return new PackageTracker(
+ helperImpl /* clock */,
+ helperImpl /* configHelper */,
+ helperImpl /* packageManagerHelper */,
+ new PackageStatusStorage(context),
+ new IntentHelperImpl(context));
+ }
+
+ // A constructor that can be used by tests to supply mocked / faked dependencies.
+ PackageTracker(ClockHelper clockHelper, ConfigHelper configHelper,
+ PackageManagerHelper packageManagerHelper, PackageStatusStorage packageStatusStorage,
+ IntentHelper intentHelper) {
+ mClockHelper = clockHelper;
+ mConfigHelper = configHelper;
+ mPackageManagerHelper = packageManagerHelper;
+ mPackageStatusStorage = packageStatusStorage;
+ mIntentHelper = intentHelper;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected synchronized void start() {
+ mTrackingEnabled = mConfigHelper.isTrackingEnabled();
+ if (!mTrackingEnabled) {
+ Slog.i(TAG, "Time zone updater / data package tracking explicitly disabled.");
+ return;
+ }
+
+ mUpdateAppPackageName = mConfigHelper.getUpdateAppPackageName();
+ mDataAppPackageName = mConfigHelper.getDataAppPackageName();
+ mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
+ mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+
+ // Validate the device configuration including the application packages.
+ // The manifest entries in the apps themselves are not validated until use as they can
+ // change and we don't want to prevent the system server starting due to a bad application.
+ throwIfDeviceSettingsOrAppsAreBad();
+
+ // Explicitly start in a reliability state where reliability triggering will do something.
+ mCheckTriggered = false;
+ mCheckFailureCount = 0;
+
+ // Initialize the intent helper.
+ mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
+
+ // Enable the reliability triggering so we will have at least one reliability trigger if
+ // a package isn't updated.
+ mIntentHelper.enableReliabilityTriggering();
+
+ Slog.i(TAG, "Time zone updater / data package tracking enabled");
+ }
+
+ /**
+ * Performs checks that confirm the system image has correctly configured package
+ * tracking configuration. Only called if package tracking is enabled. Throws an exception if
+ * the device is configured badly which will prevent the device booting.
+ */
+ private void throwIfDeviceSettingsOrAppsAreBad() {
+ // None of the checks below can be based on application manifest settings, otherwise a bad
+ // update could leave the device in an unbootable state. See validateDataAppManifest() and
+ // validateUpdaterAppManifest() for softer errors.
+
+ throwRuntimeExceptionIfNullOrEmpty(
+ mUpdateAppPackageName, "Update app package name missing.");
+ throwRuntimeExceptionIfNullOrEmpty(mDataAppPackageName, "Data app package name missing.");
+ if (mFailedCheckRetryCount < 1) {
+ throw logAndThrowRuntimeException("mFailedRetryCount=" + mFailedCheckRetryCount, null);
+ }
+ if (mCheckTimeAllowedMillis < 1000) {
+ throw logAndThrowRuntimeException(
+ "mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis, null);
+ }
+
+ // Validate the updater application package.
+ // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app
+ // after it is replaced by one in data so this check fails. http://b/35995024
+ // try {
+ // if (!mPackageManagerHelper.isPrivilegedApp(mUpdateAppPackageName)) {
+ // throw failWithException(
+ // "Update app " + mUpdateAppPackageName + " must be a priv-app.", null);
+ // }
+ // } catch (PackageManager.NameNotFoundException e) {
+ // throw failWithException("Could not determine update app package details for "
+ // + mUpdateAppPackageName, e);
+ // }
+ // TODO(nfuller) Consider permission checks. While an updated system app retains permissions
+ // obtained by the system version it's not clear how to check them.
+ Slog.d(TAG, "Update app " + mUpdateAppPackageName + " is valid.");
+
+ // Validate the data application package.
+ // TODO(nfuller) Uncomment or remove the code below. Currently an app stops being a priv-app
+ // after it is replaced by one in data. http://b/35995024
+ // try {
+ // if (!mPackageManagerHelper.isPrivilegedApp(mDataAppPackageName)) {
+ // throw failWithException(
+ // "Data app " + mDataAppPackageName + " must be a priv-app.", null);
+ // }
+ // } catch (PackageManager.NameNotFoundException e) {
+ // throw failWithException("Could not determine data app package details for "
+ // + mDataAppPackageName, e);
+ // }
+ // TODO(nfuller) Consider permission checks. While an updated system app retains permissions
+ // obtained by the system version it's not clear how to check them.
+ Slog.d(TAG, "Data app " + mDataAppPackageName + " is valid.");
+ }
+
+ /**
+ * Inspects the current in-memory state, installed packages and storage state to determine if an
+ * update check is needed and then trigger if it is.
+ *
+ * @param packageChanged true if this method was called because a known packaged definitely
+ * changed, false if the cause is a reliability trigger
+ */
+ @Override
+ public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
+ if (!mTrackingEnabled) {
+ throw new IllegalStateException("Unexpected call. Tracking is disabled.");
+ }
+
+ // Validate the applications' current manifest entries: make sure they are configured as
+ // they should be. These are not fatal and just means that no update is triggered: we don't
+ // want to take down the system server if an OEM or Google have pushed a bad update to
+ // an application.
+ boolean updaterAppManifestValid = validateUpdaterAppManifest();
+ boolean dataAppManifestValid = validateDataAppManifest();
+ if (!updaterAppManifestValid || !dataAppManifestValid) {
+ Slog.e(TAG, "No update triggered due to invalid application manifest entries."
+ + " updaterApp=" + updaterAppManifestValid
+ + ", dataApp=" + dataAppManifestValid);
+
+ // There's no point in doing reliability checks if the current packages are bad.
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+
+ if (!packageChanged) {
+ // This call was made because the device is doing a "reliability" check.
+ // 4 possible cases:
+ // 1) No check has previously triggered since restart. We want to trigger in this case.
+ // 2) A check has previously triggered and it is in progress. We want to trigger if
+ // the response is overdue.
+ // 3) A check has previously triggered and it failed. We want to trigger, but only if
+ // we're not in a persistent failure state.
+ // 4) A check has previously triggered and it succeeded.
+ // We don't want to trigger, and want to stop future triggers.
+
+ if (!mCheckTriggered) {
+ // Case 1.
+ Slog.d(TAG, "triggerUpdateIfNeeded: First reliability trigger.");
+ } else if (isCheckInProgress()) {
+ // Case 2.
+ if (!isCheckResponseOverdue()) {
+ // A check is in progress but hasn't been given time to succeed.
+ Slog.d(TAG,
+ "triggerUpdateIfNeeded: checkComplete call is not yet overdue."
+ + " Not triggering.");
+ // Not doing any work, but also not disabling future reliability triggers.
+ return;
+ }
+ } else if (mCheckFailureCount > mFailedCheckRetryCount) {
+ // Case 3. If the system is in some kind of persistent failure state we don't want
+ // to keep checking, so just stop.
+ Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
+ + " exceeded. Stopping reliability triggers until next reboot or package"
+ + " update.");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ } else if (mCheckFailureCount == 0) {
+ // Case 4.
+ Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
+ + " successful.");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+ }
+
+ // Read the currently installed data / updater package versions.
+ PackageVersions currentInstalledVersions = lookupInstalledPackageVersions();
+ if (currentInstalledVersions == null) {
+ // This should not happen if the device is configured in a valid way.
+ Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+
+ // Establish the current state using package manager and stored state. Determine if we have
+ // already successfully checked the installed versions.
+ PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
+ if (packageStatus == null) {
+ // This can imply corrupt, uninitialized storage state (e.g. first check ever on a
+ // device) or after some kind of reset.
+ Slog.i(TAG, "triggerUpdateIfNeeded: No package status data found. Data check needed.");
+ } else if (!packageStatus.mVersions.equals(currentInstalledVersions)) {
+ // The stored package version information differs from the installed version.
+ // Trigger the check in all cases.
+ Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions="
+ + packageStatus.mVersions + ", do not match current package versions="
+ + currentInstalledVersions + ". Triggering check.");
+ } else {
+ Slog.i(TAG, "triggerUpdateIfNeeded: Stored package versions match currently"
+ + " installed versions, currentInstalledVersions=" + currentInstalledVersions
+ + ", packageStatus.mCheckStatus=" + packageStatus.mCheckStatus);
+ if (packageStatus.mCheckStatus == PackageStatus.CHECK_COMPLETED_SUCCESS) {
+ // The last check succeeded and nothing has changed. Do nothing and disable
+ // reliability checks.
+ Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
+ mIntentHelper.disableReliabilityTriggering();
+ return;
+ }
+ }
+
+ // Generate a token to send to the updater app.
+ CheckToken checkToken =
+ mPackageStatusStorage.generateCheckToken(currentInstalledVersions);
+ if (checkToken == null) {
+ Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
+ + " Not sending check request.");
+ return;
+ }
+
+ // Trigger the update check.
+ mIntentHelper.sendTriggerUpdateCheck(checkToken);
+ mCheckTriggered = true;
+
+ // Update the reliability check state in case the update fails.
+ setCheckInProgress();
+
+ // Enable reliability triggering in case the check doesn't succeed and there is no
+ // response at all. Enabling reliability triggering is idempotent.
+ mIntentHelper.enableReliabilityTriggering();
+ }
+
+ /**
+ * Used to record the result of a check. Can be called even if active package tracking is
+ * disabled.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected synchronized void recordCheckResult(CheckToken checkToken, boolean success) {
+ Slog.i(TAG, "recordOperationResult: checkToken=" + checkToken + " success=" + success);
+
+ // If package tracking is disabled it means no record-keeping is required. However, we do
+ // want to clear out any stored state to make it clear that the current state is unknown and
+ // should tracking become enabled again (perhaps through an OTA) we'd need to perform an
+ // update check.
+ if (!mTrackingEnabled) {
+ // This means an updater has spontaneously modified time zone data without having been
+ // triggered. This can happen if the OEM is handling their own updates, but we don't
+ // need to do any tracking in this case.
+
+ if (checkToken == null) {
+ // This is the expected case if tracking is disabled but an OEM is handling time
+ // zone installs using their own mechanism.
+ Slog.d(TAG, "recordCheckResult: Tracking is disabled and no token has been"
+ + " provided. Resetting tracking state.");
+ } else {
+ // This is unexpected. If tracking is disabled then no check token should have been
+ // generated by the package tracker. An updater should never create its own token.
+ // This could be a bug in the updater.
+ Slog.w(TAG, "recordCheckResult: Tracking is disabled and a token " + checkToken
+ + " has been unexpectedly provided. Resetting tracking state.");
+ }
+ mPackageStatusStorage.resetCheckState();
+ return;
+ }
+
+ if (checkToken == null) {
+ /*
+ * If the checkToken is null it suggests an install / uninstall / acknowledgement has
+ * occurred without a prior trigger (or the client didn't return the token it was given
+ * for some reason, perhaps a bug).
+ *
+ * This shouldn't happen under normal circumstances:
+ *
+ * If package tracking is enabled, we assume it is the package tracker responsible for
+ * triggering updates and a token should have been produced and returned.
+ *
+ * If the OEM is handling time zone updates case package tracking should be disabled.
+ *
+ * This could happen in tests. The device should recover back to a known state by
+ * itself rather than be left in an invalid state.
+ *
+ * We treat this as putting the device into an unknown state and make sure that
+ * reliability triggering is enabled so we should recover.
+ */
+ Slog.i(TAG, "recordCheckResult: Unexpectedly missing checkToken, resetting"
+ + " storage state.");
+ mPackageStatusStorage.resetCheckState();
+
+ // Enable reliability triggering and reset the failure count so we know that the
+ // next reliability trigger will do something.
+ mIntentHelper.enableReliabilityTriggering();
+ mCheckFailureCount = 0;
+ } else {
+ // This is the expected case when tracking is enabled: a check was triggered and it has
+ // completed.
+ boolean recordedCheckCompleteSuccessfully =
+ mPackageStatusStorage.markChecked(checkToken, success);
+ if (recordedCheckCompleteSuccessfully) {
+ // If we have recorded the result (whatever it was) we know there is no check in
+ // progress.
+ setCheckComplete();
+
+ if (success) {
+ // Since the check was successful, no more reliability checks are required until
+ // there is a package change.
+ mIntentHelper.disableReliabilityTriggering();
+ mCheckFailureCount = 0;
+ } else {
+ // Enable reliability triggering to potentially check again in future.
+ mIntentHelper.enableReliabilityTriggering();
+ mCheckFailureCount++;
+ }
+ } else {
+ // The failure to record the check means an optimistic lock failure and suggests
+ // that another check was triggered after the token was generated.
+ Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
+ + " with success=" + success + ". Optimistic lock failure");
+
+ // Enable reliability triggering to potentially try again in future.
+ mIntentHelper.enableReliabilityTriggering();
+ mCheckFailureCount++;
+ }
+ }
+ }
+
+ /** Access to consecutive failure counts for use in tests. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ protected int getCheckFailureCountForTests() {
+ return mCheckFailureCount;
+ }
+
+ private void setCheckInProgress() {
+ mLastTriggerTimestamp = mClockHelper.currentTimestamp();
+ }
+
+ private void setCheckComplete() {
+ mLastTriggerTimestamp = null;
+ }
+
+ private boolean isCheckInProgress() {
+ return mLastTriggerTimestamp != null;
+ }
+
+ private boolean isCheckResponseOverdue() {
+ if (mLastTriggerTimestamp == null) {
+ return false;
+ }
+ // Risk of overflow, but highly unlikely given the implementation and not problematic.
+ return mClockHelper.currentTimestamp() > mLastTriggerTimestamp + mCheckTimeAllowedMillis;
+ }
+
+ private PackageVersions lookupInstalledPackageVersions() {
+ int updatePackageVersion;
+ int dataPackageVersion;
+ try {
+ updatePackageVersion =
+ mPackageManagerHelper.getInstalledPackageVersion(mUpdateAppPackageName);
+ dataPackageVersion =
+ mPackageManagerHelper.getInstalledPackageVersion(mDataAppPackageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "lookupInstalledPackageVersions: Unable to resolve installed package"
+ + " versions", e);
+ return null;
+ }
+ return new PackageVersions(updatePackageVersion, dataPackageVersion);
+ }
+
+ private boolean validateDataAppManifest() {
+ // We only want to talk to a provider that exposed by the known data app package
+ // so we look up the providers exposed by that app and check the well-known authority is
+ // there. This prevents the case where *even if* the data app doesn't expose the provider
+ // required, another app cannot expose one to replace it.
+ if (!mPackageManagerHelper.contentProviderRegistered(
+ TimeZoneRulesDataContract.AUTHORITY, mDataAppPackageName)) {
+ // Error! Found the package but it didn't expose the correct provider.
+ Slog.w(TAG, "validateDataAppManifest: Data app " + mDataAppPackageName
+ + " does not expose the required provider with authority="
+ + TimeZoneRulesDataContract.AUTHORITY);
+ return false;
+ }
+ // TODO(nfuller) Add any permissions checks needed.
+ return true;
+ }
+
+ private boolean validateUpdaterAppManifest() {
+ try {
+ // The updater app is expected to have the UPDATE_TIME_ZONE_RULES permission.
+ // The updater app is expected to have a receiver for the intent we are going to trigger
+ // and require the TRIGGER_TIME_ZONE_RULES_CHECK.
+ if (!mPackageManagerHelper.usesPermission(
+ mUpdateAppPackageName,
+ RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION)) {
+ Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+ + " does not use permission="
+ + RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
+ return false;
+ }
+ if (!mPackageManagerHelper.receiverRegistered(
+ RulesUpdaterContract.createUpdaterIntent(mUpdateAppPackageName),
+ RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)) {
+ return false;
+ }
+
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "validateUpdaterAppManifest: Updater app " + mDataAppPackageName
+ + " does not expose the required broadcast receiver.", e);
+ return false;
+ }
+ }
+
+ private static void throwRuntimeExceptionIfNullOrEmpty(String value, String message) {
+ if (value == null || value.trim().isEmpty()) {
+ throw logAndThrowRuntimeException(message, null);
+ }
+ }
+
+ private static RuntimeException logAndThrowRuntimeException(String message, Throwable cause) {
+ Slog.wtf(TAG, message, cause);
+ throw new RuntimeException(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
new file mode 100644
index 0000000..2e0c21b
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.internal.R;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.util.List;
+
+/**
+ * A single class that implements multiple helper interfaces for use by {@link PackageTracker}.
+ */
+final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, PackageManagerHelper {
+
+ private static final String TAG = "PackageTrackerHelperImpl";
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+
+ PackageTrackerHelperImpl(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public boolean isTrackingEnabled() {
+ return mContext.getResources().getBoolean(R.bool.config_timeZoneRulesUpdateTrackingEnabled);
+ }
+
+ @Override
+ public String getUpdateAppPackageName() {
+ return mContext.getResources().getString(R.string.config_timeZoneRulesUpdaterPackage);
+ }
+
+ @Override
+ public String getDataAppPackageName() {
+ Resources resources = mContext.getResources();
+ return resources.getString(R.string.config_timeZoneRulesDataPackage);
+ }
+
+ @Override
+ public int getCheckTimeAllowedMillis() {
+ return mContext.getResources().getInteger(
+ R.integer.config_timeZoneRulesCheckTimeMillisAllowed);
+ }
+
+ @Override
+ public int getFailedCheckRetryCount() {
+ return mContext.getResources().getInteger(R.integer.config_timeZoneRulesCheckRetryCount);
+ }
+
+ @Override
+ public long currentTimestamp() {
+ // Use of elapsedRealtime() because this is in-memory state and elapsedRealtime() shouldn't
+ // change if the system clock changes.
+ return SystemClock.elapsedRealtime();
+ }
+
+ @Override
+ public int getInstalledPackageVersion(String packageName)
+ throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ return packageInfo.versionCode;
+ }
+
+ @Override
+ public boolean isPrivilegedApp(String packageName) throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ return packageInfo.applicationInfo.isPrivilegedApp();
+ }
+
+ @Override
+ public boolean usesPermission(String packageName, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.GET_PERMISSIONS;
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName, flags);
+ if (packageInfo.requestedPermissions == null) {
+ return false;
+ }
+ for (String requestedPermission : packageInfo.requestedPermissions) {
+ if (requiredPermissionName.equals(requestedPermission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean contentProviderRegistered(String authority, String requiredPackageName) {
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ ProviderInfo providerInfo =
+ mPackageManager.resolveContentProvider(authority, flags);
+ if (providerInfo == null) {
+ Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
+ + authority);
+ return false;
+ }
+ boolean packageMatches =
+ requiredPackageName.equals(providerInfo.applicationInfo.packageName);
+ if (!packageMatches) {
+ Slog.i(TAG, "contentProviderRegistered: App with packageName=" + requiredPackageName
+ + " does not expose the a content provider with authority=" + authority);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean receiverRegistered(Intent intent, String requiredPermissionName)
+ throws PackageManager.NameNotFoundException {
+
+ int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+ if (resolveInfo.size() != 1) {
+ Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
+ + " intent=" + intent + ", found=" + resolveInfo);
+ return false;
+ }
+
+ ResolveInfo matched = resolveInfo.get(0);
+ boolean requiresPermission = requiredPermissionName.equals(matched.activityInfo.permission);
+ if (!requiresPermission) {
+ Slog.i(TAG, "receiverRegistered: Broadcast receiver registered for intent="
+ + intent + " must require permission " + requiredPermissionName);
+ }
+ return requiresPermission;
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PackageVersions.java b/services/core/java/com/android/server/timezone/PackageVersions.java
new file mode 100644
index 0000000..fc0d6e1
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PackageVersions.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * Package version information about the time zone updater and time zone data application packages.
+ */
+final class PackageVersions {
+
+ final int mUpdateAppVersion;
+ final int mDataAppVersion;
+
+ PackageVersions(int updateAppVersion, int dataAppVersion) {
+ this.mUpdateAppVersion = updateAppVersion;
+ this.mDataAppVersion = dataAppVersion;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ PackageVersions that = (PackageVersions) o;
+
+ if (mUpdateAppVersion != that.mUpdateAppVersion) {
+ return false;
+ }
+ return mDataAppVersion == that.mDataAppVersion;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUpdateAppVersion;
+ result = 31 * result + mDataAppVersion;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "PackageVersions{" +
+ "mUpdateAppVersion=" + mUpdateAppVersion +
+ ", mDataAppVersion=" + mDataAppVersion +
+ '}';
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/PermissionHelper.java b/services/core/java/com/android/server/timezone/PermissionHelper.java
new file mode 100644
index 0000000..ba91c7f
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/PermissionHelper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+/**
+ * An easy-to-mock interface around permission checks for use by {@link RulesManagerService}.
+ */
+public interface PermissionHelper {
+
+ void enforceCallerHasPermission(String requiredPermission) throws SecurityException;
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
new file mode 100644
index 0000000..82bd356
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import android.app.timezone.Callback;
+import android.app.timezone.DistroFormatVersion;
+import android.app.timezone.DistroRulesVersion;
+import android.app.timezone.ICallback;
+import android.app.timezone.IRulesManager;
+import android.app.timezone.RulesManager;
+import android.app.timezone.RulesState;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import libcore.tzdata.shared2.DistroException;
+import libcore.tzdata.shared2.DistroVersion;
+import libcore.tzdata.shared2.StagedDistroOperation;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+// TODO(nfuller) Add EventLog calls where useful in the system server.
+// TODO(nfuller) Check logging best practices in the system server.
+// TODO(nfuller) Check error handling best practices in the system server.
+public final class RulesManagerService extends IRulesManager.Stub {
+
+ private static final String TAG = "timezone.RulesManagerService";
+
+ /** The distro format supported by this device. */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static final DistroFormatVersion DISTRO_FORMAT_VERSION_SUPPORTED =
+ new DistroFormatVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
+
+ public static class Lifecycle extends SystemService {
+ private RulesManagerService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = RulesManagerService.create(getContext());
+ mService.start();
+
+ publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static final String REQUIRED_UPDATER_PERMISSION =
+ android.Manifest.permission.UPDATE_TIME_ZONE_RULES;
+ private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
+ private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+
+ private final AtomicBoolean mOperationInProgress = new AtomicBoolean(false);
+ private final PermissionHelper mPermissionHelper;
+ private final PackageTracker mPackageTracker;
+ private final Executor mExecutor;
+ private final TimeZoneDistroInstaller mInstaller;
+ private final FileDescriptorHelper mFileDescriptorHelper;
+
+ private static RulesManagerService create(Context context) {
+ RulesManagerServiceHelperImpl helper = new RulesManagerServiceHelperImpl(context);
+ return new RulesManagerService(
+ helper /* permissionHelper */,
+ helper /* executor */,
+ helper /* fileDescriptorHelper */,
+ PackageTracker.create(context),
+ new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR));
+ }
+
+ // A constructor that can be used by tests to supply mocked / faked dependencies.
+ RulesManagerService(PermissionHelper permissionHelper,
+ Executor executor,
+ FileDescriptorHelper fileDescriptorHelper, PackageTracker packageTracker,
+ TimeZoneDistroInstaller timeZoneDistroInstaller) {
+ mPermissionHelper = permissionHelper;
+ mExecutor = executor;
+ mFileDescriptorHelper = fileDescriptorHelper;
+ mPackageTracker = packageTracker;
+ mInstaller = timeZoneDistroInstaller;
+ }
+
+ public void start() {
+ mPackageTracker.start();
+ }
+
+ @Override // Binder call
+ public RulesState getRulesState() {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+ synchronized(this) {
+ String systemRulesVersion;
+ try {
+ systemRulesVersion = mInstaller.getSystemRulesVersion();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to read system rules", e);
+ return null;
+ }
+
+ boolean operationInProgress = this.mOperationInProgress.get();
+
+ // Determine the staged operation status, if possible.
+ DistroRulesVersion stagedDistroRulesVersion = null;
+ int stagedOperationStatus = RulesState.STAGED_OPERATION_UNKNOWN;
+ if (!operationInProgress) {
+ StagedDistroOperation stagedDistroOperation;
+ try {
+ stagedDistroOperation = mInstaller.getStagedDistroOperation();
+ if (stagedDistroOperation == null) {
+ stagedOperationStatus = RulesState.STAGED_OPERATION_NONE;
+ } else if (stagedDistroOperation.isUninstall) {
+ stagedOperationStatus = RulesState.STAGED_OPERATION_UNINSTALL;
+ } else {
+ // Must be an install.
+ stagedOperationStatus = RulesState.STAGED_OPERATION_INSTALL;
+ DistroVersion stagedDistroVersion = stagedDistroOperation.distroVersion;
+ stagedDistroRulesVersion = new DistroRulesVersion(
+ stagedDistroVersion.rulesVersion,
+ stagedDistroVersion.revision);
+ }
+ } catch (DistroException | IOException e) {
+ Slog.w(TAG, "Failed to read staged distro.", e);
+ }
+ }
+
+ // Determine the installed distro state, if possible.
+ DistroVersion installedDistroVersion;
+ int distroStatus = RulesState.DISTRO_STATUS_UNKNOWN;
+ DistroRulesVersion installedDistroRulesVersion = null;
+ if (!operationInProgress) {
+ try {
+ installedDistroVersion = mInstaller.getInstalledDistroVersion();
+ if (installedDistroVersion == null) {
+ distroStatus = RulesState.DISTRO_STATUS_NONE;
+ installedDistroRulesVersion = null;
+ } else {
+ distroStatus = RulesState.DISTRO_STATUS_INSTALLED;
+ installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion,
+ installedDistroVersion.revision);
+ }
+ } catch (DistroException | IOException e) {
+ Slog.w(TAG, "Failed to read installed distro.", e);
+ }
+ }
+ return new RulesState(systemRulesVersion, DISTRO_FORMAT_VERSION_SUPPORTED,
+ operationInProgress, stagedOperationStatus, stagedDistroRulesVersion,
+ distroStatus, installedDistroRulesVersion);
+ }
+ }
+
+ @Override
+ public int requestInstall(
+ ParcelFileDescriptor timeZoneDistro, byte[] checkTokenBytes, ICallback callback) {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+ CheckToken checkToken = null;
+ if (checkTokenBytes != null) {
+ checkToken = createCheckTokenOrThrow(checkTokenBytes);
+ }
+ synchronized (this) {
+ if (timeZoneDistro == null) {
+ throw new NullPointerException("timeZoneDistro == null");
+ }
+ if (callback == null) {
+ throw new NullPointerException("observer == null");
+ }
+ if (mOperationInProgress.get()) {
+ return RulesManager.ERROR_OPERATION_IN_PROGRESS;
+ }
+ mOperationInProgress.set(true);
+
+ // Execute the install asynchronously.
+ mExecutor.execute(new InstallRunnable(timeZoneDistro, checkToken, callback));
+
+ return RulesManager.SUCCESS;
+ }
+ }
+
+ private class InstallRunnable implements Runnable {
+
+ private final ParcelFileDescriptor mTimeZoneDistro;
+ private final CheckToken mCheckToken;
+ private final ICallback mCallback;
+
+ InstallRunnable(
+ ParcelFileDescriptor timeZoneDistro, CheckToken checkToken, ICallback callback) {
+ mTimeZoneDistro = timeZoneDistro;
+ mCheckToken = checkToken;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ // Adopt the ParcelFileDescriptor into this try-with-resources so it is closed
+ // when we are done.
+ boolean success = false;
+ try {
+ byte[] distroBytes =
+ RulesManagerService.this.mFileDescriptorHelper.readFully(mTimeZoneDistro);
+ int installerResult = mInstaller.stageInstallWithErrorCode(distroBytes);
+ int resultCode = mapInstallerResultToApiCode(installerResult);
+ sendFinishedStatus(mCallback, resultCode);
+
+ // All the installer failure modes are currently non-recoverable and won't be
+ // improved by trying again. Therefore success = true.
+ success = true;
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to install distro.", e);
+ sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
+ } finally {
+ // Notify the package tracker that the operation is now complete.
+ mPackageTracker.recordCheckResult(mCheckToken, success);
+
+ mOperationInProgress.set(false);
+ }
+ }
+
+ private int mapInstallerResultToApiCode(int installerResult) {
+ switch (installerResult) {
+ case TimeZoneDistroInstaller.INSTALL_SUCCESS:
+ return Callback.SUCCESS;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_STRUCTURE:
+ return Callback.ERROR_INSTALL_BAD_DISTRO_STRUCTURE;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_RULES_TOO_OLD:
+ return Callback.ERROR_INSTALL_RULES_TOO_OLD;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_BAD_DISTRO_FORMAT_VERSION:
+ return Callback.ERROR_INSTALL_BAD_DISTRO_FORMAT_VERSION;
+ case TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR:
+ return Callback.ERROR_INSTALL_VALIDATION_ERROR;
+ default:
+ return Callback.ERROR_UNKNOWN_FAILURE;
+ }
+ }
+ }
+
+ @Override
+ public int requestUninstall(byte[] checkTokenBytes, ICallback callback) {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+
+ CheckToken checkToken = null;
+ if (checkTokenBytes != null) {
+ checkToken = createCheckTokenOrThrow(checkTokenBytes);
+ }
+ synchronized(this) {
+ if (callback == null) {
+ throw new NullPointerException("callback == null");
+ }
+
+ if (mOperationInProgress.get()) {
+ return RulesManager.ERROR_OPERATION_IN_PROGRESS;
+ }
+ mOperationInProgress.set(true);
+
+ // Execute the uninstall asynchronously.
+ mExecutor.execute(new UninstallRunnable(checkToken, callback));
+
+ return RulesManager.SUCCESS;
+ }
+ }
+
+ private class UninstallRunnable implements Runnable {
+
+ private final CheckToken mCheckToken;
+ private final ICallback mCallback;
+
+ public UninstallRunnable(CheckToken checkToken, ICallback callback) {
+ mCheckToken = checkToken;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ boolean success = 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;
+ sendFinishedStatus(mCallback, resultCode);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to uninstall distro.", e);
+ sendFinishedStatus(mCallback, Callback.ERROR_UNKNOWN_FAILURE);
+ } finally {
+ // Notify the package tracker that the operation is now complete.
+ mPackageTracker.recordCheckResult(mCheckToken, success);
+
+ mOperationInProgress.set(false);
+ }
+ }
+ }
+
+ private void sendFinishedStatus(ICallback callback, int resultCode) {
+ try {
+ callback.onFinished(resultCode);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify observer of result", e);
+ }
+ }
+
+ @Override
+ public void requestNothing(byte[] checkTokenBytes, boolean success) {
+ mPermissionHelper.enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ CheckToken checkToken = null;
+ if (checkTokenBytes != null) {
+ checkToken = createCheckTokenOrThrow(checkTokenBytes);
+ }
+ mPackageTracker.recordCheckResult(checkToken, success);
+ }
+
+ private static CheckToken createCheckTokenOrThrow(byte[] checkTokenBytes) {
+ CheckToken checkToken;
+ try {
+ checkToken = CheckToken.fromByteArray(checkTokenBytes);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unable to read token bytes "
+ + Arrays.toString(checkTokenBytes), e);
+ }
+ return checkToken;
+ }
+}
diff --git a/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
new file mode 100644
index 0000000..15a571d
--- /dev/null
+++ b/services/core/java/com/android/server/timezone/RulesManagerServiceHelperImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import libcore.io.Streams;
+
+/**
+ * A single class that implements multiple helper interfaces for use by {@link RulesManagerService}.
+ */
+final class RulesManagerServiceHelperImpl
+ implements PermissionHelper, Executor, FileDescriptorHelper {
+
+ private final Context mContext;
+
+ RulesManagerServiceHelperImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void enforceCallerHasPermission(String requiredPermission) {
+ mContext.enforceCallingPermission(requiredPermission, null /* message */);
+ }
+
+ // TODO Wake lock required?
+ @Override
+ public void execute(Runnable runnable) {
+ // TODO Is there a better way?
+ new Thread(runnable).start();
+ }
+
+ @Override
+ public byte[] readFully(ParcelFileDescriptor parcelFileDescriptor) throws IOException {
+ try (ParcelFileDescriptor pfd = parcelFileDescriptor) {
+ // Read bytes
+ FileInputStream in = new FileInputStream(pfd.getFileDescriptor(), false /* isOwner */);
+ return Streams.readFully(in);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b4e806b..e1cbc91 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -171,6 +171,8 @@
"com.android.server.content.ContentService$Lifecycle";
private static final String WALLPAPER_SERVICE_CLASS =
"com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
+ private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
+ "com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -978,6 +980,13 @@
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
+ if (!disableNonCoreServices && context.getResources().getBoolean(
+ R.bool.config_enableUpdateableTimeZoneRules)) {
+ traceBeginAndSlog("StartTimeZoneRulesManagerService");
+ mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+
traceBeginAndSlog("StartAudioService");
mSystemServiceManager.startService(AudioService.Lifecycle.class);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index b0e0230..703b415 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -46,7 +46,7 @@
}
public String toString() {
- return String.format("%s{version: %d, maxSize: %d format: %d}", getClass().getSimpleName(),
+ return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
}
}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 745764b..6608167 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -524,7 +524,7 @@
try {
mNwService.registerObserver(mNetlinkTracker);
} catch (RemoteException e) {
- Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
+ logError("Couldn't register NetlinkTracker: %s", e);
}
mMultinetworkPolicyTracker.start();
@@ -611,19 +611,36 @@
return;
}
+ // Thread-unsafe access to mApfFilter but just used for debugging.
+ final ApfFilter apfFilter = mApfFilter;
+ final ProvisioningConfiguration provisioningConfig = mConfiguration;
+ final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+ ? provisioningConfig.mApfCapabilities : null;
+
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println(mTag + " APF dump:");
pw.increaseIndent();
- // Thread-unsafe access to mApfFilter but just used for debugging.
- ApfFilter apfFilter = mApfFilter;
if (apfFilter != null) {
apfFilter.dump(pw);
} else {
- pw.println("No apf support");
+ pw.print("No active ApfFilter; ");
+ if (provisioningConfig == null) {
+ pw.println("IpManager not yet started.");
+ } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+ pw.println("Hardware does not support APF.");
+ } else {
+ pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+ }
}
pw.decreaseIndent();
pw.println();
+ pw.println(mTag + " current ProvisioningConfiguration:");
+ pw.increaseIndent();
+ pw.println(Objects.toString(provisioningConfig, "N/A"));
+ pw.decreaseIndent();
+
+ pw.println();
pw.println(mTag + " StateMachine dump:");
pw.increaseIndent();
mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
@@ -684,7 +701,9 @@
// TODO: Migrate all Log.e(...) to logError(...).
private void logError(String fmt, Object... args) {
- mLocalLog.log("ERROR " + String.format(fmt, args));
+ final String msg = "ERROR " + String.format(fmt, args);
+ Log.e(mTag, msg);
+ mLocalLog.log(msg);
}
private void getNetworkInterface() {
@@ -692,7 +711,7 @@
mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
} catch (SocketException | NullPointerException e) {
// TODO: throw new IllegalStateException.
- Log.e(mTag, "ALERT: Failed to get interface object: ", e);
+ logError("Failed to get interface object: %s", e);
}
}
@@ -948,7 +967,7 @@
ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
mNwService.setInterfaceConfig(mInterfaceName, ifcg);
} catch (IllegalStateException | RemoteException e) {
- Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
+ logError("Failed to clear IPv4 address on interface %s: %s", mInterfaceName, e);
}
}
@@ -1074,13 +1093,13 @@
try {
mNwService.disableIpv6(mInterfaceName);
} catch (Exception e) {
- Log.e(mTag, "Failed to disable IPv6" + e);
+ logError("Failed to disable IPv6: %s", e);
}
try {
mNwService.clearInterfaceAddresses(mInterfaceName);
} catch (Exception e) {
- Log.e(mTag, "Failed to clear addresses " + e);
+ logError("Failed to clear addresses: %s", e);
}
}
@@ -1404,7 +1423,7 @@
if (setIPv4Address(ipAddress)) {
mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
} else {
- Log.e(mTag, "Failed to set IPv4 address!");
+ logError("Failed to set IPv4 address.");
dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
new LinkProperties(mLinkProperties));
transitionTo(mStoppingState);
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java
index 5b068c0..dae93af 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/services/net/java/android/net/util/ConnectivityPacketSummary.java
@@ -368,9 +368,9 @@
byte[] bytes = new byte[ETHER_ADDR_LEN];
mac.get(bytes, 0, bytes.length);
- Byte[] printableBytes = new Byte[bytes.length];
+ Object[] printableBytes = new Object[bytes.length];
int i = 0;
- for (byte b : bytes) printableBytes[i++] = b;
+ for (byte b : bytes) printableBytes[i++] = new Byte(b);
final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
return String.format(MAC48_FORMAT, printableBytes);
diff --git a/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java b/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java
new file mode 100644
index 0000000..9603a06
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/CheckTokenTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+@SmallTest
+public class CheckTokenTest {
+
+ @Test
+ public void toByteArray() throws Exception {
+ PackageVersions packageVersions =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ CheckToken originalToken = new CheckToken(1 /* optimisticLockId */, packageVersions);
+ assertEquals(originalToken, CheckToken.fromByteArray(originalToken.toByteArray()));
+ }
+
+ @Test
+ public void fromByteArray() {
+ PackageVersions packageVersions =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ CheckToken token = new CheckToken(1, packageVersions);
+ byte[] validTokenBytes = token.toByteArray();
+ byte[] shortTokenBytes = new byte[validTokenBytes.length - 1];
+ System.arraycopy(validTokenBytes, 0, shortTokenBytes, 0, shortTokenBytes.length);
+
+ try {
+ CheckToken.fromByteArray(shortTokenBytes);
+ fail();
+ } catch (IOException expected) {}
+ }
+
+ @Test
+ public void equals() {
+ PackageVersions packageVersions1 =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ PackageVersions packageVersions2 =
+ new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
+ assertFalse(packageVersions1.equals(packageVersions2));
+
+ CheckToken baseline = new CheckToken(1, packageVersions1);
+ assertEquals(baseline, baseline);
+
+ CheckToken deepEqual = new CheckToken(1, packageVersions1);
+ assertEquals(baseline, deepEqual);
+
+ CheckToken differentOptimisticLockId = new CheckToken(2, packageVersions1);
+ assertFalse(differentOptimisticLockId.equals(baseline));
+
+ CheckToken differentPackageVersions = new CheckToken(1, packageVersions2);
+ assertFalse(differentPackageVersions.equals(baseline));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
new file mode 100644
index 0000000..e085270
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusStorageTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@SmallTest
+public class PackageStatusStorageTest {
+ private static final PackageVersions VALID_PACKAGE_VERSIONS = new PackageVersions(1, 2);
+
+ private PackageStatusStorage mPackageStatusStorage;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+
+ // Using the instrumentation context means the database is created in a test app-specific
+ // directory.
+ mPackageStatusStorage = new PackageStatusStorage(context);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mPackageStatusStorage.deleteDatabaseForTests();
+ }
+
+ @Test
+ public void getPackageStatus_initialState() {
+ assertNull(mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void resetCheckState() {
+ // Assert initial state.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+ // There should now be a state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Now clear the state.
+ mPackageStatusStorage.resetCheckState();
+
+ // After reset, there should be no package state again.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+ // Token after a reset should still be distinct.
+ assertFalse(token1.equals(token2));
+
+ // Now clear the state again.
+ mPackageStatusStorage.resetCheckState();
+
+ // After reset, there should be no package state again.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token3 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+
+ // A CheckToken generated after a reset should still be distinct.
+ assertFalse(token2.equals(token3));
+ }
+
+ @Test
+ public void generateCheckToken_missingRowBehavior() {
+ // Assert initial state.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertNotNull(token1);
+
+ // There should now be state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Corrupt the table by removing the one row.
+ mPackageStatusStorage.deleteRowForTests();
+
+ // Check that generateCheckToken recovers.
+ assertNotNull(mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS));
+ }
+
+ @Test
+ public void getPackageStatus_missingRowBehavior() {
+ // Assert initial state.
+ assertNull(mPackageStatusStorage.getPackageStatus());
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertNotNull(token1);
+
+ // There should now be a state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Corrupt the table by removing the one row.
+ mPackageStatusStorage.deleteRowForTests();
+
+ assertNull(mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_missingRowBehavior() {
+ // Assert initial state.
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertNotNull(token1);
+
+ // There should now be a state.
+ assertNotNull(mPackageStatusStorage.getPackageStatus());
+
+ // Corrupt the table by removing the one row.
+ mPackageStatusStorage.deleteRowForTests();
+
+ // The missing row should mean token1 is now considered invalid, so we should get a false.
+ assertFalse(mPackageStatusStorage.markChecked(token1, true /* succeeded */));
+
+ // The storage should have recovered and we should be able to carry on like before.
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(VALID_PACKAGE_VERSIONS);
+ assertTrue(mPackageStatusStorage.markChecked(token2, true /* succeeded */));
+ }
+
+ @Test
+ public void checkToken_tokenIsUnique() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions);
+
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ PackageStatus actualPackageStatus1 = mPackageStatusStorage.getPackageStatus();
+ assertEquals(expectedPackageStatus, actualPackageStatus1);
+
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
+ assertEquals(packageVersions, token1.mPackageVersions);
+ assertFalse(token1.mOptimisticLockId == token2.mOptimisticLockId);
+ assertFalse(token1.equals(token2));
+ }
+
+ @Test
+ public void markChecked_checkSucceeded() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+
+ CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+ boolean writeOk = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+ assertTrue(writeOk);
+
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_checkFailed() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+
+ CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+ boolean writeOk = mPackageStatusStorage.markChecked(token, false /* succeeded */);
+ assertTrue(writeOk);
+
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_optimisticLocking_multipleToken() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+ CheckToken token1 = mPackageStatusStorage.generateCheckToken(packageVersions);
+ CheckToken token2 = mPackageStatusStorage.generateCheckToken(packageVersions);
+
+ PackageStatus packageStatusBeforeChecked = mPackageStatusStorage.getPackageStatus();
+
+ boolean writeOk1 = mPackageStatusStorage.markChecked(token1, true /* succeeded */);
+ // Generation of token2 should mean that token1 is no longer valid.
+ assertFalse(writeOk1);
+ assertEquals(packageStatusBeforeChecked, mPackageStatusStorage.getPackageStatus());
+
+ boolean writeOk2 = mPackageStatusStorage.markChecked(token2, true /* succeeded */);
+ // token2 should still be valid, and the attempt with token1 should have had no effect.
+ assertTrue(writeOk2);
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+
+ @Test
+ public void markChecked_optimisticLocking_repeatedTokenUse() {
+ PackageVersions packageVersions = VALID_PACKAGE_VERSIONS;
+ CheckToken token = mPackageStatusStorage.generateCheckToken(packageVersions);
+
+ boolean writeOk1 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+ assertTrue(writeOk1);
+
+ PackageStatus expectedPackageStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+
+ // token cannot be reused.
+ boolean writeOk2 = mPackageStatusStorage.markChecked(token, true /* succeeded */);
+ assertFalse(writeOk2);
+ assertEquals(expectedPackageStatus, mPackageStatusStorage.getPackageStatus());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java
new file mode 100644
index 0000000..c0ae81e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageStatusTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SmallTest
+public class PackageStatusTest {
+
+ @Test
+ public void equals() {
+ PackageVersions packageVersions1 =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ PackageVersions packageVersions2 =
+ new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertFalse(packageVersions1.equals(packageVersions2));
+
+ PackageStatus baseline =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
+ assertEquals(baseline, baseline);
+
+ PackageStatus deepEqual =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions1);
+ assertEquals(baseline, deepEqual);
+
+ PackageStatus differentStatus =
+ new PackageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
+ assertFalse(differentStatus.equals(baseline));
+
+ PackageStatus differentPackageVersions =
+ new PackageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
+ assertFalse(differentPackageVersions.equals(baseline));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
new file mode 100644
index 0000000..45b0af3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageTrackerTest.java
@@ -0,0 +1,1471 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.timezone.RulesUpdaterContract;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.TimeZoneRulesDataContract;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+
+/**
+ * White box interaction / unit testing of the {@link PackageTracker}.
+ */
+@SmallTest
+public class PackageTrackerTest {
+ private static final String UPDATE_APP_PACKAGE_NAME = "updateAppPackageName";
+ private static final String DATA_APP_PACKAGE_NAME = "dataAppPackageName";
+ private static final PackageVersions INITIAL_APP_PACKAGE_VERSIONS =
+ new PackageVersions(2 /* updateAppVersion */, 2 /* dataAppVersion */);
+
+ private ConfigHelper mMockConfigHelper;
+ private PackageManagerHelper mMockPackageManagerHelper;
+
+ private FakeClockHelper mFakeClock;
+ private FakeIntentHelper mFakeIntentHelper;
+ private PackageStatusStorage mPackageStatusStorage;
+ private PackageTracker mPackageTracker;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+
+ mFakeClock = new FakeClockHelper();
+
+ // Read-only interfaces so are easy to mock.
+ mMockConfigHelper = mock(ConfigHelper.class);
+ mMockPackageManagerHelper = mock(PackageManagerHelper.class);
+
+ // Using the instrumentation context means the database is created in a test app-specific
+ // directory. We can use the real thing for this test.
+ mPackageStatusStorage = new PackageStatusStorage(context);
+
+ // For other interactions with the Android framework we create a fake object.
+ mFakeIntentHelper = new FakeIntentHelper();
+
+ // Create the PackageTracker to use in tests.
+ mPackageTracker = new PackageTracker(
+ mFakeClock,
+ mMockConfigHelper,
+ mMockPackageManagerHelper,
+ mPackageStatusStorage,
+ mFakeIntentHelper);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mPackageStatusStorage != null) {
+ mPackageStatusStorage.deleteDatabaseForTests();
+ }
+ }
+
+ @Test
+ public void trackingDisabled_intentHelperNotUsed() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the IntentHelper was not initialized.
+ mFakeIntentHelper.assertNotInitialized();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ @Test
+ public void trackingDisabled_triggerUpdateIfNeededNotAllowed() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ try {
+ // This call should also not be allowed and will throw an exception if tracking is
+ // disabled.
+ mPackageTracker.triggerUpdateIfNeeded(true);
+ fail();
+ } catch (IllegalStateException expected) {}
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ @Test
+ public void trackingDisabled_unsolicitedResultsIgnored_withoutToken() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Receiving a check result when tracking is disabled should cause the storage to be
+ // reset.
+ mPackageTracker.recordCheckResult(null /* checkToken */, true /* success */);
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was reset.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingDisabled_unsolicitedResultsIgnored_withToken() {
+ // Set up device configuration.
+ configureTrackingDisabled();
+
+ // Set the storage into an arbitrary state so we can detect a reset.
+ mPackageStatusStorage.generateCheckToken(INITIAL_APP_PACKAGE_VERSIONS);
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Receiving a check result when tracking is disabled should cause the storage to be reset.
+ mPackageTracker.recordCheckResult(createArbitraryCheckToken(), true /* success */);
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was reset.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingEnabled_updateAppConfigMissing() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureUpdateAppPackageNameMissing();
+ configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+
+ try {
+ // Initialize the tracker.
+ mPackageTracker.start();
+ fail();
+ } catch (RuntimeException expected) {}
+
+ mFakeIntentHelper.assertNotInitialized();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ // TODO(nfuller): Uncomment or delete when it's clear what will happen with http://b/35995024
+ // @Test
+ // public void trackingEnabled_updateAppNotPrivileged() throws Exception {
+ // // Set up device configuration.
+ // configureTrackingEnabled();
+ // configureReliabilityConfigSettingsOk();
+ // configureUpdateAppPackageNotPrivileged(UPDATE_APP_PACKAGE_NAME);
+ // configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+ //
+ // try {
+ // // Initialize the tracker.
+ // mPackageTracker.start();
+ // fail();
+ // } catch (RuntimeException expected) {}
+ //
+ // mFakeIntentHelper.assertNotInitialized();
+ //
+ // // Check reliability triggering state.
+ // mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ // }
+
+ @Test
+ public void trackingEnabled_dataAppConfigMissing() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppPackageNameMissing();
+
+ try {
+ // Initialize the tracker.
+ mPackageTracker.start();
+ fail();
+ } catch (RuntimeException expected) {}
+
+ mFakeIntentHelper.assertNotInitialized();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+
+ // TODO(nfuller): Uncomment or delete when it's clear what will happen with http://b/35995024
+ // @Test
+ // public void trackingEnabled_dataAppNotPrivileged() throws Exception {
+ // // Set up device configuration.
+ // configureTrackingEnabled();
+ // configureReliabilityConfigSettingsOk();
+ // configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+ // configureDataAppPackageNotPrivileged(DATA_APP_PACKAGE_NAME);
+ //
+ // try {
+ // // Initialize the tracker.
+ // mPackageTracker.start();
+ // fail();
+ // } catch (RuntimeException expected) {}
+ //
+ // mFakeIntentHelper.assertNotInitialized();
+ //
+ // // Check reliability triggering state.
+ // mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ // }
+
+ @Test
+ public void trackingEnabled_packageUpdate_badUpdateAppManifestEntry() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Configure a bad manifest for the update app. Should effectively turn off tracking.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ configureUpdateAppManifestBad(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppManifestOk(DATA_APP_PACKAGE_NAME);
+ configureUpdateAppPackageVersion(
+ UPDATE_APP_PACKAGE_NAME, packageVersions.mUpdateAppVersion);
+ configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, packageVersions.mDataAppVersion);
+ // Simulate a tracked package being updated.
+ mFakeIntentHelper.simulatePackageUpdatedEvent();
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was not touched.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_badDataAppManifestEntry() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Configure a bad manifest for the data app. Should effectively turn off tracking.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ configureUpdateAppManifestOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppManifestBad(DATA_APP_PACKAGE_NAME);
+ configureUpdateAppPackageVersion(
+ UPDATE_APP_PACKAGE_NAME, packageVersions.mUpdateAppVersion);
+ configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, packageVersions.mDataAppVersion);
+ mFakeIntentHelper.simulatePackageUpdatedEvent();
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was not touched.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithToken_success() throws Exception {
+ trackingEnabled_packageUpdate_responseWithToken(true);
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithToken_failed() throws Exception {
+ trackingEnabled_packageUpdate_responseWithToken(false);
+ }
+
+ private void trackingEnabled_packageUpdate_responseWithToken(boolean success) throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate a tracked package being updated.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the token that was passed to the intent helper, and pass it back.
+ CheckToken token = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(token, success);
+
+ // Check storage and reliability triggering state.
+ if (success) {
+ checkUpdateCheckSuccessful(packageVersions);
+ } else {
+ checkUpdateCheckFailed(packageVersions);
+ }
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset_success()
+ throws Exception {
+ trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(true);
+ }
+
+ @Test
+ public void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset_failed()
+ throws Exception {
+ trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(false);
+ }
+
+ private void trackingEnabled_packageUpdate_responseWithoutTokenCausesStorageReset(
+ boolean success) throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Set up installed app versions / manifests.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Ignore the token that was given to the intent helper, just pass null.
+ mPackageTracker.recordCheckResult(null /* checkToken */, success);
+
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Assert the storage was reset.
+ checkPackageStorageStatusIsInitialOrReset();
+ }
+
+ /**
+ * Two package updates triggered for the same package versions. The second is triggered while
+ * the first is still happening.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksNoPackageChange_secondWhileFirstInProgress()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ // Now attempt to generate another check while the first is in progress and without having
+ // updated the package versions. The PackageTracker should trigger again for safety.
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token2.mPackageVersions);
+ assertEquals(token1.mPackageVersions, token2.mPackageVersions);
+ assertTrue(token1.mOptimisticLockId != token2.mOptimisticLockId);
+ }
+
+ /**
+ * Two package updates triggered for the same package versions. The second happens after
+ * the first has succeeded.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksNoPackageChange_sequential()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the token.
+ CheckToken token = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token.mPackageVersions);
+
+ // Simulate a successful check.
+ mPackageTracker.recordCheckResult(token, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+
+ // Now attempt to generate another check, but without having updated the package. The
+ // PackageTracker should be smart enough to recognize there's nothing to do here.
+ simulatePackageInstallation(packageVersions);
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ /**
+ * Two package updates triggered for the same package versions. The second is triggered after
+ * the first has failed.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_afterFailure() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ // Simulate an *unsuccessful* check.
+ mPackageTracker.recordCheckResult(token1, false /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckFailed(packageVersions);
+
+ // Now generate another check, but without having updated the package. The
+ // PackageTracker should recognize the last check failed and trigger again.
+ simulatePackageInstallation(packageVersions);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Get the second token.
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Assert some things about the tokens.
+ assertEquals(packageVersions, token2.mPackageVersions);
+ assertTrue(token1.mOptimisticLockId != token2.mOptimisticLockId);
+
+ // For completeness, now simulate this check was successful.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ /**
+ * Two package updates triggered for different package versions. The second is triggered while
+ * the first is still happening.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksWithPackageChange_firstCheckInProcess()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions1 =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions1);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions1);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions1, token1.mPackageVersions);
+
+ // Simulate a tracked package being updated a second time (before the response for the
+ // first has been received).
+ PackageVersions packageVersions2 =
+ new PackageVersions(3 /* updateAppPackageVersion */, 4 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions2);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions2);
+
+ // Get the second token.
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions2, token2.mPackageVersions);
+
+ // token1 should be invalid because the token2 was generated.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Reliability triggering should still be enabled.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Check the expected storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions2);
+
+ // token2 should still be accepted.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions2);
+ }
+
+ /**
+ * Two package updates triggered for different package versions. The second is triggered after
+ * the first has completed successfully.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_twoChecksWithPackageChange_sequential()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions1 =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions1);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions1);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions1, token1.mPackageVersions);
+
+ // token1 should be accepted.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions1);
+
+ // Simulate a tracked package being updated a second time.
+ PackageVersions packageVersions2 =
+ new PackageVersions(3 /* updateAppPackageVersion */, 4 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions2);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions2);
+
+ // Get the second token.
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions2, token2.mPackageVersions);
+
+ // token2 should still be accepted.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions2);
+ }
+
+ /**
+ * Replaying the same token twice.
+ */
+ @Test
+ public void trackingEnabled_packageUpdate_sameTokenReplayFails() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate package installation.
+ PackageVersions packageVersions1 =
+ new PackageVersions(2 /* updateAppPackageVersion */, 3 /* dataAppPackageVersion */);
+ simulatePackageInstallation(packageVersions1);
+
+ // Confirm an update was triggered.
+ checkUpdateCheckTriggered(packageVersions1);
+
+ // Get the first token.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions1, token1.mPackageVersions);
+
+ // token1 should be accepted.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions1);
+
+ // Apply token1 again.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check the expected storage state. No real way to tell if it has been updated, but
+ // we can check the final state is still what it should be.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions1);
+
+ // Under the covers we expect it to fail to update because the storage should recognize that
+ // the token is no longer valid.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(1, mPackageTracker.getCheckFailureCountForTests());
+ }
+
+ @Test
+ public void trackingEnabled_reliabilityTrigger_firstTime_initialStorage() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ PackageVersions packageVersions = configureValidApplications();
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatusIsInitialOrReset();
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(packageVersions);
+
+ // Confirm the token was correct.
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+ assertEquals(packageVersions, token1.mPackageVersions);
+
+ // token1 should be accepted.
+ mPackageTracker.recordCheckResult(token1, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ @Test
+ public void trackingEnabled_reliabilityTrigger_afterRebootNoTriggerNeeded() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+ PackageVersions packageVersions = configureValidApplications();
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did not attempt to trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(packageVersions);
+ }
+
+ /**
+ * Simulates the device starting where the storage records do not match the installed app
+ * versions. The reliability trigger should cause the package tracker to perform a check.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_afterRebootTriggerNeededBecausePreviousFailed()
+ throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+ configureReliabilityConfigSettingsOk();
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Simulate the update check completing successfully.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, true /* success */);
+
+ // Check storage and reliability triggering state.
+ checkUpdateCheckSuccessful(currentPackageVersions);
+ }
+
+ /**
+ * Simulates persistent failures of the reliability check. It should stop after the configured
+ * number of checks.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_repeatedFailures() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 3;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ for (int i = 0; i < retriesAllowed + 1; i++) {
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Check the PackageTracker failure count before calling recordCheckResult.
+ assertEquals(i, mPackageTracker.getCheckFailureCountForTests());
+
+ // Simulate a check failure.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, false /* success */);
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(i + 1, mPackageTracker.getCheckFailureCountForTests());
+
+ // Confirm nothing has changed.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE,
+ currentPackageVersions);
+
+ // Check reliability triggering is in the correct state.
+ if (i <= retriesAllowed) {
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ } else {
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+ }
+ }
+ }
+
+ @Test
+ public void trackingEnabled_reliabilityTrigger_failureCountIsReset() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 3;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Fail (retries - 1) times.
+ for (int i = 0; i < retriesAllowed - 1; i++) {
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Check the PackageTracker failure count before calling recordCheckResult.
+ assertEquals(i, mPackageTracker.getCheckFailureCountForTests());
+
+ // Simulate a check failure.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, false /* success */);
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(i + 1, mPackageTracker.getCheckFailureCountForTests());
+
+ // Confirm nothing has changed.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE,
+ currentPackageVersions);
+
+ // Check reliability triggering is still enabled.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ }
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+
+ // Check the PackageTracker failure count before calling recordCheckResult.
+ assertEquals(retriesAllowed - 1, mPackageTracker.getCheckFailureCountForTests());
+
+ // On the last possible try, succeed.
+ CheckToken checkToken = mFakeIntentHelper.captureAndResetLastToken();
+ mPackageTracker.recordCheckResult(checkToken, true /* success */);
+
+ checkUpdateCheckSuccessful(currentPackageVersions);
+ }
+
+ /**
+ * Simulates reliability triggers happening too close together. Package tracker should ignore
+ * the ones it doesn't need.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_tooSoon() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 5;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions oldPackageVersions = new PackageVersions(1, 1);
+ PackageVersions currentPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, oldPackageVersions);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(currentPackageVersions);
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Increment the clock, but not enough.
+ mFakeClock.incrementClock(checkDelayMillis - 1);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did not trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, currentPackageVersions);
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Increment the clock slightly more. Should now consider the response overdue.
+ mFakeClock.incrementClock(2);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Triggering should have happened.
+ checkUpdateCheckTriggered(currentPackageVersions);
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Check a new token was generated.
+ assertFalse(token1.equals(token2));
+ }
+
+ /**
+ * Tests what happens when a package update doesn't complete and a reliability trigger cleans
+ * up for it.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTrigger_afterPackageUpdateDidNotComplete()
+ throws Exception {
+
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 5;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions currentPackageVersions = new PackageVersions(1, 1);
+ PackageVersions newPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Simulate a reliability trigger.
+ simulatePackageInstallation(newPackageVersions);
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Increment the clock, but not enough.
+ mFakeClock.incrementClock(checkDelayMillis + 1);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker triggered an update.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Check a new token was generated.
+ assertFalse(token1.equals(token2));
+
+ // Simulate the reliability check completing.
+ mPackageTracker.recordCheckResult(token2, true /* success */);
+
+ // Check everything is now as it should be.
+ checkUpdateCheckSuccessful(newPackageVersions);
+ }
+
+ /**
+ * Simulates a reliability trigger happening too soon after a package update trigger occurred.
+ */
+ @Test
+ public void trackingEnabled_reliabilityTriggerAfterUpdate_tooSoon() throws Exception {
+ // Set up device configuration.
+ configureTrackingEnabled();
+
+ int retriesAllowed = 5;
+ int checkDelayMillis = 5 * 60 * 1000;
+ configureReliabilityConfigSettings(retriesAllowed, checkDelayMillis);
+
+ PackageVersions currentPackageVersions = new PackageVersions(1, 1);
+ PackageVersions newPackageVersions = new PackageVersions(2, 2);
+
+ // Simulate there being a newer version installed than the one recorded in storage.
+ configureValidApplications(currentPackageVersions);
+
+ // Force the storage into a state we want.
+ mPackageStatusStorage.forceCheckStateForTests(
+ PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+ // Initialize the package tracker.
+ mPackageTracker.start();
+
+ // Check the intent helper is properly configured.
+ checkIntentHelperInitializedAndReliabilityTrackingEnabled();
+
+ // Check the initial storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, currentPackageVersions);
+
+ // Simulate a package update trigger.
+ simulatePackageInstallation(newPackageVersions);
+
+ // Assert the PackageTracker did trigger an update.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token1 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Increment the clock, but not enough.
+ mFakeClock.incrementClock(checkDelayMillis - 1);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Assert the PackageTracker did not trigger an update.
+ mFakeIntentHelper.assertUpdateNotTriggered();
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, newPackageVersions);
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Increment the clock slightly more. Should now consider the response overdue.
+ mFakeClock.incrementClock(2);
+
+ // Simulate a reliability trigger.
+ mFakeIntentHelper.simulateReliabilityTrigger();
+
+ // Triggering should have happened.
+ checkUpdateCheckTriggered(newPackageVersions);
+ CheckToken token2 = mFakeIntentHelper.captureAndResetLastToken();
+
+ // Check a new token was generated.
+ assertFalse(token1.equals(token2));
+ }
+
+ private void simulatePackageInstallation(PackageVersions packageVersions) throws Exception {
+ configureApplicationsValidManifests(packageVersions);
+
+ // Simulate a tracked package being updated.
+ mFakeIntentHelper.simulatePackageUpdatedEvent();
+ }
+
+ /**
+ * Checks an update check was triggered, reliability triggering is therefore enabled and the
+ * storage state reflects that there is a check in progress.
+ */
+ private void checkUpdateCheckTriggered(PackageVersions packageVersions) {
+ // Assert the PackageTracker attempted to trigger an update.
+ mFakeIntentHelper.assertUpdateTriggered();
+
+ // If an update check was triggered reliability triggering should always be enabled to
+ // ensure that it can be completed if it fails.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Check the expected storage state.
+ checkPackageStorageStatus(PackageStatus.CHECK_STARTED, packageVersions);
+ }
+
+ private void checkUpdateCheckFailed(PackageVersions packageVersions) {
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+
+ // Assert the storage was updated.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_FAILURE, packageVersions);
+ }
+
+ private void checkUpdateCheckSuccessful(PackageVersions packageVersions) {
+ // Check reliability triggering state.
+ mFakeIntentHelper.assertReliabilityTriggeringDisabled();
+
+ // Assert the storage was updated.
+ checkPackageStorageStatus(PackageStatus.CHECK_COMPLETED_SUCCESS, packageVersions);
+
+ // Peek inside the package tracker to make sure it is tracking failure counts properly.
+ assertEquals(0, mPackageTracker.getCheckFailureCountForTests());
+ }
+
+ private PackageVersions configureValidApplications() throws Exception {
+ configureValidApplications(INITIAL_APP_PACKAGE_VERSIONS);
+ return INITIAL_APP_PACKAGE_VERSIONS;
+ }
+
+ private void configureValidApplications(PackageVersions versions) throws Exception {
+ configureUpdateAppPackageOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppPackageOk(DATA_APP_PACKAGE_NAME);
+ configureApplicationsValidManifests(versions);
+ }
+
+ private void configureApplicationsValidManifests(PackageVersions versions) throws Exception {
+ configureUpdateAppManifestOk(UPDATE_APP_PACKAGE_NAME);
+ configureDataAppManifestOk(DATA_APP_PACKAGE_NAME);
+ configureUpdateAppPackageVersion(UPDATE_APP_PACKAGE_NAME, versions.mUpdateAppVersion);
+ configureDataAppPackageVersion(DATA_APP_PACKAGE_NAME, versions.mDataAppVersion);
+ }
+
+ private void configureUpdateAppPackageVersion(String updateAppPackageName,
+ int updataAppPackageVersion) throws Exception {
+ when(mMockPackageManagerHelper.getInstalledPackageVersion(updateAppPackageName))
+ .thenReturn(updataAppPackageVersion);
+ }
+
+ private void configureDataAppPackageVersion(String dataAppPackageName,
+ int dataAppPackageVersion) throws Exception {
+ when(mMockPackageManagerHelper.getInstalledPackageVersion(dataAppPackageName))
+ .thenReturn(dataAppPackageVersion);
+ }
+
+ private void configureUpdateAppManifestOk(String updateAppPackageName) throws Exception {
+ Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(updateAppPackageName);
+ when(mMockPackageManagerHelper.receiverRegistered(
+ filterEquals(expectedIntent),
+ eq(RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)))
+ .thenReturn(true);
+ when(mMockPackageManagerHelper.usesPermission(
+ updateAppPackageName, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION))
+ .thenReturn(true);
+ }
+
+ private void configureUpdateAppManifestBad(String updateAppPackageName) throws Exception {
+ Intent expectedIntent = RulesUpdaterContract.createUpdaterIntent(updateAppPackageName);
+ when(mMockPackageManagerHelper.receiverRegistered(
+ filterEquals(expectedIntent),
+ eq(RulesUpdaterContract.TRIGGER_TIME_ZONE_RULES_CHECK_PERMISSION)))
+ .thenReturn(false);
+ // Has permission, but that shouldn't matter if the check above is false.
+ when(mMockPackageManagerHelper.usesPermission(
+ updateAppPackageName, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION))
+ .thenReturn(true);
+ }
+
+ private void configureDataAppManifestOk(String dataAppPackageName) throws Exception {
+ when(mMockPackageManagerHelper.contentProviderRegistered(
+ TimeZoneRulesDataContract.AUTHORITY, dataAppPackageName))
+ .thenReturn(true);
+ }
+
+ private void configureDataAppManifestBad(String dataAppPackageName) throws Exception {
+ // Simulate the data app not exposing the content provider we require.
+ when(mMockPackageManagerHelper.contentProviderRegistered(
+ TimeZoneRulesDataContract.AUTHORITY, dataAppPackageName))
+ .thenReturn(false);
+ }
+
+ private void configureTrackingEnabled() {
+ when(mMockConfigHelper.isTrackingEnabled()).thenReturn(true);
+ }
+
+ private void configureTrackingDisabled() {
+ when(mMockConfigHelper.isTrackingEnabled()).thenReturn(false);
+ }
+
+ private void configureReliabilityConfigSettings(int retriesAllowed, int checkDelayMillis) {
+ when(mMockConfigHelper.getFailedCheckRetryCount()).thenReturn(retriesAllowed);
+ when(mMockConfigHelper.getCheckTimeAllowedMillis()).thenReturn(checkDelayMillis);
+ }
+
+ private void configureReliabilityConfigSettingsOk() {
+ configureReliabilityConfigSettings(5, 5 * 60 * 1000);
+ }
+
+ private void configureUpdateAppPackageOk(String updateAppPackageName) throws Exception {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(updateAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(updateAppPackageName)).thenReturn(true);
+ }
+
+ private void configureUpdateAppPackageNotPrivileged(String updateAppPackageName)
+ throws Exception {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(updateAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(updateAppPackageName)).thenReturn(false);
+ }
+
+ private void configureUpdateAppPackageNameMissing() {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(null);
+ }
+
+ private void configureDataAppPackageOk(String dataAppPackageName) throws Exception {
+ when(mMockConfigHelper.getDataAppPackageName()).thenReturn(dataAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(dataAppPackageName)).thenReturn(true);
+ }
+
+ private void configureDataAppPackageNotPrivileged(String dataAppPackageName)
+ throws Exception {
+ when(mMockConfigHelper.getUpdateAppPackageName()).thenReturn(dataAppPackageName);
+ when(mMockPackageManagerHelper.isPrivilegedApp(dataAppPackageName)).thenReturn(false);
+ }
+
+ private void configureDataAppPackageNameMissing() {
+ when(mMockConfigHelper.getDataAppPackageName()).thenThrow(new RuntimeException());
+ }
+
+ private void checkIntentHelperInitializedAndReliabilityTrackingEnabled() {
+ // Verify that calling start initialized the IntentHelper as well.
+ mFakeIntentHelper.assertInitialized(UPDATE_APP_PACKAGE_NAME, DATA_APP_PACKAGE_NAME);
+
+ // Assert that reliability tracking is always enabled after initialization.
+ mFakeIntentHelper.assertReliabilityTriggeringEnabled();
+ }
+
+ private void checkPackageStorageStatus(
+ int expectedCheckStatus, PackageVersions expectedPackageVersions) {
+ PackageStatus packageStatus = mPackageStatusStorage.getPackageStatus();
+ assertEquals(expectedCheckStatus, packageStatus.mCheckStatus);
+ assertEquals(expectedPackageVersions, packageStatus.mVersions);
+ }
+
+ private void checkPackageStorageStatusIsInitialOrReset() {
+ assertNull(mPackageStatusStorage.getPackageStatus());
+ }
+
+ private static CheckToken createArbitraryCheckToken() {
+ return new CheckToken(1, INITIAL_APP_PACKAGE_VERSIONS);
+ }
+
+ /**
+ * A fake IntentHelper implementation for use in tests.
+ */
+ private static class FakeIntentHelper implements IntentHelper {
+
+ private Listener mListener;
+ private String mUpdateAppPackageName;
+ private String mDataAppPackageName;
+
+ private CheckToken mLastToken;
+
+ private boolean mReliabilityTriggeringEnabled;
+
+ @Override
+ public void initialize(String updateAppPackageName, String dataAppPackageName,
+ Listener listener) {
+ assertNotNull(updateAppPackageName);
+ assertNotNull(dataAppPackageName);
+ assertNotNull(listener);
+ mListener = listener;
+ mUpdateAppPackageName = updateAppPackageName;
+ mDataAppPackageName = dataAppPackageName;
+ }
+
+ public void assertInitialized(
+ String expectedUpdateAppPackageName, String expectedDataAppPackageName) {
+ assertNotNull(mListener);
+ assertEquals(expectedUpdateAppPackageName, mUpdateAppPackageName);
+ assertEquals(expectedDataAppPackageName, mDataAppPackageName);
+ }
+
+ public void assertNotInitialized() {
+ assertNull(mListener);
+ }
+
+ @Override
+ public void sendTriggerUpdateCheck(CheckToken checkToken) {
+ if (mLastToken != null) {
+ fail("lastToken already set");
+ }
+ mLastToken = checkToken;
+ }
+
+ @Override
+ public void enableReliabilityTriggering() {
+ mReliabilityTriggeringEnabled = true;
+ }
+
+ @Override
+ public void disableReliabilityTriggering() {
+ mReliabilityTriggeringEnabled = false;
+ }
+
+ public void assertReliabilityTriggeringEnabled() {
+ assertTrue(mReliabilityTriggeringEnabled);
+ }
+
+ public void assertReliabilityTriggeringDisabled() {
+ assertFalse(mReliabilityTriggeringEnabled);
+ }
+
+ public void assertUpdateTriggered() {
+ assertNotNull(mLastToken);
+ }
+
+ public void assertUpdateNotTriggered() {
+ assertNull(mLastToken);
+ }
+
+ public CheckToken captureAndResetLastToken() {
+ CheckToken toReturn = mLastToken;
+ assertNotNull("No update triggered", toReturn);
+ mLastToken = null;
+ return toReturn;
+ }
+
+ public void simulatePackageUpdatedEvent() {
+ mListener.triggerUpdateIfNeeded(true);
+ }
+
+ public void simulateReliabilityTrigger() {
+ mListener.triggerUpdateIfNeeded(false);
+ }
+ }
+
+ private static class FakeClockHelper implements ClockHelper {
+
+ private long currentTime = 1000;
+
+ @Override
+ public long currentTimestamp() {
+ return currentTime;
+ }
+
+ public void incrementClock(long millis) {
+ currentTime += millis;
+ }
+ }
+
+ /**
+ * Registers a mockito parameter matcher that uses {@link Intent#filterEquals(Intent)}. to
+ * check the parameter against the intent supplied.
+ */
+ private static Intent filterEquals(final Intent expected) {
+ final Matcher<Intent> m = new BaseMatcher<Intent>() {
+ @Override
+ public boolean matches(Object actual) {
+ return actual != null && expected.filterEquals((Intent) actual);
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(expected.toString());
+ }
+ };
+ return argThat(m);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java b/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java
new file mode 100644
index 0000000..a470f8f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/PackageVersionsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Test;
+
+import android.support.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+@SmallTest
+public class PackageVersionsTest {
+
+ @Test
+ public void equals() {
+ PackageVersions baseline =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertEquals(baseline, baseline);
+
+ PackageVersions deepEqual =
+ new PackageVersions(1 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertEquals(baseline, deepEqual);
+
+ PackageVersions differentUpdateAppVersion =
+ new PackageVersions(2 /* updateAppVersion */, 1 /* dataAppVersion */);
+ assertFalse(baseline.equals(differentUpdateAppVersion));
+
+ PackageVersions differentDataAppVersion =
+ new PackageVersions(1 /* updateAppVersion */, 2 /* dataAppVersion */);
+ assertFalse(baseline.equals(differentDataAppVersion));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
new file mode 100644
index 0000000..a7f4c99
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezone/RulesManagerServiceTest.java
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.timezone.Callback;
+import android.app.timezone.DistroRulesVersion;
+import android.app.timezone.ICallback;
+import android.app.timezone.RulesManager;
+import android.app.timezone.RulesState;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
+import libcore.tzdata.shared2.DistroVersion;
+import libcore.tzdata.shared2.StagedDistroOperation;
+import libcore.tzdata.update2.TimeZoneDistroInstaller;
+
+import static com.android.server.timezone.RulesManagerService.REQUIRED_UPDATER_PERMISSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+/**
+ * White box interaction / unit testing of the {@link RulesManagerService}.
+ */
+public class RulesManagerServiceTest {
+
+ private RulesManagerService mRulesManagerService;
+
+ private FakeExecutor mFakeExecutor;
+ private PermissionHelper mMockPermissionHelper;
+ private FileDescriptorHelper mMockFileDescriptorHelper;
+ private PackageTracker mMockPackageTracker;
+ private TimeZoneDistroInstaller mMockTimeZoneDistroInstaller;
+
+ @Before
+ public void setUp() {
+ mFakeExecutor = new FakeExecutor();
+
+ mMockFileDescriptorHelper = mock(FileDescriptorHelper.class);
+ mMockPackageTracker = mock(PackageTracker.class);
+ mMockPermissionHelper = mock(PermissionHelper.class);
+ mMockTimeZoneDistroInstaller = mock(TimeZoneDistroInstaller.class);
+
+ mRulesManagerService = new RulesManagerService(
+ mMockPermissionHelper,
+ mFakeExecutor,
+ mMockFileDescriptorHelper,
+ mMockPackageTracker,
+ mMockTimeZoneDistroInstaller);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void getRulesState_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.getRulesState();
+ }
+
+ @Test(expected = SecurityException.class)
+ public void requestInstall_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.requestInstall(null, null, null);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void requestUninstall_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.requestUninstall(null, null);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void requestNothing_noCallerPermission() throws Exception {
+ configureCallerDoesNotHavePermission();
+ mRulesManagerService.requestNothing(null, true);
+ }
+
+ @Test
+ public void getRulesState_systemRulesError() throws Exception {
+ configureDeviceCannotReadSystemRulesVersion();
+
+ assertNull(mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_stagedInstall() throws Exception {
+ configureCallerHasPermission();
+
+ configureDeviceSystemRulesVersion("2016a");
+
+ DistroVersion stagedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016c",
+ 3);
+ configureStagedInstall(stagedDistroVersion);
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion stagedDistroRulesVersion = new DistroRulesVersion(
+ stagedDistroVersion.rulesVersion, stagedDistroVersion.revision);
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_INSTALL, stagedDistroRulesVersion,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_nothingStaged() throws Exception {
+ configureCallerHasPermission();
+
+ configureDeviceSystemRulesVersion("2016a");
+
+ configureNoStagedOperation();
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_uninstallStaged() throws Exception {
+ configureCallerHasPermission();
+
+ configureDeviceSystemRulesVersion("2016a");
+
+ configureStagedUninstall();
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_installedRulesError() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+
+ configureStagedUninstall();
+ configureDeviceCannotReadInstalledDistroVersion();
+
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNINSTALL, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_stagedRulesError() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+
+ configureDeviceCannotReadStagedDistroOperation();
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ "2016b",
+ 4);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ DistroRulesVersion installedDistroRulesVersion = new DistroRulesVersion(
+ installedDistroVersion.rulesVersion, installedDistroVersion.revision);
+ RulesState expectedRuleState = new RulesState(
+ "2016a", RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_INSTALLED, installedDistroRulesVersion);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_noInstalledRules() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+ configureNoStagedOperation();
+ configureInstalledDistroVersion(null);
+
+ RulesState expectedRuleState = new RulesState(
+ systemRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ false /* operationInProgress */,
+ RulesState.STAGED_OPERATION_NONE, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_NONE, null /* installedDistroRulesVersion */);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void getRulesState_operationInProgress() throws Exception {
+ configureCallerHasPermission();
+
+ String systemRulesVersion = "2016a";
+ String installedRulesVersion = "2016b";
+ int revision = 3;
+
+ configureDeviceSystemRulesVersion(systemRulesVersion);
+
+ DistroVersion installedDistroVersion = new DistroVersion(
+ DistroVersion.CURRENT_FORMAT_MAJOR_VERSION,
+ DistroVersion.CURRENT_FORMAT_MINOR_VERSION - 1,
+ installedRulesVersion,
+ revision);
+ configureInstalledDistroVersion(installedDistroVersion);
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ // Start an async operation so there is one in progress. The mFakeExecutor won't actually
+ // execute it.
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+
+ RulesState expectedRuleState = new RulesState(
+ systemRulesVersion, RulesManagerService.DISTRO_FORMAT_VERSION_SUPPORTED,
+ true /* operationInProgress */,
+ RulesState.STAGED_OPERATION_UNKNOWN, null /* stagedDistroRulesVersion */,
+ RulesState.DISTRO_STATUS_UNKNOWN, null /* installedDistroRulesVersion */);
+ assertEquals(expectedRuleState, mRulesManagerService.getRulesState());
+ }
+
+ @Test
+ public void requestInstall_operationInProgress() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ // First request should succeed.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Something async should be enqueued. Clear it but do not execute it so we can detect the
+ // second request does nothing.
+ mFakeExecutor.getAndResetLastCommand();
+
+ // Second request should fail.
+ assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_badToken() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ byte[] badTokenBytes = new byte[2];
+ ICallback callback = new StubbedCallback();
+
+ try {
+ mRulesManagerService.requestInstall(parcelFileDescriptor, badTokenBytes, callback);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_nullParcelFileDescriptor() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = null;
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ try {
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+ fail();
+ } catch (NullPointerException expected) {}
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_nullCallback() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = null;
+
+ try {
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback);
+ fail();
+ } catch (NullPointerException expected) {}
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestInstall_asyncSuccess() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ byte[] expectedContent = createArbitraryBytes(1000);
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ callback.assertNoResultReceived();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+
+ // Set up the installer.
+ configureStageInstallExpectation(expectedContent, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageInstallCalled(expectedContent);
+ verifyPackageTrackerCalled(token, true /* success */);
+
+ // Check the callback was called.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestInstall_nullTokenBytes() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ byte[] expectedContent = createArbitraryBytes(1000);
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(
+ parcelFileDescriptor, null /* tokenBytes */, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageInstallExpectation(expectedContent, TimeZoneDistroInstaller.INSTALL_SUCCESS);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageInstallCalled(expectedContent);
+ verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestInstall_asyncInstallFail() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] expectedContent = createArbitraryBytes(1000);
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadSuccess(parcelFileDescriptor, expectedContent);
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageInstallExpectation(
+ expectedContent, TimeZoneDistroInstaller.INSTALL_FAIL_VALIDATION_ERROR);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageInstallCalled(expectedContent);
+
+ // Validation failure is treated like a successful check: repeating it won't improve things.
+ boolean expectedSuccess = true;
+ verifyPackageTrackerCalled(token, expectedSuccess);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.ERROR_INSTALL_VALIDATION_ERROR);
+ }
+
+ @Test
+ public void requestInstall_asyncParcelFileDescriptorReadFail() throws Exception {
+ configureCallerHasPermission();
+
+ ParcelFileDescriptor parcelFileDescriptor = createFakeParcelFileDescriptor();
+ configureParcelFileDescriptorReadFailure(parcelFileDescriptor);
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the install.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestInstall(parcelFileDescriptor, tokenBytes, callback));
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify nothing else happened.
+ verifyNoInstallerCallsMade();
+
+ // A failure to read the ParcelFileDescriptor is treated as a failure. It might be the
+ // result of a file system error. This is a fairly arbitrary choice.
+ verifyPackageTrackerCalled(token, false /* success */);
+
+ verifyNoPackageTrackerCallsMade();
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
+ }
+
+ @Test
+ public void requestUninstall_operationInProgress() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = new StubbedCallback();
+
+ // First request should succeed.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Something async should be enqueued. Clear it but do not execute it so we can detect the
+ // second request does nothing.
+ mFakeExecutor.getAndResetLastCommand();
+
+ // Second request should fail.
+ assertEquals(RulesManager.ERROR_OPERATION_IN_PROGRESS,
+ mRulesManagerService.requestUninstall(tokenBytes, callback));
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestUninstall_badToken() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] badTokenBytes = new byte[2];
+ ICallback callback = new StubbedCallback();
+
+ try {
+ mRulesManagerService.requestUninstall(badTokenBytes, callback);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestUninstall_nullCallback() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] tokenBytes = createArbitraryTokenBytes();
+ ICallback callback = null;
+
+ try {
+ mRulesManagerService.requestUninstall(tokenBytes, callback);
+ fail();
+ } catch (NullPointerException expected) {}
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestUninstall_asyncSuccess() 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(true /* 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_nullTokenBytes() throws Exception {
+ configureCallerHasPermission();
+
+ TestCallback callback = new TestCallback();
+
+ // Request the uninstall.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(null /* tokenBytes */, callback));
+
+ // Assert nothing has happened yet.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageUninstallExpectation(true /* success */);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageUninstallCalled();
+ verifyPackageTrackerCalled(null /* expectedToken */, true /* success */);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.SUCCESS);
+ }
+
+ @Test
+ public void requestUninstall_asyncUninstallFail() 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.
+ verifyNoInstallerCallsMade();
+ callback.assertNoResultReceived();
+
+ // Set up the installer.
+ configureStageUninstallExpectation(false /* success */);
+
+ // Simulate the async execution.
+ mFakeExecutor.simulateAsyncExecutionOfLastCommand();
+
+ // Verify the expected calls were made to other components.
+ verifyStageUninstallCalled();
+ verifyPackageTrackerCalled(token, false /* success */);
+
+ // Check the callback was received.
+ callback.assertResultReceived(Callback.ERROR_UNKNOWN_FAILURE);
+ }
+
+ @Test
+ public void requestNothing_operationInProgressOk() throws Exception {
+ configureCallerHasPermission();
+
+ // Set up a parallel operation.
+ assertEquals(RulesManager.SUCCESS,
+ mRulesManagerService.requestUninstall(null, new StubbedCallback()));
+ // Something async should be enqueued. Clear it but do not execute it to simulate it still
+ // being in progress.
+ mFakeExecutor.getAndResetLastCommand();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ // Make the call.
+ mRulesManagerService.requestNothing(tokenBytes, true /* success */);
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+
+ // Verify the expected calls were made to other components.
+ verifyPackageTrackerCalled(token, true /* success */);
+ verifyNoInstallerCallsMade();
+ }
+
+ @Test
+ public void requestNothing_badToken() throws Exception {
+ configureCallerHasPermission();
+
+ byte[] badTokenBytes = new byte[2];
+
+ try {
+ mRulesManagerService.requestNothing(badTokenBytes, true /* success */);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ // Assert nothing async was enqueued.
+ mFakeExecutor.assertNothingQueued();
+
+ // Assert no other calls were made.
+ verifyNoInstallerCallsMade();
+ verifyNoPackageTrackerCallsMade();
+ }
+
+ @Test
+ public void requestNothing() throws Exception {
+ configureCallerHasPermission();
+
+ CheckToken token = createArbitraryToken();
+ byte[] tokenBytes = token.toByteArray();
+
+ // Make the call.
+ mRulesManagerService.requestNothing(tokenBytes, false /* success */);
+
+ // Assert everything required was done.
+ verifyNoInstallerCallsMade();
+ verifyPackageTrackerCalled(token, false /* success */);
+ }
+
+ @Test
+ public void requestNothing_nullTokenBytes() throws Exception {
+ configureCallerHasPermission();
+
+ // Make the call.
+ mRulesManagerService.requestNothing(null /* tokenBytes */, true /* success */);
+
+ // Assert everything required was done.
+ verifyNoInstallerCallsMade();
+ verifyPackageTrackerCalled(null /* token */, true /* success */);
+ }
+
+ private void verifyNoPackageTrackerCallsMade() {
+ verifyNoMoreInteractions(mMockPackageTracker);
+ reset(mMockPackageTracker);
+ }
+
+ private void verifyPackageTrackerCalled(
+ CheckToken expectedCheckToken, boolean expectedSuccess) {
+ verify(mMockPackageTracker).recordCheckResult(expectedCheckToken, expectedSuccess);
+ reset(mMockPackageTracker);
+ }
+
+ private void configureCallerHasPermission() throws Exception {
+ doNothing()
+ .when(mMockPermissionHelper)
+ .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ }
+
+ private void configureCallerDoesNotHavePermission() {
+ doThrow(new SecurityException("Simulated permission failure"))
+ .when(mMockPermissionHelper)
+ .enforceCallerHasPermission(REQUIRED_UPDATER_PERMISSION);
+ }
+
+ private void configureParcelFileDescriptorReadSuccess(ParcelFileDescriptor parcelFileDescriptor,
+ byte[] content) throws Exception {
+ when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor)).thenReturn(content);
+ }
+
+ private void configureParcelFileDescriptorReadFailure(ParcelFileDescriptor parcelFileDescriptor)
+ throws Exception {
+ when(mMockFileDescriptorHelper.readFully(parcelFileDescriptor))
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private void configureStageInstallExpectation(byte[] expectedContent, int resultCode)
+ throws Exception {
+ when(mMockTimeZoneDistroInstaller.stageInstallWithErrorCode(eq(expectedContent)))
+ .thenReturn(resultCode);
+ }
+
+ private void configureStageUninstallExpectation(boolean success) throws Exception {
+ doReturn(success).when(mMockTimeZoneDistroInstaller).stageUninstall();
+ }
+
+ private void verifyStageInstallCalled(byte[] expectedContent) throws Exception {
+ verify(mMockTimeZoneDistroInstaller).stageInstallWithErrorCode(eq(expectedContent));
+ verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+ reset(mMockTimeZoneDistroInstaller);
+ }
+
+ private void verifyStageUninstallCalled() throws Exception {
+ verify(mMockTimeZoneDistroInstaller).stageUninstall();
+ verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+ reset(mMockTimeZoneDistroInstaller);
+ }
+
+ private void verifyNoInstallerCallsMade() {
+ verifyNoMoreInteractions(mMockTimeZoneDistroInstaller);
+ reset(mMockTimeZoneDistroInstaller);
+ }
+
+ private static byte[] createArbitraryBytes(int length) {
+ byte[] bytes = new byte[length];
+ for (int i = 0; i < length; i++) {
+ bytes[i] = (byte) i;
+ }
+ return bytes;
+ }
+
+ private byte[] createArbitraryTokenBytes() {
+ return createArbitraryToken().toByteArray();
+ }
+
+ private CheckToken createArbitraryToken() {
+ return new CheckToken(1, new PackageVersions(1, 1));
+ }
+
+ private ParcelFileDescriptor createFakeParcelFileDescriptor() {
+ return new ParcelFileDescriptor((ParcelFileDescriptor) null);
+ }
+
+ private void configureDeviceSystemRulesVersion(String systemRulesVersion) throws Exception {
+ when(mMockTimeZoneDistroInstaller.getSystemRulesVersion()).thenReturn(systemRulesVersion);
+ }
+
+ private void configureInstalledDistroVersion(@Nullable DistroVersion installedDistroVersion)
+ throws Exception {
+ when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion())
+ .thenReturn(installedDistroVersion);
+ }
+
+ private void configureStagedInstall(DistroVersion stagedDistroVersion) throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+ .thenReturn(StagedDistroOperation.install(stagedDistroVersion));
+ }
+
+ private void configureStagedUninstall() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+ .thenReturn(StagedDistroOperation.uninstall());
+ }
+
+ private void configureNoStagedOperation() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation()).thenReturn(null);
+ }
+
+ private void configureDeviceCannotReadStagedDistroOperation() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getStagedDistroOperation())
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private void configureDeviceCannotReadSystemRulesVersion() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getSystemRulesVersion())
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private void configureDeviceCannotReadInstalledDistroVersion() throws Exception {
+ when(mMockTimeZoneDistroInstaller.getInstalledDistroVersion())
+ .thenThrow(new IOException("Simulated failure"));
+ }
+
+ private static class FakeExecutor implements Executor {
+
+ private Runnable mLastCommand;
+
+ @Override
+ public void execute(Runnable command) {
+ assertNull(mLastCommand);
+ assertNotNull(command);
+ mLastCommand = command;
+ }
+
+ public Runnable getAndResetLastCommand() {
+ assertNotNull(mLastCommand);
+ Runnable toReturn = mLastCommand;
+ mLastCommand = null;
+ return toReturn;
+ }
+
+ public void simulateAsyncExecutionOfLastCommand() {
+ Runnable toRun = getAndResetLastCommand();
+ toRun.run();
+ }
+
+ public void assertNothingQueued() {
+ assertNull(mLastCommand);
+ }
+ }
+
+ private static class TestCallback extends ICallback.Stub {
+
+ private boolean mOnFinishedCalled;
+ private int mLastError;
+
+ @Override
+ public void onFinished(int error) {
+ assertFalse(mOnFinishedCalled);
+ mOnFinishedCalled = true;
+ mLastError = error;
+ }
+
+ public void assertResultReceived(int expectedResult) {
+ assertTrue(mOnFinishedCalled);
+ assertEquals(expectedResult, mLastError);
+ }
+
+ public void assertNoResultReceived() {
+ assertFalse(mOnFinishedCalled);
+ }
+ }
+
+ private static class StubbedCallback extends ICallback.Stub {
+ @Override
+ public void onFinished(int error) {
+ fail("Unexpected call");
+ }
+ }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index c42a835..5530cb7 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -43,6 +43,15 @@
/**
* {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
+ * sort order for {@link PhoneAccount}s from the same
+ * {@link android.telecom.ConnectionService}.
+ * @hide
+ */
+ public static final String EXTRA_SORT_ORDER =
+ "android.telecom.extra.SORT_ORDER";
+
+ /**
+ * {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which determines the
* maximum permitted length of a call subject specified via the
* {@link TelecomManager#EXTRA_CALL_SUBJECT} extra on an
* {@link android.content.Intent#ACTION_CALL} intent. Ultimately a {@link ConnectionService} is
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5fa5070f..e1239d0 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -604,6 +604,38 @@
"vvm_cellular_data_required_bool";
/**
+ * The default OMTP visual voicemail client prefix to use. Defaulted to "//VVM"
+ */
+ public static final String KEY_VVM_CLIENT_PREFIX_STRING =
+ "vvm_client_prefix_string";
+
+ /**
+ * Whether to use SSL to connect to the visual voicemail IMAP server. Defaulted to false.
+ */
+ public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
+
+ /**
+ * A set of capabilities that should not be used even if it is reported by the visual voicemail
+ * IMAP CAPABILITY command.
+ */
+ public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
+ "vvm_disabled_capabilities_string_array";
+
+ /**
+ * Whether legacy mode should be used when the visual voicemail client is disabled.
+ *
+ * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on
+ * the client side all network operations are disabled. SMSs are still monitored so a new
+ * message SYNC SMS will be translated to show a message waiting indicator, like traditional
+ * voicemails.
+ *
+ * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to
+ * function without the data cost.
+ */
+ public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL =
+ "vvm_legacy_mode_enabled_bool";
+
+ /**
* Whether to prefetch audio data on new voicemail arrival, defaulted to true.
*/
public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
@@ -611,10 +643,20 @@
/**
* The package name of the carrier's visual voicemail app to ensure that dialer visual voicemail
* and carrier visual voicemail are not active at the same time.
+ *
+ * @deprecated use {@link #KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY}.
*/
+ @Deprecated
public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string";
/**
+ * A list of the carrier's visual voicemail app package names to ensure that dialer visual
+ * voicemail and carrier visual voicemail are not active at the same time.
+ */
+ public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
+ "carrier_vvm_package_name_string_array";
+
+ /**
* Flag specifying whether ICCID is showed in SIM Status screen, default to false.
*/
public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
@@ -942,6 +984,20 @@
"carrier_default_actions_on_dcfailure_string_array";
/**
+ * Defines carrier-specific actions which act upon
+ * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the
+ * default carrier app
+ * Format: "CARRIER_ACTION_IDX, ..."
+ * Where {@code CARRIER_ACTION_IDX} is an integer defined in
+ * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * Example:
+ * {@link com.android.carrierdefaultapp.CarrierActionUtils
+ * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset}
+ * @hide
+ */
+ public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
+ "carrier_default_actions_on_reset_string_array";
+ /**
* Defines a list of acceptable redirection url for default carrier app
* @hides
*/
@@ -1344,8 +1400,13 @@
sDefaults.putInt(KEY_VVM_PORT_NUMBER_INT, 0);
sDefaults.putString(KEY_VVM_TYPE_STRING, "");
sDefaults.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false);
+ sDefaults.putString(KEY_VVM_CLIENT_PREFIX_STRING,"//VVM");
+ sDefaults.putBoolean(KEY_VVM_SSL_ENABLED_BOOL,false);
+ sDefaults.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_VVM_LEGACY_MODE_ENABLED_BOOL,false);
sDefaults.putBoolean(KEY_VVM_PREFETCH_BOOL, true);
sDefaults.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING, "");
+ sDefaults.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, false);
sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, "");
@@ -1457,7 +1518,8 @@
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_REDIRECTED," +
+ "com.android.internal.telephony.CARRIER_SIGNAL_RESET"
});
sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null);
@@ -1468,6 +1530,9 @@
//4: CARRIER_ACTION_DISABLE_METERED_APNS
//1: CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION
});
+ sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET, new String[]{
+ "6" //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+ });
sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null);
// Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP},
diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java
index bb4bf1e..862c919 100644
--- a/telephony/java/android/telephony/MbmsDownloadManager.java
+++ b/telephony/java/android/telephony/MbmsDownloadManager.java
@@ -16,14 +16,16 @@
package android.telephony;
-import android.app.PendingIntent;
import android.content.Context;
import android.net.Uri;
-import android.telephony.mbms.DownloadListener;
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadCallback;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStatus;
-import android.telephony.mbms.FileServiceInfo;
-import android.telephony.mbms.IMbmsDownloadManagerListener;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.MbmsInitializationException;
+import android.telephony.mbms.vendor.IMbmsDownloadService;
+import android.util.Log;
import java.util.List;
@@ -31,9 +33,135 @@
/** @hide */
public class MbmsDownloadManager {
+ private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName();
+
+ /**
+ * The MBMS middleware should send this when a download of single file has completed or
+ * failed. Mandatory extras are
+ * {@link #EXTRA_RESULT}
+ * {@link #EXTRA_INFO}
+ * {@link #EXTRA_REQUEST}
+ * {@link #EXTRA_TEMP_LIST}
+ * {@link #EXTRA_FINAL_URI}
+ *
+ * TODO: future systemapi
+ */
+ public static final String ACTION_DOWNLOAD_RESULT_INTERNAL =
+ "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL";
+
+ /**
+ * 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}
+ *
+ * Optional extras are
+ * {@link #EXTRA_FD_COUNT} (0 if not present)
+ * {@link #EXTRA_PAUSED_LIST} (empty if not present)
+ *
+ * TODO: future systemapi
+ */
+ public static final String ACTION_FILE_DESCRIPTOR_REQUEST =
+ "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST";
+
+ /**
+ * The MBMS middleware should send this when it wishes to clean up temp files in the app's
+ * filesystem. Mandatory extras are:
+ * {@link #EXTRA_TEMP_FILES_IN_USE}
+ *
+ * TODO: future systemapi
+ */
+ public static final String ACTION_CLEANUP =
+ "android.telephony.mbms.action.CLEANUP";
+
+ /**
+ * Integer extra indicating the result code of the download.
+ * TODO: put in link to error list
+ * TODO: future systemapi (here and and all extras)
+ */
+ 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_INFO = "android.telephony.mbms.extra.INFO";
+
+ /**
+ * 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 {@link List} of {@link Uri}s that were used as temp files for this
+ * completed file. These {@link Uri}s should have scheme {@code file://}, and the temp
+ * files will be deleted upon receipt of the intent.
+ * May be null.
+ */
+ public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST";
+
+ /**
+ * 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 integer indicating the number of temp files requested.
+ */
+ public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT";
+
+ /**
+ * Extra containing a list of {@link Uri}s that the middleware is requesting access to via
+ * {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s
+ * should have scheme {@code file://}.
+ */
+ public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST";
+
+ /**
+ * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+ * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant
+ * to be used for new file downloads.
+ */
+ public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST";
+
+ /**
+ * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the
+ * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These
+ * {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide
+ * access to previously paused downloads.
+ */
+ public static final String EXTRA_PAUSED_URI_LIST =
+ "android.telephony.mbms.extra.PAUSED_URI_LIST";
+
+ /**
+ * Extra containing a list of {@link Uri}s indicating temp files which the middleware is
+ * still using.
+ */
+ public static final String EXTRA_TEMP_FILES_IN_USE =
+ "android.telephony.mbms.extra.TEMP_FILES_IN_USE";
+
+ public static final int RESULT_SUCCESSFUL = 1;
+ public static final int RESULT_CANCELLED = 2;
+ public static final int RESULT_EXPIRED = 3;
+ // TODO - more results!
+
private final Context mContext;
private int mSubId = INVALID_SUBSCRIPTION_ID;
+ private IMbmsDownloadService mService;
+ private final IMbmsDownloadManagerCallback mCallback;
+ private final String mDownloadAppName;
+
+ private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback,
+ String downloadAppName, int subId) {
+ mContext = context;
+ mCallback = callback;
+ mDownloadAppName = downloadAppName;
+ mSubId = subId;
+ }
+
/**
* Create a new MbmsDownloadManager using the system default data subscription ID.
*
@@ -42,9 +170,13 @@
*
* @hide
*/
- public MbmsDownloadManager(Context context, IMbmsDownloadManagerListener listener,
- String downloadAppName) {
- mContext = context;
+ public static MbmsDownloadManager createManager(Context context,
+ IMbmsDownloadManagerCallback listener, String downloadAppName)
+ throws MbmsInitializationException{
+ MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
+ SubscriptionManager.getDefaultSubscriptionId());
+ mdm.bindAndInitialize();
+ return mdm;
}
/**
@@ -55,9 +187,23 @@
*
* @hide
*/
- public MbmsDownloadManager(Context context, IMbmsDownloadManagerListener listener,
- String downloadAppName, int subId) {
- mContext = context;
+
+ public static MbmsDownloadManager createManager(Context context,
+ IMbmsDownloadManagerCallback listener, String downloadAppName, int subId)
+ throws MbmsInitializationException {
+ MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName,
+ subId);
+ mdm.bindAndInitialize();
+ return mdm;
+ }
+
+ private void bindAndInitialize() throws MbmsInitializationException {
+ // TODO: bind
+ try {
+ mService.initialize(mDownloadAppName, mSubId, mCallback);
+ } catch (RemoteException e) {
+ throw new MbmsInitializationException(0); // TODO: proper error code
+ }
}
/**
@@ -84,31 +230,9 @@
}
- public static final String EXTRA_REQUEST = "extraRequest";
-
- public static final int RESULT_SUCCESSFUL = 1;
- public static final int RESULT_CANCELLED = 2;
- public static final int RESULT_EXPIRED = 3;
- // TODO - more results!
-
- public static final String EXTRA_RESULT = "extraResult";
- public static final String EXTRA_URI = "extraDownloadedUri";
-
/**
* Requests a future download.
* returns a token which may be used to cancel a download.
- * fileServiceInfo indicates what FileService to download from
- * source indicates which file to download from the given FileService. This is
- * an optional field - it may be null or empty to indicate download everything from
- * the FileService.
- * destination is a file URI for where in the apps accessible storage locations to write
- * the content. This URI may be used to store temporary data and should not be
- * accessed until the PendingIntent is called indicating success.
- * resultIntent is sent when each file is completed and when the request is concluded
- * either via TTL expiration, cancel or error.
- * This intent is sent with three extras: a {@link DownloadRequest} typed extra called
- * {@link #EXTRA_REQUEST}, an Integer called {@link #EXTRA_RESULT} for the result code
- * and a {@link Uri} called {@link #EXTRA_URI} to the resulting file (if successful).
* 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
@@ -118,7 +242,7 @@
*
* Asynchronous errors through the listener include any of the errors
*/
- public DownloadRequest download(DownloadRequest downloadRequest, DownloadListener listener) {
+ public DownloadRequest download(DownloadRequest downloadRequest, DownloadCallback listener) {
return null;
}
@@ -168,7 +292,7 @@
}
/**
- * Resets middleware knowldge regarding this download request.
+ * Resets middleware knowledge regarding this download request.
*
* This state consists of knowledge of what files have already been downloaded.
* Normally the middleware won't download files who's hash matches previously downloaded
@@ -187,5 +311,15 @@
}
public void dispose() {
+ try {
+ if (mService != null) {
+ mService.dispose(mDownloadAppName, mSubId);
+ } else {
+ Log.i(LOG_TAG, "Service already dead");
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ Log.i(LOG_TAG, "Remote exception while disposing of service");
+ }
}
}
diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java
index 9a2ba6d..770a04a 100644
--- a/telephony/java/android/telephony/MbmsStreamingManager.java
+++ b/telephony/java/android/telephony/MbmsStreamingManager.java
@@ -17,10 +17,13 @@
package android.telephony;
import android.content.Context;
-import android.telephony.mbms.IMbmsStreamingManagerListener;
-import android.telephony.mbms.IStreamingServiceListener;
+import android.os.RemoteException;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.MbmsInitializationException;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceInfo;
+import android.telephony.mbms.vendor.IMbmsStreamingService;
import android.util.Log;
import java.util.List;
@@ -31,19 +34,20 @@
public class MbmsStreamingManager {
private static final String LOG_TAG = "MbmsStreamingManager";
private static final boolean DEBUG = true;
+ private IMbmsStreamingService mService;
+ private IMbmsStreamingManagerCallback mCallbackToApp;
+ private final String mAppName;
private final Context mContext;
- private int mSubId = INVALID_SUBSCRIPTION_ID;
+ private int mSubscriptionId = INVALID_SUBSCRIPTION_ID;
- /**
- * Create a new MbmsStreamingManager using the system default data subscription ID.
- *
- * Note that this call will bind a remote service and that may take a bit. This
- * may throw an IllegalArgumentException or RemoteException.
- */
- public MbmsStreamingManager(Context context, IMbmsStreamingManagerListener listener,
- String streamingAppName) {
+ /** @hide */
+ private MbmsStreamingManager(Context context, IMbmsStreamingManagerCallback listener,
+ String streamingAppName, int subscriptionId) {
mContext = context;
+ mAppName = streamingAppName;
+ mCallbackToApp = listener;
+ mSubscriptionId = subscriptionId;
}
/**
@@ -51,10 +55,39 @@
*
* Note that this call will bind a remote service and that may take a bit. This
* may throw an IllegalArgumentException or RemoteException.
+ * TODO: document this and add exceptions that can be thrown for synchronous
+ * initialization/bind errors
+ *
+ * @param context
+ * @param listener
+ * @param streamingAppName
+ * @param subscriptionId
+ * @return
*/
- public MbmsStreamingManager(Context context, IMbmsStreamingManagerListener listener,
- String streamingAppName, int subId) {
- mContext = context;
+ public static MbmsStreamingManager create(Context context,
+ IMbmsStreamingManagerCallback listener, String streamingAppName, int subscriptionId)
+ throws MbmsInitializationException {
+ MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
+ streamingAppName, subscriptionId);
+ manager.bindAndInitialize();
+ return manager;
+ }
+
+ /**
+ * Create a new MbmsStreamingManager using the system default data subscription ID.
+ *
+ * Note that this call will bind a remote service and that may take a bit. This
+ * may throw an IllegalArgumentException or RemoteException.
+ */
+ public static MbmsStreamingManager create(Context context,
+ IMbmsStreamingManagerCallback listener, String streamingAppName)
+ throws MbmsInitializationException {
+ // TODO: get default sub id
+ int subId = INVALID_SUBSCRIPTION_ID;
+ MbmsStreamingManager manager = new MbmsStreamingManager(context, listener,
+ streamingAppName, subId);
+ manager.bindAndInitialize();
+ return manager;
}
/**
@@ -97,7 +130,7 @@
* Asynchronous errors through the listener include any of the errors
*/
public StreamingService startStreaming(StreamingServiceInfo serviceInfo,
- IStreamingServiceListener listener) {
+ IStreamingServiceCallback listener) {
return null;
}
@@ -125,4 +158,20 @@
private void logd(String str) {
Log.d(LOG_TAG, str);
}
+
+ private boolean isServiceConnected() {
+ return mService != null;
+ }
+
+ private void bindAndInitialize() throws MbmsInitializationException {
+ // TODO: bind to the service
+ try {
+ int returnCode = mService.initialize(mCallbackToApp, mAppName, mSubscriptionId);
+ if (returnCode != 0) {
+ throw new MbmsInitializationException(returnCode);
+ }
+ } catch (RemoteException e) {
+ throw new MbmsInitializationException(/* some error */ 0);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
new file mode 100644
index 0000000..07bcb38
--- /dev/null
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -0,0 +1,1680 @@
+/*
+ * Copyright (C) 2008 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.SystemApi;
+import android.app.ActivityThread;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.telephony.IMms;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * TODO(code review): Curious question... Why are a lot of these
+ * methods not declared as static, since they do not seem to require
+ * any local object state? Presumably this cannot be changed without
+ * interfering with the API...
+ */
+
+/**
+ * Manages SMS operations such as sending data, text, and pdu SMS messages.
+ * Get this object by calling the static method {@link #getDefault()}.
+ *
+ * <p>For information about how to behave as the default SMS app on Android 4.4 (API level 19)
+ * and higher, see {@link android.provider.Telephony}.
+ */
+public final class SmsManager {
+ private static final String TAG = "SmsManager";
+ /**
+ * A psuedo-subId that represents the default subId at any given time. The actual subId it
+ * represents changes as the default subId is changed.
+ */
+ private static final int DEFAULT_SUBSCRIPTION_ID = -1002;
+
+ /** Singleton object constructed during class initialization. */
+ private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID);
+ private static final Object sLockObject = new Object();
+
+ /** @hide */
+ public static final int CELL_BROADCAST_RAN_TYPE_GSM = 0;
+ /** @hide */
+ public static final int CELL_BROADCAST_RAN_TYPE_CDMA = 1;
+
+ /** SMS record length from TS 51.011 10.5.3
+ * @hide
+ */
+ public static final int SMS_RECORD_LENGTH = 176;
+
+ /** SMS record length from C.S0023 3.4.27
+ * @hide
+ */
+ public static final int CDMA_SMS_RECORD_LENGTH = 255;
+
+ private static final Map<Integer, SmsManager> sSubInstances =
+ new ArrayMap<Integer, SmsManager>();
+
+ /** A concrete subscription id, or the pseudo DEFAULT_SUBSCRIPTION_ID */
+ private int mSubId;
+
+ /*
+ * Key for the various carrier-dependent configuration values.
+ * Some of the values are used by the system in processing SMS or MMS messages. Others
+ * are provided for the convenience of SMS applications.
+ */
+
+ /**
+ * Whether to append transaction id to MMS WAP Push M-Notification.ind's content location URI
+ * when constructing the download URL of a new MMS (boolean type)
+ */
+ public static final String MMS_CONFIG_APPEND_TRANSACTION_ID =
+ CarrierConfigManager.KEY_MMS_APPEND_TRANSACTION_ID_BOOL;
+ /**
+ * Whether MMS is enabled for the current carrier (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL;
+ /**
+ * Whether group MMS is enabled for the current carrier (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_GROUP_MMS_ENABLED = CarrierConfigManager.KEY_MMS_GROUP_MMS_ENABLED_BOOL;
+ /**
+ * If this is enabled, M-NotifyResp.ind should be sent to the WAP Push content location instead
+ * of the default MMSC (boolean type)
+ */
+ public static final String MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED =
+ CarrierConfigManager.KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL;
+ /**
+ * Whether alias is enabled (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_ALIAS_ENABLED = CarrierConfigManager.KEY_MMS_ALIAS_ENABLED_BOOL;
+ /**
+ * Whether audio is allowed to be attached for MMS messages (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_ALLOW_ATTACH_AUDIO = CarrierConfigManager.KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL;
+ /**
+ * Whether multipart SMS is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_MULTIPART_SMS_ENABLED =
+ CarrierConfigManager.KEY_MMS_MULTIPART_SMS_ENABLED_BOOL;
+ /**
+ * Whether SMS delivery report is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED =
+ CarrierConfigManager.KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL;
+ /**
+ * Whether content-disposition field should be expected in an MMS PDU (boolean type)
+ */
+ public static final String MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION =
+ CarrierConfigManager.KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL;
+ /**
+ * Whether multipart SMS should be sent as separate messages
+ */
+ public static final String MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES =
+ CarrierConfigManager.KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL;
+ /**
+ * Whether MMS read report is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_MMS_READ_REPORT_ENABLED =
+ CarrierConfigManager.KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL;
+ /**
+ * Whether MMS delivery report is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED =
+ CarrierConfigManager.KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL;
+ /**
+ * Max MMS message size in bytes (int type)
+ */
+ public static final String
+ MMS_CONFIG_MAX_MESSAGE_SIZE = CarrierConfigManager.KEY_MMS_MAX_MESSAGE_SIZE_INT;
+ /**
+ * Max MMS image width (int type)
+ */
+ public static final String
+ MMS_CONFIG_MAX_IMAGE_WIDTH = CarrierConfigManager.KEY_MMS_MAX_IMAGE_WIDTH_INT;
+ /**
+ * Max MMS image height (int type)
+ */
+ public static final String
+ MMS_CONFIG_MAX_IMAGE_HEIGHT = CarrierConfigManager.KEY_MMS_MAX_IMAGE_HEIGHT_INT;
+ /**
+ * Limit of recipients of MMS messages (int type)
+ */
+ public static final String
+ MMS_CONFIG_RECIPIENT_LIMIT = CarrierConfigManager.KEY_MMS_RECIPIENT_LIMIT_INT;
+ /**
+ * Min alias character count (int type)
+ */
+ public static final String
+ MMS_CONFIG_ALIAS_MIN_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MIN_CHARS_INT;
+ /**
+ * Max alias character count (int type)
+ */
+ public static final String
+ MMS_CONFIG_ALIAS_MAX_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MAX_CHARS_INT;
+ /**
+ * When the number of parts of a multipart SMS reaches this threshold, it should be converted
+ * into an MMS (int type)
+ */
+ public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD =
+ CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT;
+ /**
+ * Some carriers require SMS to be converted into MMS when text length reaches this threshold
+ * (int type)
+ */
+ public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD =
+ CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT;
+ /**
+ * Max message text size (int type)
+ */
+ public static final String MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE =
+ CarrierConfigManager.KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT;
+ /**
+ * Max message subject length (int type)
+ */
+ public static final String
+ MMS_CONFIG_SUBJECT_MAX_LENGTH = CarrierConfigManager.KEY_MMS_SUBJECT_MAX_LENGTH_INT;
+ /**
+ * MMS HTTP socket timeout in milliseconds (int type)
+ */
+ public static final String
+ MMS_CONFIG_HTTP_SOCKET_TIMEOUT = CarrierConfigManager.KEY_MMS_HTTP_SOCKET_TIMEOUT_INT;
+ /**
+ * The name of the UA Prof URL HTTP header for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_UA_PROF_TAG_NAME = CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING;
+ /**
+ * The User-Agent header value for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_USER_AGENT = CarrierConfigManager.KEY_MMS_USER_AGENT_STRING;
+ /**
+ * The UA Profile URL header value for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_UA_PROF_URL = CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING;
+ /**
+ * A list of HTTP headers to add to MMS HTTP request, separated by "|" (String type)
+ */
+ public static final String
+ MMS_CONFIG_HTTP_PARAMS = CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING;
+ /**
+ * Email gateway number (String type)
+ */
+ public static final String MMS_CONFIG_EMAIL_GATEWAY_NUMBER =
+ CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING;
+ /**
+ * The suffix to append to the NAI header value for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_NAI_SUFFIX = CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING;
+ /**
+ * If true, show the cell broadcast (amber alert) in the SMS settings. Some carriers don't want
+ * this shown. (Boolean type)
+ */
+ public static final String MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS =
+ CarrierConfigManager.KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL;
+ /**
+ * Whether the carrier MMSC supports charset field in Content-Type header. If this is false,
+ * then we don't add "charset" to "Content-Type"
+ */
+ public static final String MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER =
+ CarrierConfigManager.KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL;
+ /**
+ * If true, add "Connection: close" header to MMS HTTP requests so the connection
+ * is immediately closed (disabling keep-alive). (Boolean type)
+ * @hide
+ */
+ public static final String MMS_CONFIG_CLOSE_CONNECTION =
+ CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL;
+
+ /*
+ * Forwarded constants from SimDialogActivity.
+ */
+ private static String DIALOG_TYPE_KEY = "dialog_type";
+ private static final int SMS_PICK = 2;
+
+ /**
+ * Send a text based SMS.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ true /* persistMessage*/);
+ }
+
+ private void sendTextMessageInternal(String destinationAddress, String scAddress,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ boolean persistMessage) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+ destinationAddress,
+ scAddress, text, sentIntent, deliveryIntent,
+ persistMessage);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+ * @hide
+ */
+ @SystemApi
+ public void sendTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ false /* persistMessage */);
+ }
+
+ /**
+ * A variant of {@link SmsManager#sendTextMessage} that allows self to be the caller. This is
+ * for internal use only.
+ *
+ * @param persistMessage whether to persist the sent message in the SMS app. the caller must be
+ * the Phone process if set to false.
+ *
+ * @hide
+ */
+ public void sendTextMessageWithSelfPermissions(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendTextForSubscriberWithSelfPermissions(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ destinationAddress,
+ scAddress, text, sentIntent, deliveryIntent, persistMessage);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Inject an SMS PDU into the android application framework.
+ *
+ * The caller should have carrier privileges.
+ * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ *
+ * @param pdu is the byte array of pdu to be injected into android application framework
+ * @param format is the format of SMS pdu (3gpp or 3gpp2)
+ * @param receivedIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully received by the
+ * android application framework, or failed. This intent is broadcasted at
+ * the same time an SMS received from radio is acknowledged back.
+ * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
+ * <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+ *
+ * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+ */
+ public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+ if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
+ // Format must be either 3gpp or 3gpp2.
+ throw new IllegalArgumentException(
+ "Invalid pdu format. format must be either 3gpp or 3gpp2");
+ }
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ iccISms.injectSmsPduForSubscriber(
+ getSubscriptionId(), pdu, format, receivedIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Divide a message text into several fragments, none bigger than
+ * the maximum SMS message size.
+ *
+ * @param text the original message. Must not be null.
+ * @return an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ public ArrayList<String> divideMessage(String text) {
+ if (null == text) {
+ throw new IllegalArgumentException("text is null");
+ }
+ return SmsMessage.fragmentText(text);
+ }
+
+ /**
+ * Send a multi-part text based SMS. The callee should have already
+ * divided the message into correctly sized parts by calling
+ * <code>divideMessage</code>.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, true /* persistMessage*/);
+ }
+
+ private void sendMultipartTextMessageInternal(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ boolean persistMessage) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+ if (parts == null || parts.size() < 1) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (parts.size() > 1) {
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendMultipartTextForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ destinationAddress, scAddress, parts,
+ sentIntents, deliveryIntents, persistMessage);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else {
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+ if (sentIntents != null && sentIntents.size() > 0) {
+ sentIntent = sentIntents.get(0);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > 0) {
+ deliveryIntent = deliveryIntents.get(0);
+ }
+ sendTextMessage(destinationAddress, scAddress, parts.get(0),
+ sentIntent, deliveryIntent);
+ }
+ }
+
+ /**
+ * Send a multi-part text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
+ * @hide
+ **/
+ @SystemApi
+ public void sendMultipartTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, false /* persistMessage*/);
+ }
+
+ /**
+ * Send a data based SMS to a specific application port.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param destinationPort the port to deliver the message to
+ * @param data the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendDataMessage(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+ destinationAddress, scAddress, destinationPort & 0xFFFF,
+ data, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is
+ * for internal use only.
+ *
+ * @hide
+ */
+ public void sendDataMessageWithSelfPermissions(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress,
+ destinationPort & 0xFFFF, data, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+
+
+ /**
+ * Get the SmsManager associated with the default subscription id. The instance will always be
+ * associated with the default subscription id, even if the default subscription id is changed.
+ *
+ * @return the SmsManager associated with the default subscription id
+ */
+ public static SmsManager getDefault() {
+ return sInstance;
+ }
+
+ /**
+ * Get the the instance of the SmsManager associated with a particular subscription id
+ *
+ * @param subId an SMS subscription id, typically accessed using
+ * {@link android.telephony.SubscriptionManager}
+ * @return the instance of the SmsManager associated with subId
+ */
+ public static SmsManager getSmsManagerForSubscriptionId(int subId) {
+ // TODO(shri): Add javadoc link once SubscriptionManager is made public api
+ synchronized(sLockObject) {
+ SmsManager smsManager = sSubInstances.get(subId);
+ if (smsManager == null) {
+ smsManager = new SmsManager(subId);
+ sSubInstances.put(subId, smsManager);
+ }
+ return smsManager;
+ }
+ }
+
+ private SmsManager(int subId) {
+ mSubId = subId;
+ }
+
+ /**
+ * Get the associated subscription id. If the instance was returned by {@link #getDefault()},
+ * then this method may return different values at different points in time (if the user
+ * changes the default subscription id). It will return < 0 if the default subscription id
+ * cannot be determined.
+ *
+ * Additionally, to support legacy applications that are not multi-SIM aware,
+ * if the following are true:
+ * - We are using a multi-SIM device
+ * - A default SMS SIM has not been selected
+ * - At least one SIM subscription is available
+ * then ask the user to set the default SMS SIM.
+ *
+ * @return associated subscription id
+ */
+ public int getSubscriptionId() {
+ final int subId = (mSubId == DEFAULT_SUBSCRIPTION_ID)
+ ? getDefaultSmsSubscriptionId() : mSubId;
+ boolean isSmsSimPickActivityNeeded = false;
+ final Context context = ActivityThread.currentApplication().getApplicationContext();
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ isSmsSimPickActivityNeeded = iccISms.isSmsSimPickActivityNeeded(subId);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception in getSubscriptionId");
+ }
+
+ if (isSmsSimPickActivityNeeded) {
+ Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true");
+ // ask the user for a default SMS SIM.
+ Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.sim.SimDialogActivity");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK);
+ try {
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException anfe) {
+ // If Settings is not installed, only log the error as we do not want to break
+ // legacy applications.
+ Log.e(TAG, "Unable to launch Settings application.");
+ }
+ }
+
+ return subId;
+ }
+
+ /**
+ * Returns the ISms service, or throws an UnsupportedOperationException if
+ * the service does not exist.
+ */
+ private static ISms getISmsServiceOrThrow() {
+ ISms iccISms = getISmsService();
+ if (iccISms == null) {
+ throw new UnsupportedOperationException("Sms is not supported");
+ }
+ return iccISms;
+ }
+
+ private static ISms getISmsService() {
+ return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ }
+
+ /**
+ * Copy a raw SMS PDU to the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param smsc the SMSC for this message, or NULL for the default SMSC
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return true for success
+ *
+ * @throws IllegalArgumentException if pdu is NULL
+ * {@hide}
+ */
+ public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
+ boolean success = false;
+
+ if (null == pdu) {
+ throw new IllegalArgumentException("pdu is NULL");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ status, pdu, smsc);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Delete the specified message from the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex is the record index of the message on ICC
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean
+ deleteMessageFromIcc(int messageIndex) {
+ boolean success = false;
+ byte[] pdu = new byte[SMS_RECORD_LENGTH-1];
+ Arrays.fill(pdu, (byte)0xff);
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ messageIndex, STATUS_ON_ICC_FREE, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Update the specified message on the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus new message status (STATUS_ON_ICC_READ,
+ * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+ * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+ * @param pdu the raw PDU to store
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ messageIndex, newStatus, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Retrieves all messages currently stored on ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+ *
+ * {@hide}
+ */
+ public ArrayList<SmsMessage> getAllMessagesFromIcc() {
+ List<SmsRawData> records = null;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ records = iccISms.getAllMessagesFromIccEfForSubscriber(
+ getSubscriptionId(),
+ ActivityThread.currentPackageName());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return createMessageListFromRawRecords(records);
+ }
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier and RAN type. The RAN type specify this message ID
+ * belong to 3GPP (GSM) or 3GPP2(CDMA).Note that if two different clients
+ * enable the same message identifier, they must both disable it for the device to stop
+ * receiving those messages. All received messages will be broadcast in an
+ * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ * @see #disableCellBroadcast(int, int)
+ *
+ * {@hide}
+ */
+ public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.enableCellBroadcastForSubscriber(
+ getSubscriptionId(), messageIdentifier, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier and RAN type. The RAN type specify this message ID
+ * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
+ * enable the same message identifier, they must both disable it for the
+ * device to stop receiving those messages.
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcast(int, int)
+ *
+ * {@hide}
+ */
+ public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.disableCellBroadcastForSubscriber(
+ getSubscriptionId(), messageIdentifier, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range and RAN type. The RAN type specify this message ID
+ * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
+ * the same message identifier, they must both disable it for the device to stop
+ * receiving those messages. All received messages will be broadcast in an
+ * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ * @see #disableCellBroadcastRange(int, int, int)
+ *
+ * @throws IllegalArgumentException if endMessageId < startMessageId
+ * {@hide}
+ */
+ public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+ boolean success = false;
+
+ if (endMessageId < startMessageId) {
+ throw new IllegalArgumentException("endMessageId < startMessageId");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+ startMessageId, endMessageId, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range and RAN type. The RAN type specify this message
+ * ID range belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different
+ * clients enable the same message identifier, they must both disable it for
+ * the device to stop receiving those messages.
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcastRange(int, int, int)
+ *
+ * @throws IllegalArgumentException if endMessageId < startMessageId
+ * {@hide}
+ */
+ public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+ boolean success = false;
+
+ if (endMessageId < startMessageId) {
+ throw new IllegalArgumentException("endMessageId < startMessageId");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+ startMessageId, endMessageId, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
+ * records returned by <code>getAllMessagesFromIcc()</code>
+ *
+ * @param records SMS EF records, returned by
+ * <code>getAllMessagesFromIcc</code>
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
+ */
+ private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+ ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
+ if (records != null) {
+ int count = records.size();
+ for (int i = 0; i < count; i++) {
+ SmsRawData data = records.get(i);
+ // List contains all records, including "free" records (null)
+ if (data != null) {
+ SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ if (sms != null) {
+ messages.add(sms);
+ }
+ }
+ }
+ }
+ return messages;
+ }
+
+ /**
+ * SMS over IMS is supported if IMS is registered and SMS is supported
+ * on IMS.
+ *
+ * @return true if SMS over IMS is supported, false otherwise
+ *
+ * @see #getImsSmsFormat()
+ *
+ * @hide
+ */
+ public boolean isImsSmsSupported() {
+ boolean boSupported = false;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ boSupported = iccISms.isImsSmsSupportedForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return boSupported;
+ }
+
+ /**
+ * Gets SMS format supported on IMS. SMS over IMS format is
+ * either 3GPP or 3GPP2.
+ *
+ * @return SmsMessage.FORMAT_3GPP,
+ * SmsMessage.FORMAT_3GPP2
+ * or SmsMessage.FORMAT_UNKNOWN
+ *
+ * @see #isImsSmsSupported()
+ *
+ * @hide
+ */
+ public String getImsSmsFormat() {
+ String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ format = iccISms.getImsSmsFormatForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return format;
+ }
+
+ /**
+ * Get default sms subscription id
+ *
+ * @return the default SMS subscription id
+ */
+ public static int getDefaultSmsSubscriptionId() {
+ ISms iccISms = null;
+ try {
+ iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ return iccISms.getPreferredSmsSubscription();
+ } catch (RemoteException ex) {
+ return -1;
+ } catch (NullPointerException ex) {
+ return -1;
+ }
+ }
+
+ /**
+ * Get SMS prompt property, enabled or not
+ *
+ * @return true if enabled, false otherwise
+ * @hide
+ */
+ public boolean isSMSPromptEnabled() {
+ ISms iccISms = null;
+ try {
+ iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ return iccISms.isSMSPromptEnabled();
+ } catch (RemoteException ex) {
+ return false;
+ } catch (NullPointerException ex) {
+ return false;
+ }
+ }
+
+ // see SmsMessage.getStatusOnIcc
+
+ /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_FREE = 0;
+
+ /** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_READ = 1;
+
+ /** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_UNREAD = 3;
+
+ /** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_SENT = 5;
+
+ /** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_UNSENT = 7;
+
+ // SMS send failure result codes
+
+ /** Generic failure cause */
+ static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
+ /** Failed because radio was explicitly turned off */
+ static public final int RESULT_ERROR_RADIO_OFF = 2;
+ /** Failed because no pdu provided */
+ static public final int RESULT_ERROR_NULL_PDU = 3;
+ /** Failed because service is currently unavailable */
+ static public final int RESULT_ERROR_NO_SERVICE = 4;
+ /** Failed because we reached the sending queue limit. {@hide} */
+ static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5;
+ /** Failed because FDN is enabled. {@hide} */
+ static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6;
+
+ static private final String PHONE_PACKAGE_NAME = "com.android.phone";
+
+ /**
+ * Send an MMS message
+ *
+ * @param context application context
+ * @param contentUri the content Uri from which the message pdu will be read
+ * @param locationUrl the optional location url where message should be sent to
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * sending the message.
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed
+ * @throws IllegalArgumentException if contentUri is empty
+ */
+ public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
+ Bundle configOverrides, PendingIntent sentIntent) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms == null) {
+ return;
+ }
+
+ iMms.sendMessage(getSubscriptionId(), ActivityThread.currentPackageName(), contentUri,
+ locationUrl, configOverrides, sentIntent);
+ } catch (RemoteException e) {
+ // Ignore it
+ }
+ }
+
+ /**
+ * Download an MMS message from carrier by a given location URL
+ *
+ * @param context application context
+ * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
+ * from the MMS WAP push notification
+ * @param contentUri the content uri to which the downloaded pdu will be written
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * downloading the message.
+ * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is downloaded, or the download is failed
+ * @throws IllegalArgumentException if locationUrl or contentUri is empty
+ */
+ public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
+ Bundle configOverrides, PendingIntent downloadedIntent) {
+ if (TextUtils.isEmpty(locationUrl)) {
+ throw new IllegalArgumentException("Empty MMS location URL");
+ }
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms == null) {
+ return;
+ }
+ iMms.downloadMessage(
+ getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl,
+ contentUri, configOverrides, downloadedIntent);
+ } catch (RemoteException e) {
+ // Ignore it
+ }
+ }
+
+ // MMS send/download failure result codes
+ public static final int MMS_ERROR_UNSPECIFIED = 1;
+ public static final int MMS_ERROR_INVALID_APN = 2;
+ public static final int MMS_ERROR_UNABLE_CONNECT_MMS = 3;
+ public static final int MMS_ERROR_HTTP_FAILURE = 4;
+ public static final int MMS_ERROR_IO_ERROR = 5;
+ public static final int MMS_ERROR_RETRY = 6;
+ public static final int MMS_ERROR_CONFIGURATION_ERROR = 7;
+ public static final int MMS_ERROR_NO_DATA_NETWORK = 8;
+
+ /** Intent extra name for MMS sending result data in byte array type */
+ public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
+ /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
+ public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
+
+ /**
+ * Import a text message into system's SMS store
+ *
+ * Only default SMS apps can import SMS
+ *
+ * @param address the destination(source) address of the sent(received) message
+ * @param type the type of the message
+ * @param text the message text
+ * @param timestampMillis the message timestamp in milliseconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ * @hide
+ */
+ public Uri importTextMessage(String address, int type, String text, long timestampMillis,
+ boolean seen, boolean read) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.importTextMessage(ActivityThread.currentPackageName(),
+ address, type, text, timestampMillis, seen, read);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /** Represents the received SMS message for importing {@hide} */
+ public static final int SMS_TYPE_INCOMING = 0;
+ /** Represents the sent SMS message for importing {@hide} */
+ public static final int SMS_TYPE_OUTGOING = 1;
+
+ /**
+ * Import a multimedia message into system's MMS store. Only the following PDU type is
+ * supported: Retrieve.conf, Send.req, Notification.ind, Delivery.ind, Read-Orig.ind
+ *
+ * Only default SMS apps can import MMS
+ *
+ * @param contentUri the content uri from which to read the PDU of the message to import
+ * @param messageId the optional message id. Use null if not specifying
+ * @param timestampSecs the optional message timestamp. Use -1 if not specifying
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ * @throws IllegalArgumentException if pdu is empty
+ * {@hide}
+ */
+ public Uri importMultimediaMessage(Uri contentUri, String messageId, long timestampSecs,
+ boolean seen, boolean read) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.importMultimediaMessage(ActivityThread.currentPackageName(),
+ contentUri, messageId, timestampSecs, seen, read);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Delete a system stored SMS or MMS message
+ *
+ * Only default SMS apps can delete system stored SMS and MMS messages
+ *
+ * @param messageUri the URI of the stored message
+ * @return true if deletion is successful, false otherwise
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public boolean deleteStoredMessage(Uri messageUri) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.deleteStoredMessage(ActivityThread.currentPackageName(), messageUri);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Delete a system stored SMS or MMS thread
+ *
+ * Only default SMS apps can delete system stored SMS and MMS conversations
+ *
+ * @param conversationId the ID of the message conversation
+ * @return true if deletion is successful, false otherwise
+ * {@hide}
+ */
+ public boolean deleteStoredConversation(long conversationId) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.deleteStoredConversation(
+ ActivityThread.currentPackageName(), conversationId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Update the status properties of a system stored SMS or MMS message, e.g.
+ * the read status of a message, etc.
+ *
+ * @param messageUri the URI of the stored message
+ * @param statusValues a list of status properties in key-value pairs to update
+ * @return true if update is successful, false otherwise
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public boolean updateStoredMessageStatus(Uri messageUri, ContentValues statusValues) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.updateStoredMessageStatus(ActivityThread.currentPackageName(),
+ messageUri, statusValues);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */
+ public static final String MESSAGE_STATUS_SEEN = "seen";
+ /** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */
+ public static final String MESSAGE_STATUS_READ = "read";
+
+ /**
+ * Archive or unarchive a stored conversation
+ *
+ * @param conversationId the ID of the message conversation
+ * @param archived true to archive the conversation, false to unarchive
+ * @return true if update is successful, false otherwise
+ * {@hide}
+ */
+ public boolean archiveStoredConversation(long conversationId, boolean archived) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.archiveStoredConversation(ActivityThread.currentPackageName(),
+ conversationId, archived);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Add a text message draft to system SMS store
+ *
+ * Only default SMS apps can add SMS draft
+ *
+ * @param address the destination address of message
+ * @param text the body of the message to send
+ * @return the URI of the stored draft message
+ * {@hide}
+ */
+ public Uri addTextMessageDraft(String address, String text) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.addTextMessageDraft(ActivityThread.currentPackageName(), address, text);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Add a multimedia message draft to system MMS store
+ *
+ * Only default SMS apps can add MMS draft
+ *
+ * @param contentUri the content uri from which to read the PDU data of the draft MMS
+ * @return the URI of the stored draft message
+ * @throws IllegalArgumentException if pdu is empty
+ * {@hide}
+ */
+ public Uri addMultimediaMessageDraft(Uri contentUri) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.addMultimediaMessageDraft(ActivityThread.currentPackageName(),
+ contentUri);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Send a system stored text message.
+ *
+ * You can only send a failed text message or a draft text message.
+ *
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use the current default SMSC
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredTextMessage(Uri messageUri, String scAddress, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendStoredText(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ scAddress, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a system stored multi-part text message.
+ *
+ * You can only send a failed text message or a draft text message.
+ * The provided <code>PendingIntent</code> lists should match the part number of the
+ * divided text of the stored message by using <code>divideMessage</code>
+ *
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredMultipartTextMessage(Uri messageUri, String scAddress,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendStoredMultipartText(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ scAddress, sentIntents, deliveryIntents);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a system stored MMS message
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ *
+ * @param messageUri the URI of the stored message
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * sending the message.
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredMultimediaMessage(Uri messageUri, Bundle configOverrides,
+ PendingIntent sentIntent) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ iMms.sendStoredMessage(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ configOverrides, sentIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Turns on/off the flag to automatically write sent/received SMS/MMS messages into system
+ *
+ * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+ * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+ * automatically
+ *
+ * This flag can only be changed by default SMS apps
+ *
+ * @param enabled Whether to enable message auto persisting
+ * {@hide}
+ */
+ public void setAutoPersisting(boolean enabled) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ iMms.setAutoPersisting(ActivityThread.currentPackageName(), enabled);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Get the value of the flag to automatically write sent/received SMS/MMS messages into system
+ *
+ * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+ * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+ * automatically
+ *
+ * @return the current value of the auto persist flag
+ * {@hide}
+ */
+ public boolean getAutoPersisting() {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.getAutoPersisting();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Get carrier-dependent configuration values.
+ *
+ * @return bundle key/values pairs of configuration values
+ */
+ public Bundle getCarrierConfigValues() {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.getCarrierConfigValues(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Filters a bundle to only contain MMS config variables.
+ *
+ * This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
+ * config and unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the
+ * supplied bundle.
+ *
+ * @param config a Bundle that contains MMS config variables and possibly more.
+ * @return a new Bundle that only contains the MMS_CONFIG_* keys defined above.
+ * @hide
+ */
+ public static Bundle getMmsConfig(BaseBundle config) {
+ Bundle filtered = new Bundle();
+ filtered.putBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID,
+ config.getBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID));
+ filtered.putBoolean(MMS_CONFIG_MMS_ENABLED, config.getBoolean(MMS_CONFIG_MMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_GROUP_MMS_ENABLED,
+ config.getBoolean(MMS_CONFIG_GROUP_MMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED,
+ config.getBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_ALIAS_ENABLED, config.getBoolean(MMS_CONFIG_ALIAS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO,
+ config.getBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO));
+ filtered.putBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED,
+ config.getBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+ config.getBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION));
+ filtered.putBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+ config.getBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES));
+ filtered.putBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_CLOSE_CONNECTION,
+ config.getBoolean(MMS_CONFIG_CLOSE_CONNECTION));
+ filtered.putInt(MMS_CONFIG_MAX_MESSAGE_SIZE, config.getInt(MMS_CONFIG_MAX_MESSAGE_SIZE));
+ filtered.putInt(MMS_CONFIG_MAX_IMAGE_WIDTH, config.getInt(MMS_CONFIG_MAX_IMAGE_WIDTH));
+ filtered.putInt(MMS_CONFIG_MAX_IMAGE_HEIGHT, config.getInt(MMS_CONFIG_MAX_IMAGE_HEIGHT));
+ filtered.putInt(MMS_CONFIG_RECIPIENT_LIMIT, config.getInt(MMS_CONFIG_RECIPIENT_LIMIT));
+ filtered.putInt(MMS_CONFIG_ALIAS_MIN_CHARS, config.getInt(MMS_CONFIG_ALIAS_MIN_CHARS));
+ filtered.putInt(MMS_CONFIG_ALIAS_MAX_CHARS, config.getInt(MMS_CONFIG_ALIAS_MAX_CHARS));
+ filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
+ config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD));
+ filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+ config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD));
+ filtered.putInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE,
+ config.getInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE));
+ filtered.putInt(MMS_CONFIG_SUBJECT_MAX_LENGTH,
+ config.getInt(MMS_CONFIG_SUBJECT_MAX_LENGTH));
+ filtered.putInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT,
+ config.getInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
+ filtered.putString(MMS_CONFIG_UA_PROF_TAG_NAME,
+ config.getString(MMS_CONFIG_UA_PROF_TAG_NAME));
+ filtered.putString(MMS_CONFIG_USER_AGENT, config.getString(MMS_CONFIG_USER_AGENT));
+ filtered.putString(MMS_CONFIG_UA_PROF_URL, config.getString(MMS_CONFIG_UA_PROF_URL));
+ filtered.putString(MMS_CONFIG_HTTP_PARAMS, config.getString(MMS_CONFIG_HTTP_PARAMS));
+ filtered.putString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER,
+ config.getString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER));
+ filtered.putString(MMS_CONFIG_NAI_SUFFIX, config.getString(MMS_CONFIG_NAI_SUFFIX));
+ filtered.putBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS,
+ config.getBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS));
+ filtered.putBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+ config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER));
+ return filtered;
+ }
+
+}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
new file mode 100644
index 0000000..d6e74cf
--- /dev/null
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2008 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.os.Binder;
+import android.os.Parcel;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+
+/**
+ * A Short Message Service message.
+ * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+ */
+public class SmsMessage {
+ private static final String LOG_TAG = "SmsMessage";
+
+ /**
+ * SMS Class enumeration.
+ * See TS 23.038.
+ *
+ */
+ public enum MessageClass{
+ UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+ }
+
+ /** User data text encoding code unit size */
+ public static final int ENCODING_UNKNOWN = 0;
+ public static final int ENCODING_7BIT = 1;
+ public static final int ENCODING_8BIT = 2;
+ public static final int ENCODING_16BIT = 3;
+ /**
+ * @hide This value is not defined in global standard. Only in Korea, this is used.
+ */
+ public static final int ENCODING_KSC5601 = 4;
+
+ /** The maximum number of payload bytes per message */
+ public static final int MAX_USER_DATA_BYTES = 140;
+
+ /**
+ * The maximum number of payload bytes per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
+
+ /** The maximum number of payload septets per message */
+ public static final int MAX_USER_DATA_SEPTETS = 160;
+
+ /**
+ * The maximum number of payload septets per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+
+ /**
+ * Indicates a 3GPP format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP = "3gpp";
+
+ /**
+ * Indicates a 3GPP2 format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP2 = "3gpp2";
+
+ /** Contains actual SmsMessage. Only public for debugging and for framework layer.
+ *
+ * @hide
+ */
+ public SmsMessageBase mWrappedSmsMessage;
+
+ /** Indicates the subId
+ *
+ * @hide
+ */
+ private int mSubId = 0;
+
+ /** set Subscription information
+ *
+ * @hide
+ */
+ public void setSubId(int subId) {
+ mSubId = subId;
+ }
+
+ /** get Subscription information
+ *
+ * @hide
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ public static class SubmitPdu {
+
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ @Override
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+
+ /**
+ * @hide
+ */
+ protected SubmitPdu(SubmitPduBase spb) {
+ this.encodedMessage = spb.encodedMessage;
+ this.encodedScAddress = spb.encodedScAddress;
+ }
+
+ }
+
+ /**
+ * @hide
+ */
+ public SmsMessage(SmsMessageBase smb) {
+ mWrappedSmsMessage = smb;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU. Guess format based on Voice
+ * technology first, if it fails use other format.
+ * All applications which handle
+ * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
+ * intent <b>must</b> now pass the new {@code format} String extra from the intent
+ * into the new method {@code createFromPdu(byte[], String)} which takes an
+ * extra format parameter. This is required in order to correctly decode the PDU on
+ * devices that require support for both 3GPP and 3GPP2 formats at the same time,
+ * such as dual-mode GSM/CDMA and CDMA/LTE phones.
+ * @deprecated Use {@link #createFromPdu(byte[], String)} instead.
+ */
+ @Deprecated
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessage message = null;
+
+ // cdma(3gpp2) vs gsm(3gpp) format info was not given,
+ // guess from active voice phone type
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ String format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
+ message = createFromPdu(pdu, format);
+
+ if (null == message || null == message.mWrappedSmsMessage) {
+ // decoding pdu failed based on activePhone type, must be other format
+ format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2;
+ message = createFromPdu(pdu, format);
+ }
+ return message;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU with the specified message format. The
+ * message format is passed in the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format}
+ * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+ * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+ *
+ * @param pdu the message PDU from the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+ * @param format the format extra from the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+ */
+ public static SmsMessage createFromPdu(byte[] pdu, String format) {
+ SmsMessageBase wrappedMessage;
+
+ if (SmsConstants.FORMAT_3GPP2.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+ } else if (SmsConstants.FORMAT_3GPP.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+ } else {
+ Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
+ return null;
+ }
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [<alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging and for RIL
+ *
+ * {@hide}
+ */
+ public static SmsMessage newFromCMT(byte[] pdu) {
+ // received SMS in 3GPP format
+ SmsMessageBase wrappedMessage =
+ com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu);
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdmaVoice()) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ index, data);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ index, data);
+ }
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ *
+ * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
+ * We should probably deprecate it and remove the obsolete test case.
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ if (isCdmaVoice()) {
+ return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
+ } else {
+ return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
+ }
+ }
+
+ /*
+ * TODO(cleanup): It would make some sense if the result of
+ * preprocessing a message to determine the proper encoding (i.e.
+ * the resulting data structure from calculateLength) could be
+ * passed as an argument to the actual final encoding function.
+ * This would better ensure that the logic behind size calculation
+ * actually matched the encoding.
+ */
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly if true, characters that are not part of the
+ * radio-specific 7-bit encoding are counted as single
+ * space chars. If false, and if the messageBody contains
+ * non-7-bit encodable characters, length is calculated
+ * using a 16-bit encoding.
+ * @return an int[4] with int[0] being the number of SMS's
+ * required, int[1] the number of code units used, and
+ * int[2] is the number of code units remaining until the
+ * next message. int[3] is an indicator of the encoding
+ * code unit size (see the ENCODING_* definitions in SmsConstants)
+ */
+ public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
+ // this function is for MO SMS
+ TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly,
+ true) :
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ int ret[] = new int[4];
+ ret[0] = ted.msgCount;
+ ret[1] = ted.codeUnitCount;
+ ret[2] = ted.codeUnitsRemaining;
+ ret[3] = ted.codeUnitSize;
+ return ret;
+ }
+
+ /**
+ * Divide a message text into several fragments, none bigger than
+ * the maximum SMS message text size.
+ *
+ * @param text text, must not be null.
+ * @return an <code>ArrayList</code> of strings that, in order,
+ * comprise the original msg text
+ *
+ * @hide
+ */
+ public static ArrayList<String> fragmentText(String text) {
+ // This function is for MO SMS
+ TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false, true) :
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);
+
+ // TODO(cleanup): The code here could be rolled into the logic
+ // below cleanly if these MAX_* constants were defined more
+ // flexibly...
+
+ int limit;
+ if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ int udhLength;
+ if (ted.languageTable != 0 && ted.languageShiftTable != 0) {
+ udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES;
+ } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) {
+ udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE;
+ } else {
+ udhLength = 0;
+ }
+
+ if (ted.msgCount > 1) {
+ udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE;
+ }
+
+ if (udhLength != 0) {
+ udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH;
+ }
+
+ limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
+ } else {
+ if (ted.msgCount > 1) {
+ limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ // If EMS is not supported, break down EMS into single segment SMS
+ // and add page info " x/y".
+ // In the case of UCS2 encoding, we need 8 bytes for this,
+ // but we only have 6 bytes from UDH, so truncate the limit for
+ // each segment by 2 bytes (1 char).
+ // Make sure total number of segments is less than 10.
+ if (!hasEmsSupport() && ted.msgCount < 10) {
+ limit -= 2;
+ }
+ } else {
+ limit = SmsConstants.MAX_USER_DATA_BYTES;
+ }
+ }
+
+ String newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(text);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = text;
+ }
+ int pos = 0; // Index in code units.
+ int textLen = newMsgBody.length();
+ ArrayList<String> result = new ArrayList<String>(ted.msgCount);
+ while (pos < textLen) {
+ int nextPos = 0; // Counts code units.
+ if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ if (useCdmaFormatForMoSms() && ted.msgCount == 1) {
+ // For a singleton CDMA message, the encoding must be ASCII...
+ nextPos = pos + Math.min(limit, textLen - pos);
+ } else {
+ // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
+ nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit,
+ ted.languageTable, ted.languageShiftTable);
+ }
+ } else { // Assume unicode.
+ nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody);
+ }
+ if ((nextPos <= pos) || (nextPos > textLen)) {
+ Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
+ nextPos + " >= " + textLen + ")");
+ break;
+ }
+ result.add(newMsgBody.substring(pos, nextPos));
+ pos = nextPos;
+ }
+ return result;
+ }
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message, given the
+ * current encoding.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly if true, characters that are not part of the radio
+ * specific (GSM / CDMA) alphabet encoding are converted to as a
+ * single space characters. If false, a messageBody containing
+ * non-GSM or non-CDMA alphabet characters are encoded using
+ * 16-bit encoding.
+ * @return an int[4] with int[0] being the number of SMS's required, int[1]
+ * the number of code units used, and int[2] is the number of code
+ * units remaining until the next message. int[3] is the encoding
+ * type that should be used for the message.
+ */
+ public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
+ return calculateLength((CharSequence)messageBody, use7bitOnly);
+ }
+
+ /*
+ * TODO(cleanup): It looks like there is now no useful reason why
+ * apps should generate pdus themselves using these routines,
+ * instead of handing the raw data to SMSDispatcher (and thereby
+ * have the phone process do the encoding). Moreover, CDMA now
+ * has shared state (in the form of the msgId system property)
+ * which can only be modified by the phone process, and hence
+ * makes the output of these routines incorrect. Since they now
+ * serve no purpose, they should probably just return null
+ * directly, and be deprecated. Going further in that direction,
+ * the above parsers of serialized pdu data should probably also
+ * be gotten rid of, hiding all but the necessarily visible
+ * structured data from client apps. A possible concern with
+ * doing this is that apps may be using these routines to generate
+ * pdus that are then sent elsewhere, some network server, for
+ * example, and that always returning null would thereby break
+ * otherwise useful apps.
+ */
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message, boolean statusReportRequested) {
+ SubmitPduBase spb;
+
+ if (useCdmaFormatForMoSms()) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested, null);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, short destinationPort, byte[] data,
+ boolean statusReportRequested) {
+ SubmitPduBase spb;
+
+ if (useCdmaFormatForMoSms()) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return mWrappedSmsMessage.getServiceCenterAddress();
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ return mWrappedSmsMessage.getOriginatingAddress();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ */
+ public String getDisplayOriginatingAddress() {
+ return mWrappedSmsMessage.getDisplayOriginatingAddress();
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ */
+ public String getMessageBody() {
+ return mWrappedSmsMessage.getMessageBody();
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public MessageClass getMessageClass() {
+ switch(mWrappedSmsMessage.getMessageClass()) {
+ case CLASS_0: return MessageClass.CLASS_0;
+ case CLASS_1: return MessageClass.CLASS_1;
+ case CLASS_2: return MessageClass.CLASS_2;
+ case CLASS_3: return MessageClass.CLASS_3;
+ default: return MessageClass.UNKNOWN;
+
+ }
+ }
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ */
+ public String getDisplayMessageBody() {
+ return mWrappedSmsMessage.getDisplayMessageBody();
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return mWrappedSmsMessage.getPseudoSubject();
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return mWrappedSmsMessage.getTimestampMillis();
+ }
+
+ /**
+ * Returns true if message is an email.
+ *
+ * @return true if this message came through an email gateway and email
+ * sender / subject / parsed body are available
+ */
+ public boolean isEmail() {
+ return mWrappedSmsMessage.isEmail();
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return mWrappedSmsMessage.getEmailBody();
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return mWrappedSmsMessage.getEmailFrom();
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public int getProtocolIdentifier() {
+ return mWrappedSmsMessage.getProtocolIdentifier();
+ }
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public boolean isReplace() {
+ return mWrappedSmsMessage.isReplace();
+ }
+
+ /**
+ * Returns true for CPHS MWI toggle message.
+ *
+ * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+ * B.4.2
+ */
+ public boolean isCphsMwiMessage() {
+ return mWrappedSmsMessage.isCphsMwiMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public boolean isMWIClearMessage() {
+ return mWrappedSmsMessage.isMWIClearMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public boolean isMWISetMessage() {
+ return mWrappedSmsMessage.isMWISetMessage();
+ }
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public boolean isMwiDontStore() {
+ return mWrappedSmsMessage.isMwiDontStore();
+ }
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return mWrappedSmsMessage.getUserData();
+ }
+
+ /**
+ * Returns the raw PDU for the message.
+ *
+ * @return the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mWrappedSmsMessage.getPdu();
+ }
+
+ /**
+ * Returns the status of the message on the SIM (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the SIM. These are:
+ * SmsManager.STATUS_ON_SIM_FREE
+ * SmsManager.STATUS_ON_SIM_READ
+ * SmsManager.STATUS_ON_SIM_UNREAD
+ * SmsManager.STATUS_ON_SIM_SEND
+ * SmsManager.STATUS_ON_SIM_UNSENT
+ * @deprecated Use getStatusOnIcc instead.
+ */
+ @Deprecated public int getStatusOnSim() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the SIM (1-based index).
+ * @return the record index of the message on the SIM, or -1 if this
+ * SmsMessage was not created from a SIM SMS EF record.
+ * @deprecated Use getIndexOnIcc instead.
+ */
+ @Deprecated public int getIndexOnSim() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * GSM:
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ * CDMA:
+ * For not interfering with status codes from GSM, the value is
+ * shifted to the bits 31-16.
+ * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
+ * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
+ * for a description of other possible values.
+ */
+ public int getStatus() {
+ return mWrappedSmsMessage.getStatus();
+ }
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public boolean isStatusReportMessage() {
+ return mWrappedSmsMessage.isStatusReportMessage();
+ }
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public boolean isReplyPathPresent() {
+ return mWrappedSmsMessage.isReplyPathPresent();
+ }
+
+ /**
+ * Determines whether or not to use CDMA format for MO SMS.
+ * If SMS over IMS is supported, then format is based on IMS SMS format,
+ * otherwise format is based on current phone type.
+ *
+ * @return true if Cdma format should be used for MO SMS, false otherwise.
+ */
+ private static boolean useCdmaFormatForMoSms() {
+ if (!SmsManager.getDefault().isImsSmsSupported()) {
+ // use Voice technology to determine SMS format.
+ return isCdmaVoice();
+ }
+ // IMS is registered with SMS support, check the SMS format supported
+ return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+ }
+
+ /**
+ * Determines whether or not to current phone type is cdma.
+ *
+ * @return true if current phone type is cdma, false otherwise.
+ */
+ private static boolean isCdmaVoice() {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ return (PHONE_TYPE_CDMA == activePhone);
+ }
+
+ /**
+ * Decide if the carrier supports long SMS.
+ * {@hide}
+ */
+ public static boolean hasEmsSupport() {
+ if (!isNoEmsSupportConfigListExisted()) {
+ return true;
+ }
+
+ String simOperator;
+ String gid;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+ gid = TelephonyManager.getDefault().getGroupIdLevel1();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (!TextUtils.isEmpty(simOperator)) {
+ for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+ (TextUtils.isEmpty(currentConfig.mGid1) ||
+ (!TextUtils.isEmpty(currentConfig.mGid1) &&
+ currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check where to add " x/y" in each SMS segment, begin or end.
+ * {@hide}
+ */
+ public static boolean shouldAppendPageNumberAsPrefix() {
+ if (!isNoEmsSupportConfigListExisted()) {
+ return false;
+ }
+
+ String simOperator;
+ String gid;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+ gid = TelephonyManager.getDefault().getGroupIdLevel1();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+ (TextUtils.isEmpty(currentConfig.mGid1) ||
+ (!TextUtils.isEmpty(currentConfig.mGid1)
+ && currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+ return currentConfig.mIsPrefix;
+ }
+ }
+ return false;
+ }
+
+ private static class NoEmsSupportConfig {
+ String mOperatorNumber;
+ String mGid1;
+ boolean mIsPrefix;
+
+ public NoEmsSupportConfig(String[] config) {
+ mOperatorNumber = config[0];
+ mIsPrefix = "prefix".equals(config[1]);
+ mGid1 = config.length > 2 ? config[2] : null;
+ }
+
+ @Override
+ public String toString() {
+ return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber
+ + ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }";
+ }
+ }
+
+ private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null;
+ private static boolean mIsNoEmsSupportConfigListLoaded = false;
+
+ private static boolean isNoEmsSupportConfigListExisted() {
+ if (!mIsNoEmsSupportConfigListLoaded) {
+ Resources r = Resources.getSystem();
+ if (r != null) {
+ String[] listArray = r.getStringArray(
+ com.android.internal.R.array.no_ems_support_sim_operators);
+ if ((listArray != null) && (listArray.length > 0)) {
+ mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
+ for (int i=0; i<listArray.length; i++) {
+ mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
+ }
+ }
+ mIsNoEmsSupportConfigListLoaded = true;
+ }
+ }
+
+ if (mNoEmsSupportConfigList != null && mNoEmsSupportConfigList.length != 0) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/telephony/Telephony.java
new file mode 100644
index 0000000..a91e05e
--- /dev/null
+++ b/telephony/java/android/telephony/Telephony.java
@@ -0,0 +1,3246 @@
+/*
+ * Copyright (C) 2006 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.provider;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
+import android.net.Uri;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Patterns;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SmsApplication;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The Telephony provider contains data related to phone operation, specifically SMS and MMS
+ * messages, access to the APN list, including the MMSC to use, and the service state.
+ *
+ * <p class="note"><strong>Note:</strong> These APIs are not available on all Android-powered
+ * devices. If your app depends on telephony features such as for managing SMS messages, include
+ * a <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}
+ * </a> element in your manifest that declares the {@code "android.hardware.telephony"} hardware
+ * feature. Alternatively, you can check for telephony availability at runtime using either
+ * {@link android.content.pm.PackageManager#hasSystemFeature
+ * hasSystemFeature(PackageManager.FEATURE_TELEPHONY)} or {@link
+ * android.telephony.TelephonyManager#getPhoneType}.</p>
+ *
+ * <h3>Creating an SMS app</h3>
+ *
+ * <p>Only the default SMS app (selected by the user in system settings) is able to write to the
+ * SMS Provider (the tables defined within the {@code Telephony} class) and only the default SMS
+ * app receives the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} broadcast
+ * when the user receives an SMS or the {@link
+ * android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION} broadcast when the user
+ * receives an MMS.</p>
+ *
+ * <p>Any app that wants to behave as the user's default SMS app must handle the following intents:
+ * <ul>
+ * <li>In a broadcast receiver, include an intent filter for {@link Sms.Intents#SMS_DELIVER_ACTION}
+ * (<code>"android.provider.Telephony.SMS_DELIVER"</code>). The broadcast receiver must also
+ * require the {@link android.Manifest.permission#BROADCAST_SMS} permission.
+ * <p>This allows your app to directly receive incoming SMS messages.</p></li>
+ * <li>In a broadcast receiver, include an intent filter for {@link
+ * Sms.Intents#WAP_PUSH_DELIVER_ACTION}} ({@code "android.provider.Telephony.WAP_PUSH_DELIVER"})
+ * with the MIME type <code>"application/vnd.wap.mms-message"</code>.
+ * The broadcast receiver must also require the {@link
+ * android.Manifest.permission#BROADCAST_WAP_PUSH} permission.
+ * <p>This allows your app to directly receive incoming MMS messages.</p></li>
+ * <li>In your activity that delivers new messages, include an intent filter for
+ * {@link android.content.Intent#ACTION_SENDTO} (<code>"android.intent.action.SENDTO"
+ * </code>) with schemas, <code>sms:</code>, <code>smsto:</code>, <code>mms:</code>, and
+ * <code>mmsto:</code>.
+ * <p>This allows your app to receive intents from other apps that want to deliver a
+ * message.</p></li>
+ * <li>In a service, include an intent filter for {@link
+ * android.telephony.TelephonyManager#ACTION_RESPOND_VIA_MESSAGE}
+ * (<code>"android.intent.action.RESPOND_VIA_MESSAGE"</code>) with schemas,
+ * <code>sms:</code>, <code>smsto:</code>, <code>mms:</code>, and <code>mmsto:</code>.
+ * This service must also require the {@link
+ * android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE} permission.
+ * <p>This allows users to respond to incoming phone calls with an immediate text message
+ * using your app.</p></li>
+ * </ul>
+ *
+ * <p>Other apps that are not selected as the default SMS app can only <em>read</em> the SMS
+ * Provider, but may also be notified when a new SMS arrives by listening for the {@link
+ * Sms.Intents#SMS_RECEIVED_ACTION}
+ * broadcast, which is a non-abortable broadcast that may be delivered to multiple apps. This
+ * broadcast is intended for apps that—while not selected as the default SMS app—need to
+ * read special incoming messages such as to perform phone number verification.</p>
+ *
+ * <p>For more information about building SMS apps, read the blog post, <a
+ * href="http://android-developers.blogspot.com/2013/10/getting-your-sms-apps-ready-for-kitkat.html"
+ * >Getting Your SMS Apps Ready for KitKat</a>.</p>
+ *
+ */
+public final class Telephony {
+ private static final String TAG = "Telephony";
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Telephony() {
+ }
+
+ /**
+ * Base columns for tables that contain text-based SMSs.
+ */
+ public interface TextBasedSmsColumns {
+
+ /** Message type: all messages. */
+ public static final int MESSAGE_TYPE_ALL = 0;
+
+ /** Message type: inbox. */
+ public static final int MESSAGE_TYPE_INBOX = 1;
+
+ /** Message type: sent messages. */
+ public static final int MESSAGE_TYPE_SENT = 2;
+
+ /** Message type: drafts. */
+ public static final int MESSAGE_TYPE_DRAFT = 3;
+
+ /** Message type: outbox. */
+ public static final int MESSAGE_TYPE_OUTBOX = 4;
+
+ /** Message type: failed outgoing message. */
+ public static final int MESSAGE_TYPE_FAILED = 5;
+
+ /** Message type: queued to send later. */
+ public static final int MESSAGE_TYPE_QUEUED = 6;
+
+ /**
+ * The type of message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The thread ID of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The address of the other party.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+
+ /**
+ * The date the message was received.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
+ * Has the message been read?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * Has the message been seen by the user? The "seen" flag determines
+ * whether we need to show a notification.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String SEEN = "seen";
+
+ /**
+ * {@code TP-Status} value for the message, or -1 if no status has been received.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String STATUS = "status";
+
+ /** TP-Status: no status received. */
+ public static final int STATUS_NONE = -1;
+ /** TP-Status: complete. */
+ public static final int STATUS_COMPLETE = 0;
+ /** TP-Status: pending. */
+ public static final int STATUS_PENDING = 32;
+ /** TP-Status: failed. */
+ public static final int STATUS_FAILED = 64;
+
+ /**
+ * The subject of the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SUBJECT = "subject";
+
+ /**
+ * The body of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BODY = "body";
+
+ /**
+ * The ID of the sender of the conversation, if present.
+ * <P>Type: INTEGER (reference to item in {@code content://contacts/people})</P>
+ */
+ public static final String PERSON = "person";
+
+ /**
+ * The protocol identifier code.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * Is the {@code TP-Reply-Path} flag set?
+ * <P>Type: BOOLEAN</P>
+ */
+ public static final String REPLY_PATH_PRESENT = "reply_path_present";
+
+ /**
+ * The service center (SC) through which to send the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SERVICE_CENTER = "service_center";
+
+ /**
+ * Is the message locked?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be
+ * < 0 if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
+ * The MTU size of the mobile interface to which the APN connected
+ * @hide
+ */
+ public static final String MTU = "mtu";
+
+ /**
+ * Error code associated with sending or receiving this message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "error_code";
+
+ /**
+ * The identity of the sender of a sent message. It is
+ * usually the package name of the app which sends the message.
+ * <p class="note"><strong>Note:</strong>
+ * This column is read-only. It is set by the provider and can not be changed by apps.
+ * <p>Type: TEXT</p>
+ */
+ public static final String CREATOR = "creator";
+ }
+
+ /**
+ * Contains all text-based SMS messages.
+ */
+ public static final class Sms implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Sms() {
+ }
+
+ /**
+ * Used to determine the currently configured default SMS package.
+ * @param context context of the requesting application
+ * @return package name for the default SMS package or null
+ */
+ public static String getDefaultSmsPackage(Context context) {
+ ComponentName component = SmsApplication.getDefaultSmsApplication(context, false);
+ if (component != null) {
+ return component.getPackageName();
+ }
+ return null;
+ }
+
+ /**
+ * Return cursor for table query.
+ * @hide
+ */
+ public static Cursor query(ContentResolver cr, String[] projection) {
+ return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+ }
+
+ /**
+ * Return cursor for table query.
+ * @hide
+ */
+ public static Cursor query(ContentResolver cr, String[] projection,
+ String where, String orderBy) {
+ return cr.query(CONTENT_URI, projection, where,
+ null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the given URI.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, uri, address, body, subject, date, read, deliveryReport, -1L);
+ }
+
+ /**
+ * Add an SMS to the given URI.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(int subId, ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport) {
+ return addMessageToUri(subId, resolver, uri, address, body, subject,
+ date, read, deliveryReport, -1L);
+ }
+
+ /**
+ * Add an SMS to the given URI with the specified thread ID.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param threadId the thread_id of the message
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport, long threadId) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, uri, address, body, subject,
+ date, read, deliveryReport, threadId);
+ }
+
+ /**
+ * Add an SMS to the given URI with thread_id specified.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param threadId the thread_id of the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(int subId, ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport, long threadId) {
+ ContentValues values = new ContentValues(8);
+ Rlog.v(TAG,"Telephony addMessageToUri sub id: " + subId);
+
+ values.put(SUBSCRIPTION_ID, subId);
+ values.put(ADDRESS, address);
+ if (date != null) {
+ values.put(DATE, date);
+ }
+ values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+ values.put(SUBJECT, subject);
+ values.put(BODY, body);
+ if (deliveryReport) {
+ values.put(STATUS, STATUS_PENDING);
+ }
+ if (threadId != -1L) {
+ values.put(THREAD_ID, threadId);
+ }
+ return resolver.insert(uri, values);
+ }
+
+ /**
+ * Move a message to the given folder.
+ *
+ * @param context the context to use
+ * @param uri the message to move
+ * @param folder the folder to move to
+ * @return true if the operation succeeded
+ * @hide
+ */
+ public static boolean moveMessageToFolder(Context context,
+ Uri uri, int folder, int error) {
+ if (uri == null) {
+ return false;
+ }
+
+ boolean markAsUnread = false;
+ boolean markAsRead = false;
+ switch(folder) {
+ case MESSAGE_TYPE_INBOX:
+ case MESSAGE_TYPE_DRAFT:
+ break;
+ case MESSAGE_TYPE_OUTBOX:
+ case MESSAGE_TYPE_SENT:
+ markAsRead = true;
+ break;
+ case MESSAGE_TYPE_FAILED:
+ case MESSAGE_TYPE_QUEUED:
+ markAsUnread = true;
+ break;
+ default:
+ return false;
+ }
+
+ ContentValues values = new ContentValues(3);
+
+ values.put(TYPE, folder);
+ if (markAsUnread) {
+ values.put(READ, 0);
+ } else if (markAsRead) {
+ values.put(READ, 1);
+ }
+ values.put(ERROR_CODE, error);
+
+ return 1 == SqliteWrapper.update(context, context.getContentResolver(),
+ uri, values, null, null);
+ }
+
+ /**
+ * Returns true iff the folder (message type) identifies an
+ * outgoing message.
+ * @hide
+ */
+ public static boolean isOutgoingFolder(int messageType) {
+ return (messageType == MESSAGE_TYPE_FAILED)
+ || (messageType == MESSAGE_TYPE_OUTBOX)
+ || (messageType == MESSAGE_TYPE_SENT)
+ || (messageType == MESSAGE_TYPE_QUEUED);
+ }
+
+ /**
+ * Contains all text-based SMS messages in the SMS app inbox.
+ */
+ public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Inbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean read) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date, read, false);
+ }
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date, boolean read) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, read, false);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app.
+ */
+ public static final class Sent implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Sent() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/sent");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date, true, false);
+ }
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, true, false);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app.
+ */
+ public static final class Draft implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Draft() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/draft");
+
+ /**
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date, true, false);
+ }
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, true, false);
+ }
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all pending outgoing text-based SMS messages.
+ */
+ public static final class Outbox implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Outbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/outbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the outbox.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param deliveryReport whether a delivery report was requested for the message
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean deliveryReport, long threadId) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date,
+ true, deliveryReport, threadId);
+ }
+
+ /**
+ * Add an SMS to the Out box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param deliveryReport whether a delivery report was requested for the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean deliveryReport, long threadId) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, true, deliveryReport, threadId);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app.
+ */
+ public static final class Conversations
+ implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Conversations() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/conversations");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * The first 45 characters of the body of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SNIPPET = "snippet";
+
+ /**
+ * The number of messages in the conversation.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "msg_count";
+ }
+
+ /**
+ * Contains constants for SMS related Intents that are broadcast.
+ */
+ public static final class Intents {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Intents() {
+ }
+
+ /**
+ * Set by BroadcastReceiver to indicate that the message was handled
+ * successfully.
+ */
+ public static final int RESULT_SMS_HANDLED = 1;
+
+ /**
+ * Set by BroadcastReceiver to indicate a generic error while
+ * processing the message.
+ */
+ public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+ /**
+ * Set by BroadcastReceiver to indicate insufficient memory to store
+ * the message.
+ */
+ public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+ /**
+ * Set by BroadcastReceiver to indicate that the message, while
+ * possibly valid, is of a format or encoding that is not
+ * supported.
+ */
+ public static final int RESULT_SMS_UNSUPPORTED = 4;
+
+ /**
+ * Set by BroadcastReceiver to indicate a duplicate incoming message.
+ */
+ public static final int RESULT_SMS_DUPLICATED = 5;
+
+ /**
+ * Activity action: Ask the user to change the default
+ * SMS application. This will show a dialog that asks the
+ * user whether they want to replace the current default
+ * SMS application with the one specified in
+ * {@link #EXTRA_PACKAGE_NAME}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_DEFAULT =
+ "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+
+ /**
+ * The PackageName string passed in as an
+ * extra for {@link #ACTION_CHANGE_DEFAULT}
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_PACKAGE_NAME = "package";
+
+ /**
+ * Broadcast Action: A new text-based SMS message has been received
+ * by the device. This intent will only be delivered to the default
+ * sms app. That app is responsible for writing the message and notifying
+ * the user. The intent will have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</li>
+ * <li><em>"format"</em> - A String describing the format of the PDUs. It can
+ * be either "3gpp" or "3gpp2".</li>
+ * <li><em>"subscription"</em> - An optional long value of the subscription id which
+ * received the message.</li>
+ * <li><em>"slot"</em> - An optional int value of the SIM slot containing the
+ * subscription.</li>
+ * <li><em>"phone"</em> - An optional int value of the phone id associated with the
+ * subscription.</li>
+ * <li><em>"errorCode"</em> - An optional int error code associated with receiving
+ * the message.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p class="note"><strong>Note:</strong>
+ * The broadcast receiver that filters for this intent must declare
+ * {@link android.Manifest.permission#BROADCAST_SMS} as a required permission in
+ * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+ * <receiver>}</a> tag.
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_DELIVER_ACTION =
+ "android.provider.Telephony.SMS_DELIVER";
+
+ /**
+ * Broadcast Action: A new text-based SMS message has been received
+ * by the device. This intent will be delivered to all registered
+ * receivers as a notification. These apps are not expected to write the
+ * message or notify the user. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_RECEIVED";
+
+ /**
+ * Broadcast Action: A new data based SMS message has been received
+ * by the device. This intent will be delivered to all registered
+ * receivers as a notification. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String DATA_SMS_RECEIVED_ACTION =
+ "android.intent.action.DATA_SMS_RECEIVED";
+
+ /**
+ * Broadcast Action: A new WAP PUSH message has been received by the
+ * device. This intent will only be delivered to the default
+ * sms app. That app is responsible for writing the message and notifying
+ * the user. The intent will have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>"transactionId"</em> - (Integer) The WAP transaction ID</li>
+ * <li><em>"pduType"</em> - (Integer) The WAP PDU type</li>
+ * <li><em>"header"</em> - (byte[]) The header of the message</li>
+ * <li><em>"data"</em> - (byte[]) The data payload of the message</li>
+ * <li><em>"contentTypeParameters" </em>
+ * -(HashMap<String,String>) Any parameters associated with the content type
+ * (decoded from the WSP Content-Type header)</li>
+ * <li><em>"subscription"</em> - An optional long value of the subscription id which
+ * received the message.</li>
+ * <li><em>"slot"</em> - An optional int value of the SIM slot containing the
+ * subscription.</li>
+ * <li><em>"phone"</em> - An optional int value of the phone id associated with the
+ * subscription.</li>
+ * </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>The contentTypeParameters extra value is map of content parameters keyed by
+ * their names.</p>
+ *
+ * <p>If any unassigned well-known parameters are encountered, the key of the map will
+ * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If
+ * a parameter has No-Value the value in the map will be null.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_MMS} or
+ * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to
+ * receive.</p>
+ *
+ * <p class="note"><strong>Note:</strong>
+ * The broadcast receiver that filters for this intent must declare
+ * {@link android.Manifest.permission#BROADCAST_WAP_PUSH} as a required permission in
+ * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+ * <receiver>}</a> tag.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WAP_PUSH_DELIVER_ACTION =
+ "android.provider.Telephony.WAP_PUSH_DELIVER";
+
+ /**
+ * Broadcast Action: A new WAP PUSH message has been received by the
+ * device. This intent will be delivered to all registered
+ * receivers as a notification. These apps are not expected to write the
+ * message or notify the user. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"transactionId"</em> - (Integer) The WAP transaction ID</li>
+ * <li><em>"pduType"</em> - (Integer) The WAP PDU type</li>
+ * <li><em>"header"</em> - (byte[]) The header of the message</li>
+ * <li><em>"data"</em> - (byte[]) The data payload of the message</li>
+ * <li><em>"contentTypeParameters"</em>
+ * - (HashMap<String,String>) Any parameters associated with the content type
+ * (decoded from the WSP Content-Type header)</li>
+ * </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>The contentTypeParameters extra value is map of content parameters keyed by
+ * their names.</p>
+ *
+ * <p>If any unassigned well-known parameters are encountered, the key of the map will
+ * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If
+ * a parameter has No-Value the value in the map will be null.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_MMS} or
+ * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to
+ * receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WAP_PUSH_RECEIVED_ACTION =
+ "android.provider.Telephony.WAP_PUSH_RECEIVED";
+
+ /**
+ * Broadcast Action: A new Cell Broadcast message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"message"</em> - An SmsCbMessage object containing the broadcast message
+ * data. This is not an emergency alert, so ETWS and CMAS data will be null.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_CB_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_CB_RECEIVED";
+
+ /**
+ * Action: A SMS based carrier provision intent. Used to identify default
+ * carrier provisioning app on the device.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @TestApi
+ public static final String SMS_CARRIER_PROVISION_ACTION =
+ "android.provider.Telephony.SMS_CARRIER_PROVISION";
+
+ /**
+ * Broadcast Action: A new Emergency Broadcast message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"message"</em> - An SmsCbMessage object containing the broadcast message
+ * data, including ETWS or CMAS warning notification info if present.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST} to
+ * receive.</p>
+ * @removed
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
+
+ /**
+ * Broadcast Action: A new CDMA SMS has been received containing Service Category
+ * Program Data (updates the list of enabled broadcast channels). The intent will
+ * have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>"operations"</em> - An array of CdmaSmsCbProgramData objects containing
+ * the service category operations (add/delete/clear) to perform.</li>
+ * </ul>
+ *
+ * <p>The extra values can be extracted using
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
+
+ /**
+ * Broadcast Action: The SIM storage for SMS messages is full. If
+ * space is not freed, messages targeted for the SIM (class 2) may
+ * not be saved.
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SIM_FULL_ACTION =
+ "android.provider.Telephony.SIM_FULL";
+
+ /**
+ * Broadcast Action: An incoming SMS has been rejected by the
+ * telephony framework. This intent is sent in lieu of any
+ * of the RECEIVED_ACTION intents. The intent will have the
+ * following extra value:</p>
+ *
+ * <ul>
+ * <li><em>"result"</em> - An int result code, e.g. {@link #RESULT_SMS_OUT_OF_MEMORY}
+ * indicating the error returned to the network.</li>
+ * </ul>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_REJECTED_ACTION =
+ "android.provider.Telephony.SMS_REJECTED";
+
+ /**
+ * Broadcast Action: An incoming MMS has been downloaded. The intent is sent to all
+ * users, except for secondary users where SMS has been disabled and to managed
+ * profiles.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String MMS_DOWNLOADED_ACTION =
+ "android.provider.Telephony.MMS_DOWNLOADED";
+
+ /**
+ * Broadcast action: When the default SMS package changes,
+ * the previous default SMS package and the new default SMS
+ * package are sent this broadcast to notify them of the change.
+ * A boolean is specified in {@link #EXTRA_IS_DEFAULT_SMS_APP} to
+ * indicate whether the package is the new default SMS package.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED =
+ "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+
+ /**
+ * The IsDefaultSmsApp boolean passed as an
+ * extra for {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED} to indicate whether the
+ * SMS app is becoming the default SMS app or is no longer the default.
+ *
+ * @see #ACTION_DEFAULT_SMS_PACKAGE_CHANGED
+ */
+ public static final String EXTRA_IS_DEFAULT_SMS_APP =
+ "android.provider.extra.IS_DEFAULT_SMS_APP";
+
+ /**
+ * Broadcast action: When a change is made to the SmsProvider or
+ * MmsProvider by a process other than the default SMS application,
+ * this intent is broadcast to the default SMS application so it can
+ * re-sync or update the change. The uri that was used to call the provider
+ * can be retrieved from the intent with getData(). The actual affected uris
+ * (which would depend on the selection specified) are not included.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_EXTERNAL_PROVIDER_CHANGE =
+ "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
+
+ /**
+ * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
+ * {@link #DATA_SMS_RECEIVED_ACTION} intent.
+ *
+ * @param intent the intent to read from
+ * @return an array of SmsMessages for the PDUs
+ */
+ public static SmsMessage[] getMessagesFromIntent(Intent intent) {
+ Object[] messages;
+ try {
+ messages = (Object[]) intent.getSerializableExtra("pdus");
+ }
+ catch (ClassCastException e) {
+ Rlog.e(TAG, "getMessagesFromIntent: " + e);
+ return null;
+ }
+
+ if (messages == null) {
+ Rlog.e(TAG, "pdus does not exist in the intent");
+ return null;
+ }
+
+ String format = intent.getStringExtra("format");
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultSmsSubscriptionId());
+
+ Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId);
+
+ int pduCount = messages.length;
+ SmsMessage[] msgs = new SmsMessage[pduCount];
+
+ for (int i = 0; i < pduCount; i++) {
+ byte[] pdu = (byte[]) messages[i];
+ msgs[i] = SmsMessage.createFromPdu(pdu, format);
+ if (msgs[i] != null) msgs[i].setSubId(subId);
+ }
+ return msgs;
+ }
+ }
+ }
+
+ /**
+ * Base columns for tables that contain MMSs.
+ */
+ public interface BaseMmsColumns extends BaseColumns {
+
+ /** Message box: all messages. */
+ public static final int MESSAGE_BOX_ALL = 0;
+ /** Message box: inbox. */
+ public static final int MESSAGE_BOX_INBOX = 1;
+ /** Message box: sent messages. */
+ public static final int MESSAGE_BOX_SENT = 2;
+ /** Message box: drafts. */
+ public static final int MESSAGE_BOX_DRAFTS = 3;
+ /** Message box: outbox. */
+ public static final int MESSAGE_BOX_OUTBOX = 4;
+ /** Message box: failed. */
+ public static final int MESSAGE_BOX_FAILED = 5;
+
+ /**
+ * The thread ID of the message.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The date the message was received.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
+ * The box which the message belongs to, e.g. {@link #MESSAGE_BOX_INBOX}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_BOX = "msg_box";
+
+ /**
+ * Has the message been read?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * Has the message been seen by the user? The "seen" flag determines
+ * whether we need to show a new message notification.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String SEEN = "seen";
+
+ /**
+ * Does the message have only a text part (can also have a subject) with
+ * no picture, slideshow, sound, etc. parts?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String TEXT_ONLY = "text_only";
+
+ /**
+ * The {@code Message-ID} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_ID = "m_id";
+
+ /**
+ * The subject of the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SUBJECT = "sub";
+
+ /**
+ * The character set of the subject, if present.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SUBJECT_CHARSET = "sub_cs";
+
+ /**
+ * The {@code Content-Type} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_TYPE = "ct_t";
+
+ /**
+ * The {@code Content-Location} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_LOCATION = "ct_l";
+
+ /**
+ * The expiry time of the message.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String EXPIRY = "exp";
+
+ /**
+ * The class of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_CLASS = "m_cls";
+
+ /**
+ * The type of the message defined by MMS spec.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_TYPE = "m_type";
+
+ /**
+ * The version of the specification that this message conforms to.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MMS_VERSION = "v";
+
+ /**
+ * The size of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_SIZE = "m_size";
+
+ /**
+ * The priority of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PRIORITY = "pri";
+
+ /**
+ * The {@code read-report} of the message.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ_REPORT = "rr";
+
+ /**
+ * Is read report allowed?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String REPORT_ALLOWED = "rpt_a";
+
+ /**
+ * The {@code response-status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RESPONSE_STATUS = "resp_st";
+
+ /**
+ * The {@code status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String STATUS = "st";
+
+ /**
+ * The {@code transaction-id} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TRANSACTION_ID = "tr_id";
+
+ /**
+ * The {@code retrieve-status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRIEVE_STATUS = "retr_st";
+
+ /**
+ * The {@code retrieve-text} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RETRIEVE_TEXT = "retr_txt";
+
+ /**
+ * The character set of the retrieve-text.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs";
+
+ /**
+ * The {@code read-status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String READ_STATUS = "read_status";
+
+ /**
+ * The {@code content-class} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_CLASS = "ct_cls";
+
+ /**
+ * The {@code delivery-report} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_REPORT = "d_rpt";
+
+ /**
+ * The {@code delivery-time-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String DELIVERY_TIME_TOKEN = "d_tm_tok";
+
+ /**
+ * The {@code delivery-time} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_TIME = "d_tm";
+
+ /**
+ * The {@code response-text} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RESPONSE_TEXT = "resp_txt";
+
+ /**
+ * The {@code sender-visibility} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String SENDER_VISIBILITY = "s_vis";
+
+ /**
+ * The {@code reply-charging} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING = "r_chg";
+
+ /**
+ * The {@code reply-charging-deadline-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok";
+
+ /**
+ * The {@code reply-charging-deadline} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl";
+
+ /**
+ * The {@code reply-charging-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_ID = "r_chg_id";
+
+ /**
+ * The {@code reply-charging-size} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_SIZE = "r_chg_sz";
+
+ /**
+ * The {@code previously-sent-by} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String PREVIOUSLY_SENT_BY = "p_s_by";
+
+ /**
+ * The {@code previously-sent-date} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String PREVIOUSLY_SENT_DATE = "p_s_d";
+
+ /**
+ * The {@code store} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORE = "store";
+
+ /**
+ * The {@code mm-state} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MM_STATE = "mm_st";
+
+ /**
+ * The {@code mm-flags-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MM_FLAGS_TOKEN = "mm_flg_tok";
+
+ /**
+ * The {@code mm-flags} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MM_FLAGS = "mm_flg";
+
+ /**
+ * The {@code store-status} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORE_STATUS = "store_st";
+
+ /**
+ * The {@code store-status-text} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORE_STATUS_TEXT = "store_st_txt";
+
+ /**
+ * The {@code stored} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORED = "stored";
+
+ /**
+ * The {@code totals} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String TOTALS = "totals";
+
+ /**
+ * The {@code mbox-totals} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_TOTALS = "mb_t";
+
+ /**
+ * The {@code mbox-totals-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_TOTALS_TOKEN = "mb_t_tok";
+
+ /**
+ * The {@code quotas} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String QUOTAS = "qt";
+
+ /**
+ * The {@code mbox-quotas} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_QUOTAS = "mb_qt";
+
+ /**
+ * The {@code mbox-quotas-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok";
+
+ /**
+ * The {@code message-count} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MESSAGE_COUNT = "m_cnt";
+
+ /**
+ * The {@code start} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String START = "start";
+
+ /**
+ * The {@code distribution-indicator} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String DISTRIBUTION_INDICATOR = "d_ind";
+
+ /**
+ * The {@code element-descriptor} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String ELEMENT_DESCRIPTOR = "e_des";
+
+ /**
+ * The {@code limit} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String LIMIT = "limit";
+
+ /**
+ * The {@code recommended-retrieval-mode} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod";
+
+ /**
+ * The {@code recommended-retrieval-mode-text} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt";
+
+ /**
+ * The {@code status-text} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STATUS_TEXT = "st_txt";
+
+ /**
+ * The {@code applic-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String APPLIC_ID = "apl_id";
+
+ /**
+ * The {@code reply-applic-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_APPLIC_ID = "r_apl_id";
+
+ /**
+ * The {@code aux-applic-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String AUX_APPLIC_ID = "aux_apl_id";
+
+ /**
+ * The {@code drm-content} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String DRM_CONTENT = "drm_c";
+
+ /**
+ * The {@code adaptation-allowed} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String ADAPTATION_ALLOWED = "adp_a";
+
+ /**
+ * The {@code replace-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLACE_ID = "repl_id";
+
+ /**
+ * The {@code cancel-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String CANCEL_ID = "cl_id";
+
+ /**
+ * The {@code cancel-status} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String CANCEL_STATUS = "cl_st";
+
+ /**
+ * Is the message locked?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be
+ * < 0 if the sub id cannot be determined.
+ * <p>Type: INTEGER (long)</p>
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
+ * The identity of the sender of a sent message. It is
+ * usually the package name of the app which sends the message.
+ * <p class="note"><strong>Note:</strong>
+ * This column is read-only. It is set by the provider and can not be changed by apps.
+ * <p>Type: TEXT</p>
+ */
+ public static final String CREATOR = "creator";
+ }
+
+ /**
+ * Columns for the "canonical_addresses" table used by MMS and SMS.
+ */
+ public interface CanonicalAddressesColumns extends BaseColumns {
+ /**
+ * An address used in MMS or SMS. Email addresses are
+ * converted to lower case and are compared by string
+ * equality. Other addresses are compared using
+ * PHONE_NUMBERS_EQUAL.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+ }
+
+ /**
+ * Columns for the "threads" table used by MMS and SMS.
+ */
+ public interface ThreadsColumns extends BaseColumns {
+
+ /**
+ * The date at which the thread was created.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * A string encoding of the recipient IDs of the recipients of
+ * the message, in numerical order and separated by spaces.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RECIPIENT_IDS = "recipient_ids";
+
+ /**
+ * The message count of the thread.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "message_count";
+
+ /**
+ * Indicates whether all messages of the thread have been read.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * The snippet of the latest message in the thread.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SNIPPET = "snippet";
+
+ /**
+ * The charset of the snippet.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SNIPPET_CHARSET = "snippet_cs";
+
+ /**
+ * Type of the thread, either {@link Threads#COMMON_THREAD} or
+ * {@link Threads#BROADCAST_THREAD}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * Indicates whether there is a transmission error in the thread.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR = "error";
+
+ /**
+ * Indicates whether this thread contains any attachments.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String HAS_ATTACHMENT = "has_attachment";
+
+ /**
+ * If the thread is archived
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String ARCHIVED = "archived";
+ }
+
+ /**
+ * Helper functions for the "threads" table used by MMS and SMS.
+ */
+ public static final class Threads implements ThreadsColumns {
+
+ private static final String[] ID_PROJECTION = { BaseColumns._ID };
+
+ /**
+ * Private {@code content://} style URL for this table. Used by
+ * {@link #getOrCreateThreadId(android.content.Context, java.util.Set)}.
+ */
+ private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+ "content://mms-sms/threadID");
+
+ /**
+ * The {@code content://} style URL for this table, by conversation.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ MmsSms.CONTENT_URI, "conversations");
+
+ /**
+ * The {@code content://} style URL for this table, for obsolete threads.
+ */
+ public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath(
+ CONTENT_URI, "obsolete");
+
+ /** Thread type: common thread. */
+ public static final int COMMON_THREAD = 0;
+
+ /** Thread type: broadcast thread. */
+ public static final int BROADCAST_THREAD = 1;
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Threads() {
+ }
+
+ /**
+ * This is a single-recipient version of {@code getOrCreateThreadId}.
+ * It's convenient for use with SMS messages.
+ * @param context the context object to use.
+ * @param recipient the recipient to send to.
+ */
+ public static long getOrCreateThreadId(Context context, String recipient) {
+ Set<String> recipients = new HashSet<String>();
+
+ recipients.add(recipient);
+ return getOrCreateThreadId(context, recipients);
+ }
+
+ /**
+ * Given the recipients list and subject of an unsaved message,
+ * return its thread ID. If the message starts a new thread,
+ * allocate a new thread ID. Otherwise, use the appropriate
+ * existing thread ID.
+ *
+ * <p>Find the thread ID of the same set of recipients (in any order,
+ * without any additions). If one is found, return it. Otherwise,
+ * return a unique thread ID.</p>
+ */
+ public static long getOrCreateThreadId(
+ Context context, Set<String> recipients) {
+ Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+ for (String recipient : recipients) {
+ if (Mms.isEmailAddress(recipient)) {
+ recipient = Mms.extractAddrSpec(recipient);
+ }
+
+ uriBuilder.appendQueryParameter("recipient", recipient);
+ }
+
+ Uri uri = uriBuilder.build();
+ //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri);
+
+ Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+ uri, ID_PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ } else {
+ Rlog.e(TAG, "getOrCreateThreadId returned no rows!");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ Rlog.e(TAG, "getOrCreateThreadId failed with " + recipients.size() + " recipients");
+ throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+ }
+ }
+
+ /**
+ * Contains all MMS messages.
+ */
+ public static final class Mms implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Mms() {
+ }
+
+ /**
+ * The {@code content://} URI for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://mms");
+
+ /**
+ * Content URI for getting MMS report requests.
+ */
+ public static final Uri REPORT_REQUEST_URI = Uri.withAppendedPath(
+ CONTENT_URI, "report-request");
+
+ /**
+ * Content URI for getting MMS report status.
+ */
+ public static final Uri REPORT_STATUS_URI = Uri.withAppendedPath(
+ CONTENT_URI, "report-status");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Regex pattern for names and email addresses.
+ * <ul>
+ * <li><em>mailbox</em> = {@code name-addr}</li>
+ * <li><em>name-addr</em> = {@code [display-name] angle-addr}</li>
+ * <li><em>angle-addr</em> = {@code [CFWS] "<" addr-spec ">" [CFWS]}</li>
+ * </ul>
+ * @hide
+ */
+ public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+ Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+ /**
+ * Helper method to query this table.
+ * @hide
+ */
+ public static Cursor query(
+ ContentResolver cr, String[] projection) {
+ return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+ }
+
+ /**
+ * Helper method to query this table.
+ * @hide
+ */
+ public static Cursor query(
+ ContentResolver cr, String[] projection,
+ String where, String orderBy) {
+ return cr.query(CONTENT_URI, projection,
+ where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ }
+
+ /**
+ * Helper method to extract email address from address string.
+ * @hide
+ */
+ public static String extractAddrSpec(String address) {
+ Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+ if (match.matches()) {
+ return match.group(2);
+ }
+ return address;
+ }
+
+ /**
+ * Is the specified address an email address?
+ *
+ * @param address the input address to test
+ * @return true if address is an email address; false otherwise.
+ * @hide
+ */
+ public static boolean isEmailAddress(String address) {
+ if (TextUtils.isEmpty(address)) {
+ return false;
+ }
+
+ String s = extractAddrSpec(address);
+ Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
+ return match.matches();
+ }
+
+ /**
+ * Is the specified number a phone number?
+ *
+ * @param number the input number to test
+ * @return true if number is a phone number; false otherwise.
+ * @hide
+ */
+ public static boolean isPhoneNumber(String number) {
+ if (TextUtils.isEmpty(number)) {
+ return false;
+ }
+
+ Matcher match = Patterns.PHONE.matcher(number);
+ return match.matches();
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app inbox.
+ */
+ public static final class Inbox implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Inbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/inbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app sent folder.
+ */
+ public static final class Sent implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Sent() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/sent");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app drafts folder.
+ */
+ public static final class Draft implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Draft() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/drafts");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app outbox.
+ */
+ public static final class Outbox implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Outbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/outbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains address information for an MMS message.
+ */
+ public static final class Addr implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Addr() {
+ }
+
+ /**
+ * The ID of MM which this address entry belongs to.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String MSG_ID = "msg_id";
+
+ /**
+ * The ID of contact entry in Phone Book.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String CONTACT_ID = "contact_id";
+
+ /**
+ * The address text.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+
+ /**
+ * Type of address: must be one of {@code PduHeaders.BCC},
+ * {@code PduHeaders.CC}, {@code PduHeaders.FROM}, {@code PduHeaders.TO}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * Character set of this entry (MMS charset value).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CHARSET = "charset";
+ }
+
+ /**
+ * Contains message parts.
+ */
+ public static final class Part implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Part() {
+ }
+
+ /**
+ * The identifier of the message which this part belongs to.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MSG_ID = "mid";
+
+ /**
+ * The order of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SEQ = "seq";
+
+ /**
+ * The content type of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_TYPE = "ct";
+
+ /**
+ * The name of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NAME = "name";
+
+ /**
+ * The charset of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CHARSET = "chset";
+
+ /**
+ * The file name of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FILENAME = "fn";
+
+ /**
+ * The content disposition of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_DISPOSITION = "cd";
+
+ /**
+ * The content ID of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_ID = "cid";
+
+ /**
+ * The content location of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_LOCATION = "cl";
+
+ /**
+ * The start of content-type of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CT_START = "ctt_s";
+
+ /**
+ * The type of content-type of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CT_TYPE = "ctt_t";
+
+ /**
+ * The location (on filesystem) of the binary data of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String _DATA = "_data";
+
+ /**
+ * The message text.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TEXT = "text";
+ }
+
+ /**
+ * Message send rate table.
+ */
+ public static final class Rate {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Rate() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ Mms.CONTENT_URI, "rate");
+
+ /**
+ * When a message was successfully sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String SENT_TIME = "sent_time";
+ }
+
+ /**
+ * Intents class.
+ */
+ public static final class Intents {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Intents() {
+ }
+
+ /**
+ * Indicates that the contents of specified URIs were changed.
+ * The application which is showing or caching these contents
+ * should be updated.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String CONTENT_CHANGED_ACTION
+ = "android.intent.action.CONTENT_CHANGED";
+
+ /**
+ * An extra field which stores the URI of deleted contents.
+ */
+ public static final String DELETED_CONTENTS = "deleted_contents";
+ }
+ }
+
+ /**
+ * Contains all MMS and SMS messages.
+ */
+ public static final class MmsSms implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private MmsSms() {
+ }
+
+ /**
+ * The column to distinguish SMS and MMS messages in query results.
+ */
+ public static final String TYPE_DISCRIMINATOR_COLUMN =
+ "transport_type";
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/");
+
+ /**
+ * The {@code content://} style URL for this table, by conversation.
+ */
+ public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse(
+ "content://mms-sms/conversations");
+
+ /**
+ * The {@code content://} style URL for this table, by phone number.
+ */
+ public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse(
+ "content://mms-sms/messages/byphone");
+
+ /**
+ * The {@code content://} style URL for undelivered messages in this table.
+ */
+ public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse(
+ "content://mms-sms/undelivered");
+
+ /**
+ * The {@code content://} style URL for draft messages in this table.
+ */
+ public static final Uri CONTENT_DRAFT_URI = Uri.parse(
+ "content://mms-sms/draft");
+
+ /**
+ * The {@code content://} style URL for locked messages in this table.
+ */
+ public static final Uri CONTENT_LOCKED_URI = Uri.parse(
+ "content://mms-sms/locked");
+
+ /**
+ * Pass in a query parameter called "pattern" which is the text to search for.
+ * The sort order is fixed to be: {@code thread_id ASC, date DESC}.
+ */
+ public static final Uri SEARCH_URI = Uri.parse(
+ "content://mms-sms/search");
+
+ // Constants for message protocol types.
+
+ /** SMS protocol type. */
+ public static final int SMS_PROTO = 0;
+
+ /** MMS protocol type. */
+ public static final int MMS_PROTO = 1;
+
+ // Constants for error types of pending messages.
+
+ /** Error type: no error. */
+ public static final int NO_ERROR = 0;
+
+ /** Error type: generic transient error. */
+ public static final int ERR_TYPE_GENERIC = 1;
+
+ /** Error type: SMS protocol transient error. */
+ public static final int ERR_TYPE_SMS_PROTO_TRANSIENT = 2;
+
+ /** Error type: MMS protocol transient error. */
+ public static final int ERR_TYPE_MMS_PROTO_TRANSIENT = 3;
+
+ /** Error type: transport failure. */
+ public static final int ERR_TYPE_TRANSPORT_FAILURE = 4;
+
+ /** Error type: permanent error (along with all higher error values). */
+ public static final int ERR_TYPE_GENERIC_PERMANENT = 10;
+
+ /** Error type: SMS protocol permanent error. */
+ public static final int ERR_TYPE_SMS_PROTO_PERMANENT = 11;
+
+ /** Error type: MMS protocol permanent error. */
+ public static final int ERR_TYPE_MMS_PROTO_PERMANENT = 12;
+
+ /**
+ * Contains pending messages info.
+ */
+ public static final class PendingMessages implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private PendingMessages() {
+ }
+
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ MmsSms.CONTENT_URI, "pending");
+
+ /**
+ * The type of transport protocol (MMS or SMS).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PROTO_TYPE = "proto_type";
+
+ /**
+ * The ID of the message to be sent or downloaded.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String MSG_ID = "msg_id";
+
+ /**
+ * The type of the message to be sent or downloaded.
+ * This field is only valid for MM. For SM, its value is always set to 0.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MSG_TYPE = "msg_type";
+
+ /**
+ * The type of the error code.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_TYPE = "err_type";
+
+ /**
+ * The error code of sending/retrieving process.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "err_code";
+
+ /**
+ * How many times we tried to send or download the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRY_INDEX = "retry_index";
+
+ /**
+ * The time to do next retry.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DUE_TIME = "due_time";
+
+ /**
+ * The time we last tried to send or download the message.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String LAST_TRY = "last_try";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be
+ * < 0 if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ */
+ public static final String SUBSCRIPTION_ID = "pending_sub_id";
+ }
+
+ /**
+ * Words table used by provider for full-text searches.
+ * @hide
+ */
+ public static final class WordsTable {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private WordsTable() {}
+
+ /**
+ * Primary key.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String ID = "_id";
+
+ /**
+ * Source row ID.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String SOURCE_ROW_ID = "source_id";
+
+ /**
+ * Table ID (either 1 or 2).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TABLE_ID = "table_to_use";
+
+ /**
+ * The words to index.
+ * <P>Type: TEXT</P>
+ */
+ public static final String INDEXED_TEXT = "index_text";
+ }
+ }
+
+ /**
+ * Carriers class contains information about APNs, including MMSC information.
+ */
+ public static final class Carriers implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Carriers() {}
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://telephony/carriers");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+ /**
+ * Entry name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NAME = "name";
+
+ /**
+ * APN name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String APN = "apn";
+
+ /**
+ * Proxy address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PROXY = "proxy";
+
+ /**
+ * Proxy port.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PORT = "port";
+
+ /**
+ * MMS proxy address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MMSPROXY = "mmsproxy";
+
+ /**
+ * MMS proxy port.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MMSPORT = "mmsport";
+
+ /**
+ * Server address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SERVER = "server";
+
+ /**
+ * APN username.
+ * <P>Type: TEXT</P>
+ */
+ public static final String USER = "user";
+
+ /**
+ * APN password.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PASSWORD = "password";
+
+ /**
+ * MMSC URL.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MMSC = "mmsc";
+
+ /**
+ * Mobile Country Code (MCC).
+ * <P>Type: TEXT</P>
+ */
+ public static final String MCC = "mcc";
+
+ /**
+ * Mobile Network Code (MNC).
+ * <P>Type: TEXT</P>
+ */
+ public static final String MNC = "mnc";
+
+ /**
+ * Numeric operator ID (as String). Usually {@code MCC + MNC}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NUMERIC = "numeric";
+
+ /**
+ * Authentication type.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String AUTH_TYPE = "authtype";
+
+ /**
+ * Comma-delimited list of APN types.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The protocol to use to connect to this APN.
+ *
+ * One of the {@code PDP_type} values in TS 27.007 section 10.1.1.
+ * For example: {@code IP}, {@code IPV6}, {@code IPV4V6}, or {@code PPP}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * The protocol to use to connect to this APN when roaming.
+ * The syntax is the same as protocol.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ROAMING_PROTOCOL = "roaming_protocol";
+
+ /**
+ * Is this the current APN?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CURRENT = "current";
+
+ /**
+ * Is this APN enabled?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CARRIER_ENABLED = "carrier_enabled";
+
+ /**
+ * Radio Access Technology info.
+ * To check what values are allowed, refer to {@link android.telephony.ServiceState}.
+ * This should be spread to other technologies,
+ * but is currently only used for LTE (14) and eHRPD (13).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String BEARER = "bearer";
+
+ /**
+ * Radio Access Technology bitmask.
+ * To check what values can be contained, refer to {@link android.telephony.ServiceState}.
+ * 0 indicates all techs otherwise first bit refers to RAT/bearer 1, second bit refers to
+ * RAT/bearer 2 and so on.
+ * Bitmask for a radio tech R is (1 << (R - 1))
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String BEARER_BITMASK = "bearer_bitmask";
+
+ /**
+ * MVNO type:
+ * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MVNO_TYPE = "mvno_type";
+
+ /**
+ * MVNO data.
+ * Use the following examples.
+ * <ul>
+ * <li>SPN: A MOBILE, BEN NL, ...</li>
+ * <li>IMSI: 302720x94, 2060188, ...</li>
+ * <li>GID: 4E, 33, ...</li>
+ * </ul>
+ * <P>Type: TEXT</P>
+ */
+ public static final String MVNO_MATCH_DATA = "mvno_match_data";
+
+ /**
+ * The subscription to which the APN belongs to
+ * <p>Type: INTEGER (long) </p>
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
+ * The profile_id to which the APN saved in modem
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String PROFILE_ID = "profile_id";
+
+ /**
+ * Is the apn setting to be set in modem
+ * <P>Type: INTEGER (boolean)</P>
+ *@hide
+ */
+ public static final String MODEM_COGNITIVE = "modem_cognitive";
+
+ /**
+ * The max connections of this apn
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String MAX_CONNS = "max_conns";
+
+ /**
+ * The wait time for retry of the apn
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String WAIT_TIME = "wait_time";
+
+ /**
+ * The time to limit max connection for the apn
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String MAX_CONNS_TIME = "max_conns_time";
+
+ /**
+ * The MTU size of the mobile interface to which the APN connected
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ public static final String MTU = "mtu";
+
+ /**
+ * Is this APN added/edited/deleted by a user or carrier?
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ public static final String EDITED = "edited";
+
+ /**
+ * Is this APN visible to the user?
+ * <p>Type: INTEGER (boolean) </p>
+ * @hide
+ */
+ public static final String USER_VISIBLE = "user_visible";
+
+ /**
+ * Following are possible values for the EDITED field
+ * @hide
+ */
+ public static final int UNEDITED = 0;
+ /**
+ * @hide
+ */
+ public static final int USER_EDITED = 1;
+ /**
+ * @hide
+ */
+ public static final int USER_DELETED = 2;
+ /**
+ * DELETED_BUT_PRESENT is an intermediate value used to indicate that an entry deleted
+ * by the user is still present in the new APN database and therefore must remain tagged
+ * as user deleted rather than completely removed from the database
+ * @hide
+ */
+ public static final int USER_DELETED_BUT_PRESENT_IN_XML = 3;
+ /**
+ * @hide
+ */
+ public static final int CARRIER_EDITED = 4;
+ /**
+ * CARRIER_DELETED values are currently not used as there is no usecase. If they are used,
+ * delete() will have to change accordingly. Currently it is hardcoded to USER_DELETED.
+ * @hide
+ */
+ public static final int CARRIER_DELETED = 5;
+ /**
+ * @hide
+ */
+ public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6;
+ }
+
+ /**
+ * Contains received SMS cell broadcast messages.
+ * @hide
+ */
+ public static final class CellBroadcasts implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private CellBroadcasts() {}
+
+ /**
+ * The {@code content://} URI for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
+
+ /**
+ * Message geographical scope.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+
+ /**
+ * Message serial number.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERIAL_NUMBER = "serial_number";
+
+ /**
+ * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies
+ * a broadcast for duplicate detection purposes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PLMN = "plmn";
+
+ /**
+ * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
+ * Only included if Geographical Scope of message is not PLMN wide (01).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String LAC = "lac";
+
+ /**
+ * Cell ID of message sender (GSM/UMTS). Unused for CDMA. Only included when the
+ * Geographical Scope of message is cell wide (00 or 11).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CID = "cid";
+
+ /**
+ * Message code. <em>OBSOLETE: merged into SERIAL_NUMBER.</em>
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_CODE = "message_code";
+
+ /**
+ * Message identifier. <em>OBSOLETE: renamed to SERVICE_CATEGORY.</em>
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_IDENTIFIER = "message_id";
+
+ /**
+ * Service category (GSM/UMTS: message identifier; CDMA: service category).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERVICE_CATEGORY = "service_category";
+
+ /**
+ * Message language code.
+ * <P>Type: TEXT</P>
+ */
+ public static final String LANGUAGE_CODE = "language";
+
+ /**
+ * Message body.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_BODY = "body";
+
+ /**
+ * Message delivery time.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DELIVERY_TIME = "date";
+
+ /**
+ * Has the message been viewed?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String MESSAGE_READ = "read";
+
+ /**
+ * Message format (3GPP or 3GPP2).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_FORMAT = "format";
+
+ /**
+ * Message priority (including emergency).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_PRIORITY = "priority";
+
+ /**
+ * ETWS warning type (ETWS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+
+ /**
+ * CMAS message class (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+
+ /**
+ * CMAS category (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CATEGORY = "cmas_category";
+
+ /**
+ * CMAS response type (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+
+ /**
+ * CMAS severity (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_SEVERITY = "cmas_severity";
+
+ /**
+ * CMAS urgency (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_URGENCY = "cmas_urgency";
+
+ /**
+ * CMAS certainty (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CERTAINTY = "cmas_certainty";
+
+ /** The default sort order for this table. */
+ public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC";
+
+ /**
+ * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+ */
+ public static final String[] QUERY_COLUMNS = {
+ _ID,
+ GEOGRAPHICAL_SCOPE,
+ PLMN,
+ LAC,
+ CID,
+ SERIAL_NUMBER,
+ SERVICE_CATEGORY,
+ LANGUAGE_CODE,
+ MESSAGE_BODY,
+ DELIVERY_TIME,
+ MESSAGE_READ,
+ MESSAGE_FORMAT,
+ MESSAGE_PRIORITY,
+ ETWS_WARNING_TYPE,
+ CMAS_MESSAGE_CLASS,
+ CMAS_CATEGORY,
+ CMAS_RESPONSE_TYPE,
+ CMAS_SEVERITY,
+ CMAS_URGENCY,
+ CMAS_CERTAINTY
+ };
+ }
+
+ /**
+ * Constants for interfacing with the ServiceStateProvider and the different fields of the
+ * {@link ServiceState} class accessible through the provider.
+ */
+ public static final class ServiceStateTable {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private ServiceStateTable() {}
+
+ /**
+ * The authority string for the ServiceStateProvider
+ */
+ public static final String AUTHORITY = "service-state";
+
+ /**
+ * The {@code content://} style URL for the ServiceStateProvider
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://service-state/");
+
+ /**
+ * Generates a content {@link Uri} used to receive updates on a specific field in the
+ * ServiceState provider.
+ * <p>
+ * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
+ * {@link ServiceState} while your app is running. You can also use a {@link JobService} to
+ * ensure your app is notified of changes to the {@link Uri} even when it is not running.
+ * Note, however, that using a {@link JobService} does not guarantee timely delivery of
+ * updates to the {@link Uri}.
+ *
+ * @param subscriptionId the subscriptionId to receive updates on
+ * @param field the ServiceState field to receive updates on
+ * @return the Uri used to observe {@link ServiceState} changes
+ */
+ public static Uri getUriForSubscriptionIdAndField(int subscriptionId, String field) {
+ return CONTENT_URI.buildUpon().appendEncodedPath(String.valueOf(subscriptionId))
+ .appendEncodedPath(field).build();
+ }
+
+ /**
+ * Generates a content {@link Uri} used to receive updates on every field in the
+ * ServiceState provider.
+ * <p>
+ * Use this {@link Uri} with a {@link ContentObserver} to be notified of changes to the
+ * {@link ServiceState} while your app is running. You can also use a {@link JobService} to
+ * ensure your app is notified of changes to the {@link Uri} even when it is not running.
+ * Note, however, that using a {@link JobService} does not guarantee timely delivery of
+ * updates to the {@link Uri}.
+ *
+ * @param subscriptionId the subscriptionId to receive updates on
+ * @return the Uri used to observe {@link ServiceState} changes
+ */
+ public static Uri getUriForSubscriptionId(int subscriptionId) {
+ return CONTENT_URI.buildUpon()
+ .appendEncodedPath(String.valueOf(subscriptionId)).build();
+ }
+
+ /**
+ * Used to insert a ServiceState into the ServiceStateProvider as a ContentValues instance.
+ *
+ * @param state the ServiceState to convert into ContentValues
+ * @return the convertedContentValues instance
+ * @hide
+ */
+ public static ContentValues getContentValuesForServiceState(ServiceState state) {
+ ContentValues values = new ContentValues();
+ values.put(VOICE_REG_STATE, state.getVoiceRegState());
+ values.put(DATA_REG_STATE, state.getDataRegState());
+ values.put(VOICE_ROAMING_TYPE, state.getVoiceRoamingType());
+ values.put(DATA_ROAMING_TYPE, state.getDataRoamingType());
+ values.put(VOICE_OPERATOR_ALPHA_LONG, state.getVoiceOperatorAlphaLong());
+ values.put(VOICE_OPERATOR_ALPHA_SHORT, state.getVoiceOperatorAlphaShort());
+ values.put(VOICE_OPERATOR_NUMERIC, state.getVoiceOperatorNumeric());
+ values.put(DATA_OPERATOR_ALPHA_LONG, state.getDataOperatorAlphaLong());
+ values.put(DATA_OPERATOR_ALPHA_SHORT, state.getDataOperatorAlphaShort());
+ values.put(DATA_OPERATOR_NUMERIC, state.getDataOperatorNumeric());
+ values.put(IS_MANUAL_NETWORK_SELECTION, state.getIsManualSelection());
+ values.put(RIL_VOICE_RADIO_TECHNOLOGY, state.getRilVoiceRadioTechnology());
+ values.put(RIL_DATA_RADIO_TECHNOLOGY, state.getRilDataRadioTechnology());
+ values.put(CSS_INDICATOR, state.getCssIndicator());
+ values.put(NETWORK_ID, state.getNetworkId());
+ values.put(SYSTEM_ID, state.getSystemId());
+ values.put(CDMA_ROAMING_INDICATOR, state.getCdmaRoamingIndicator());
+ values.put(CDMA_DEFAULT_ROAMING_INDICATOR, state.getCdmaDefaultRoamingIndicator());
+ values.put(CDMA_ERI_ICON_INDEX, state.getCdmaEriIconIndex());
+ values.put(CDMA_ERI_ICON_MODE, state.getCdmaEriIconMode());
+ values.put(IS_EMERGENCY_ONLY, state.isEmergencyOnly());
+ values.put(IS_DATA_ROAMING_FROM_REGISTRATION, state.getDataRoamingFromRegistration());
+ values.put(IS_USING_CARRIER_AGGREGATION, state.isUsingCarrierAggregation());
+ return values;
+ }
+
+ /**
+ * An integer value indicating the current voice service state.
+ * <p>
+ * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+ * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+ * {@link ServiceState#STATE_POWER_OFF}.
+ * <p>
+ * This is the same as {@link ServiceState#getState()}.
+ */
+ public static final String VOICE_REG_STATE = "voice_reg_state";
+
+ /**
+ * An integer value indicating the current data service state.
+ * <p>
+ * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+ * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+ * {@link ServiceState#STATE_POWER_OFF}.
+ * <p>
+ * This is the same as {@link ServiceState#getDataRegState()}.
+ * @hide
+ */
+ public static final String DATA_REG_STATE = "data_reg_state";
+
+ /**
+ * An integer value indicating the current voice roaming type.
+ * <p>
+ * This is the same as {@link ServiceState#getVoiceRoamingType()}.
+ * @hide
+ */
+ public static final String VOICE_ROAMING_TYPE = "voice_roaming_type";
+
+ /**
+ * An integer value indicating the current data roaming type.
+ * <p>
+ * This is the same as {@link ServiceState#getDataRoamingType()}.
+ * @hide
+ */
+ public static final String DATA_ROAMING_TYPE = "data_roaming_type";
+
+ /**
+ * The current registered voice network operator name in long alphanumeric format.
+ * <p>
+ * This is the same as {@link ServiceState#getVoiceOperatorAlphaLong()}.
+ * @hide
+ */
+ public static final String VOICE_OPERATOR_ALPHA_LONG = "voice_operator_alpha_long";
+
+ /**
+ * The current registered operator name in short alphanumeric format.
+ * <p>
+ * In GSM/UMTS, short format can be up to 8 characters long. The current registered voice
+ * network operator name in long alphanumeric format.
+ * <p>
+ * This is the same as {@link ServiceState#getVoiceOperatorAlphaShort()}.
+ * @hide
+ */
+ public static final String VOICE_OPERATOR_ALPHA_SHORT = "voice_operator_alpha_short";
+
+
+ /**
+ * The current registered operator numeric id.
+ * <p>
+ * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
+ * network code.
+ * <p>
+ * This is the same as {@link ServiceState#getOperatorNumeric()}.
+ */
+ public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
+
+ /**
+ * The current registered data network operator name in long alphanumeric format.
+ * <p>
+ * This is the same as {@link ServiceState#getDataOperatorAlphaLong()}.
+ * @hide
+ */
+ public static final String DATA_OPERATOR_ALPHA_LONG = "data_operator_alpha_long";
+
+ /**
+ * The current registered data network operator name in short alphanumeric format.
+ * <p>
+ * This is the same as {@link ServiceState#getDataOperatorAlphaShort()}.
+ * @hide
+ */
+ public static final String DATA_OPERATOR_ALPHA_SHORT = "data_operator_alpha_short";
+
+ /**
+ * The current registered data network operator numeric id.
+ * <p>
+ * This is the same as {@link ServiceState#getDataOperatorNumeric()}.
+ * @hide
+ */
+ public static final String DATA_OPERATOR_NUMERIC = "data_operator_numeric";
+
+ /**
+ * The current network selection mode.
+ * <p>
+ * This is the same as {@link ServiceState#getIsManualSelection()}.
+ */
+ public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
+
+ /**
+ * This is the same as {@link ServiceState#getRilVoiceRadioTechnology()}.
+ * @hide
+ */
+ public static final String RIL_VOICE_RADIO_TECHNOLOGY = "ril_voice_radio_technology";
+
+ /**
+ * This is the same as {@link ServiceState#getRilDataRadioTechnology()}.
+ * @hide
+ */
+ public static final String RIL_DATA_RADIO_TECHNOLOGY = "ril_data_radio_technology";
+
+ /**
+ * This is the same as {@link ServiceState#getCssIndicator()}.
+ * @hide
+ */
+ public static final String CSS_INDICATOR = "css_indicator";
+
+ /**
+ * This is the same as {@link ServiceState#getNetworkId()}.
+ * @hide
+ */
+ public static final String NETWORK_ID = "network_id";
+
+ /**
+ * This is the same as {@link ServiceState#getSystemId()}.
+ * @hide
+ */
+ public static final String SYSTEM_ID = "system_id";
+
+ /**
+ * This is the same as {@link ServiceState#getCdmaRoamingIndicator()}.
+ * @hide
+ */
+ public static final String CDMA_ROAMING_INDICATOR = "cdma_roaming_indicator";
+
+ /**
+ * This is the same as {@link ServiceState#getCdmaDefaultRoamingIndicator()}.
+ * @hide
+ */
+ public static final String CDMA_DEFAULT_ROAMING_INDICATOR =
+ "cdma_default_roaming_indicator";
+
+ /**
+ * This is the same as {@link ServiceState#getCdmaEriIconIndex()}.
+ * @hide
+ */
+ public static final String CDMA_ERI_ICON_INDEX = "cdma_eri_icon_index";
+
+ /**
+ * This is the same as {@link ServiceState#getCdmaEriIconMode()}.
+ * @hide
+ */
+ public static final String CDMA_ERI_ICON_MODE = "cdma_eri_icon_mode";
+
+ /**
+ * This is the same as {@link ServiceState#isEmergencyOnly()}.
+ * @hide
+ */
+ public static final String IS_EMERGENCY_ONLY = "is_emergency_only";
+
+ /**
+ * This is the same as {@link ServiceState#getDataRoamingFromRegistration()}.
+ * @hide
+ */
+ public static final String IS_DATA_ROAMING_FROM_REGISTRATION =
+ "is_data_roaming_from_registration";
+
+ /**
+ * This is the same as {@link ServiceState#isUsingCarrierAggregation()}.
+ * @hide
+ */
+ public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
+ }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5d222d4..32536fc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -24,6 +24,8 @@
import android.annotation.SystemApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.WorkerThread;
+import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -125,6 +127,20 @@
static final int NEVER_USE = 2;
}
+ /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
+ /** @hide */
+ static public final int OTASP_UNINITIALIZED = 0;
+ /** @hide */
+ static public final int OTASP_UNKNOWN = 1;
+ /** @hide */
+ static public final int OTASP_NEEDED = 2;
+ /** @hide */
+ static public final int OTASP_NOT_NEEDED = 3;
+ /* OtaUtil has conflict enum 4: OtaUtils.OTASP_FAILURE_SPC_RETRIES */
+ /** @hide */
+ static public final int OTASP_SIM_UNPROVISIONED = 5;
+
+
private final Context mContext;
private final int mSubId;
private SubscriptionManager mSubscriptionManager;
@@ -883,7 +899,7 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getDeviceSoftwareVersion() {
- return getDeviceSoftwareVersion(getDefaultSim());
+ return getDeviceSoftwareVersion(getSlotIndex());
}
/**
@@ -969,7 +985,7 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getImei() {
- return getImei(getDefaultSim());
+ return getImei(getSlotIndex());
}
/**
@@ -1001,7 +1017,7 @@
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*/
public String getMeid() {
- return getMeid(getDefaultSim());
+ return getMeid(getSlotIndex());
}
/**
@@ -1031,7 +1047,7 @@
*/
/** {@hide}*/
public String getNai() {
- return getNai(getDefaultSim());
+ return getNai(getSlotIndex());
}
/**
@@ -1275,7 +1291,7 @@
}
private int getPhoneTypeFromProperty() {
- return getPhoneTypeFromProperty(getDefaultPhone());
+ return getPhoneTypeFromProperty(getPhoneId());
}
/** {@hide} */
@@ -1289,7 +1305,7 @@
}
private int getPhoneTypeFromNetworkType() {
- return getPhoneTypeFromNetworkType(getDefaultPhone());
+ return getPhoneTypeFromNetworkType(getPhoneId());
}
/** {@hide} */
@@ -1472,7 +1488,7 @@
* on a CDMA network).
*/
public String getNetworkOperator() {
- return getNetworkOperatorForPhone(getDefaultPhone());
+ return getNetworkOperatorForPhone(getPhoneId());
}
/**
@@ -1508,18 +1524,23 @@
/**
- * Returns the network specifier of the subscription ID pinned to the TelephonyManager.
+ * Returns the network specifier of the subscription ID pinned to the TelephonyManager. The
+ * network specifier is used by {@link
+ * android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to create a {@link
+ * android.net.NetworkRequest} that connects through the subscription.
*
* @see android.net.NetworkRequest.Builder#setNetworkSpecifier(String)
* @see #createForSubscriptionId(int)
* @see #createForPhoneAccountHandle(PhoneAccountHandle)
*/
public String getNetworkSpecifier() {
- return String.valueOf(mSubId);
+ return String.valueOf(getSubId());
}
/**
- * Returns the carrier config of the subscription ID pinned to the TelephonyManager.
+ * Returns the carrier config of the subscription ID pinned to the TelephonyManager. If an
+ * invalid subscription ID is pinned to the TelephonyManager, the returned config will contain
+ * default values.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
* READ_PHONE_STATE}
@@ -1528,10 +1549,11 @@
* @see #createForSubscriptionId(int)
* @see #createForPhoneAccountHandle(PhoneAccountHandle)
*/
+ @WorkerThread
public PersistableBundle getCarrierConfig() {
CarrierConfigManager carrierConfigManager = mContext
.getSystemService(CarrierConfigManager.class);
- return carrierConfigManager.getConfigForSubId(mSubId);
+ return carrierConfigManager.getConfigForSubId(getSubId());
}
/**
@@ -1568,7 +1590,7 @@
* on a CDMA network).
*/
public String getNetworkCountryIso() {
- return getNetworkCountryIsoForPhone(getDefaultPhone());
+ return getNetworkCountryIsoForPhone(getPhoneId());
}
/**
@@ -1714,6 +1736,9 @@
* Returns a constant indicating the radio technology (network type)
* currently in use on the device for data transmission.
*
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
* <p>
* Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1738,7 +1763,7 @@
* @see #NETWORK_TYPE_HSPAP
*/
public int getDataNetworkType() {
- return getDataNetworkType(getSubId());
+ return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
}
/**
@@ -1962,7 +1987,7 @@
* @return true if a ICC card is present
*/
public boolean hasIccCard() {
- return hasIccCard(getDefaultSim());
+ return hasIccCard(getSlotIndex());
}
/**
@@ -2003,7 +2028,7 @@
* @see #SIM_STATE_CARD_RESTRICTED
*/
public int getSimState() {
- int slotIndex = getDefaultSim();
+ int slotIndex = getSlotIndex();
// slotIndex may be invalid due to sim being absent. In that case query all slots to get
// sim state
if (slotIndex < 0) {
@@ -2132,7 +2157,7 @@
* @see #getSimState
*/
public String getSimOperatorName() {
- return getSimOperatorNameForPhone(getDefaultPhone());
+ return getSimOperatorNameForPhone(getPhoneId());
}
/**
@@ -2164,7 +2189,7 @@
* Returns the ISO country code equivalent for the SIM provider's country code.
*/
public String getSimCountryIso() {
- return getSimCountryIsoForPhone(getDefaultPhone());
+ return getSimCountryIsoForPhone(getPhoneId());
}
/**
@@ -2712,6 +2737,33 @@
return false;
}
+
+ /**
+ * Returns the package responsible of processing visual voicemail for the subscription ID pinned
+ * to the TelephonyManager. Returns {@code null} when there is no package responsible for
+ * processing visual voicemail for the subscription.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE
+ * READ_PHONE_STATE}
+ *
+ * @see #createForSubscriptionId(int)
+ * @see #createForPhoneAccountHandle(PhoneAccountHandle)
+ * @see VisualVoicemailService
+ */
+ @Nullable
+ public String getVisualVoicemailPackageName() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony
+ .getVisualVoicemailPackageName(mContext.getOpPackageName(), getSubId());
+ }
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return null;
+ }
+
/**
* Enables the visual voicemail SMS filter for a phone account. When the filter is
* enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the
@@ -3982,35 +4034,67 @@
* subId is returned. Otherwise, the default subId will be returned.
*/
private int getSubId() {
- if (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
- return getDefaultSubscription();
+ if (SubscriptionManager.isUsableSubIdValue(mSubId)) {
+ return mSubId;
}
- return mSubId;
+ return SubscriptionManager.getDefaultSubscriptionId();
}
/**
- * Returns Default subscription.
+ * Return an appropriate subscription ID for any situation.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, then the provided
+ * subId is returned. Otherwise, the preferred subId which is based on caller's context is
+ * returned.
+ * {@see SubscriptionManager#getDefaultDataSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultSmsSubscriptionId()}
*/
- private static int getDefaultSubscription() {
- return SubscriptionManager.getDefaultSubscriptionId();
+ private int getSubId(int preferredSubId) {
+ if (SubscriptionManager.isUsableSubIdValue(mSubId)) {
+ return mSubId;
+ }
+ return preferredSubId;
}
/**
- * Returns Default phone.
+ * Return an appropriate phone ID for any situation.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, then the phoneId
+ * associated with the provided subId is returned. Otherwise, the default phoneId associated
+ * with the default subId will be returned.
*/
- private static int getDefaultPhone() {
- return SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId());
+ private int getPhoneId() {
+ return SubscriptionManager.getPhoneId(getSubId());
}
/**
- * @return default SIM's slot index. If SIM is not inserted, return default SIM slot index.
+ * Return an appropriate phone ID for any situation.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, then the phoneId
+ * associated with the provided subId is returned. Otherwise, return the phoneId associated
+ * with the preferred subId based on caller's context.
+ * {@see SubscriptionManager#getDefaultDataSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()}
+ * {@see SubscriptionManager#getDefaultSmsSubscriptionId()}
+ */
+ private int getPhoneId(int preferredSubId) {
+ return SubscriptionManager.getPhoneId(getSubId(preferredSubId));
+ }
+
+ /**
+ * Return an appropriate slot index for any situation.
+ *
+ * if this object has been created with {@link #createForSubscriptionId}, then the slot index
+ * associated with the provided subId is returned. Otherwise, return the slot index associated
+ * with the default subId.
+ * If SIM is not inserted, return default SIM slot index.
*
* {@hide}
*/
@VisibleForTesting
- public int getDefaultSim() {
- int slotIndex = SubscriptionManager.getSlotIndex(
- SubscriptionManager.getDefaultSubscriptionId());
+ public int getSlotIndex() {
+ int slotIndex = SubscriptionManager.getSlotIndex(getSubId());
if (slotIndex == SubscriptionManager.SIM_NOT_INSERTED) {
slotIndex = SubscriptionManager.DEFAULT_SIM_SLOT_INDEX;
}
@@ -4827,7 +4911,7 @@
/** @hide */
@SystemApi
public List<String> getCarrierPackageNamesForIntent(Intent intent) {
- return getCarrierPackageNamesForIntentAndPhone(intent, getDefaultPhone());
+ return getCarrierPackageNamesForIntentAndPhone(intent, getPhoneId());
}
/** @hide */
@@ -5035,26 +5119,21 @@
public void onReceiveUssdResponseFailed(String request, int failureCode) {};
}
- /* <p>Requires permission:
- * @link android.Manifest.permission#CALL_PHONE}
+ /**
+ * Sends an Unstructured Supplementary Service Data (USSD) request to the cellular network and
+ * informs the caller of the response via {@code callback}.
+ * <p>Carriers define USSD codes which can be sent by the user to request information such as
+ * the user's current data balance or minutes balance.
+ * <p>Requires permission:
+ * {@link android.Manifest.permission#CALL_PHONE}
* @param ussdRequest the USSD command to be executed.
- * @param wrappedCallback receives a callback result.
+ * @param callback called by the framework to inform the caller of the result of executing the
+ * USSD request (see {@link OnReceiveUssdResponseCallback}).
+ * @param handler the {@link Handler} to run the request on.
*/
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
public void sendUssdRequest(String ussdRequest,
final OnReceiveUssdResponseCallback callback, Handler handler) {
- sendUssdRequest(ussdRequest, getSubId(), callback, handler);
- }
-
- /* <p>Requires permission:
- * @link android.Manifest.permission#CALL_PHONE}
- * @param subId The subscription to use.
- * @param ussdRequest the USSD command to be executed.
- * @param wrappedCallback receives a callback result.
- */
- @RequiresPermission(android.Manifest.permission.CALL_PHONE)
- public void sendUssdRequest(String ussdRequest, int subId,
- final OnReceiveUssdResponseCallback callback, Handler handler) {
checkNotNull(callback, "OnReceiveUssdResponseCallback cannot be null.");
ResultReceiver wrappedCallback = new ResultReceiver(handler) {
@@ -5076,7 +5155,7 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- telephony.handleUssdRequest(subId, ussdRequest, wrappedCallback);
+ telephony.handleUssdRequest(getSubId(), ussdRequest, wrappedCallback);
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e);
@@ -5087,6 +5166,21 @@
}
}
+ /*
+ * @return true, if the device is currently on a technology (e.g. UMTS or LTE) which can support
+ * voice and data simultaneously. This can change based on location or network condition.
+ */
+ public boolean isConcurrentVoiceAndDataAllowed() {
+ try {
+ ITelephony telephony = getITelephony();
+ return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed(
+ getSubId()));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e);
+ }
+ return false;
+ }
+
/** @hide */
@SystemApi
public boolean handlePinMmi(String dialString) {
@@ -5217,6 +5311,8 @@
/**
* Turns mobile data on or off.
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
@@ -5227,7 +5323,7 @@
* @see #hasCarrierPrivileges
*/
public void setDataEnabled(boolean enable) {
- setDataEnabled(getSubId(), enable);
+ setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
}
/** @hide */
@@ -5243,23 +5339,45 @@
}
}
+
+ /**
+ * @deprecated use {@link #isDataEnabled()} instead.
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ public boolean getDataEnabled() {
+ return isDataEnabled();
+ }
+
/**
* Returns whether mobile data is enabled or not.
*
- * <p>Requires Permission:
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
- * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
- * calling app has carrier privileges.
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the given
+ * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()}
+ *
+ * <p>Requires one of the following permissions:
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+ * calling app has carrier privileges.
+ *
+ * <p>Note that this does not take into account any data restrictions that may be present on the
+ * calling app. Such restrictions may be inspected with
+ * {@link ConnectivityManager#getRestrictBackgroundStatus}.
*
* @return true if mobile data is enabled.
*
* @see #hasCarrierPrivileges
*/
- public boolean getDataEnabled() {
- return getDataEnabled(getSubId());
+ @SuppressWarnings("deprecation")
+ public boolean isDataEnabled() {
+ return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
}
- /** @hide */
+ /**
+ * @deprecated use {@link #isDataEnabled(int)} instead.
+ * @hide
+ */
@SystemApi
public boolean getDataEnabled(int subId) {
boolean retVal = false;
@@ -5503,7 +5621,7 @@
* @hide
*/
public void setSimOperatorNumeric(String numeric) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimOperatorNumericForPhone(phoneId, numeric);
}
@@ -5523,7 +5641,7 @@
* @hide
*/
public void setSimOperatorName(String name) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimOperatorNameForPhone(phoneId, name);
}
@@ -5543,7 +5661,7 @@
* @hide
*/
public void setSimCountryIso(String iso) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimCountryIsoForPhone(phoneId, iso);
}
@@ -5563,7 +5681,7 @@
* @hide
*/
public void setSimState(String state) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setSimStateForPhone(phoneId, state);
}
@@ -5588,7 +5706,7 @@
* @hide
**/
public void setSimPowerState(boolean powerUp) {
- setSimPowerStateForSlot(getDefaultSim(), powerUp);
+ setSimPowerStateForSlot(getSlotIndex(), powerUp);
}
/**
@@ -5622,7 +5740,7 @@
* @hide
*/
public void setBasebandVersion(String version) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setBasebandVersionForPhone(phoneId, version);
}
@@ -5649,7 +5767,7 @@
* @hide
*/
public void setPhoneType(int type) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setPhoneType(phoneId, type);
}
@@ -5677,7 +5795,7 @@
* @hide
*/
public String getOtaSpNumberSchema(String defaultValue) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
return getOtaSpNumberSchemaForPhone(phoneId, defaultValue);
}
@@ -5708,7 +5826,7 @@
* @hide
*/
public boolean getSmsReceiveCapable(boolean defaultValue) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
return getSmsReceiveCapableForPhone(phoneId, defaultValue);
}
@@ -5739,7 +5857,7 @@
* @hide
*/
public boolean getSmsSendCapable(boolean defaultValue) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
return getSmsSendCapableForPhone(phoneId, defaultValue);
}
@@ -5767,7 +5885,7 @@
* @hide
*/
public void setNetworkOperatorName(String name) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkOperatorNameForPhone(phoneId, name);
}
@@ -5789,7 +5907,7 @@
* @hide
*/
public void setNetworkOperatorNumeric(String numeric) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkOperatorNumericForPhone(phoneId, numeric);
}
@@ -5809,7 +5927,7 @@
* @hide
*/
public void setNetworkRoaming(boolean isRoaming) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkRoamingForPhone(phoneId, isRoaming);
}
@@ -5833,7 +5951,7 @@
* @hide
*/
public void setNetworkCountryIso(String iso) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId();
setNetworkCountryIsoForPhone(phoneId, iso);
}
@@ -5853,11 +5971,15 @@
/**
* Set the network type currently in use on the device for data transmission.
+ *
+ * If this object has been created with {@link #createForSubscriptionId}, applies to the
+ * phoneId associated with the given subId. Otherwise, applies to the phoneId associated with
+ * {@link SubscriptionManager#getDefaultDataSubscriptionId()}
* @param type the network type currently in use on the device for data transmission
* @hide
*/
public void setDataNetworkType(int type) {
- int phoneId = getDefaultPhone();
+ int phoneId = getPhoneId(SubscriptionManager.getDefaultDataSubscriptionId());
setDataNetworkTypeForPhone(phoneId, type);
}
@@ -6029,7 +6151,7 @@
* @hide
*/
public String getAidForAppType(int appType) {
- return getAidForAppType(getDefaultSubscription(), appType);
+ return getAidForAppType(getSubId(), appType);
}
/**
@@ -6063,7 +6185,7 @@
* @hide
*/
public String getEsn() {
- return getEsn(getDefaultSubscription());
+ return getEsn(getSubId());
}
/**
@@ -6096,7 +6218,7 @@
* @hide
*/
public String getCdmaPrlVersion() {
- return getCdmaPrlVersion(getDefaultSubscription());
+ return getCdmaPrlVersion(getSubId());
}
/**
diff --git a/telephony/java/android/telephony/VisualVoicemailSms.java b/telephony/java/android/telephony/VisualVoicemailSms.java
index 6235c10..1e6ea4b 100644
--- a/telephony/java/android/telephony/VisualVoicemailSms.java
+++ b/telephony/java/android/telephony/VisualVoicemailSms.java
@@ -63,7 +63,8 @@
/**
* The key-value pairs sent by the SMS, or {@code null} if the framework cannot parse the SMS as
- * voicemail but the carrier pattern indicates it is.
+ * voicemail but the carrier pattern indicates it is. The interpretation of the fields is
+ * carrier dependent.
*/
public Bundle getFields() {
return mFields;
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
index 9d19d08..56a8c62 100644
--- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
@@ -16,7 +16,6 @@
package android.telephony;
import android.content.Context;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -55,8 +54,17 @@
*/
public static final int DESTINATION_PORT_DATA_SMS = -2;
+ /**
+ * @hide
+ */
public static final String DEFAULT_CLIENT_PREFIX = "//VVM";
+ /**
+ * @hide
+ */
public static final List<String> DEFAULT_ORIGINATING_NUMBERS = Collections.emptyList();
+ /**
+ * @hide
+ */
public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY;
/**
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index 38ea6e6f..a75cd86 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -120,7 +120,7 @@
public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
incomingCallIntent, listener);
}
@@ -129,7 +129,7 @@
@Override
public void endSession(int sessionId) throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
}
}
@@ -138,7 +138,7 @@
public boolean isConnected(int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
callServiceType, callType);
}
@@ -147,7 +147,7 @@
@Override
public boolean isOpened() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
}
}
@@ -156,7 +156,7 @@
public void addRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
listener);
}
@@ -166,7 +166,7 @@
public void removeRegistrationListener(IImsRegistrationListener listener)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
listener);
}
@@ -176,7 +176,7 @@
public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
sessionId, callServiceType, callType);
}
@@ -186,7 +186,7 @@
public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
IImsCallSessionListener listener) throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
sessionId, profile, listener);
}
@@ -196,7 +196,7 @@
public IImsCallSession getPendingCallSession(int sessionId, String callId)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
sessionId, callId);
}
@@ -205,7 +205,7 @@
@Override
public IImsUt getUtInterface() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
}
}
@@ -213,7 +213,7 @@
@Override
public IImsConfig getConfigInterface() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
}
}
@@ -221,7 +221,7 @@
@Override
public void turnOnIms() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
}
}
@@ -229,7 +229,7 @@
@Override
public void turnOffIms() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
}
}
@@ -237,7 +237,7 @@
@Override
public IImsEcbm getEcbmInterface() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
}
}
@@ -246,7 +246,7 @@
public void setUiTTYMode(int uiTtyMode, Message onComplete)
throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
onComplete);
}
@@ -255,7 +255,7 @@
@Override
public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
synchronized (mLock) {
- checkBinderConnection();
+ checkServiceIsReady();
return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
mSupportedFeature);
}
@@ -264,7 +264,8 @@
@Override
public int getFeatureStatus() {
synchronized (mLock) {
- if (mFeatureStatusCached != null) {
+ if (isBinderAlive() && mFeatureStatusCached != null) {
+ Log.i(LOG_TAG, "getFeatureStatus - returning cached: " + mFeatureStatusCached);
return mFeatureStatusCached;
}
}
@@ -277,6 +278,7 @@
// Cache only non-null value for feature status.
mFeatureStatusCached = status;
}
+ Log.i(LOG_TAG, "getFeatureStatus - returning " + status);
return status;
}
@@ -301,10 +303,28 @@
mStatusCallback = c;
}
+ /**
+ * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
+ * method returns false, it doesn't mean that the Binder connection is not available (use
+ * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
+ * at this time.
+ *
+ * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
+ * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
+ */
+ public boolean isBinderReady() {
+ return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
+ }
+
@Override
public boolean isBinderAlive() {
- return mIsAvailable && getFeatureStatus() == ImsFeature.STATE_READY && mBinder != null &&
- mBinder.isBinderAlive();
+ return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
+ }
+
+ protected void checkServiceIsReady() throws RemoteException {
+ if (!isBinderReady()) {
+ throw new RemoteException("ImsServiceProxy is not ready to accept commands.");
+ }
}
private IImsServiceController getServiceInterface(IBinder b) {
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 988dd58..395f1cc 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -150,7 +150,7 @@
private void notifyFeatureState(@ImsState int state) {
if (mStatusCallback != null) {
try {
- Log.i(LOG_TAG, "notifying ImsFeatureState");
+ Log.i(LOG_TAG, "notifying ImsFeatureState=" + state);
mStatusCallback.notifyImsFeatureStatus(state);
} catch (RemoteException e) {
mStatusCallback = null;
diff --git a/telephony/java/android/telephony/mbms/DownloadListener.java b/telephony/java/android/telephony/mbms/DownloadCallback.java
similarity index 96%
rename from telephony/java/android/telephony/mbms/DownloadListener.java
rename to telephony/java/android/telephony/mbms/DownloadCallback.java
index 8d1bd02..0c6fec4 100644
--- a/telephony/java/android/telephony/mbms/DownloadListener.java
+++ b/telephony/java/android/telephony/mbms/DownloadCallback.java
@@ -20,7 +20,7 @@
* A optional listener class used by download clients to track progress.
* @hide
*/
-public class DownloadListener extends IDownloadListener.Stub {
+public class DownloadCallback extends IDownloadCallback.Stub {
/**
* Gives process callbacks for a given DownloadRequest.
* request indicates which download is being referenced.
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 42a82da..dbaf10bb 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -16,44 +16,101 @@
package android.telephony.mbms;
-import android.app.PendingIntent;
+import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import java.net.URISyntaxException;
+
/**
* A Parcelable class describing a pending Cell-Broadcast download request
* @hide
*/
public class DownloadRequest implements Parcelable {
- public DownloadRequest(int id, FileServiceInfo serviceInfo, Uri source, Uri dest,
- PendingIntent resultPI, int sub) {
+ /** @hide */
+ public static class Builder {
+ private int id;
+ private FileServiceInfo serviceInfo;
+ private Uri source;
+ private Uri dest;
+ private int sub;
+ private String appIntent;
+
+ public Builder setId(int id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder setServiceInfo(FileServiceInfo serviceInfo) {
+ this.serviceInfo = serviceInfo;
+ return this;
+ }
+
+ public Builder setSource(Uri source) {
+ this.source = source;
+ return this;
+ }
+
+ public Builder setDest(Uri dest) {
+ this.dest = dest;
+ return this;
+ }
+
+ public Builder setSub(int sub) {
+ this.sub = sub;
+ return this;
+ }
+
+ public Builder setAppIntent(Intent intent) {
+ this.appIntent = intent.toUri(0);
+ return this;
+ }
+
+ public DownloadRequest build() {
+ return new DownloadRequest(id, serviceInfo, source, dest, sub, appIntent);
+ }
+ }
+
+ private final int downloadId;
+ private final FileServiceInfo fileServiceInfo;
+ private final Uri sourceUri;
+ private final Uri destinationUri;
+ private final int subId;
+ private final String serializedResultIntentForApp;
+
+ private DownloadRequest(int id, FileServiceInfo serviceInfo,
+ Uri source, Uri dest,
+ int sub, String appIntent) {
downloadId = id;
fileServiceInfo = serviceInfo;
sourceUri = source;
destinationUri = dest;
subId = sub;
+ serializedResultIntentForApp = appIntent;
}
- /** @hide */
- public DownloadRequest(DownloadRequest dr, PendingIntent fdRequestPI, PendingIntent cleanupPI) {
+ public static DownloadRequest copy(DownloadRequest other) {
+ return new DownloadRequest(other);
+ }
+
+ private DownloadRequest(DownloadRequest dr) {
downloadId = dr.downloadId;
fileServiceInfo = dr.fileServiceInfo;
sourceUri = dr.sourceUri;
destinationUri = dr.destinationUri;
subId = dr.subId;
- /*
- * resultPI = new PI
- * fileDescriptorRequstPI = fdRequestPI;
- * this.cleanupPI = cleanupPI;
- */
+ serializedResultIntentForApp = dr.serializedResultIntentForApp;
}
- public final int downloadId;
- public final FileServiceInfo fileServiceInfo;
- public final Uri sourceUri;
- public final Uri destinationUri;
- public final int subId;
+ private DownloadRequest(Parcel in) {
+ downloadId = in.readInt();
+ fileServiceInfo = in.readParcelable(getClass().getClassLoader());
+ sourceUri = in.readParcelable(getClass().getClassLoader());
+ destinationUri = in.readParcelable(getClass().getClassLoader());
+ subId = in.readInt();
+ serializedResultIntentForApp = in.readString();
+ }
public int describeContents() {
return 0;
@@ -65,14 +122,35 @@
out.writeParcelable(sourceUri, flags);
out.writeParcelable(destinationUri, flags);
out.writeInt(subId);
+ out.writeString(serializedResultIntentForApp);
}
- private DownloadRequest(Parcel in) {
- downloadId = in.readInt();
- fileServiceInfo = in.readParcelable(null);
- sourceUri = in.readParcelable(null);
- destinationUri = in.readParcelable(null);
- subId = in.readInt();
+ public int getDownloadId() {
+ return downloadId;
+ }
+
+ public FileServiceInfo getFileServiceInfo() {
+ return fileServiceInfo;
+ }
+
+ public Uri getSourceUri() {
+ return sourceUri;
+ }
+
+ public Uri getDestinationUri() {
+ return destinationUri;
+ }
+
+ public int getSubId() {
+ return subId;
+ }
+
+ public Intent getIntentForApp() {
+ try {
+ return Intent.parseUri(serializedResultIntentForApp, 0);
+ } catch (URISyntaxException e) {
+ return null;
+ }
}
public static final Parcelable.Creator<DownloadRequest> CREATOR =
diff --git a/telephony/java/android/telephony/mbms/IDownloadListener.aidl b/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
similarity index 97%
rename from telephony/java/android/telephony/mbms/IDownloadListener.aidl
rename to telephony/java/android/telephony/mbms/IDownloadCallback.aidl
index 9838682..a6bd7e5 100755
--- a/telephony/java/android/telephony/mbms/IDownloadListener.aidl
+++ b/telephony/java/android/telephony/mbms/IDownloadCallback.aidl
@@ -23,7 +23,7 @@
* The optional interface used by download clients to track progress.
* @hide
*/
-interface IDownloadListener
+interface IDownloadCallback
{
/**
* Gives progress callbacks for a given DownloadRequest. Includes a FileInfo
diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerListener.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
similarity index 96%
rename from telephony/java/android/telephony/mbms/IMbmsDownloadManagerListener.aidl
rename to telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
index 6b5c021..03227d0 100755
--- a/telephony/java/android/telephony/mbms/IMbmsDownloadManagerListener.aidl
+++ b/telephony/java/android/telephony/mbms/IMbmsDownloadManagerCallback.aidl
@@ -24,7 +24,7 @@
* The interface the clients top-level file download listener will satisfy.
* @hide
*/
-interface IMbmsDownloadManagerListener
+interface IMbmsDownloadManagerCallback
{
void error(int errorCode, String message);
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerListener.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
similarity index 97%
rename from telephony/java/android/telephony/mbms/IMbmsStreamingManagerListener.aidl
rename to telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
index 80176bf..cbf0fca 100755
--- a/telephony/java/android/telephony/mbms/IMbmsStreamingManagerListener.aidl
+++ b/telephony/java/android/telephony/mbms/IMbmsStreamingManagerCallback.aidl
@@ -24,7 +24,7 @@
* The interface the clients top-level streaming listener will satisfy.
* @hide
*/
-interface IMbmsStreamingManagerListener
+interface IMbmsStreamingManagerCallback
{
void error(int errorCode, String message);
diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceListener.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
similarity index 84%
rename from telephony/java/android/telephony/mbms/IStreamingServiceListener.aidl
rename to telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
index a41aca3..7b4ecf2 100755
--- a/telephony/java/android/telephony/mbms/IStreamingServiceListener.aidl
+++ b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
@@ -18,13 +18,14 @@
import android.net.Uri;
import android.telephony.SignalStrength;
+import android.telephony.mbms.StreamingService;
/**
* @hide
*/
-oneway interface IStreamingServiceListener {
+oneway interface IStreamingServiceCallback {
void error(int errorCode, String message);
- void stateUpdated(int state);
+ void streamStateChanged(in StreamingService service, int state);
void uriUpdated(in Uri uri);
void signalStrengthUpdated(in SignalStrength signalStrength);
}
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadManagerListener.java b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
similarity index 94%
rename from telephony/java/android/telephony/mbms/MbmsDownloadManagerListener.java
rename to telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
index 04c2f9a..16fafe4 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadManagerListener.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadManagerCallback.java
@@ -22,7 +22,7 @@
* A Parcelable class with Cell-Broadcast service information.
* @hide
*/
-public class MbmsDownloadManagerListener extends IMbmsDownloadManagerListener.Stub {
+public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub {
public final static int ERROR_CARRIER_NOT_SUPPORTED = 1;
public final static int ERROR_UNABLE_TO_INITIALIZE = 2;
diff --git a/telephony/java/android/telephony/mbms/MbmsInitializationException.java b/telephony/java/android/telephony/mbms/MbmsInitializationException.java
new file mode 100644
index 0000000..1612bc9
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/MbmsInitializationException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/** @hide */
+public class MbmsInitializationException extends Exception {
+ private final int mErrorCode;
+
+ /** @hide */
+ public MbmsInitializationException(int errorCode) {
+ super();
+ mErrorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/MbmsStreamingManagerListener.java b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
similarity index 95%
rename from telephony/java/android/telephony/mbms/MbmsStreamingManagerListener.java
rename to telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
index c64ad44..b3bc814 100644
--- a/telephony/java/android/telephony/mbms/MbmsStreamingManagerListener.java
+++ b/telephony/java/android/telephony/mbms/MbmsStreamingManagerCallback.java
@@ -22,7 +22,7 @@
* A Parcelable class with Cell-Broadcast service information.
* @hide
*/
-public class MbmsStreamingManagerListener extends IMbmsStreamingManagerListener.Stub {
+public class MbmsStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub {
public final static int ERROR_CARRIER_NOT_SUPPORTED = 1;
public final static int ERROR_UNABLE_TO_INITIALIZE = 2;
diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java
index ee0d894..8cc6043 100644
--- a/telephony/java/android/telephony/mbms/StreamingService.java
+++ b/telephony/java/android/telephony/mbms/StreamingService.java
@@ -33,7 +33,7 @@
/**
*/
StreamingService(StreamingServiceInfo streamingServiceInfo,
- IStreamingServiceListener listener) {
+ IStreamingServiceCallback listener) {
}
/**
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceListener.java b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
similarity index 93%
rename from telephony/java/android/telephony/mbms/StreamingServiceListener.java
rename to telephony/java/android/telephony/mbms/StreamingServiceCallback.java
index bc5aebb..7f5c486 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceListener.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceCallback.java
@@ -23,7 +23,7 @@
* A Callback class for use when the applicaiton is actively streaming content.
* @hide
*/
-public class StreamingServiceListener extends IStreamingServiceListener.Stub {
+public class StreamingServiceCallback extends IStreamingServiceCallback.Stub {
public void error(int errorCode, String message) {
@@ -36,7 +36,7 @@
* See {@link StreamingService#STATE_STOPPED}, {@link StreamingService#STATE_STARTED}
* and {@link StreamingService#STATE_STALLED}.
*/
- public void stateUpdated(int state) {
+ public void streamStateChanged(StreamingService service, int state) {
// default implementation empty
}
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.aidl b/telephony/java/android/telephony/mbms/UriPathPair.aidl
new file mode 100755
index 0000000..8bf76824
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/UriPathPair.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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;
+
+parcelable UriPathPair;
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java
new file mode 100644
index 0000000..7acc270
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/UriPathPair.java
@@ -0,0 +1,80 @@
+/*
+ * 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.content.ContentResolver;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class UriPathPair implements Parcelable {
+ private final Uri mFilePathUri;
+ private final Uri mContentUri;
+
+ /** @hide */
+ public UriPathPair(Uri fileUri, Uri contentUri) {
+ if (fileUri == null || !ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) {
+ throw new IllegalArgumentException("File URI must have file scheme");
+ }
+ if (contentUri == null || !ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+ throw new IllegalArgumentException("Content URI must have content scheme");
+ }
+
+ mFilePathUri = fileUri;
+ mContentUri = contentUri;
+ }
+
+ /** @hide */
+ protected UriPathPair(Parcel in) {
+ mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
+ mContentUri = in.readParcelable(Uri.class.getClassLoader());
+ }
+
+ public static final Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() {
+ @Override
+ public UriPathPair createFromParcel(Parcel in) {
+ return new UriPathPair(in);
+ }
+
+ @Override
+ public UriPathPair[] newArray(int size) {
+ return new UriPathPair[size];
+ }
+ };
+
+ /** future systemapi */
+ public Uri getFilePathUri() {
+ return mFilePathUri;
+ }
+
+ /** future systemapi */
+ public Uri getContentUri() {
+ return mContentUri;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mFilePathUri, flags);
+ dest.writeParcelable(mContentUri, flags);
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
index 4ec40da..3090e12 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -20,8 +20,8 @@
import android.net.Uri;
import android.telephony.mbms.DownloadRequest;
import android.telephony.mbms.DownloadStatus;
-import android.telephony.mbms.IMbmsDownloadManagerListener;
-import android.telephony.mbms.IDownloadListener;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+import android.telephony.mbms.IDownloadCallback;
/**
* The interface the opaque MbmsStreamingService will satisfy.
@@ -35,7 +35,7 @@
*
* No return value. Async errors may be reported, but none expected (not doing anything yet).
*/
- void initialize(String appName, int subId, IMbmsDownloadManagerListener listener);
+ void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener);
/**
* - Registers serviceClasses of interest with the uid/appName/subId key.
@@ -50,20 +50,20 @@
/**
* should move the params into a DownloadRequest parcelable
*/
- int download(in DownloadRequest downloadRequest, IDownloadListener listener);
+ int download(String appName, in DownloadRequest downloadRequest, IDownloadCallback listener);
- List<DownloadRequest> listPendingDownloads();
+ List<DownloadRequest> listPendingDownloads(String appName);
- int cancelDownload(in DownloadRequest downloadRequest);
+ int cancelDownload(String appName, in DownloadRequest downloadRequest);
- DownloadStatus getDownloadStatus(in DownloadRequest downloadRequest);
+ DownloadStatus getDownloadStatus(String appName, in DownloadRequest downloadRequest);
/*
* named this for 2 reasons:
* 1 don't want 'State' here as it conflicts with 'Status' of the previous function
* 2 want to perfect typing 'Knowledge'
*/
- void resetDownloadKnowledge(in DownloadRequest downloadRequest);
+ void resetDownloadKnowledge(String appName, in DownloadRequest downloadRequest);
/**
* End of life for this MbmsDownloadManager.
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
index cb83969..fbc0931 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
@@ -17,8 +17,8 @@
package android.telephony.mbms.vendor;
import android.net.Uri;
-import android.telephony.mbms.IMbmsStreamingManagerListener;
-import android.telephony.mbms.IStreamingServiceListener;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
import android.telephony.mbms.StreamingService;
import android.telephony.mbms.StreamingServiceInfo;
import android.telephony.SignalStrength;
@@ -34,7 +34,7 @@
* Registers this listener, subId with this appName
*
*/
- int initialize(IMbmsStreamingManagerListener listener, String appName, int subId);
+ int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId);
/**
@@ -55,7 +55,7 @@
* URL-change and State-change pair.
*/
StreamingService startStreaming(String appName, int subId, String serviceId,
- IStreamingServiceListener listener);
+ IStreamingServiceCallback listener);
/**
* Asynchronously fetches all Services being streamed by this uid/appName/subId.
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
new file mode 100644
index 0000000..369aef1
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.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 android.telephony.mbms.vendor;
+
+import android.os.RemoteException;
+import android.telephony.mbms.DownloadRequest;
+import android.telephony.mbms.DownloadStatus;
+import android.telephony.mbms.IDownloadCallback;
+import android.telephony.mbms.IMbmsDownloadManagerCallback;
+
+import java.util.List;
+
+/**
+ * Base class for MbmsDownloadService. The middleware should extend this base class rather than
+ * the aidl stub for compatibility
+ * @hide
+ * TODO: future systemapi
+ */
+public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub {
+ @Override
+ public void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener)
+ throws RemoteException {
+ }
+
+ @Override
+ public int getFileServices(String appName, int subId, List<String> serviceClasses) throws
+ RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int download(String appName, DownloadRequest downloadRequest, IDownloadCallback listener)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public List<DownloadRequest> listPendingDownloads(String appName) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int cancelDownload(String appName, DownloadRequest downloadRequest)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public DownloadStatus getDownloadStatus(String appName, DownloadRequest downloadRequest)
+ throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void resetDownloadKnowledge(String appName, DownloadRequest downloadRequest)
+ throws RemoteException {
+ }
+
+ @Override
+ public void dispose(String appName, int subId) throws RemoteException {
+ }
+}
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
new file mode 100644
index 0000000..1c8ab7c
--- /dev/null
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -0,0 +1,82 @@
+/*
+ * 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.vendor;
+
+import android.net.Uri;
+import android.os.RemoteException;
+import android.telephony.mbms.IMbmsStreamingManagerCallback;
+import android.telephony.mbms.IStreamingServiceCallback;
+import android.telephony.mbms.StreamingService;
+
+import java.util.List;
+
+/**
+ * @hide
+ * TODO: future systemapi
+ */
+public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
+
+ @Override
+ public int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public int getStreamingServices(String appName, int subId, List<String> serviceClasses)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public StreamingService startStreaming(String appName, int subId,
+ String serviceId, IStreamingServiceCallback listener) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public int getActiveStreamingServices(String appName, int subId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public Uri getPlaybackUri(String appName, int subId, String serviceId) throws RemoteException {
+ return null;
+ }
+
+ @Override
+ public void switchStreams(String appName, int subId, String oldServiceId, String newServiceId)
+ throws RemoteException {
+ }
+
+ @Override
+ public int getState(String appName, int subId, String serviceId) throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public void stopStreaming(String appName, int subId, String serviceId) throws RemoteException {
+ }
+
+ @Override
+ public void disposeStream(String appName, int subId, String serviceId) throws RemoteException {
+ }
+
+ @Override
+ public void dispose(String appName, int subId) throws RemoteException {
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index f01e4c0..256e13b 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -106,6 +106,7 @@
public static final int EVENT_REDIRECTION_DETECTED = BASE + 44;
public static final int EVENT_PCO_DATA_RECEIVED = BASE + 45;
public static final int EVENT_SET_CARRIER_DATA_ENABLED = BASE + 46;
+ public static final int EVENT_DATA_RECONNECT = BASE + 47;
/***** Constants *****/
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 6b9c81d..f2e1e26 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -492,12 +492,20 @@
*/
int getVoiceMessageCountForSubscriber(int subId);
+ /**
+ * Returns true if current state supports both voice and data
+ * simultaneously. This can change based on location or network condition.
+ */
+ boolean isConcurrentVoiceAndDataAllowed(int subId);
+
oneway void setVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle, boolean enabled);
boolean isVisualVoicemailEnabled(String callingPackage,
in PhoneAccountHandle accountHandle);
+ String getVisualVoicemailPackageName(String callingPackage, int subId);
+
// Not oneway, caller needs to make sure the vaule is set before receiving a SMS
void enableVisualVoicemailSmsFilter(String callingPackage, int subId,
in VisualVoicemailSmsFilterSettings settings);
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
new file mode 100644
index 0000000..f7f0f29
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2006 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.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.PreciseCallState;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+
+public class PhoneConstantConversions {
+ /**
+ * Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
+ * constants for the public API.
+ */
+ public static int convertCallState(PhoneConstants.State state) {
+ switch (state) {
+ case RINGING:
+ return TelephonyManager.CALL_STATE_RINGING;
+ case OFFHOOK:
+ return TelephonyManager.CALL_STATE_OFFHOOK;
+ default:
+ return TelephonyManager.CALL_STATE_IDLE;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.CALL_STATE_* constants into the
+ * {@link PhoneConstants.State} enum for the public API.
+ */
+ public static PhoneConstants.State convertCallState(int state) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ return PhoneConstants.State.RINGING;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ return PhoneConstants.State.OFFHOOK;
+ default:
+ return PhoneConstants.State.IDLE;
+ }
+ }
+
+ /**
+ * Convert the {@link PhoneConstants.DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataState(PhoneConstants.DataState state) {
+ switch (state) {
+ case CONNECTING:
+ return TelephonyManager.DATA_CONNECTING;
+ case CONNECTED:
+ return TelephonyManager.DATA_CONNECTED;
+ case SUSPENDED:
+ return TelephonyManager.DATA_SUSPENDED;
+ default:
+ return TelephonyManager.DATA_DISCONNECTED;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.DATA_* constants into {@link PhoneConstants.DataState} enum
+ * for the public API.
+ */
+ public static PhoneConstants.DataState convertDataState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_CONNECTING:
+ return PhoneConstants.DataState.CONNECTING;
+ case TelephonyManager.DATA_CONNECTED:
+ return PhoneConstants.DataState.CONNECTED;
+ case TelephonyManager.DATA_SUSPENDED:
+ return PhoneConstants.DataState.SUSPENDED;
+ default:
+ return PhoneConstants.DataState.DISCONNECTED;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 1e1d7a6..954b17f 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -414,6 +414,7 @@
int RIL_REQUEST_SEND_DEVICE_STATE = 138;
int RIL_REQUEST_SET_UNSOLICITED_RESPONSE_FILTER = 139;
int RIL_REQUEST_SET_SIM_CARD_POWER = 140;
+ int RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION = 141;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -465,4 +466,5 @@
int RIL_UNSOL_STK_CC_ALPHA_NOTIFY = 1044;
int RIL_UNSOL_LCEDATA_RECV = 1045;
int RIL_UNSOL_PCO_DATA = 1046;
+ int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1047;
}
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
new file mode 100644
index 0000000..439eaea
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -0,0 +1,235 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.Rlog;
+import android.os.Build;
+import android.util.SparseIntArray;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.telephony.cdma.sms.UserData;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class Sms7BitEncodingTranslator {
+ private static final String TAG = "Sms7BitEncodingTranslator";
+ private static final boolean DBG = Build.IS_DEBUGGABLE ;
+ private static boolean mIs7BitTranslationTableLoaded = false;
+ private static SparseIntArray mTranslationTable = null;
+ private static SparseIntArray mTranslationTableCommon = null;
+ private static SparseIntArray mTranslationTableGSM = null;
+ private static SparseIntArray mTranslationTableCDMA = null;
+
+ // Parser variables
+ private static final String XML_START_TAG = "SmsEnforce7BitTranslationTable";
+ private static final String XML_TRANSLATION_TYPE_TAG = "TranslationType";
+ private static final String XML_CHARACTOR_TAG = "Character";
+ private static final String XML_FROM_TAG = "from";
+ private static final String XML_TO_TAG = "to";
+
+ /**
+ * Translates each message character that is not supported by GSM 7bit
+ * alphabet into a supported one
+ *
+ * @param message
+ * message to be translated
+ * @param throwsException
+ * if true and some error occurs during translation, an exception
+ * is thrown; otherwise a null String is returned
+ * @return translated message or null if some error occur
+ */
+ public static String translate(CharSequence message) {
+ if (message == null) {
+ Rlog.w(TAG, "Null message can not be translated");
+ return null;
+ }
+
+ int size = message.length();
+ if (size <= 0) {
+ return "";
+ }
+
+ if (!mIs7BitTranslationTableLoaded) {
+ mTranslationTableCommon = new SparseIntArray();
+ mTranslationTableGSM = new SparseIntArray();
+ mTranslationTableCDMA = new SparseIntArray();
+ load7BitTranslationTableFromXml();
+ mIs7BitTranslationTableLoaded = true;
+ }
+
+ if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
+ (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
+ (mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
+ char[] output = new char[size];
+ boolean isCdmaFormat = useCdmaFormatForMoSms();
+ for (int i = 0; i < size; i++) {
+ output[i] = translateIfNeeded(message.charAt(i), isCdmaFormat);
+ }
+
+ return String.valueOf(output);
+ }
+
+ return null;
+ }
+
+ /**
+ * Translates a single character into its corresponding acceptable one, if
+ * needed, based on GSM 7-bit alphabet
+ *
+ * @param c
+ * character to be translated
+ * @return original character, if it's present on GSM 7-bit alphabet; a
+ * corresponding character, based on the translation table or white
+ * space, if no mapping is found in the translation table for such
+ * character
+ */
+ private static char translateIfNeeded(char c, boolean isCdmaFormat) {
+ if (noTranslationNeeded(c, isCdmaFormat)) {
+ if (DBG) {
+ Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
+ }
+ return c;
+ }
+
+ /*
+ * Trying to translate unicode to Gsm 7-bit alphabet; If c is not
+ * present on translation table, c does not belong to Unicode Latin-1
+ * (Basic + Supplement), so we don't know how to translate it to a Gsm
+ * 7-bit character! We replace c for an empty space and advises the user
+ * about it.
+ */
+ int translation = -1;
+
+ if (mTranslationTableCommon != null) {
+ translation = mTranslationTableCommon.get(c, -1);
+ }
+
+ if (translation == -1) {
+ if (isCdmaFormat) {
+ if (mTranslationTableCDMA != null) {
+ translation = mTranslationTableCDMA.get(c, -1);
+ }
+ } else {
+ if (mTranslationTableGSM != null) {
+ translation = mTranslationTableGSM.get(c, -1);
+ }
+ }
+ }
+
+ if (translation != -1) {
+ if (DBG) {
+ Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
+ + Integer.toHexString(translation) + " (" + (char) translation + ")");
+ }
+ return (char) translation;
+ } else {
+ if (DBG) {
+ Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
+ + "! Replacing for empty space");
+ }
+ return ' ';
+ }
+ }
+
+ private static boolean noTranslationNeeded(char c, boolean isCdmaFormat) {
+ if (isCdmaFormat) {
+ return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
+ }
+ else {
+ return GsmAlphabet.isGsmSeptets(c);
+ }
+ }
+
+ private static boolean useCdmaFormatForMoSms() {
+ if (!SmsManager.getDefault().isImsSmsSupported()) {
+ // use Voice technology to determine SMS format.
+ return TelephonyManager.getDefault().getCurrentPhoneType()
+ == PhoneConstants.PHONE_TYPE_CDMA;
+ }
+ // IMS is registered with SMS support, check the SMS format supported
+ return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+ }
+
+ /**
+ * Load the whole translation table file from the framework resource
+ * encoded in XML.
+ */
+ private static void load7BitTranslationTableFromXml() {
+ XmlResourceParser parser = null;
+ Resources r = Resources.getSystem();
+
+ if (parser == null) {
+ if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
+ parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
+ }
+
+ try {
+ XmlUtils.beginDocument(parser, XML_START_TAG);
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String tag = parser.getName();
+ if (DBG) {
+ Rlog.d(TAG, "tag: " + tag);
+ }
+ if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
+ String type = parser.getAttributeValue(null, "Type");
+ if (DBG) {
+ Rlog.d(TAG, "type: " + type);
+ }
+ if (type.equals("common")) {
+ mTranslationTable = mTranslationTableCommon;
+ } else if (type.equals("gsm")) {
+ mTranslationTable = mTranslationTableGSM;
+ } else if (type.equals("cdma")) {
+ mTranslationTable = mTranslationTableCDMA;
+ } else {
+ Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
+ }
+ } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
+ int from = parser.getAttributeUnsignedIntValue(null,
+ XML_FROM_TAG, -1);
+ int to = parser.getAttributeUnsignedIntValue(null,
+ XML_TO_TAG, -1);
+ if ((from != -1) && (to != -1)) {
+ if (DBG) {
+ Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
+ .toUpperCase() + " -> " + Integer.toHexString(to)
+ .toUpperCase());
+ }
+ mTranslationTable.put (from, to);
+ } else {
+ Rlog.d(TAG, "Invalid translation table file format");
+ }
+ } else {
+ break;
+ }
+ }
+ if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
+ } catch (Exception e) {
+ Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
+ } finally {
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser)parser).close();
+ }
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsAddress.java b/telephony/java/com/android/internal/telephony/SmsAddress.java
new file mode 100644
index 0000000..b3892cb
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsAddress.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008 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;
+
+public abstract class SmsAddress {
+ // From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
+ // and C.S0005-D table 2.7.1.3.2.4-2
+ public static final int TON_UNKNOWN = 0;
+ public static final int TON_INTERNATIONAL = 1;
+ public static final int TON_NATIONAL = 2;
+ public static final int TON_NETWORK = 3;
+ public static final int TON_SUBSCRIBER = 4;
+ public static final int TON_ALPHANUMERIC = 5;
+ public static final int TON_ABBREVIATED = 6;
+
+ public int ton;
+ public String address;
+ public byte[] origBytes;
+
+ /**
+ * Returns the address of the SMS message in String form or null if unavailable
+ */
+ public String getAddressString() {
+ return address;
+ }
+
+ /**
+ * Returns true if this is an alphanumeric address
+ */
+ public boolean isAlphanumeric() {
+ return ton == TON_ALPHANUMERIC;
+ }
+
+ /**
+ * Returns true if this is a network address
+ */
+ public boolean isNetworkSpecific() {
+ return ton == TON_NETWORK;
+ }
+
+ public boolean couldBeEmailGateway() {
+ // Some carriers seems to send email gateway messages in this form:
+ // from: an UNKNOWN TON, 3 or 4 digits long, beginning with a 5
+ // PID: 0x00, Data coding scheme 0x03
+ // So we just attempt to treat any message from an address length <= 4
+ // as an email gateway
+
+ return address.length() <= 4;
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
new file mode 100644
index 0000000..0d1f205
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -0,0 +1,980 @@
+/*
+ * Copyright (C) 2013 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.Manifest.permission;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.Rlog;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class for managing the primary application that we will deliver SMS/MMS messages to
+ *
+ * {@hide}
+ */
+public final class SmsApplication {
+ static final String LOG_TAG = "SmsApplication";
+ private static final String PHONE_PACKAGE_NAME = "com.android.phone";
+ private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
+ private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
+ private static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
+
+ private static final String SCHEME_SMS = "sms";
+ private static final String SCHEME_SMSTO = "smsto";
+ private static final String SCHEME_MMS = "mms";
+ private static final String SCHEME_MMSTO = "mmsto";
+ private static final boolean DEBUG_MULTIUSER = false;
+
+ private static SmsPackageMonitor sSmsPackageMonitor = null;
+
+ public static class SmsApplicationData {
+ /**
+ * Name of this SMS app for display.
+ */
+ private String mApplicationName;
+
+ /**
+ * Package name for this SMS app.
+ */
+ public String mPackageName;
+
+ /**
+ * The class name of the SMS_DELIVER_ACTION receiver in this app.
+ */
+ private String mSmsReceiverClass;
+
+ /**
+ * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app.
+ */
+ private String mMmsReceiverClass;
+
+ /**
+ * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app.
+ */
+ private String mRespondViaMessageClass;
+
+ /**
+ * The class name of the ACTION_SENDTO intent in this app.
+ */
+ private String mSendToClass;
+
+ /**
+ * The class name of the ACTION_DEFAULT_SMS_PACKAGE_CHANGED receiver in this app.
+ */
+ private String mSmsAppChangedReceiverClass;
+
+ /**
+ * The class name of the ACTION_EXTERNAL_PROVIDER_CHANGE receiver in this app.
+ */
+ private String mProviderChangedReceiverClass;
+
+ /**
+ * The class name of the SIM_FULL_ACTION receiver in this app.
+ */
+ private String mSimFullReceiverClass;
+
+ /**
+ * The user-id for this application
+ */
+ private int mUid;
+
+ /**
+ * Returns true if this SmsApplicationData is complete (all intents handled).
+ * @return
+ */
+ public boolean isComplete() {
+ return (mSmsReceiverClass != null && mMmsReceiverClass != null
+ && mRespondViaMessageClass != null && mSendToClass != null);
+ }
+
+ public SmsApplicationData(String packageName, int uid) {
+ mPackageName = packageName;
+ mUid = uid;
+ }
+
+ public String getApplicationName(Context context) {
+ if (mApplicationName == null) {
+ PackageManager pm = context.getPackageManager();
+ ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfoAsUser(mPackageName, 0,
+ UserHandle.getUserId(mUid));
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ if (appInfo != null) {
+ CharSequence label = pm.getApplicationLabel(appInfo);
+ mApplicationName = (label == null) ? null : label.toString();
+ }
+ }
+ return mApplicationName;
+ }
+
+ @Override
+ public String toString() {
+ return " mPackageName: " + mPackageName
+ + " mSmsReceiverClass: " + mSmsReceiverClass
+ + " mMmsReceiverClass: " + mMmsReceiverClass
+ + " mRespondViaMessageClass: " + mRespondViaMessageClass
+ + " mSendToClass: " + mSendToClass
+ + " mSmsAppChangedClass: " + mSmsAppChangedReceiverClass
+ + " mProviderChangedReceiverClass: " + mProviderChangedReceiverClass
+ + " mSimFullReceiverClass: " + mSimFullReceiverClass
+ + " mUid: " + mUid;
+ }
+ }
+
+ /**
+ * Returns the userId of the Context object, if called from a system app,
+ * otherwise it returns the caller's userId
+ * @param context The context object passed in by the caller.
+ * @return
+ */
+ private static int getIncomingUserId(Context context) {
+ int contextUserId = context.getUserId();
+ final int callingUid = Binder.getCallingUid();
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid="
+ + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4));
+ }
+ if (UserHandle.getAppId(callingUid)
+ < android.os.Process.FIRST_APPLICATION_UID) {
+ return contextUserId;
+ } else {
+ return UserHandle.getUserId(callingUid);
+ }
+ }
+
+ /**
+ * Returns the list of available SMS apps defined as apps that are registered for both the
+ * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
+ * receivers are enabled)
+ *
+ * Requirements to be an SMS application:
+ * Implement SMS_DELIVER_ACTION broadcast receiver.
+ * Require BROADCAST_SMS permission.
+ *
+ * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver.
+ * Require BROADCAST_WAP_PUSH permission.
+ *
+ * Implement RESPOND_VIA_MESSAGE intent.
+ * Support smsto Uri scheme.
+ * Require SEND_RESPOND_VIA_MESSAGE permission.
+ *
+ * Implement ACTION_SENDTO intent.
+ * Support smsto Uri scheme.
+ */
+ public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getApplicationCollectionInternal(context, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static Collection<SmsApplicationData> getApplicationCollectionInternal(
+ Context context, int userId) {
+ PackageManager packageManager = context.getPackageManager();
+
+ // Get the list of apps registered for SMS
+ Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
+ List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ userId);
+
+ HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
+
+ // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
+ for (ResolveInfo resolveInfo : smsReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ if (!receivers.containsKey(packageName)) {
+ final SmsApplicationData smsApplicationData = new SmsApplicationData(packageName,
+ activityInfo.applicationInfo.uid);
+ smsApplicationData.mSmsReceiverClass = activityInfo.name;
+ receivers.put(packageName, smsApplicationData);
+ }
+ }
+
+ // Update any existing entries with mms receiver class
+ intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
+ intent.setDataAndType(null, "application/vnd.wap.mms-message");
+ List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ userId);
+ for (ResolveInfo resolveInfo : mmsReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ smsApplicationData.mMmsReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with respond via message intent class.
+ intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
+ Uri.fromParts(SCHEME_SMSTO, "", null));
+ List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
+ userId);
+ for (ResolveInfo resolveInfo : respondServices) {
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (serviceInfo == null) {
+ continue;
+ }
+ if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
+ continue;
+ }
+ final String packageName = serviceInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ smsApplicationData.mRespondViaMessageClass = serviceInfo.name;
+ }
+ }
+
+ // Update any existing entries with supports send to.
+ intent = new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts(SCHEME_SMSTO, "", null));
+ List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
+ userId);
+ for (ResolveInfo resolveInfo : sendToActivities) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ smsApplicationData.mSendToClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with the default sms changed handler.
+ intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+ List<ResolveInfo> smsAppChangedReceivers =
+ packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
+ smsAppChangedReceivers);
+ }
+ for (ResolveInfo resolveInfo : smsAppChangedReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
+ packageName + " smsApplicationData: " + smsApplicationData +
+ " activityInfo.name: " + activityInfo.name);
+ }
+ if (smsApplicationData != null) {
+ smsApplicationData.mSmsAppChangedReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with the external provider changed handler.
+ intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
+ List<ResolveInfo> providerChangedReceivers =
+ packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
+ providerChangedReceivers);
+ }
+ for (ResolveInfo resolveInfo : providerChangedReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
+ packageName + " smsApplicationData: " + smsApplicationData +
+ " activityInfo.name: " + activityInfo.name);
+ }
+ if (smsApplicationData != null) {
+ smsApplicationData.mProviderChangedReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with the sim full handler.
+ intent = new Intent(Intents.SIM_FULL_ACTION);
+ List<ResolveInfo> simFullReceivers =
+ packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ + simFullReceivers);
+ }
+ for (ResolveInfo resolveInfo : simFullReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal packageName="
+ + packageName + " smsApplicationData: " + smsApplicationData
+ + " activityInfo.name: " + activityInfo.name);
+ }
+ if (smsApplicationData != null) {
+ smsApplicationData.mSimFullReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Remove any entries for which we did not find all required intents.
+ for (ResolveInfo resolveInfo : smsReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ if (!smsApplicationData.isComplete()) {
+ receivers.remove(packageName);
+ }
+ }
+ }
+ return receivers.values();
+ }
+
+ /**
+ * Checks to see if we have a valid installed SMS application for the specified package name
+ * @return Data for the specified package name or null if there isn't one
+ */
+ private static SmsApplicationData getApplicationForPackage(
+ Collection<SmsApplicationData> applications, String packageName) {
+ if (packageName == null) {
+ return null;
+ }
+ // Is there an entry in the application list for the specified package?
+ for (SmsApplicationData application : applications) {
+ if (application.mPackageName.contentEquals(packageName)) {
+ return application;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the application we will use for delivering SMS/MMS messages.
+ *
+ * We return the preferred sms application with the following order of preference:
+ * (1) User selected SMS app (if selected, and if still valid)
+ * (2) Android Messaging (if installed)
+ * (3) The currently configured highest priority broadcast receiver
+ * (4) Null
+ */
+ private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded,
+ int userId) {
+ TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isSmsCapable()) {
+ // No phone, no SMS
+ return null;
+ }
+
+ Collection<SmsApplicationData> applications = getApplicationCollectionInternal(context,
+ userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication userId=" + userId);
+ }
+ // Determine which application receives the broadcast
+ String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
+ }
+
+ SmsApplicationData applicationData = null;
+ if (defaultApplication != null) {
+ applicationData = getApplicationForPackage(applications, defaultApplication);
+ }
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication appData=" + applicationData);
+ }
+ // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
+ // this if the caller asked us to.
+ if (updateIfNeeded && applicationData == null) {
+ // Try to find the default SMS package for this device
+ Resources r = context.getResources();
+ String defaultPackage =
+ r.getString(com.android.internal.R.string.default_sms_application);
+ applicationData = getApplicationForPackage(applications, defaultPackage);
+
+ if (applicationData == null) {
+ // Are there any applications?
+ if (applications.size() != 0) {
+ applicationData = (SmsApplicationData)applications.toArray()[0];
+ }
+ }
+
+ // If we found a new default app, update the setting
+ if (applicationData != null) {
+ setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
+ }
+ }
+
+ // If we found a package, make sure AppOps permissions are set up correctly
+ if (applicationData != null) {
+ AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+ // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we
+ // are checking is for our current uid. Doing this check from the unprivileged current
+ // SMS app allows us to tell the current SMS app that it is not in a good state and
+ // needs to ask to be the current SMS app again to work properly.
+ if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) {
+ // Verify that the SMS app has permissions
+ int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+ applicationData.mPackageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ Rlog.e(LOG_TAG, applicationData.mPackageName + " lost OP_WRITE_SMS: " +
+ (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
+ if (updateIfNeeded) {
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+ applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
+ } else {
+ // We can not return a package if permissions are not set up correctly
+ applicationData = null;
+ }
+ }
+ }
+
+ // We can only verify the phone and BT app's permissions from a privileged caller
+ if (updateIfNeeded) {
+ // Ensure this component is still configured as the preferred activity. Usually the
+ // current SMS app will already be the preferred activity - but checking whether or
+ // not this is true is just as expensive as reconfiguring the preferred activity so
+ // we just reconfigure every time.
+ PackageManager packageManager = context.getPackageManager();
+ configurePreferredActivity(packageManager, new ComponentName(
+ applicationData.mPackageName, applicationData.mSendToClass),
+ userId);
+ // Assign permission to special system apps
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ PHONE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ BLUETOOTH_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ MMS_SERVICE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ TELEPHONY_PROVIDER_PACKAGE_NAME);
+ // Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
+ // apps, all of them should be able to write to telephony provider.
+ // This is to allow the proxy package permission check in telephony provider
+ // to pass.
+ assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
+ }
+ }
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication returning appData=" + applicationData);
+ }
+ return applicationData;
+ }
+
+ /**
+ * Sets the specified package as the default SMS/MMS application. The caller of this method
+ * needs to have permission to set AppOps and write to secure settings.
+ */
+ public static void setDefaultApplication(String packageName, Context context) {
+ TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isSmsCapable()) {
+ // No phone, no SMS
+ return;
+ }
+
+ final int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setDefaultApplicationInternal(packageName, context, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static void setDefaultApplicationInternal(String packageName, Context context,
+ int userId) {
+ // Get old package name
+ String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName +
+ " new=" + packageName);
+ }
+
+ if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
+ // No change
+ return;
+ }
+
+ // We only make the change if the new package is valid
+ PackageManager packageManager = context.getPackageManager();
+ Collection<SmsApplicationData> applications = getApplicationCollection(context);
+ SmsApplicationData oldAppData = oldPackageName != null ?
+ getApplicationForPackage(applications, oldPackageName) : null;
+ SmsApplicationData applicationData = getApplicationForPackage(applications, packageName);
+ if (applicationData != null) {
+ // Ignore OP_WRITE_SMS for the previously configured default SMS app.
+ AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ if (oldPackageName != null) {
+ try {
+ PackageInfo info = packageManager.getPackageInfo(oldPackageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES);
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ oldPackageName, AppOpsManager.MODE_IGNORED);
+ } catch (NameNotFoundException e) {
+ Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+ }
+ }
+
+ // Update the secure setting.
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
+ userId);
+
+ // Configure this as the preferred activity for SENDTO sms/mms intents
+ configurePreferredActivity(packageManager, new ComponentName(
+ applicationData.mPackageName, applicationData.mSendToClass), userId);
+
+ // Allow OP_WRITE_SMS for the newly configured default SMS app.
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+ applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
+
+ // Assign permission to special system apps
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ PHONE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ BLUETOOTH_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ MMS_SERVICE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ TELEPHONY_PROVIDER_PACKAGE_NAME);
+ // Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
+ // apps, all of them should be able to write to telephony provider.
+ // This is to allow the proxy package permission check in telephony provider
+ // to pass.
+ assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
+
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
+ }
+ if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
+ // Notify the old sms app that it's no longer the default
+ final Intent oldAppIntent =
+ new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+ final ComponentName component = new ComponentName(oldAppData.mPackageName,
+ oldAppData.mSmsAppChangedReceiverClass);
+ oldAppIntent.setComponent(component);
+ oldAppIntent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
+ }
+ context.sendBroadcast(oldAppIntent);
+ }
+ // Notify the new sms app that it's now the default (if the new sms app has a receiver
+ // to handle the changed default sms intent).
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" +
+ applicationData);
+ }
+ if (applicationData.mSmsAppChangedReceiverClass != null) {
+ final Intent intent =
+ new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+ final ComponentName component = new ComponentName(applicationData.mPackageName,
+ applicationData.mSmsAppChangedReceiverClass);
+ intent.setComponent(component);
+ intent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + packageName);
+ }
+ context.sendBroadcast(intent);
+ }
+ MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED,
+ applicationData.mPackageName);
+ }
+ }
+
+ /**
+ * Assign WRITE_SMS AppOps permission to some special system apps.
+ *
+ * @param context The context
+ * @param packageManager The package manager instance
+ * @param appOps The AppOps manager instance
+ * @param packageName The package name of the system app
+ */
+ private static void assignWriteSmsPermissionToSystemApp(Context context,
+ PackageManager packageManager, AppOpsManager appOps, String packageName) {
+ // First check package signature matches the caller's package signature.
+ // Since this class is only used internally by the system, this check makes sure
+ // the package signature matches system signature.
+ final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
+ if (result != PackageManager.SIGNATURE_MATCH) {
+ Rlog.e(LOG_TAG, packageName + " does not have system signature");
+ return;
+ }
+ try {
+ PackageInfo info = packageManager.getPackageInfo(packageName, 0);
+ int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ packageName, AppOpsManager.MODE_ALLOWED);
+ }
+ } catch (NameNotFoundException e) {
+ // No whitelisted system app on this device
+ Rlog.e(LOG_TAG, "Package not found: " + packageName);
+ }
+
+ }
+
+ private static void assignWriteSmsPermissionToSystemUid(AppOpsManager appOps, int uid) {
+ appOps.setUidMode(AppOpsManager.OP_WRITE_SMS, uid, AppOpsManager.MODE_ALLOWED);
+ }
+
+ /**
+ * Tracks package changes and ensures that the default SMS app is always configured to be the
+ * preferred activity for SENDTO sms/mms intents.
+ */
+ private static final class SmsPackageMonitor extends PackageMonitor {
+ final Context mContext;
+
+ public SmsPackageMonitor(Context context) {
+ super();
+ mContext = context;
+ }
+
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ onPackageChanged();
+ }
+
+ @Override
+ public void onPackageAppeared(String packageName, int reason) {
+ onPackageChanged();
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ onPackageChanged();
+ }
+
+ private void onPackageChanged() {
+ PackageManager packageManager = mContext.getPackageManager();
+ Context userContext = mContext;
+ final int userId = getSendingUserId();
+ if (userId != UserHandle.USER_SYSTEM) {
+ try {
+ userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
+ new UserHandle(userId));
+ } catch (NameNotFoundException nnfe) {
+ if (DEBUG_MULTIUSER) {
+ Log.w(LOG_TAG, "Unable to create package context for user " + userId);
+ }
+ }
+ }
+ // Ensure this component is still configured as the preferred activity
+ ComponentName componentName = getDefaultSendToApplication(userContext, true);
+ if (componentName != null) {
+ configurePreferredActivity(packageManager, componentName, userId);
+ }
+ }
+ }
+
+ public static void initSmsPackageMonitor(Context context) {
+ sSmsPackageMonitor = new SmsPackageMonitor(context);
+ sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false);
+ }
+
+ private static void configurePreferredActivity(PackageManager packageManager,
+ ComponentName componentName, int userId) {
+ // Add the four activity preferences we want to direct to this app.
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
+ }
+
+ /**
+ * Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
+ */
+ private static void replacePreferredActivity(PackageManager packageManager,
+ ComponentName componentName, int userId, String scheme) {
+ // Build the set of existing activities that handle this scheme
+ Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
+ userId);
+
+ // Build the set of ComponentNames for these activities
+ final int n = resolveInfoList.size();
+ ComponentName[] set = new ComponentName[n];
+ for (int i = 0; i < n; i++) {
+ ResolveInfo info = resolveInfoList.get(i);
+ set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+ }
+
+ // Update the preferred SENDTO activity for the specified scheme
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SENDTO);
+ intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ intentFilter.addDataScheme(scheme);
+ packageManager.replacePreferredActivityAsUser(intentFilter,
+ IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
+ set, componentName, userId);
+ }
+
+ /**
+ * Returns SmsApplicationData for this package if this package is capable of being set as the
+ * default SMS application.
+ */
+ public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
+ Collection<SmsApplicationData> applications = getApplicationCollection(context);
+ return getApplicationForPackage(applications, packageName);
+ }
+
+ /**
+ * Gets the default SMS application
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver SMS messages to
+ */
+ public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mSmsReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default MMS application
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver MMS messages to
+ */
+ public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mMmsReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default Respond Via Message application
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to direct Respond Via Message intent to
+ */
+ public static ComponentName getDefaultRespondViaMessageApplication(Context context,
+ boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mRespondViaMessageClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default Send To (smsto) application.
+ * <p>
+ * Caller must pass in the correct user context if calling from a singleton service.
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to direct SEND_TO (smsto) intent to
+ */
+ public static ComponentName getDefaultSendToApplication(Context context,
+ boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mSendToClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default application that handles external changes to the SmsProvider and
+ * MmsProvider.
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver change intents to
+ */
+ public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
+ Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null
+ && smsApplicationData.mProviderChangedReceiverClass != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mProviderChangedReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default application that handles sim full event.
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver change intents to
+ */
+ public static ComponentName getDefaultSimFullApplication(
+ Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null
+ && smsApplicationData.mSimFullReceiverClass != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mSimFullReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Returns whether need to write the SMS message to SMS database for this package.
+ * <p>
+ * Caller must pass in the correct user context if calling from a singleton service.
+ */
+ public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
+ if (SmsManager.getDefault().getAutoPersisting()) {
+ return true;
+ }
+ return !isDefaultSmsApplication(context, packageName);
+ }
+
+ /**
+ * Check if a package is default sms app (or equivalent, like bluetooth)
+ *
+ * @param context context from the calling app
+ * @param packageName the name of the package to be checked
+ * @return true if the package is default sms app or bluetooth
+ */
+ public static boolean isDefaultSmsApplication(Context context, String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+ final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
+ if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
+ || BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String getDefaultSmsApplicationPackageName(Context context) {
+ final ComponentName component = getDefaultSmsApplication(context, false);
+ if (component != null) {
+ return component.getPackageName();
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000..c912924
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
+ * 3GPP TS 23.041 (for GSM/UMTS).
+ *
+ * {@hide}
+ */
+public class SmsCbCmasInfo implements Parcelable {
+
+ // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
+
+ /** Presidential-level alert (Korean Public Alert System Class 0 message). */
+ public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
+
+ /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
+ public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
+
+ /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
+ public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
+
+ /** Child abduction emergency (AMBER Alert). */
+ public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
+
+ /** CMAS test message. */
+ public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
+
+ /** CMAS exercise. */
+ public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
+
+ /** CMAS category for operator defined use. */
+ public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
+
+ /** CMAS category for warning types that are reserved for future extension. */
+ public static final int CMAS_CLASS_UNKNOWN = -1;
+
+ // CMAS alert category (in CDMA type 1 elements record).
+
+ /** CMAS alert category: Geophysical including landslide. */
+ public static final int CMAS_CATEGORY_GEO = 0x00;
+
+ /** CMAS alert category: Meteorological including flood. */
+ public static final int CMAS_CATEGORY_MET = 0x01;
+
+ /** CMAS alert category: General emergency and public safety. */
+ public static final int CMAS_CATEGORY_SAFETY = 0x02;
+
+ /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
+ public static final int CMAS_CATEGORY_SECURITY = 0x03;
+
+ /** CMAS alert category: Rescue and recovery. */
+ public static final int CMAS_CATEGORY_RESCUE = 0x04;
+
+ /** CMAS alert category: Fire suppression and rescue. */
+ public static final int CMAS_CATEGORY_FIRE = 0x05;
+
+ /** CMAS alert category: Medical and public health. */
+ public static final int CMAS_CATEGORY_HEALTH = 0x06;
+
+ /** CMAS alert category: Pollution and other environmental. */
+ public static final int CMAS_CATEGORY_ENV = 0x07;
+
+ /** CMAS alert category: Public and private transportation. */
+ public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
+
+ /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
+ public static final int CMAS_CATEGORY_INFRA = 0x09;
+
+ /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
+ public static final int CMAS_CATEGORY_CBRNE = 0x0a;
+
+ /** CMAS alert category: Other events. */
+ public static final int CMAS_CATEGORY_OTHER = 0x0b;
+
+ /**
+ * CMAS alert category is unknown. The category is only available for CDMA broadcasts
+ * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+ */
+ public static final int CMAS_CATEGORY_UNKNOWN = -1;
+
+ // CMAS response type (in CDMA type 1 elements record).
+
+ /** CMAS response type: Take shelter in place. */
+ public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
+
+ /** CMAS response type: Evacuate (Relocate). */
+ public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
+
+ /** CMAS response type: Make preparations. */
+ public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
+
+ /** CMAS response type: Execute a pre-planned activity. */
+ public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
+
+ /** CMAS response type: Attend to information sources. */
+ public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
+
+ /** CMAS response type: Avoid hazard. */
+ public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
+
+ /** CMAS response type: Evaluate the information in this message (not for public warnings). */
+ public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
+
+ /** CMAS response type: No action recommended. */
+ public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
+
+ /**
+ * CMAS response type is unknown. The response type is only available for CDMA broadcasts
+ * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+ */
+ public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
+
+ // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS severity type: Extraordinary threat to life or property. */
+ public static final int CMAS_SEVERITY_EXTREME = 0x0;
+
+ /** CMAS severity type: Significant threat to life or property. */
+ public static final int CMAS_SEVERITY_SEVERE = 0x1;
+
+ /**
+ * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_SEVERITY_UNKNOWN = -1;
+
+ // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS urgency type: Responsive action should be taken immediately. */
+ public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
+
+ /** CMAS urgency type: Responsive action should be taken within the next hour. */
+ public static final int CMAS_URGENCY_EXPECTED = 0x1;
+
+ /**
+ * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_URGENCY_UNKNOWN = -1;
+
+ // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS certainty type: Determined to have occurred or to be ongoing. */
+ public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
+
+ /** CMAS certainty type: Likely (probability > ~50%). */
+ public static final int CMAS_CERTAINTY_LIKELY = 0x1;
+
+ /**
+ * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_CERTAINTY_UNKNOWN = -1;
+
+ /** CMAS message class. */
+ private final int mMessageClass;
+
+ /** CMAS category. */
+ private final int mCategory;
+
+ /** CMAS response type. */
+ private final int mResponseType;
+
+ /** CMAS severity. */
+ private final int mSeverity;
+
+ /** CMAS urgency. */
+ private final int mUrgency;
+
+ /** CMAS certainty. */
+ private final int mCertainty;
+
+ /** Create a new SmsCbCmasInfo object with the specified values. */
+ public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
+ int urgency, int certainty) {
+ mMessageClass = messageClass;
+ mCategory = category;
+ mResponseType = responseType;
+ mSeverity = severity;
+ mUrgency = urgency;
+ mCertainty = certainty;
+ }
+
+ /** Create a new SmsCbCmasInfo object from a Parcel. */
+ SmsCbCmasInfo(Parcel in) {
+ mMessageClass = in.readInt();
+ mCategory = in.readInt();
+ mResponseType = in.readInt();
+ mSeverity = in.readInt();
+ mUrgency = in.readInt();
+ mCertainty = in.readInt();
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageClass);
+ dest.writeInt(mCategory);
+ dest.writeInt(mResponseType);
+ dest.writeInt(mSeverity);
+ dest.writeInt(mUrgency);
+ dest.writeInt(mCertainty);
+ }
+
+ /**
+ * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
+ * @return one of the {@code CMAS_CLASS} values
+ */
+ public int getMessageClass() {
+ return mMessageClass;
+ }
+
+ /**
+ * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
+ * @return one of the {@code CMAS_CATEGORY} values
+ */
+ public int getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
+ * @return one of the {@code CMAS_RESPONSE_TYPE} values
+ */
+ public int getResponseType() {
+ return mResponseType;
+ }
+
+ /**
+ * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
+ * @return one of the {@code CMAS_SEVERITY} values
+ */
+ public int getSeverity() {
+ return mSeverity;
+ }
+
+ /**
+ * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
+ * @return one of the {@code CMAS_URGENCY} values
+ */
+ public int getUrgency() {
+ return mUrgency;
+ }
+
+ /**
+ * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+ * @return one of the {@code CMAS_CERTAINTY} values
+ */
+ public int getCertainty() {
+ return mCertainty;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
+ + ", responseType=" + mResponseType + ", severity=" + mSeverity
+ + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Creator for unparcelling objects. */
+ public static final Parcelable.Creator<SmsCbCmasInfo>
+ CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
+ @Override
+ public SmsCbCmasInfo createFromParcel(Parcel in) {
+ return new SmsCbCmasInfo(in);
+ }
+
+ @Override
+ public SmsCbCmasInfo[] newArray(int size) {
+ return new SmsCbCmasInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
new file mode 100644
index 0000000..14e02de
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.Time;
+
+import com.android.internal.telephony.uicc.IccUtils;
+
+import java.util.Arrays;
+
+/**
+ * Contains information elements for a GSM or UMTS ETWS warning notification.
+ * Supported values for each element are defined in 3GPP TS 23.041.
+ *
+ * {@hide}
+ */
+public class SmsCbEtwsInfo implements Parcelable {
+
+ /** ETWS warning type for earthquake. */
+ public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
+
+ /** ETWS warning type for tsunami. */
+ public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
+
+ /** ETWS warning type for earthquake and tsunami. */
+ public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
+
+ /** ETWS warning type for test messages. */
+ public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03;
+
+ /** ETWS warning type for other emergency types. */
+ public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04;
+
+ /** Unknown ETWS warning type. */
+ public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
+
+ /** One of the ETWS warning type constants defined in this class. */
+ private final int mWarningType;
+
+ /** Whether or not to activate the emergency user alert tone and vibration. */
+ private final boolean mEmergencyUserAlert;
+
+ /** Whether or not to activate a popup alert. */
+ private final boolean mActivatePopup;
+
+ /** Whether ETWS primary message or not/ */
+ private final boolean mPrimary;
+
+ /**
+ * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
+ * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp
+ * and digital signature if received. Therefore it is treated as a raw byte array and
+ * parceled with the broadcast intent if present, but the timestamp is only computed if an
+ * application asks for the individual components.
+ */
+ private final byte[] mWarningSecurityInformation;
+
+ /** Create a new SmsCbEtwsInfo object with the specified values. */
+ public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
+ boolean primary, byte[] warningSecurityInformation) {
+ mWarningType = warningType;
+ mEmergencyUserAlert = emergencyUserAlert;
+ mActivatePopup = activatePopup;
+ mPrimary = primary;
+ mWarningSecurityInformation = warningSecurityInformation;
+ }
+
+ /** Create a new SmsCbEtwsInfo object from a Parcel. */
+ SmsCbEtwsInfo(Parcel in) {
+ mWarningType = in.readInt();
+ mEmergencyUserAlert = (in.readInt() != 0);
+ mActivatePopup = (in.readInt() != 0);
+ mPrimary = (in.readInt() != 0);
+ mWarningSecurityInformation = in.createByteArray();
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mWarningType);
+ dest.writeInt(mEmergencyUserAlert ? 1 : 0);
+ dest.writeInt(mActivatePopup ? 1 : 0);
+ dest.writeInt(mPrimary ? 1 : 0);
+ dest.writeByteArray(mWarningSecurityInformation);
+ }
+
+ /**
+ * Returns the ETWS warning type.
+ * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
+ */
+ public int getWarningType() {
+ return mWarningType;
+ }
+
+ /**
+ * Returns the ETWS emergency user alert flag.
+ * @return true to notify terminal to activate emergency user alert; false otherwise
+ */
+ public boolean isEmergencyUserAlert() {
+ return mEmergencyUserAlert;
+ }
+
+ /**
+ * Returns the ETWS activate popup flag.
+ * @return true to notify terminal to activate display popup; false otherwise
+ */
+ public boolean isPopupAlert() {
+ return mActivatePopup;
+ }
+
+ /**
+ * Returns the ETWS format flag.
+ * @return true if the message is primary message, otherwise secondary message
+ */
+ public boolean isPrimary() {
+ return mPrimary;
+ }
+
+ /**
+ * Returns the Warning-Security-Information timestamp (GSM primary notifications only).
+ * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
+ * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
+ */
+ public long getPrimaryNotificationTimestamp() {
+ if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
+ return 0;
+ }
+
+ int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]);
+ int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]);
+ int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]);
+ int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]);
+ int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]);
+ int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]);
+
+ // For the timezone, the most significant bit of the
+ // least significant nibble is the sign byte
+ // (meaning the max range of this field is 79 quarter-hours,
+ // which is more than enough)
+
+ byte tzByte = mWarningSecurityInformation[6];
+
+ // Mask out sign bit.
+ int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+ timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // We only need to support years above 2000.
+ time.year = year + 2000;
+ time.month = month - 1;
+ time.monthDay = day;
+ time.hour = hour;
+ time.minute = minute;
+ time.second = second;
+
+ // Timezone offset is in quarter hours.
+ return time.toMillis(true) - timezoneOffset * 15 * 60 * 1000;
+ }
+
+ /**
+ * Returns the digital signature (GSM primary notifications only). As of Release 10,
+ * 3GPP TS 23.041 states that the UE shall ignore this value if received.
+ * @return a byte array containing a copy of the primary notification digital signature
+ */
+ public byte[] getPrimaryNotificationSignature() {
+ if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
+ return null;
+ }
+ return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50);
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
+ + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Creator for unparcelling objects. */
+ public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
+ @Override
+ public SmsCbEtwsInfo createFromParcel(Parcel in) {
+ return new SmsCbEtwsInfo(in);
+ }
+
+ @Override
+ public SmsCbEtwsInfo[] newArray(int size) {
+ return new SmsCbEtwsInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbLocation.java b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
new file mode 100644
index 0000000..6eb72a8
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2012 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents the location and geographical scope of a cell broadcast message.
+ * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast
+ * geographical scope is cell wide or Location Area wide. For CDMA, the
+ * broadcast geographical scope is always PLMN wide.
+ *
+ * @hide
+ */
+public class SmsCbLocation implements Parcelable {
+
+ /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
+ private final String mPlmn;
+
+ private final int mLac;
+ private final int mCid;
+
+ /**
+ * Construct an empty location object. This is used for some test cases, and for
+ * cell broadcasts saved in older versions of the database without location info.
+ */
+ public SmsCbLocation() {
+ mPlmn = "";
+ mLac = -1;
+ mCid = -1;
+ }
+
+ /**
+ * Construct a location object for the PLMN. This class is immutable, so
+ * the same object can be reused for multiple broadcasts.
+ */
+ public SmsCbLocation(String plmn) {
+ mPlmn = plmn;
+ mLac = -1;
+ mCid = -1;
+ }
+
+ /**
+ * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
+ * the same object can be reused for multiple broadcasts.
+ */
+ public SmsCbLocation(String plmn, int lac, int cid) {
+ mPlmn = plmn;
+ mLac = lac;
+ mCid = cid;
+ }
+
+ /**
+ * Initialize the object from a Parcel.
+ */
+ public SmsCbLocation(Parcel in) {
+ mPlmn = in.readString();
+ mLac = in.readInt();
+ mCid = in.readInt();
+ }
+
+ /**
+ * Returns the MCC/MNC of the network as a String.
+ * @return the PLMN identifier (MCC+MNC) as a String
+ */
+ public String getPlmn() {
+ return mPlmn;
+ }
+
+ /**
+ * Returns the GSM location area code, or UMTS service area code.
+ * @return location area code, -1 if unknown, 0xffff max legal value
+ */
+ public int getLac() {
+ return mLac;
+ }
+
+ /**
+ * Returns the GSM or UMTS cell ID.
+ * @return gsm cell id, -1 if unknown, 0xffff max legal value
+ */
+ public int getCid() {
+ return mCid;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = mPlmn.hashCode();
+ hash = hash * 31 + mLac;
+ hash = hash * 31 + mCid;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o == null || !(o instanceof SmsCbLocation)) {
+ return false;
+ }
+ SmsCbLocation other = (SmsCbLocation) o;
+ return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid;
+ }
+
+ @Override
+ public String toString() {
+ return '[' + mPlmn + ',' + mLac + ',' + mCid + ']';
+ }
+
+ /**
+ * Test whether this location is within the location area of the specified object.
+ *
+ * @param area the location area to compare with this location
+ * @return true if this location is contained within the specified location area
+ */
+ public boolean isInLocationArea(SmsCbLocation area) {
+ if (mCid != -1 && mCid != area.mCid) {
+ return false;
+ }
+ if (mLac != -1 && mLac != area.mLac) {
+ return false;
+ }
+ return mPlmn.equals(area.mPlmn);
+ }
+
+ /**
+ * Test whether this location is within the location area of the CellLocation.
+ *
+ * @param plmn the PLMN to use for comparison
+ * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with
+ * @param cid the Cell ID to compare with
+ * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
+ */
+ public boolean isInLocationArea(String plmn, int lac, int cid) {
+ if (!mPlmn.equals(plmn)) {
+ return false;
+ }
+
+ if (mLac != -1 && mLac != lac) {
+ return false;
+ }
+
+ if (mCid != -1 && mCid != cid) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPlmn);
+ dest.writeInt(mLac);
+ dest.writeInt(mCid);
+ }
+
+ public static final Parcelable.Creator<SmsCbLocation> CREATOR
+ = new Parcelable.Creator<SmsCbLocation>() {
+ @Override
+ public SmsCbLocation createFromParcel(Parcel in) {
+ return new SmsCbLocation(in);
+ }
+
+ @Override
+ public SmsCbLocation[] newArray(int size) {
+ return new SmsCbLocation[size];
+ }
+ };
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
new file mode 100644
index 0000000..046bf8c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2010 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.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object containing a received cell broadcast message. There are four different types
+ * of Cell Broadcast messages:
+ *
+ * <ul>
+ * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
+ * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
+ * roaming purposes (required to display on the idle screen in Brazil)</li>
+ * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
+ * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
+ * </ul>
+ *
+ * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
+ */
+public class SmsCbMessage implements Parcelable {
+
+ protected static final String LOG_TAG = "SMSCB";
+
+ /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+
+ /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
+ public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+ /** Location / service area wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
+
+ /** Cell wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+ /** GSM or UMTS format cell broadcast. */
+ public static final int MESSAGE_FORMAT_3GPP = 1;
+
+ /** CDMA format cell broadcast. */
+ public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+ /** Normal message priority. */
+ public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+ /** Interactive message priority. */
+ public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+ /** Urgent message priority. */
+ public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+ /** Emergency message priority. */
+ public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+ /** Format of this message (for interpretation of service category values). */
+ private final int mMessageFormat;
+
+ /** Geographical scope of broadcast. */
+ private final int mGeographicalScope;
+
+ /**
+ * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+ * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+ * a cell broadcast for duplicate detection.
+ */
+ private final int mSerialNumber;
+
+ /**
+ * Location identifier for this message. It consists of the current operator MCC/MNC as a
+ * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+ * message is not binary 01, the Location Area is included for comparison. If the GS is
+ * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+ */
+ private final SmsCbLocation mLocation;
+
+ /**
+ * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+ * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+ * or {@link #getCmasWarningInfo()}.
+ */
+ private final int mServiceCategory;
+
+ /** Message language, as a two-character string, e.g. "en". */
+ private final String mLanguage;
+
+ /** Message body, as a String. */
+ private final String mBody;
+
+ /** Message priority (including emergency priority). */
+ private final int mPriority;
+
+ /** ETWS warning notification information (ETWS warnings only). */
+ private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+ /** CMAS warning notification information (CMAS warnings only). */
+ private final SmsCbCmasInfo mCmasWarningInfo;
+
+ /**
+ * Create a new SmsCbMessage with the specified data.
+ */
+ public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+ SmsCbLocation location, int serviceCategory, String language, String body,
+ int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
+ mMessageFormat = messageFormat;
+ mGeographicalScope = geographicalScope;
+ mSerialNumber = serialNumber;
+ mLocation = location;
+ mServiceCategory = serviceCategory;
+ mLanguage = language;
+ mBody = body;
+ mPriority = priority;
+ mEtwsWarningInfo = etwsWarningInfo;
+ mCmasWarningInfo = cmasWarningInfo;
+ }
+
+ /** Create a new SmsCbMessage object from a Parcel. */
+ public SmsCbMessage(Parcel in) {
+ mMessageFormat = in.readInt();
+ mGeographicalScope = in.readInt();
+ mSerialNumber = in.readInt();
+ mLocation = new SmsCbLocation(in);
+ mServiceCategory = in.readInt();
+ mLanguage = in.readString();
+ mBody = in.readString();
+ mPriority = in.readInt();
+ int type = in.readInt();
+ switch (type) {
+ case 'E':
+ // unparcel ETWS warning information
+ mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+ mCmasWarningInfo = null;
+ break;
+
+ case 'C':
+ // unparcel CMAS warning information
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = new SmsCbCmasInfo(in);
+ break;
+
+ default:
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = null;
+ }
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageFormat);
+ dest.writeInt(mGeographicalScope);
+ dest.writeInt(mSerialNumber);
+ mLocation.writeToParcel(dest, flags);
+ dest.writeInt(mServiceCategory);
+ dest.writeString(mLanguage);
+ dest.writeString(mBody);
+ dest.writeInt(mPriority);
+ if (mEtwsWarningInfo != null) {
+ // parcel ETWS warning information
+ dest.writeInt('E');
+ mEtwsWarningInfo.writeToParcel(dest, flags);
+ } else if (mCmasWarningInfo != null) {
+ // parcel CMAS warning information
+ dest.writeInt('C');
+ mCmasWarningInfo.writeToParcel(dest, flags);
+ } else {
+ // no ETWS or CMAS warning information
+ dest.writeInt('0');
+ }
+ }
+
+ public static final Parcelable.Creator<SmsCbMessage> CREATOR
+ = new Parcelable.Creator<SmsCbMessage>() {
+ @Override
+ public SmsCbMessage createFromParcel(Parcel in) {
+ return new SmsCbMessage(in);
+ }
+
+ @Override
+ public SmsCbMessage[] newArray(int size) {
+ return new SmsCbMessage[size];
+ }
+ };
+
+ /**
+ * Return the geographical scope of this message (GSM/UMTS only).
+ *
+ * @return Geographical scope
+ */
+ public int getGeographicalScope() {
+ return mGeographicalScope;
+ }
+
+ /**
+ * Return the broadcast serial number of broadcast (message identifier for CDMA, or
+ * geographical scope + message code + update number for GSM/UMTS). The serial number plus
+ * the location code uniquely identify a cell broadcast for duplicate detection.
+ *
+ * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
+ */
+ public int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ /**
+ * Return the location identifier for this message, consisting of the MCC/MNC as a
+ * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+ * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
+ * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
+ * if the location is included within another location area or within a PLMN and CellLocation.
+ *
+ * @return the geographical location code for duplicate message detection
+ */
+ public SmsCbLocation getLocation() {
+ return mLocation;
+ }
+
+ /**
+ * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
+ * of the category is radio technology specific. For ETWS and CMAS warnings, the information
+ * provided by the category is available via {@link #getEtwsWarningInfo()} or
+ * {@link #getCmasWarningInfo()} in a radio technology independent format.
+ *
+ * @return the radio technology specific service category
+ */
+ public int getServiceCategory() {
+ return mServiceCategory;
+ }
+
+ /**
+ * Get the ISO-639-1 language code for this message, or null if unspecified
+ *
+ * @return Language code
+ */
+ public String getLanguageCode() {
+ return mLanguage;
+ }
+
+ /**
+ * Get the body of this message, or null if no body available
+ *
+ * @return Body, or null
+ */
+ public String getMessageBody() {
+ return mBody;
+ }
+
+ /**
+ * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+ * @return an integer representing 3GPP or 3GPP2 message format
+ */
+ public int getMessageFormat() {
+ return mMessageFormat;
+ }
+
+ /**
+ * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
+ * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
+ * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
+ * @return an integer representing the message priority
+ */
+ public int getMessagePriority() {
+ return mPriority;
+ }
+
+ /**
+ * If this is an ETWS warning notification then this method will return an object containing
+ * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
+ * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
+ * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
+ * ETWS primary notification timestamp and digital signature if received.
+ *
+ * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
+ */
+ public SmsCbEtwsInfo getEtwsWarningInfo() {
+ return mEtwsWarningInfo;
+ }
+
+ /**
+ * If this is a CMAS warning notification then this method will return an object containing
+ * the CMAS message class, category, response type, severity, urgency and certainty.
+ * The message class is always present. Severity, urgency and certainty are present for CDMA
+ * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
+ * except for the Presidential-level alert category. Category and response type are only
+ * available for CDMA notifications containing a type 1 elements record.
+ *
+ * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
+ */
+ public SmsCbCmasInfo getCmasWarningInfo() {
+ return mCmasWarningInfo;
+ }
+
+ /**
+ * Return whether this message is an emergency (PWS) message type.
+ * @return true if the message is a public warning notification; false otherwise
+ */
+ public boolean isEmergencyMessage() {
+ return mPriority == MESSAGE_PRIORITY_EMERGENCY;
+ }
+
+ /**
+ * Return whether this message is an ETWS warning alert.
+ * @return true if the message is an ETWS warning notification; false otherwise
+ */
+ public boolean isEtwsMessage() {
+ return mEtwsWarningInfo != null;
+ }
+
+ /**
+ * Return whether this message is a CMAS warning alert.
+ * @return true if the message is a CMAS warning notification; false otherwise
+ */
+ public boolean isCmasMessage() {
+ return mCmasWarningInfo != null;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
+ + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
+ + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
+ + ", priority=" + mPriority
+ + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+ + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
new file mode 100644
index 0000000..b519b70
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2006 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 com.android.internal.telephony.SmsConstants;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.util.ArrayList;
+
+/**
+ * SMS user data header, as specified in TS 23.040 9.2.3.24.
+ */
+public class SmsHeader {
+
+ // TODO(cleanup): this data structure is generally referred to as
+ // the 'user data header' or UDH, and so the class name should
+ // change to reflect this...
+
+ /** SMS user data header information element identifiers.
+ * (see TS 23.040 9.2.3.24)
+ */
+ public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00;
+ public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
+ public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
+ public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
+ public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06;
+ public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07;
+ public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08;
+ public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09;
+ public static final int ELT_ID_TEXT_FORMATTING = 0x0A;
+ public static final int ELT_ID_PREDEFINED_SOUND = 0x0B;
+ public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C;
+ public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D;
+ public static final int ELT_ID_LARGE_ANIMATION = 0x0E;
+ public static final int ELT_ID_SMALL_ANIMATION = 0x0F;
+ public static final int ELT_ID_LARGE_PICTURE = 0x10;
+ public static final int ELT_ID_SMALL_PICTURE = 0x11;
+ public static final int ELT_ID_VARIABLE_PICTURE = 0x12;
+ public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13;
+ public static final int ELT_ID_EXTENDED_OBJECT = 0x14;
+ public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15;
+ public static final int ELT_ID_COMPRESSION_CONTROL = 0x16;
+ public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17;
+ public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18;
+ public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19;
+ public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A;
+ public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20;
+ public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21;
+ public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22;
+ public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
+ public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
+ public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
+
+ public static final int PORT_WAP_PUSH = 2948;
+ public static final int PORT_WAP_WSP = 9200;
+
+ public static class PortAddrs {
+ public int destPort;
+ public int origPort;
+ public boolean areEightBits;
+ }
+
+ public static class ConcatRef {
+ public int refNumber;
+ public int seqNumber;
+ public int msgCount;
+ public boolean isEightBits;
+ }
+
+ public static class SpecialSmsMsg {
+ public int msgIndType;
+ public int msgCount;
+ }
+
+ /**
+ * A header element that is not explicitly parsed, meaning not
+ * PortAddrs or ConcatRef or SpecialSmsMsg.
+ */
+ public static class MiscElt {
+ public int id;
+ public byte[] data;
+ }
+
+ public PortAddrs portAddrs;
+ public ConcatRef concatRef;
+ public ArrayList<SpecialSmsMsg> specialSmsMsgList = new ArrayList<SpecialSmsMsg>();
+ public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
+
+ /** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
+ public int languageTable;
+
+ /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
+ public int languageShiftTable;
+
+ public SmsHeader() {}
+
+ /**
+ * Create structured SmsHeader object from serialized byte array representation.
+ * (see TS 23.040 9.2.3.24)
+ * @param data is user data header bytes
+ * @return SmsHeader object
+ */
+ public static SmsHeader fromByteArray(byte[] data) {
+ ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+ SmsHeader smsHeader = new SmsHeader();
+ while (inStream.available() > 0) {
+ /**
+ * NOTE: as defined in the spec, ConcatRef and PortAddr
+ * fields should not reoccur, but if they do the last
+ * occurrence is to be used. Also, for ConcatRef
+ * elements, if the count is zero, sequence is zero, or
+ * sequence is larger than count, the entire element is to
+ * be ignored.
+ */
+ int id = inStream.read();
+ int length = inStream.read();
+ ConcatRef concatRef;
+ PortAddrs portAddrs;
+ switch (id) {
+ case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
+ concatRef = new ConcatRef();
+ concatRef.refNumber = inStream.read();
+ concatRef.msgCount = inStream.read();
+ concatRef.seqNumber = inStream.read();
+ concatRef.isEightBits = true;
+ if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+ concatRef.seqNumber <= concatRef.msgCount) {
+ smsHeader.concatRef = concatRef;
+ }
+ break;
+ case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
+ concatRef = new ConcatRef();
+ concatRef.refNumber = (inStream.read() << 8) | inStream.read();
+ concatRef.msgCount = inStream.read();
+ concatRef.seqNumber = inStream.read();
+ concatRef.isEightBits = false;
+ if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+ concatRef.seqNumber <= concatRef.msgCount) {
+ smsHeader.concatRef = concatRef;
+ }
+ break;
+ case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
+ portAddrs = new PortAddrs();
+ portAddrs.destPort = inStream.read();
+ portAddrs.origPort = inStream.read();
+ portAddrs.areEightBits = true;
+ smsHeader.portAddrs = portAddrs;
+ break;
+ case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
+ portAddrs = new PortAddrs();
+ portAddrs.destPort = (inStream.read() << 8) | inStream.read();
+ portAddrs.origPort = (inStream.read() << 8) | inStream.read();
+ portAddrs.areEightBits = false;
+ smsHeader.portAddrs = portAddrs;
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+ smsHeader.languageShiftTable = inStream.read();
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+ smsHeader.languageTable = inStream.read();
+ break;
+ case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION:
+ SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg();
+ specialSmsMsg.msgIndType = inStream.read();
+ specialSmsMsg.msgCount = inStream.read();
+ smsHeader.specialSmsMsgList.add(specialSmsMsg);
+ break;
+ default:
+ MiscElt miscElt = new MiscElt();
+ miscElt.id = id;
+ miscElt.data = new byte[length];
+ inStream.read(miscElt.data, 0, length);
+ smsHeader.miscEltList.add(miscElt);
+ }
+ }
+ return smsHeader;
+ }
+
+ /**
+ * Create serialized byte array representation from structured SmsHeader object.
+ * (see TS 23.040 9.2.3.24)
+ * @return Byte array representing the SmsHeader
+ */
+ public static byte[] toByteArray(SmsHeader smsHeader) {
+ if ((smsHeader.portAddrs == null) &&
+ (smsHeader.concatRef == null) &&
+ (smsHeader.specialSmsMsgList.isEmpty()) &&
+ (smsHeader.miscEltList.isEmpty()) &&
+ (smsHeader.languageShiftTable == 0) &&
+ (smsHeader.languageTable == 0)) {
+ return null;
+ }
+
+ ByteArrayOutputStream outStream =
+ new ByteArrayOutputStream(SmsConstants.MAX_USER_DATA_BYTES);
+ ConcatRef concatRef = smsHeader.concatRef;
+ if (concatRef != null) {
+ if (concatRef.isEightBits) {
+ outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
+ outStream.write(3);
+ outStream.write(concatRef.refNumber);
+ } else {
+ outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
+ outStream.write(4);
+ outStream.write(concatRef.refNumber >>> 8);
+ outStream.write(concatRef.refNumber & 0x00FF);
+ }
+ outStream.write(concatRef.msgCount);
+ outStream.write(concatRef.seqNumber);
+ }
+ PortAddrs portAddrs = smsHeader.portAddrs;
+ if (portAddrs != null) {
+ if (portAddrs.areEightBits) {
+ outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
+ outStream.write(2);
+ outStream.write(portAddrs.destPort);
+ outStream.write(portAddrs.origPort);
+ } else {
+ outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
+ outStream.write(4);
+ outStream.write(portAddrs.destPort >>> 8);
+ outStream.write(portAddrs.destPort & 0x00FF);
+ outStream.write(portAddrs.origPort >>> 8);
+ outStream.write(portAddrs.origPort & 0x00FF);
+ }
+ }
+ if (smsHeader.languageShiftTable != 0) {
+ outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
+ outStream.write(1);
+ outStream.write(smsHeader.languageShiftTable);
+ }
+ if (smsHeader.languageTable != 0) {
+ outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
+ outStream.write(1);
+ outStream.write(smsHeader.languageTable);
+ }
+ for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) {
+ outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION);
+ outStream.write(2);
+ outStream.write(specialSmsMsg.msgIndType & 0xFF);
+ outStream.write(specialSmsMsg.msgCount & 0xFF);
+ }
+ for (MiscElt miscElt : smsHeader.miscEltList) {
+ outStream.write(miscElt.id);
+ outStream.write(miscElt.data.length);
+ outStream.write(miscElt.data, 0, miscElt.data.length);
+ }
+ return outStream.toByteArray();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UserDataHeader ");
+ builder.append("{ ConcatRef ");
+ if (concatRef == null) {
+ builder.append("unset");
+ } else {
+ builder.append("{ refNumber=" + concatRef.refNumber);
+ builder.append(", msgCount=" + concatRef.msgCount);
+ builder.append(", seqNumber=" + concatRef.seqNumber);
+ builder.append(", isEightBits=" + concatRef.isEightBits);
+ builder.append(" }");
+ }
+ builder.append(", PortAddrs ");
+ if (portAddrs == null) {
+ builder.append("unset");
+ } else {
+ builder.append("{ destPort=" + portAddrs.destPort);
+ builder.append(", origPort=" + portAddrs.origPort);
+ builder.append(", areEightBits=" + portAddrs.areEightBits);
+ builder.append(" }");
+ }
+ if (languageShiftTable != 0) {
+ builder.append(", languageShiftTable=" + languageShiftTable);
+ }
+ if (languageTable != 0) {
+ builder.append(", languageTable=" + languageTable);
+ }
+ for (SpecialSmsMsg specialSmsMsg : specialSmsMsgList) {
+ builder.append(", SpecialSmsMsg ");
+ builder.append("{ msgIndType=" + specialSmsMsg.msgIndType);
+ builder.append(", msgCount=" + specialSmsMsg.msgCount);
+ builder.append(" }");
+ }
+ for (MiscElt miscElt : miscEltList) {
+ builder.append(", MiscElt ");
+ builder.append("{ id=" + miscElt.id);
+ builder.append(", length=" + miscElt.data.length);
+ builder.append(", data=" + HexDump.toHexString(miscElt.data));
+ builder.append(" }");
+ }
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
new file mode 100644
index 0000000..e5821dc
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2008 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 com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import java.text.BreakIterator;
+import java.util.Arrays;
+
+import android.provider.Telephony;
+import android.telephony.SmsMessage;
+import android.text.Emoji;
+
+/**
+ * Base class declaring the specific methods and members for SmsMessage.
+ * {@hide}
+ */
+public abstract class SmsMessageBase {
+ /** {@hide} The address of the SMSC. May be null */
+ protected String mScAddress;
+
+ /** {@hide} The address of the sender */
+ protected SmsAddress mOriginatingAddress;
+
+ /** {@hide} The message body as a string. May be null if the message isn't text */
+ protected String mMessageBody;
+
+ /** {@hide} */
+ protected String mPseudoSubject;
+
+ /** {@hide} Non-null if this is an email gateway message */
+ protected String mEmailFrom;
+
+ /** {@hide} Non-null if this is an email gateway message */
+ protected String mEmailBody;
+
+ /** {@hide} */
+ protected boolean mIsEmail;
+
+ /** {@hide} Time when SC (service centre) received the message */
+ protected long mScTimeMillis;
+
+ /** {@hide} The raw PDU of the message */
+ protected byte[] mPdu;
+
+ /** {@hide} The raw bytes for the user data section of the message */
+ protected byte[] mUserData;
+
+ /** {@hide} */
+ protected SmsHeader mUserDataHeader;
+
+ // "Message Waiting Indication Group"
+ // 23.038 Section 4
+ /** {@hide} */
+ protected boolean mIsMwi;
+
+ /** {@hide} */
+ protected boolean mMwiSense;
+
+ /** {@hide} */
+ protected boolean mMwiDontStore;
+
+ /**
+ * Indicates status for messages stored on the ICC.
+ */
+ protected int mStatusOnIcc = -1;
+
+ /**
+ * Record index of message in the EF.
+ */
+ protected int mIndexOnIcc = -1;
+
+ /** TP-Message-Reference - Message Reference of sent message. @hide */
+ public int mMessageRef;
+
+ // TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
+ public static abstract class SubmitPduBase {
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ @Override
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return mScAddress;
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ if (mOriginatingAddress == null) {
+ return null;
+ }
+
+ return mOriginatingAddress.getAddressString();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ */
+ public String getDisplayOriginatingAddress() {
+ if (mIsEmail) {
+ return mEmailFrom;
+ } else {
+ return getOriginatingAddress();
+ }
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ */
+ public String getMessageBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public abstract SmsConstants.MessageClass getMessageClass();
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ */
+ public String getDisplayMessageBody() {
+ if (mIsEmail) {
+ return mEmailBody;
+ } else {
+ return getMessageBody();
+ }
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return mPseudoSubject == null ? "" : mPseudoSubject;
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return mScTimeMillis;
+ }
+
+ /**
+ * Returns true if message is an email.
+ *
+ * @return true if this message came through an email gateway and email
+ * sender / subject / parsed body are available
+ */
+ public boolean isEmail() {
+ return mIsEmail;
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return mEmailBody;
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return mEmailFrom;
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public abstract int getProtocolIdentifier();
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public abstract boolean isReplace();
+
+ /**
+ * Returns true for CPHS MWI toggle message.
+ *
+ * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+ * B.4.2
+ */
+ public abstract boolean isCphsMwiMessage();
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public abstract boolean isMWIClearMessage();
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public abstract boolean isMWISetMessage();
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public abstract boolean isMwiDontStore();
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return mUserData;
+ }
+
+ /**
+ * Returns an object representing the user data header
+ *
+ * {@hide}
+ */
+ public SmsHeader getUserDataHeader() {
+ return mUserDataHeader;
+ }
+
+ /**
+ * TODO(cleanup): The term PDU is used in a seemingly non-unique
+ * manner -- for example, what is the difference between this byte
+ * array and the contents of SubmitPdu objects. Maybe a more
+ * illustrative term would be appropriate.
+ */
+
+ /**
+ * Returns the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mPdu;
+ }
+
+ /**
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 for a description of other possible
+ * values.
+ */
+ public abstract int getStatus();
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public abstract boolean isStatusReportMessage();
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public abstract boolean isReplyPathPresent();
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mStatusOnIcc;
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mIndexOnIcc;
+ }
+
+ protected void parseMessageBody() {
+ // originatingAddress could be null if this message is from a status
+ // report.
+ if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) {
+ extractEmailAddressFromMessageBody();
+ }
+ }
+
+ /**
+ * Try to parse this message as an email gateway message
+ * There are two ways specified in TS 23.040 Section 3.8 :
+ * - SMS message "may have its TP-PID set for Internet electronic mail - MT
+ * SMS format: [<from-address><space>]<message> - "Depending on the
+ * nature of the gateway, the destination/origination address is either
+ * derived from the content of the SMS TP-OA or TP-DA field, or the
+ * TP-OA/TP-DA field contains a generic gateway address and the to/from
+ * address is added at the beginning as shown above." (which is supported here)
+ * - Multiple addresses separated by commas, no spaces, Subject field delimited
+ * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
+ */
+ protected void extractEmailAddressFromMessageBody() {
+
+ /* Some carriers may use " /" delimiter as below
+ *
+ * 1. [x@y][ ]/[subject][ ]/[body]
+ * -or-
+ * 2. [x@y][ ]/[body]
+ */
+ String[] parts = mMessageBody.split("( /)|( )", 2);
+ if (parts.length < 2) return;
+ mEmailFrom = parts[0];
+ mEmailBody = parts[1];
+ mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom);
+ }
+
+ /**
+ * Find the next position to start a new fragment of a multipart SMS.
+ *
+ * @param currentPosition current start position of the fragment
+ * @param byteLimit maximum number of bytes in the fragment
+ * @param msgBody text of the SMS in UTF-16 encoding
+ * @return the position to start the next fragment
+ */
+ public static int findNextUnicodePosition(
+ int currentPosition, int byteLimit, CharSequence msgBody) {
+ int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length());
+ // Check whether the fragment ends in a character boundary. Some characters take 4-bytes
+ // in UTF-16 encoding. Many carriers cannot handle
+ // a fragment correctly if it does not end at a character boundary.
+ if (nextPos < msgBody.length()) {
+ BreakIterator breakIterator = BreakIterator.getCharacterInstance();
+ breakIterator.setText(msgBody.toString());
+ if (!breakIterator.isBoundary(nextPos)) {
+ int breakPos = breakIterator.preceding(nextPos);
+ while (breakPos + 4 <= nextPos
+ && Emoji.isRegionalIndicatorSymbol(
+ Character.codePointAt(msgBody, breakPos))
+ && Emoji.isRegionalIndicatorSymbol(
+ Character.codePointAt(msgBody, breakPos + 2))) {
+ // skip forward over flags (pairs of Regional Indicator Symbol)
+ breakPos += 4;
+ }
+ if (breakPos > currentPosition) {
+ nextPos = breakPos;
+ } else if (Character.isHighSurrogate(msgBody.charAt(nextPos - 1))) {
+ // no character boundary in this fragment, try to at least land on a code point
+ nextPos -= 1;
+ }
+ }
+ }
+ return nextPos;
+ }
+
+ /**
+ * Calculate the TextEncodingDetails of a message encoded in Unicode.
+ */
+ public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) {
+ TextEncodingDetails ted = new TextEncodingDetails();
+ int octets = msgBody.length() * 2;
+ ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
+ ted.codeUnitCount = msgBody.length();
+ if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
+ // If EMS is not supported, break down EMS into single segment SMS
+ // and add page info " x/y".
+ // In the case of UCS2 encoding type, we need 8 bytes for this
+ // but we only have 6 bytes from UDH, so truncate the limit for
+ // each segment by 2 bytes (1 char).
+ int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ if (!SmsMessage.hasEmsSupport()) {
+ // make sure total number of segments is less than 10
+ if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) {
+ maxUserDataBytesWithHeader -= 2;
+ }
+ }
+
+ int pos = 0; // Index in code units.
+ int msgCount = 0;
+ while (pos < msgBody.length()) {
+ int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader,
+ msgBody);
+ if (nextPos == msgBody.length()) {
+ ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 -
+ msgBody.length();
+ }
+ pos = nextPos;
+ msgCount++;
+ }
+ ted.msgCount = msgCount;
+ } else {
+ ted.msgCount = 1;
+ ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2;
+ }
+
+ return ted;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index ce4e7e2..55a8b0b 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -402,7 +402,7 @@
* <ul>
* <li>apnType</li><dd>A string with the apn type.</dd>
* <li>redirectionUrl</li><dd>redirection url string</dd>
- * <li>subId</dt><li>Sub Id which associated the data connection failure.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
* </ul>
* <p class="note">This is a protected intent that can only be sent by the system.</p>
*/
@@ -415,7 +415,7 @@
* <ul>
* <li>apnType</li><dd>A string with the apn type.</dd>
* <li>errorCode</li><dd>A integer with dataFailCause.</dd>
- * <li>subId</dt><li>Sub Id which associated the data connection failure.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
* </ul>
* <p class="note">This is a protected intent that can only be sent by the system. </p>
*/
@@ -432,13 +432,25 @@
* IPV4V6)</dd>
* <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
* <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
- * <li>subId</dt><li>Sub Id which associated the data connection.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection.</dd>
* </ul>
* <p class="note">This is a protected intent that can only be sent by the system. </p>
*/
public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
"com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+ /**
+ * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
+ * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ */
+ public static final String ACTION_CARRIER_SIGNAL_RESET =
+ "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+
// CARRIER_SIGNAL_ACTION extra keys
public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
diff --git a/telephony/java/com/android/internal/telephony/cdma/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/BearerData.java
new file mode 100644
index 0000000..1de72db
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/BearerData.java
@@ -0,0 +1,2000 @@
+/*
+ * Copyright (C) 2008 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.cdma.sms;
+
+import android.content.res.Resources;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.telephony.cdma.CdmaSmsCbProgramResults;
+import android.text.format.Time;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.ArrayList;
+import java.util.TimeZone;
+
+/**
+ * An object to encode and decode CDMA SMS bearer data.
+ */
+public final class BearerData {
+ private final static String LOG_TAG = "BearerData";
+
+ /**
+ * Bearer Data Subparameter Identifiers
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
+ * NOTE: Commented subparameter types are not implemented.
+ */
+ private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00;
+ private final static byte SUBPARAM_USER_DATA = 0x01;
+ private final static byte SUBPARAM_USER_RESPONSE_CODE = 0x02;
+ private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP = 0x03;
+ private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04;
+ private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05;
+ private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06;
+ private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07;
+ private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08;
+ private final static byte SUBPARAM_PRIVACY_INDICATOR = 0x09;
+ private final static byte SUBPARAM_REPLY_OPTION = 0x0A;
+ private final static byte SUBPARAM_NUMBER_OF_MESSAGES = 0x0B;
+ private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY = 0x0C;
+ private final static byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D;
+ private final static byte SUBPARAM_CALLBACK_NUMBER = 0x0E;
+ private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F;
+ //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10;
+ private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11;
+ private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12;
+ private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
+ private final static byte SUBPARAM_MESSAGE_STATUS = 0x14;
+ //private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15;
+ //private final static byte SUBPARAM_ENHANCED_VMN = 0x16;
+ //private final static byte SUBPARAM_ENHANCED_VMN_ACK = 0x17;
+
+ // All other values after this are reserved.
+ private final static byte SUBPARAM_ID_LAST_DEFINED = 0x17;
+
+ /**
+ * Supported message types for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
+ */
+ public static final int MESSAGE_TYPE_DELIVER = 0x01;
+ public static final int MESSAGE_TYPE_SUBMIT = 0x02;
+ public static final int MESSAGE_TYPE_CANCELLATION = 0x03;
+ public static final int MESSAGE_TYPE_DELIVERY_ACK = 0x04;
+ public static final int MESSAGE_TYPE_USER_ACK = 0x05;
+ public static final int MESSAGE_TYPE_READ_ACK = 0x06;
+ public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07;
+ public static final int MESSAGE_TYPE_SUBMIT_REPORT = 0x08;
+
+ public int messageType;
+
+ /**
+ * 16-bit value indicating the message ID, which increments modulo 65536.
+ * (Special rules apply for WAP-messages.)
+ * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ */
+ public int messageId;
+
+ /**
+ * Supported priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+ */
+ public static final int PRIORITY_NORMAL = 0x0;
+ public static final int PRIORITY_INTERACTIVE = 0x1;
+ public static final int PRIORITY_URGENT = 0x2;
+ public static final int PRIORITY_EMERGENCY = 0x3;
+
+ public boolean priorityIndicatorSet = false;
+ public int priority = PRIORITY_NORMAL;
+
+ /**
+ * Supported privacy modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1)
+ */
+ public static final int PRIVACY_NOT_RESTRICTED = 0x0;
+ public static final int PRIVACY_RESTRICTED = 0x1;
+ public static final int PRIVACY_CONFIDENTIAL = 0x2;
+ public static final int PRIVACY_SECRET = 0x3;
+
+ public boolean privacyIndicatorSet = false;
+ public int privacy = PRIVACY_NOT_RESTRICTED;
+
+ /**
+ * Supported alert priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1)
+ */
+ public static final int ALERT_DEFAULT = 0x0;
+ public static final int ALERT_LOW_PRIO = 0x1;
+ public static final int ALERT_MEDIUM_PRIO = 0x2;
+ public static final int ALERT_HIGH_PRIO = 0x3;
+
+ public boolean alertIndicatorSet = false;
+ public int alert = ALERT_DEFAULT;
+
+ /**
+ * Supported display modes for CDMA SMS messages. Display mode is
+ * a 2-bit value used to indicate to the mobile station when to
+ * display the received message. (See 3GPP2 C.S0015-B, v2,
+ * 4.5.16)
+ */
+ public static final int DISPLAY_MODE_IMMEDIATE = 0x0;
+ public static final int DISPLAY_MODE_DEFAULT = 0x1;
+ public static final int DISPLAY_MODE_USER = 0x2;
+
+ public boolean displayModeSet = false;
+ public int displayMode = DISPLAY_MODE_DEFAULT;
+
+ /**
+ * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B,
+ * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
+ * refers to C.R1001-D but that reference has been crossed out.
+ * It would seem reasonable to assume the values from C.R1001-F
+ * (table 9.2-1) are to be used instead.
+ */
+ public static final int LANGUAGE_UNKNOWN = 0x00;
+ public static final int LANGUAGE_ENGLISH = 0x01;
+ public static final int LANGUAGE_FRENCH = 0x02;
+ public static final int LANGUAGE_SPANISH = 0x03;
+ public static final int LANGUAGE_JAPANESE = 0x04;
+ public static final int LANGUAGE_KOREAN = 0x05;
+ public static final int LANGUAGE_CHINESE = 0x06;
+ public static final int LANGUAGE_HEBREW = 0x07;
+
+ public boolean languageIndicatorSet = false;
+ public int language = LANGUAGE_UNKNOWN;
+
+ /**
+ * SMS Message Status Codes. The first component of the Message
+ * status indicates if an error has occurred and whether the error
+ * is considered permanent or temporary. The second component of
+ * the Message status indicates the cause of the error (if any).
+ * (See 3GPP2 C.S0015-B, v2.0, 4.5.21)
+ */
+ /* no-error codes */
+ public static final int ERROR_NONE = 0x00;
+ public static final int STATUS_ACCEPTED = 0x00;
+ public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01;
+ public static final int STATUS_DELIVERED = 0x02;
+ public static final int STATUS_CANCELLED = 0x03;
+ /* temporary-error and permanent-error codes */
+ public static final int ERROR_TEMPORARY = 0x02;
+ public static final int STATUS_NETWORK_CONGESTION = 0x04;
+ public static final int STATUS_NETWORK_ERROR = 0x05;
+ public static final int STATUS_UNKNOWN_ERROR = 0x1F;
+ /* permanent-error codes */
+ public static final int ERROR_PERMANENT = 0x03;
+ public static final int STATUS_CANCEL_FAILED = 0x06;
+ public static final int STATUS_BLOCKED_DESTINATION = 0x07;
+ public static final int STATUS_TEXT_TOO_LONG = 0x08;
+ public static final int STATUS_DUPLICATE_MESSAGE = 0x09;
+ public static final int STATUS_INVALID_DESTINATION = 0x0A;
+ public static final int STATUS_MESSAGE_EXPIRED = 0x0D;
+ /* undefined-status codes */
+ public static final int ERROR_UNDEFINED = 0xFF;
+ public static final int STATUS_UNDEFINED = 0xFF;
+
+ public boolean messageStatusSet = false;
+ public int errorClass = ERROR_UNDEFINED;
+ public int messageStatus = STATUS_UNDEFINED;
+
+ /**
+ * 1-bit value that indicates whether a User Data Header (UDH) is present.
+ * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ *
+ * NOTE: during encoding, this value will be set based on the
+ * presence of a UDH in the structured data, any existing setting
+ * will be overwritten.
+ */
+ public boolean hasUserDataHeader;
+
+ /**
+ * provides the information for the user data
+ * (e.g. padding bits, user data, user data header, etc)
+ * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
+ */
+ public UserData userData;
+
+ /**
+ * The User Response Code subparameter is used in the SMS User
+ * Acknowledgment Message to respond to previously received short
+ * messages. This message center-specific element carries the
+ * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2,
+ * 4.5.3)
+ */
+ public boolean userResponseCodeSet = false;
+ public int userResponseCode;
+
+ /**
+ * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
+ */
+ public static class TimeStamp extends Time {
+
+ public TimeStamp() {
+ super(TimeZone.getDefault().getID()); // 3GPP2 timestamps use the local timezone
+ }
+
+ public static TimeStamp fromByteArray(byte[] data) {
+ TimeStamp ts = new TimeStamp();
+ // C.S0015-B v2.0, 4.5.4: range is 1996-2095
+ int year = IccUtils.cdmaBcdByteToInt(data[0]);
+ if (year > 99 || year < 0) return null;
+ ts.year = year >= 96 ? year + 1900 : year + 2000;
+ int month = IccUtils.cdmaBcdByteToInt(data[1]);
+ if (month < 1 || month > 12) return null;
+ ts.month = month - 1;
+ int day = IccUtils.cdmaBcdByteToInt(data[2]);
+ if (day < 1 || day > 31) return null;
+ ts.monthDay = day;
+ int hour = IccUtils.cdmaBcdByteToInt(data[3]);
+ if (hour < 0 || hour > 23) return null;
+ ts.hour = hour;
+ int minute = IccUtils.cdmaBcdByteToInt(data[4]);
+ if (minute < 0 || minute > 59) return null;
+ ts.minute = minute;
+ int second = IccUtils.cdmaBcdByteToInt(data[5]);
+ if (second < 0 || second > 59) return null;
+ ts.second = second;
+ return ts;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("TimeStamp ");
+ builder.append("{ year=" + year);
+ builder.append(", month=" + month);
+ builder.append(", day=" + monthDay);
+ builder.append(", hour=" + hour);
+ builder.append(", minute=" + minute);
+ builder.append(", second=" + second);
+ builder.append(" }");
+ return builder.toString();
+ }
+ }
+
+ public TimeStamp msgCenterTimeStamp;
+ public TimeStamp validityPeriodAbsolute;
+ public TimeStamp deferredDeliveryTimeAbsolute;
+
+ /**
+ * Relative time is specified as one byte, the value of which
+ * falls into a series of ranges, as specified below. The idea is
+ * that shorter time intervals allow greater precision -- the
+ * value means minutes from zero until the MINS_LIMIT (inclusive),
+ * upon which it means hours until the HOURS_LIMIT, and so
+ * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1)
+ */
+ public static final int RELATIVE_TIME_MINS_LIMIT = 143;
+ public static final int RELATIVE_TIME_HOURS_LIMIT = 167;
+ public static final int RELATIVE_TIME_DAYS_LIMIT = 196;
+ public static final int RELATIVE_TIME_WEEKS_LIMIT = 244;
+ public static final int RELATIVE_TIME_INDEFINITE = 245;
+ public static final int RELATIVE_TIME_NOW = 246;
+ public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247;
+ public static final int RELATIVE_TIME_RESERVED = 248;
+
+ public boolean validityPeriodRelativeSet;
+ public int validityPeriodRelative;
+ public boolean deferredDeliveryTimeRelativeSet;
+ public int deferredDeliveryTimeRelative;
+
+ /**
+ * The Reply Option subparameter contains 1-bit values which
+ * indicate whether SMS acknowledgment is requested or not. (See
+ * 3GPP2 C.S0015-B, v2, 4.5.11)
+ */
+ public boolean userAckReq;
+ public boolean deliveryAckReq;
+ public boolean readAckReq;
+ public boolean reportReq;
+
+ /**
+ * The Number of Messages subparameter (8-bit value) is a decimal
+ * number in the 0 to 99 range representing the number of messages
+ * stored at the Voice Mail System. This element is used by the
+ * Voice Mail Notification service. (See 3GPP2 C.S0015-B, v2,
+ * 4.5.12)
+ */
+ public int numberOfMessages;
+
+ /**
+ * The Message Deposit Index subparameter is assigned by the
+ * message center as a unique index to the contents of the User
+ * Data subparameter in each message sent to a particular mobile
+ * station. The mobile station, when replying to a previously
+ * received short message which included a Message Deposit Index
+ * subparameter, may include the Message Deposit Index of the
+ * received message to indicate to the message center that the
+ * original contents of the message are to be included in the
+ * reply. (See 3GPP2 C.S0015-B, v2, 4.5.18)
+ */
+ public int depositIndex;
+
+ /**
+ * 4-bit or 8-bit value that indicates the number to be dialed in reply to a
+ * received SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 4.5.15)
+ */
+ public CdmaSmsAddress callbackNumber;
+
+ /**
+ * CMAS warning notification information.
+ * @see #decodeCmasUserData(BearerData, int)
+ */
+ public SmsCbCmasInfo cmasWarningInfo;
+
+ /**
+ * The Service Category Program Data subparameter is used to enable and disable
+ * SMS broadcast service categories to display. If this subparameter is present,
+ * this field will contain a list of one or more
+ * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
+ * operation(s) to perform.
+ */
+ public ArrayList<CdmaSmsCbProgramData> serviceCategoryProgramData;
+
+ /**
+ * The Service Category Program Results subparameter informs the message center
+ * of the results of a Service Category Program Data request.
+ */
+ public ArrayList<CdmaSmsCbProgramResults> serviceCategoryProgramResults;
+
+
+ private static class CodingException extends Exception {
+ public CodingException(String s) {
+ super(s);
+ }
+ }
+
+ /**
+ * Returns the language indicator as a two-character ISO 639 string.
+ * @return a two character ISO 639 language code
+ */
+ public String getLanguage() {
+ return getLanguageCodeForValue(language);
+ }
+
+ /**
+ * Converts a CDMA language indicator value to an ISO 639 two character language code.
+ * @param languageValue the CDMA language value to convert
+ * @return the two character ISO 639 language code for the specified value, or null if unknown
+ */
+ private static String getLanguageCodeForValue(int languageValue) {
+ switch (languageValue) {
+ case LANGUAGE_ENGLISH:
+ return "en";
+
+ case LANGUAGE_FRENCH:
+ return "fr";
+
+ case LANGUAGE_SPANISH:
+ return "es";
+
+ case LANGUAGE_JAPANESE:
+ return "ja";
+
+ case LANGUAGE_KOREAN:
+ return "ko";
+
+ case LANGUAGE_CHINESE:
+ return "zh";
+
+ case LANGUAGE_HEBREW:
+ return "he";
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("BearerData ");
+ builder.append("{ messageType=" + messageType);
+ builder.append(", messageId=" + messageId);
+ builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
+ builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
+ builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
+ builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
+ builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
+ builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
+ builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
+ builder.append(", msgCenterTimeStamp=" +
+ ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset"));
+ builder.append(", validityPeriodAbsolute=" +
+ ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset"));
+ builder.append(", validityPeriodRelative=" +
+ ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset"));
+ builder.append(", deferredDeliveryTimeAbsolute=" +
+ ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset"));
+ builder.append(", deferredDeliveryTimeRelative=" +
+ ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset"));
+ builder.append(", userAckReq=" + userAckReq);
+ builder.append(", deliveryAckReq=" + deliveryAckReq);
+ builder.append(", readAckReq=" + readAckReq);
+ builder.append(", reportReq=" + reportReq);
+ builder.append(", numberOfMessages=" + numberOfMessages);
+ builder.append(", callbackNumber=" + Rlog.pii(LOG_TAG, callbackNumber));
+ builder.append(", depositIndex=" + depositIndex);
+ builder.append(", hasUserDataHeader=" + hasUserDataHeader);
+ builder.append(", userData=" + userData);
+ builder.append(" }");
+ return builder.toString();
+ }
+
+ private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 3);
+ outStream.write(4, bData.messageType);
+ outStream.write(8, bData.messageId >> 8);
+ outStream.write(8, bData.messageId);
+ outStream.write(1, bData.hasUserDataHeader ? 1 : 0);
+ outStream.skip(3);
+ }
+
+ private static int countAsciiSeptets(CharSequence msg, boolean force) {
+ int msgLen = msg.length();
+ if (force) return msgLen;
+ for (int i = 0; i < msgLen; i++) {
+ if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) {
+ return -1;
+ }
+ }
+ return msgLen;
+ }
+
+ /**
+ * Calculate the message text encoding length, fragmentation, and other details.
+ *
+ * @param msg message text
+ * @param force7BitEncoding ignore (but still count) illegal characters if true
+ * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+ * @return septet count, or -1 on failure
+ */
+ public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
+ boolean force7BitEncoding, boolean isEntireMsg) {
+ TextEncodingDetails ted;
+ int septets = countAsciiSeptets(msg, force7BitEncoding);
+ if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) {
+ ted = new TextEncodingDetails();
+ ted.msgCount = 1;
+ ted.codeUnitCount = septets;
+ ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
+ ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
+ } else {
+ ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
+ msg, force7BitEncoding);
+ if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT &&
+ isEntireMsg) {
+ // We don't support single-segment EMS, so calculate for 16-bit
+ // TODO: Consider supporting single-segment EMS
+ return SmsMessageBase.calcUnicodeEncodingDetails(msg);
+ }
+ }
+ return ted;
+ }
+
+ private static byte[] encode7bitAscii(String msg, boolean force)
+ throws CodingException
+ {
+ try {
+ BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length());
+ int msgLen = msg.length();
+ for (int i = 0; i < msgLen; i++) {
+ int charCode = UserData.charToAscii.get(msg.charAt(i), -1);
+ if (charCode == -1) {
+ if (force) {
+ outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR);
+ } else {
+ throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")");
+ }
+ } else {
+ outStream.write(7, charCode);
+ }
+ }
+ return outStream.toByteArray();
+ } catch (BitwiseOutputStream.AccessException ex) {
+ throw new CodingException("7bit ASCII encode failed: " + ex);
+ }
+ }
+
+ private static byte[] encodeUtf16(String msg)
+ throws CodingException
+ {
+ try {
+ return msg.getBytes("utf-16be");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("UTF-16 encode failed: " + ex);
+ }
+ }
+
+ private static class Gsm7bitCodingResult {
+ int septets;
+ byte[] data;
+ }
+
+ private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
+ throws CodingException
+ {
+ try {
+ /*
+ * TODO(cleanup): It would be nice if GsmAlphabet provided
+ * an option to produce just the data without prepending
+ * the septet count, as this function is really just a
+ * wrapper to strip that off. Not to mention that the
+ * septet count is generally known prior to invocation of
+ * the encoder. Note that it cannot be derived from the
+ * resulting array length, since that cannot distinguish
+ * if the last contains either 1 or 8 valid bits.
+ *
+ * TODO(cleanup): The BitwiseXStreams could also be
+ * extended with byte-wise reversed endianness read/write
+ * routines to allow a corresponding implementation of
+ * stringToGsm7BitPacked, and potentially directly support
+ * access to the main bitwise stream from encode/decode.
+ */
+ byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
+ Gsm7bitCodingResult result = new Gsm7bitCodingResult();
+ result.data = new byte[fullData.length - 1];
+ System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
+ result.septets = fullData[0] & 0x00FF;
+ return result;
+ } catch (com.android.internal.telephony.EncodeException ex) {
+ throw new CodingException("7bit GSM encode failed: " + ex);
+ }
+ }
+
+ private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
+ throws CodingException
+ {
+ int udhBytes = udhData.length + 1; // Add length octet.
+ int udhSeptets = ((udhBytes * 8) + 6) / 7;
+ Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
+ uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ uData.msgEncodingSet = true;
+ uData.numFields = gcr.septets;
+ uData.payload = gcr.data;
+ uData.payload[0] = (byte)udhData.length;
+ System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+ }
+
+ private static void encode16bitEms(UserData uData, byte[] udhData)
+ throws CodingException
+ {
+ byte[] payload = encodeUtf16(uData.payloadStr);
+ int udhBytes = udhData.length + 1; // Add length octet.
+ int udhCodeUnits = (udhBytes + 1) / 2;
+ int payloadCodeUnits = payload.length / 2;
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ uData.msgEncodingSet = true;
+ uData.numFields = udhCodeUnits + payloadCodeUnits;
+ uData.payload = new byte[uData.numFields * 2];
+ uData.payload[0] = (byte)udhData.length;
+ System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+ System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
+ }
+
+ private static void encodeEmsUserDataPayload(UserData uData)
+ throws CodingException
+ {
+ byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
+ if (uData.msgEncodingSet) {
+ if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+ encode7bitEms(uData, headerData, true);
+ } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+ encode16bitEms(uData, headerData);
+ } else {
+ throw new CodingException("unsupported EMS user data encoding (" +
+ uData.msgEncoding + ")");
+ }
+ } else {
+ try {
+ encode7bitEms(uData, headerData, false);
+ } catch (CodingException ex) {
+ encode16bitEms(uData, headerData);
+ }
+ }
+ }
+
+ private static byte[] encodeShiftJis(String msg) throws CodingException {
+ try {
+ return msg.getBytes("Shift_JIS");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("Shift-JIS encode failed: " + ex);
+ }
+ }
+
+ private static void encodeUserDataPayload(UserData uData)
+ throws CodingException
+ {
+ if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
+ Rlog.e(LOG_TAG, "user data with null payloadStr");
+ uData.payloadStr = "";
+ }
+
+ if (uData.userDataHeader != null) {
+ encodeEmsUserDataPayload(uData);
+ return;
+ }
+
+ if (uData.msgEncodingSet) {
+ if (uData.msgEncoding == UserData.ENCODING_OCTET) {
+ if (uData.payload == null) {
+ Rlog.e(LOG_TAG, "user data with octet encoding but null payload");
+ uData.payload = new byte[0];
+ uData.numFields = 0;
+ } else {
+ uData.numFields = uData.payload.length;
+ }
+ } else {
+ if (uData.payloadStr == null) {
+ Rlog.e(LOG_TAG, "non-octet user data with null payloadStr");
+ uData.payloadStr = "";
+ }
+ if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+ Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
+ uData.payload = gcr.data;
+ uData.numFields = gcr.septets;
+ } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
+ uData.payload = encode7bitAscii(uData.payloadStr, true);
+ uData.numFields = uData.payloadStr.length();
+ } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+ uData.payload = encodeUtf16(uData.payloadStr);
+ uData.numFields = uData.payloadStr.length();
+ } else if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) {
+ uData.payload = encodeShiftJis(uData.payloadStr);
+ uData.numFields = uData.payload.length;
+ } else {
+ throw new CodingException("unsupported user data encoding (" +
+ uData.msgEncoding + ")");
+ }
+ }
+ } else {
+ try {
+ uData.payload = encode7bitAscii(uData.payloadStr, false);
+ uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
+ } catch (CodingException ex) {
+ uData.payload = encodeUtf16(uData.payloadStr);
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ }
+ uData.numFields = uData.payloadStr.length();
+ uData.msgEncodingSet = true;
+ }
+ }
+
+ private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException, CodingException
+ {
+ /*
+ * TODO(cleanup): Do we really need to set userData.payload as
+ * a side effect of encoding? If not, we could avoid data
+ * copies by passing outStream directly.
+ */
+ encodeUserDataPayload(bData.userData);
+ bData.hasUserDataHeader = bData.userData.userDataHeader != null;
+
+ if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) {
+ throw new CodingException("encoded user data too large (" +
+ bData.userData.payload.length +
+ " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
+ }
+
+ /*
+ * TODO(cleanup): figure out what the right answer is WRT paddingBits field
+ *
+ * userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
+ * userData.paddingBits = 0; // XXX this seems better, but why?
+ *
+ */
+ int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
+ int paramBits = dataBits + 13;
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ paramBits += 8;
+ }
+ int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+ int paddingBits = (paramBytes * 8) - paramBits;
+ outStream.write(8, paramBytes);
+ outStream.write(5, bData.userData.msgEncoding);
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ outStream.write(8, bData.userData.msgType);
+ }
+ outStream.write(8, bData.userData.numFields);
+ outStream.writeByteArray(dataBits, bData.userData.payload);
+ if (paddingBits > 0) outStream.write(paddingBits, 0);
+ }
+
+ private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(1, bData.userAckReq ? 1 : 0);
+ outStream.write(1, bData.deliveryAckReq ? 1 : 0);
+ outStream.write(1, bData.readAckReq ? 1 : 0);
+ outStream.write(1, bData.reportReq ? 1 : 0);
+ outStream.write(4, 0);
+ }
+
+ private static byte[] encodeDtmfSmsAddress(String address) {
+ int digits = address.length();
+ int dataBits = digits * 4;
+ int dataBytes = (dataBits / 8);
+ dataBytes += (dataBits % 8) > 0 ? 1 : 0;
+ byte[] rawData = new byte[dataBytes];
+ for (int i = 0; i < digits; i++) {
+ char c = address.charAt(i);
+ int val = 0;
+ if ((c >= '1') && (c <= '9')) val = c - '0';
+ else if (c == '0') val = 10;
+ else if (c == '*') val = 11;
+ else if (c == '#') val = 12;
+ else return null;
+ rawData[i / 2] |= val << (4 - ((i % 2) * 4));
+ }
+ return rawData;
+ }
+
+ /*
+ * TODO(cleanup): CdmaSmsAddress encoding should make use of
+ * CdmaSmsAddress.parse provided that DTMF encoding is unified,
+ * and the difference in 4-bit vs. 8-bit is resolved.
+ */
+
+ private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ try {
+ addr.origBytes = addr.address.getBytes("US-ASCII");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("invalid SMS address, cannot convert to ASCII");
+ }
+ } else {
+ addr.origBytes = encodeDtmfSmsAddress(addr.address);
+ }
+ }
+
+ private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException, CodingException
+ {
+ CdmaSmsAddress addr = bData.callbackNumber;
+ encodeCdmaSmsAddress(addr);
+ int paramBits = 9;
+ int dataBits = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ paramBits += 7;
+ dataBits = addr.numberOfDigits * 8;
+ } else {
+ dataBits = addr.numberOfDigits * 4;
+ }
+ paramBits += dataBits;
+ int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+ int paddingBits = (paramBytes * 8) - paramBits;
+ outStream.write(8, paramBytes);
+ outStream.write(1, addr.digitMode);
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ outStream.write(3, addr.ton);
+ outStream.write(4, addr.numberPlan);
+ }
+ outStream.write(8, addr.numberOfDigits);
+ outStream.writeByteArray(dataBits, addr.origBytes);
+ if (paddingBits > 0) outStream.write(paddingBits, 0);
+ }
+
+ private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.errorClass);
+ outStream.write(6, bData.messageStatus);
+ }
+
+ private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.numberOfMessages);
+ }
+
+ private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.validityPeriodRelative);
+ }
+
+ private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.privacy);
+ outStream.skip(6);
+ }
+
+ private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.language);
+ }
+
+ private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.displayMode);
+ outStream.skip(6);
+ }
+
+ private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.priority);
+ outStream.skip(6);
+ }
+
+ private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.alert);
+ outStream.skip(6);
+ }
+
+ private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults;
+ outStream.write(8, (results.size() * 4)); // 4 octets per program result
+ for (CdmaSmsCbProgramResults result : results) {
+ int category = result.getCategory();
+ outStream.write(8, category >> 8);
+ outStream.write(8, category);
+ outStream.write(8, result.getLanguage());
+ outStream.write(4, result.getCategoryResult());
+ outStream.skip(4);
+ }
+ }
+
+ /**
+ * Create serialized representation for BearerData object.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param bData an instance of BearerData.
+ *
+ * @return byte array of raw encoded SMS bearer data.
+ */
+ public static byte[] encode(BearerData bData) {
+ bData.hasUserDataHeader = ((bData.userData != null) &&
+ (bData.userData.userDataHeader != null));
+ try {
+ BitwiseOutputStream outStream = new BitwiseOutputStream(200);
+ outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
+ encodeMessageId(bData, outStream);
+ if (bData.userData != null) {
+ outStream.write(8, SUBPARAM_USER_DATA);
+ encodeUserData(bData, outStream);
+ }
+ if (bData.callbackNumber != null) {
+ outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
+ encodeCallbackNumber(bData, outStream);
+ }
+ if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
+ outStream.write(8, SUBPARAM_REPLY_OPTION);
+ encodeReplyOption(bData, outStream);
+ }
+ if (bData.numberOfMessages != 0) {
+ outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
+ encodeMsgCount(bData, outStream);
+ }
+ if (bData.validityPeriodRelativeSet) {
+ outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
+ encodeValidityPeriodRel(bData, outStream);
+ }
+ if (bData.privacyIndicatorSet) {
+ outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
+ encodePrivacyIndicator(bData, outStream);
+ }
+ if (bData.languageIndicatorSet) {
+ outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
+ encodeLanguageIndicator(bData, outStream);
+ }
+ if (bData.displayModeSet) {
+ outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
+ encodeDisplayMode(bData, outStream);
+ }
+ if (bData.priorityIndicatorSet) {
+ outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
+ encodePriorityIndicator(bData, outStream);
+ }
+ if (bData.alertIndicatorSet) {
+ outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
+ encodeMsgDeliveryAlert(bData, outStream);
+ }
+ if (bData.messageStatusSet) {
+ outStream.write(8, SUBPARAM_MESSAGE_STATUS);
+ encodeMsgStatus(bData, outStream);
+ }
+ if (bData.serviceCategoryProgramResults != null) {
+ outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS);
+ encodeScpResults(bData, outStream);
+ }
+ return outStream.toByteArray();
+ } catch (BitwiseOutputStream.AccessException ex) {
+ Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
+ } catch (CodingException ex) {
+ Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
+ }
+ return null;
+ }
+
+ private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 3 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.messageType = inStream.read(4);
+ bData.messageId = inStream.read(8) << 8;
+ bData.messageId |= inStream.read(8);
+ bData.hasUserDataHeader = (inStream.read(1) == 1);
+ inStream.skip(3);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeReserved(
+ BearerData bData, BitwiseInputStream inStream, int subparamId)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ boolean decodeSuccess = false;
+ int subparamLen = inStream.read(8); // SUBPARAM_LEN
+ int paramBits = subparamLen * 8;
+ if (paramBits <= inStream.available()) {
+ decodeSuccess = true;
+ inStream.skip(paramBits);
+ }
+ Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
+ + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
+ if (!decodeSuccess) {
+ throw new CodingException("RESERVED bearer data subparameter " + subparamId
+ + " had invalid SUBPARAM_LEN " + subparamLen);
+ }
+
+ return decodeSuccess;
+ }
+
+ private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException
+ {
+ int paramBits = inStream.read(8) * 8;
+ bData.userData = new UserData();
+ bData.userData.msgEncoding = inStream.read(5);
+ bData.userData.msgEncodingSet = true;
+ bData.userData.msgType = 0;
+ int consumedBits = 5;
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ bData.userData.msgType = inStream.read(8);
+ consumedBits += 8;
+ }
+ bData.userData.numFields = inStream.read(8);
+ consumedBits += 8;
+ int dataBits = paramBits - consumedBits;
+ bData.userData.payload = inStream.readByteArray(dataBits);
+ return true;
+ }
+
+ private static String decodeUtf8(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ return decodeCharset(data, offset, numFields, 1, "UTF-8");
+ }
+
+ private static String decodeUtf16(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ // Subtract header and possible padding byte (at end) from num fields.
+ int padding = offset % 2;
+ numFields -= (offset + padding) / 2;
+ return decodeCharset(data, offset, numFields, 2, "utf-16be");
+ }
+
+ private static String decodeCharset(byte[] data, int offset, int numFields, int width,
+ String charset) throws CodingException
+ {
+ if (numFields < 0 || (numFields * width + offset) > data.length) {
+ // Try to decode the max number of characters in payload
+ int padding = offset % width;
+ int maxNumFields = (data.length - offset - padding) / width;
+ if (maxNumFields < 0) {
+ throw new CodingException(charset + " decode failed: offset out of range");
+ }
+ Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
+ + numFields + " data.length = " + data.length + " maxNumFields = "
+ + maxNumFields);
+ numFields = maxNumFields;
+ }
+ try {
+ return new String(data, offset, numFields * width, charset);
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException(charset + " decode failed: " + ex);
+ }
+ }
+
+ private static String decode7bitAscii(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ try {
+ offset *= 8;
+ StringBuffer strBuf = new StringBuffer(numFields);
+ BitwiseInputStream inStream = new BitwiseInputStream(data);
+ int wantedBits = (offset * 8) + (numFields * 7);
+ if (inStream.available() < wantedBits) {
+ throw new CodingException("insufficient data (wanted " + wantedBits +
+ " bits, but only have " + inStream.available() + ")");
+ }
+ inStream.skip(offset);
+ for (int i = 0; i < numFields; i++) {
+ int charCode = inStream.read(7);
+ if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
+ (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
+ strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
+ } else if (charCode == UserData.ASCII_NL_INDEX) {
+ strBuf.append('\n');
+ } else if (charCode == UserData.ASCII_CR_INDEX) {
+ strBuf.append('\r');
+ } else {
+ /* For other charCodes, they are unprintable, and so simply use SPACE. */
+ strBuf.append(' ');
+ }
+ }
+ return strBuf.toString();
+ } catch (BitwiseInputStream.AccessException ex) {
+ throw new CodingException("7bit ASCII decode failed: " + ex);
+ }
+ }
+
+ private static String decode7bitGsm(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ // Start reading from the next 7-bit aligned boundary after offset.
+ int offsetBits = offset * 8;
+ int offsetSeptets = (offsetBits + 6) / 7;
+ numFields -= offsetSeptets;
+ int paddingBits = (offsetSeptets * 7) - offsetBits;
+ String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
+ 0, 0);
+ if (result == null) {
+ throw new CodingException("7bit GSM decoding failed");
+ }
+ return result;
+ }
+
+ private static String decodeLatin(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
+ }
+
+ private static String decodeShiftJis(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
+ }
+
+ private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
+ throws CodingException
+ {
+ int offset = 0;
+ if (hasUserDataHeader) {
+ int udhLen = userData.payload[0] & 0x00FF;
+ offset += udhLen + 1;
+ byte[] headerData = new byte[udhLen];
+ System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
+ userData.userDataHeader = SmsHeader.fromByteArray(headerData);
+ }
+ switch (userData.msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ /*
+ * Octet decoding depends on the carrier service.
+ */
+ boolean decodingtypeUTF8 = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_sms_utf8_support);
+
+ // Strip off any padding bytes, meaning any differences between the length of the
+ // array and the target length specified by numFields. This is to avoid any
+ // confusion by code elsewhere that only considers the payload array length.
+ byte[] payload = new byte[userData.numFields];
+ int copyLen = userData.numFields < userData.payload.length
+ ? userData.numFields : userData.payload.length;
+
+ System.arraycopy(userData.payload, 0, payload, 0, copyLen);
+ userData.payload = payload;
+
+ if (!decodingtypeUTF8) {
+ // There are many devices in the market that send 8bit text sms (latin encoded) as
+ // octet encoded.
+ userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+ } else {
+ userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
+ }
+ break;
+
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_7BIT_ASCII:
+ userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_UNICODE_16:
+ userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_LATIN:
+ userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_SHIFT_JIS:
+ userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
+ break;
+ default:
+ throw new CodingException("unsupported user data encoding ("
+ + userData.msgEncoding + ")");
+ }
+ }
+
+ /**
+ * IS-91 Voice Mail message decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ * (For character encodings, see TIA/EIA/IS-91, Annex B)
+ *
+ * Protocol Summary: The user data payload may contain 3-14
+ * characters. The first two characters are parsed as a number
+ * and indicate the number of voicemails. The third character is
+ * either a SPACE or '!' to indicate normal or urgent priority,
+ * respectively. Any following characters are treated as normal
+ * text user data payload.
+ *
+ * Note that the characters encoding is 6-bit packed.
+ */
+ private static void decodeIs91VoicemailStatus(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 6; // 6-bit packed character encoding.
+ int numFields = bData.userData.numFields;
+ if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 voicemail status decoding failed");
+ }
+ try {
+ StringBuffer strbuf = new StringBuffer(dataLen);
+ while (inStream.available() >= 6) {
+ strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+ }
+ String data = strbuf.toString();
+ bData.numberOfMessages = Integer.parseInt(data.substring(0, 2));
+ char prioCode = data.charAt(2);
+ if (prioCode == ' ') {
+ bData.priority = PRIORITY_NORMAL;
+ } else if (prioCode == '!') {
+ bData.priority = PRIORITY_URGENT;
+ } else {
+ throw new CodingException("IS-91 voicemail status decoding failed: " +
+ "illegal priority setting (" + prioCode + ")");
+ }
+ bData.priorityIndicatorSet = true;
+ bData.userData.payloadStr = data.substring(3, numFields - 3);
+ } catch (java.lang.NumberFormatException ex) {
+ throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+ } catch (java.lang.IndexOutOfBoundsException ex) {
+ throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+ }
+ }
+
+ /**
+ * IS-91 Short Message decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ * (For character encodings, see TIA/EIA/IS-91, Annex B)
+ *
+ * Protocol Summary: The user data payload may contain 1-14
+ * characters, which are treated as normal text user data payload.
+ * Note that the characters encoding is 6-bit packed.
+ */
+ private static void decodeIs91ShortMessage(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 6; // 6-bit packed character encoding.
+ int numFields = bData.userData.numFields;
+ // dataLen may be > 14 characters due to octet padding
+ if ((numFields > 14) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 short message decoding failed");
+ }
+ StringBuffer strbuf = new StringBuffer(dataLen);
+ for (int i = 0; i < numFields; i++) {
+ strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+ }
+ bData.userData.payloadStr = strbuf.toString();
+ }
+
+ /**
+ * IS-91 CLI message (callback number) decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ *
+ * Protocol Summary: The data payload may contain 1-32 digits,
+ * encoded using standard 4-bit DTMF, which are treated as a
+ * callback number.
+ */
+ private static void decodeIs91Cli(BearerData bData) throws CodingException {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding.
+ int numFields = bData.userData.numFields;
+ if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 voicemail status decoding failed");
+ }
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
+ addr.origBytes = bData.userData.payload;
+ addr.numberOfDigits = (byte)numFields;
+ decodeSmsAddress(addr);
+ bData.callbackNumber = addr;
+ }
+
+ private static void decodeIs91(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ switch (bData.userData.msgType) {
+ case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS:
+ decodeIs91VoicemailStatus(bData);
+ break;
+ case UserData.IS91_MSG_TYPE_CLI:
+ decodeIs91Cli(bData);
+ break;
+ case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL:
+ case UserData.IS91_MSG_TYPE_SHORT_MESSAGE:
+ decodeIs91ShortMessage(bData);
+ break;
+ default:
+ throw new CodingException("unsupported IS-91 message type (" +
+ bData.userData.msgType + ")");
+ }
+ }
+
+ private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.userAckReq = (inStream.read(1) == 1);
+ bData.deliveryAckReq = (inStream.read(1) == 1);
+ bData.readAckReq = (inStream.read(1) == 1);
+ bData.reportReq = (inStream.read(1) == 1);
+ inStream.skip(4);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "REPLY_OPTION decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 2 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static String decodeDtmfSmsAddress(byte[] rawData, int numFields)
+ throws CodingException
+ {
+ /* DTMF 4-bit digit encoding, defined in at
+ * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */
+ StringBuffer strBuf = new StringBuffer(numFields);
+ for (int i = 0; i < numFields; i++) {
+ int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4)));
+ if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10));
+ else if (val == 10) strBuf.append('0');
+ else if (val == 11) strBuf.append('*');
+ else if (val == 12) strBuf.append('#');
+ else throw new CodingException("invalid SMS address DTMF code (" + val + ")");
+ }
+ return strBuf.toString();
+ }
+
+ private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException {
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ try {
+ /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually
+ * just 7-bit ASCII encoding, with the MSB being zero. */
+ addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("invalid SMS address ASCII code");
+ }
+ } else {
+ addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits);
+ }
+ }
+
+ private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8; //at least
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits < EXPECTED_PARAM_SIZE) {
+ inStream.skip(paramBits);
+ return false;
+ }
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = inStream.read(1);
+ byte fieldBits = 4;
+ byte consumedBits = 1;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ addr.ton = inStream.read(3);
+ addr.numberPlan = inStream.read(4);
+ fieldBits = 8;
+ consumedBits += 7;
+ }
+ addr.numberOfDigits = inStream.read(8);
+ consumedBits += 8;
+ int remainingBits = paramBits - consumedBits;
+ int dataBits = addr.numberOfDigits * fieldBits;
+ int paddingBits = remainingBits - dataBits;
+ if (remainingBits < dataBits) {
+ throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" +
+ "remainingBits + " + remainingBits + ", dataBits + " +
+ dataBits + ", paddingBits + " + paddingBits + ")");
+ }
+ addr.origBytes = inStream.readByteArray(dataBits);
+ inStream.skip(paddingBits);
+ decodeSmsAddress(addr);
+ bData.callbackNumber = addr;
+ return true;
+ }
+
+ private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.errorClass = inStream.read(2);
+ bData.messageStatus = inStream.read(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.messageStatusSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
+ inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.deferredDeliveryTimeRelative = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.deferredDeliveryTimeRelativeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.validityPeriodRelative = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.validityPeriodRelativeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.privacy = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.privacyIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.language = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.languageIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.displayMode = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "DISPLAY_MODE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.displayModeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.priority = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.priorityIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.alert = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.alertIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.userResponseCode = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.userResponseCodeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeServiceCategoryProgramData(BearerData bData,
+ BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
+ {
+ if (inStream.available() < 13) {
+ throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+ + inStream.available() + " bits available");
+ }
+
+ int paramBits = inStream.read(8) * 8;
+ int msgEncoding = inStream.read(5);
+ paramBits -= 5;
+
+ if (inStream.available() < paramBits) {
+ throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+ + inStream.available() + " bits available (" + paramBits + " bits expected)");
+ }
+
+ ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
+
+ final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
+ int operation = inStream.read(4);
+ int category = (inStream.read(8) << 8) | inStream.read(8);
+ int language = inStream.read(8);
+ int maxMessages = inStream.read(8);
+ int alertOption = inStream.read(4);
+ int numFields = inStream.read(8);
+ paramBits -= CATEGORY_FIELD_MIN_SIZE;
+
+ int textBits = getBitsForNumFields(msgEncoding, numFields);
+ if (paramBits < textBits) {
+ throw new CodingException("category name is " + textBits + " bits in length,"
+ + " but there are only " + paramBits + " bits available");
+ }
+
+ UserData userData = new UserData();
+ userData.msgEncoding = msgEncoding;
+ userData.msgEncodingSet = true;
+ userData.numFields = numFields;
+ userData.payload = inStream.readByteArray(textBits);
+ paramBits -= textBits;
+
+ decodeUserDataPayload(userData, false);
+ String categoryName = userData.payloadStr;
+ CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
+ language, maxMessages, alertOption, categoryName);
+ programDataList.add(programData);
+
+ decodeSuccess = true;
+ }
+
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ')');
+ }
+
+ inStream.skip(paramBits);
+ bData.serviceCategoryProgramData = programDataList;
+ return decodeSuccess;
+ }
+
+ private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
+ switch (serviceCategory) {
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
+ return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+ return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+ return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+ return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+ case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Calculates the number of bits to read for the specified number of encoded characters.
+ * @param msgEncoding the message encoding to use
+ * @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
+ * this is the number of bytes to read.
+ * @return the number of bits to read from the stream
+ * @throws CodingException if the specified encoding is not supported
+ */
+ private static int getBitsForNumFields(int msgEncoding, int numFields)
+ throws CodingException {
+ switch (msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ case UserData.ENCODING_SHIFT_JIS:
+ case UserData.ENCODING_KOREAN:
+ case UserData.ENCODING_LATIN:
+ case UserData.ENCODING_LATIN_HEBREW:
+ return numFields * 8;
+
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_7BIT_ASCII:
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ return numFields * 7;
+
+ case UserData.ENCODING_UNICODE_16:
+ return numFields * 16;
+
+ default:
+ throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
+ }
+ }
+
+ /**
+ * CMAS message decoding.
+ * (See TIA-1149-0-1, CMAS over CDMA)
+ *
+ * @param serviceCategory is the service category from the SMS envelope
+ */
+ private static void decodeCmasUserData(BearerData bData, int serviceCategory)
+ throws BitwiseInputStream.AccessException, CodingException {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ if (inStream.available() < 8) {
+ throw new CodingException("emergency CB with no CMAE_protocol_version");
+ }
+ int protocolVersion = inStream.read(8);
+ if (protocolVersion != 0) {
+ throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
+ }
+
+ int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
+ int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+ int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+ int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+
+ while (inStream.available() >= 16) {
+ int recordType = inStream.read(8);
+ int recordLen = inStream.read(8);
+ switch (recordType) {
+ case 0: // Type 0 elements (Alert text)
+ UserData alertUserData = new UserData();
+ alertUserData.msgEncoding = inStream.read(5);
+ alertUserData.msgEncodingSet = true;
+ alertUserData.msgType = 0;
+
+ int numFields; // number of chars to decode
+ switch (alertUserData.msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ case UserData.ENCODING_LATIN:
+ numFields = recordLen - 1; // subtract 1 byte for encoding
+ break;
+
+ case UserData.ENCODING_IA5:
+ case UserData.ENCODING_7BIT_ASCII:
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding
+ break;
+
+ case UserData.ENCODING_UNICODE_16:
+ numFields = (recordLen - 1) / 2;
+ break;
+
+ default:
+ numFields = 0; // unsupported encoding
+ }
+
+ alertUserData.numFields = numFields;
+ alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
+ decodeUserDataPayload(alertUserData, false);
+ bData.userData = alertUserData;
+ break;
+
+ case 1: // Type 1 elements
+ category = inStream.read(8);
+ responseType = inStream.read(8);
+ severity = inStream.read(4);
+ urgency = inStream.read(4);
+ certainty = inStream.read(4);
+ inStream.skip(recordLen * 8 - 28);
+ break;
+
+ default:
+ Rlog.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
+ inStream.skip(recordLen * 8);
+ break;
+ }
+ }
+
+ bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
+ urgency, certainty);
+ }
+
+ /**
+ * Create BearerData object from serialized representation.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param smsData byte array of raw encoded SMS bearer data.
+ * @return an instance of BearerData.
+ */
+ public static BearerData decode(byte[] smsData) {
+ return decode(smsData, 0);
+ }
+
+ private static boolean isCmasAlertCategory(int category) {
+ return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
+ && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
+ }
+
+ /**
+ * Create BearerData object from serialized representation.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param smsData byte array of raw encoded SMS bearer data.
+ * @param serviceCategory the envelope service category (for CMAS alert handling)
+ * @return an instance of BearerData.
+ */
+ public static BearerData decode(byte[] smsData, int serviceCategory) {
+ try {
+ BitwiseInputStream inStream = new BitwiseInputStream(smsData);
+ BearerData bData = new BearerData();
+ int foundSubparamMask = 0;
+ while (inStream.available() > 0) {
+ int subparamId = inStream.read(8);
+ int subparamIdBit = 1 << subparamId;
+ // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
+ // as 32th bit is the max bit in int.
+ // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
+ // last defined subparam ID is 23 (00010111 = 0x17 = 23).
+ // Only do duplicate subparam ID check if subparam is within defined value as
+ // reserved subparams are just skipped.
+ if ((foundSubparamMask & subparamIdBit) != 0 &&
+ (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+ subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+ throw new CodingException("illegal duplicate subparameter (" +
+ subparamId + ")");
+ }
+ boolean decodeSuccess;
+ switch (subparamId) {
+ case SUBPARAM_MESSAGE_IDENTIFIER:
+ decodeSuccess = decodeMessageId(bData, inStream);
+ break;
+ case SUBPARAM_USER_DATA:
+ decodeSuccess = decodeUserData(bData, inStream);
+ break;
+ case SUBPARAM_USER_RESPONSE_CODE:
+ decodeSuccess = decodeUserResponseCode(bData, inStream);
+ break;
+ case SUBPARAM_REPLY_OPTION:
+ decodeSuccess = decodeReplyOption(bData, inStream);
+ break;
+ case SUBPARAM_NUMBER_OF_MESSAGES:
+ decodeSuccess = decodeMsgCount(bData, inStream);
+ break;
+ case SUBPARAM_CALLBACK_NUMBER:
+ decodeSuccess = decodeCallbackNumber(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_STATUS:
+ decodeSuccess = decodeMsgStatus(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_CENTER_TIME_STAMP:
+ decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream);
+ break;
+ case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE:
+ decodeSuccess = decodeValidityAbs(bData, inStream);
+ break;
+ case SUBPARAM_VALIDITY_PERIOD_RELATIVE:
+ decodeSuccess = decodeValidityRel(bData, inStream);
+ break;
+ case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE:
+ decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream);
+ break;
+ case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE:
+ decodeSuccess = decodeDeferredDeliveryRel(bData, inStream);
+ break;
+ case SUBPARAM_PRIVACY_INDICATOR:
+ decodeSuccess = decodePrivacyIndicator(bData, inStream);
+ break;
+ case SUBPARAM_LANGUAGE_INDICATOR:
+ decodeSuccess = decodeLanguageIndicator(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_DISPLAY_MODE:
+ decodeSuccess = decodeDisplayMode(bData, inStream);
+ break;
+ case SUBPARAM_PRIORITY_INDICATOR:
+ decodeSuccess = decodePriorityIndicator(bData, inStream);
+ break;
+ case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY:
+ decodeSuccess = decodeMsgDeliveryAlert(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
+ decodeSuccess = decodeDepositIndex(bData, inStream);
+ break;
+ case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
+ decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
+ break;
+ default:
+ decodeSuccess = decodeReserved(bData, inStream, subparamId);
+ }
+ if (decodeSuccess &&
+ (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+ subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+ foundSubparamMask |= subparamIdBit;
+ }
+ }
+ if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
+ throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+ }
+ if (bData.userData != null) {
+ if (isCmasAlertCategory(serviceCategory)) {
+ decodeCmasUserData(bData, serviceCategory);
+ } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+ if ((foundSubparamMask ^
+ (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
+ (1 << SUBPARAM_USER_DATA))
+ != 0) {
+ Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" +
+ foundSubparamMask + ")");
+ }
+ decodeIs91(bData);
+ } else {
+ decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
+ }
+ }
+ return bData;
+ } catch (BitwiseInputStream.AccessException ex) {
+ Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
+ } catch (CodingException ex) {
+ Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
new file mode 100644
index 0000000..5f2e561
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2008 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.cdma.sms;
+
+import android.util.SparseBooleanArray;
+
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.HexDump;
+
+public class CdmaSmsAddress extends SmsAddress {
+
+ /**
+ * Digit Mode Indicator is a 1-bit value that indicates whether
+ * the address digits are 4-bit DTMF codes or 8-bit codes. (See
+ * 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ static public final int DIGIT_MODE_4BIT_DTMF = 0x00;
+ static public final int DIGIT_MODE_8BIT_CHAR = 0x01;
+
+ public int digitMode;
+
+ /**
+ * Number Mode Indicator is 1-bit value that indicates whether the
+ * address type is a data network address or not. (See 3GPP2
+ * C.S0015-B, v2, 3.4.3.3)
+ */
+ static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00;
+ static public final int NUMBER_MODE_DATA_NETWORK = 0x01;
+
+ public int numberMode;
+
+ /**
+ * Number Types for data networks.
+ * (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table)
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset)
+ * NOTE: value is stored in the parent class ton field.
+ */
+ static public final int TON_UNKNOWN = 0x00;
+ static public final int TON_INTERNATIONAL_OR_IP = 0x01;
+ static public final int TON_NATIONAL_OR_EMAIL = 0x02;
+ static public final int TON_NETWORK = 0x03;
+ static public final int TON_SUBSCRIBER = 0x04;
+ static public final int TON_ALPHANUMERIC = 0x05;
+ static public final int TON_ABBREVIATED = 0x06;
+ static public final int TON_RESERVED = 0x07;
+
+ /**
+ * Maximum lengths for fields as defined in ril_cdma_sms.h.
+ */
+ static public final int SMS_ADDRESS_MAX = 36;
+ static public final int SMS_SUBADDRESS_MAX = 36;
+
+ /**
+ * This field shall be set to the number of address digits
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public int numberOfDigits;
+
+ /**
+ * Numbering Plan identification is a 0 or 4-bit value that
+ * indicates which numbering plan identification is set. (See
+ * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3)
+ */
+ static public final int NUMBERING_PLAN_UNKNOWN = 0x0;
+ static public final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1;
+ //static protected final int NUMBERING_PLAN_DATA = 0x3;
+ //static protected final int NUMBERING_PLAN_TELEX = 0x4;
+ //static protected final int NUMBERING_PLAN_PRIVATE = 0x9;
+
+ public int numberPlan;
+
+ /**
+ * NOTE: the parsed string address and the raw byte array values
+ * are stored in the parent class address and origBytes fields,
+ * respectively.
+ */
+
+ public CdmaSmsAddress(){
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CdmaSmsAddress ");
+ builder.append("{ digitMode=" + digitMode);
+ builder.append(", numberMode=" + numberMode);
+ builder.append(", numberPlan=" + numberPlan);
+ builder.append(", numberOfDigits=" + numberOfDigits);
+ builder.append(", ton=" + ton);
+ builder.append(", address=\"" + address + "\"");
+ builder.append(", origBytes=" + HexDump.toHexString(origBytes));
+ builder.append(" }");
+ return builder.toString();
+ }
+
+ /*
+ * TODO(cleanup): Refactor the parsing for addresses to better
+ * share code and logic with GSM. Also, gather all DTMF/BCD
+ * processing code in one place.
+ */
+
+ private static byte[] parseToDtmf(String address) {
+ int digits = address.length();
+ byte[] result = new byte[digits];
+ for (int i = 0; i < digits; i++) {
+ char c = address.charAt(i);
+ int val = 0;
+ if ((c >= '1') && (c <= '9')) val = c - '0';
+ else if (c == '0') val = 10;
+ else if (c == '*') val = 11;
+ else if (c == '#') val = 12;
+ else return null;
+ result[i] = (byte)val;
+ }
+ return result;
+ }
+
+ private static final char[] numericCharsDialable = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'
+ };
+
+ private static final char[] numericCharsSugar = {
+ '(', ')', ' ', '-', '+', '.', '/', '\\'
+ };
+
+ private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray (
+ numericCharsDialable.length + numericCharsSugar.length);
+ static {
+ for (int i = 0; i < numericCharsDialable.length; i++) {
+ numericCharDialableMap.put(numericCharsDialable[i], true);
+ }
+ for (int i = 0; i < numericCharsSugar.length; i++) {
+ numericCharDialableMap.put(numericCharsSugar[i], false);
+ }
+ }
+
+ /**
+ * Given a numeric address string, return the string without
+ * syntactic sugar, meaning parens, spaces, hyphens/minuses, or
+ * plus signs. If the input string contains non-numeric
+ * non-punctuation characters, return null.
+ */
+ private static String filterNumericSugar(String address) {
+ StringBuilder builder = new StringBuilder();
+ int len = address.length();
+ for (int i = 0; i < len; i++) {
+ char c = address.charAt(i);
+ int mapIndex = numericCharDialableMap.indexOfKey(c);
+ if (mapIndex < 0) return null;
+ if (! numericCharDialableMap.valueAt(mapIndex)) continue;
+ builder.append(c);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given a string, return the string without whitespace,
+ * including CR/LF.
+ */
+ private static String filterWhitespace(String address) {
+ StringBuilder builder = new StringBuilder();
+ int len = address.length();
+ for (int i = 0; i < len; i++) {
+ char c = address.charAt(i);
+ if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue;
+ builder.append(c);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given a string, create a corresponding CdmaSmsAddress object.
+ *
+ * The result will be null if the input string is not
+ * representable using printable ASCII.
+ *
+ * For numeric addresses, the string is cleaned up by removing
+ * common punctuation. For alpha addresses, the string is cleaned
+ * up by removing whitespace.
+ */
+ public static CdmaSmsAddress parse(String address) {
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.address = address;
+ addr.ton = CdmaSmsAddress.TON_UNKNOWN;
+ byte[] origBytes = null;
+ String filteredAddr = filterNumericSugar(address);
+ if (filteredAddr != null) {
+ origBytes = parseToDtmf(filteredAddr);
+ }
+ if (origBytes != null) {
+ addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+ if (address.indexOf('+') != -1) {
+ addr.ton = TON_INTERNATIONAL_OR_IP;
+ }
+ } else {
+ filteredAddr = filterWhitespace(address);
+ origBytes = UserData.stringToAscii(filteredAddr);
+ if (origBytes == null) {
+ return null;
+ }
+ addr.digitMode = DIGIT_MODE_8BIT_CHAR;
+ addr.numberMode = NUMBER_MODE_DATA_NETWORK;
+ if (address.indexOf('@') != -1) {
+ addr.ton = TON_NATIONAL_OR_EMAIL;
+ }
+ }
+ addr.origBytes = origBytes;
+ addr.numberOfDigits = origBytes.length;
+ return addr;
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
new file mode 100644
index 0000000..0d5b502
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project. All rights reserved.
+ *
+ * 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.cdma.sms;
+
+public class CdmaSmsSubaddress {
+ public int type;
+
+ public byte odd;
+
+ public byte[] origBytes;
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
new file mode 100644
index 0000000..f73df56
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 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.cdma.sms;
+
+
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+
+public final class SmsEnvelope {
+ /**
+ * Message Types
+ * (See 3GPP2 C.S0015-B 3.4.1)
+ */
+ static public final int MESSAGE_TYPE_POINT_TO_POINT = 0x00;
+ static public final int MESSAGE_TYPE_BROADCAST = 0x01;
+ static public final int MESSAGE_TYPE_ACKNOWLEDGE = 0x02;
+
+ /**
+ * Supported Teleservices
+ * (See 3GPP2 N.S0005 and TIA-41)
+ */
+ static public final int TELESERVICE_NOT_SET = 0x0000;
+ static public final int TELESERVICE_WMT = 0x1002;
+ static public final int TELESERVICE_VMN = 0x1003;
+ static public final int TELESERVICE_WAP = 0x1004;
+ static public final int TELESERVICE_WEMT = 0x1005;
+ static public final int TELESERVICE_SCPT = 0x1006;
+
+ /**
+ * The following are defined as extensions to the standard teleservices
+ */
+ // Voice mail notification through Message Waiting Indication in CDMA mode or Analog mode.
+ // Defined in 3GPP2 C.S-0005, 3.7.5.6, an Info Record containing an 8-bit number with the
+ // number of messages waiting, it's used by some CDMA carriers for a voice mail count.
+ static public final int TELESERVICE_MWI = 0x40000;
+
+ // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
+ // static final int SERVICE_CATEGORY_EMERGENCY = 0x0001;
+ //...
+
+ // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
+ public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 0x1000;
+ public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT = 0x1001;
+ public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT = 0x1002;
+ public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003;
+ public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE = 0x1004;
+ public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE = 0x10ff;
+
+ /**
+ * Provides the type of a SMS message like point to point, broadcast or acknowledge
+ */
+ public int messageType;
+
+ /**
+ * The 16-bit Teleservice parameter identifies which upper layer service access point is sending
+ * or receiving the message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.1)
+ */
+ public int teleService = TELESERVICE_NOT_SET;
+
+ /**
+ * The 16-bit service category parameter identifies the type of service provided
+ * by the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.2)
+ */
+ public int serviceCategory;
+
+ /**
+ * The origination address identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public CdmaSmsAddress origAddress;
+
+ /**
+ * The destination address identifies the target of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public CdmaSmsAddress destAddress;
+
+ /**
+ * The origination subaddress identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+ */
+ public CdmaSmsSubaddress origSubaddress;
+
+ /**
+ * The 6-bit bearer reply parameter is used to request the return of a
+ * SMS Acknowledge Message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
+ */
+ public int bearerReply;
+
+ /**
+ * Cause Code values:
+ * The cause code parameters are an indication whether an SMS error has occurred and if so,
+ * whether the condition is considered temporary or permanent.
+ * ReplySeqNo 6-bit value,
+ * ErrorClass 2-bit value,
+ * CauseCode 0-bit or 8-bit value
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.6)
+ */
+ public byte replySeqNo;
+ public byte errorClass;
+ public byte causeCode;
+
+ /**
+ * encoded bearer data
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.7)
+ */
+ public byte[] bearerData;
+
+ public SmsEnvelope() {
+ // nothing to see here
+ }
+
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
new file mode 100644
index 0000000..629173d
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -0,0 +1,968 @@
+/*
+ * Copyright (C) 2008 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.cdma;
+
+import android.os.Parcel;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.telephony.Rlog;
+import android.util.Log;
+import android.text.TextUtils;
+import android.content.res.Resources;
+
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.HexDump;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * TODO(cleanup): these constants are disturbing... are they not just
+ * different interpretations on one number? And if we did not have
+ * terrible class name overlap, they would not need to be directly
+ * imported like this. The class in this file could just as well be
+ * named CdmaSmsMessage, could it not?
+ */
+
+/**
+ * TODO(cleanup): internally returning null in many places makes
+ * debugging very hard (among many other reasons) and should be made
+ * more meaningful (replaced with exceptions for example). Null
+ * returns should only occur at the very outside of the module/class
+ * scope.
+ */
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+ static final String LOG_TAG = "SmsMessage";
+ static private final String LOGGABLE_TAG = "CDMA:SMS";
+ private static final boolean VDBG = false;
+
+ private final static byte TELESERVICE_IDENTIFIER = 0x00;
+ private final static byte SERVICE_CATEGORY = 0x01;
+ private final static byte ORIGINATING_ADDRESS = 0x02;
+ private final static byte ORIGINATING_SUB_ADDRESS = 0x03;
+ private final static byte DESTINATION_ADDRESS = 0x04;
+ private final static byte DESTINATION_SUB_ADDRESS = 0x05;
+ private final static byte BEARER_REPLY_OPTION = 0x06;
+ private final static byte CAUSE_CODES = 0x07;
+ private final static byte BEARER_DATA = 0x08;
+
+ /**
+ * Status of a previously submitted SMS.
+ * This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
+ * Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
+ * See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
+ */
+ private int status;
+
+ /** Specifies if a return of an acknowledgment is requested for send SMS */
+ private static final int RETURN_NO_ACK = 0;
+ private static final int RETURN_ACK = 1;
+
+ private SmsEnvelope mEnvelope;
+ private BearerData mBearerData;
+
+ /** @hide */
+ public SmsMessage(SmsAddress addr, SmsEnvelope env) {
+ mOriginatingAddress = addr;
+ mEnvelope = env;
+ createPdu();
+ }
+
+ public SmsMessage() {}
+
+ public static class SubmitPdu extends SubmitPduBase {
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ * Note: In CDMA the PDU is just a byte representation of the received Sms.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessage msg = new SmsMessage();
+
+ try {
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ } catch (OutOfMemoryError e) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ try {
+ SmsMessage msg = new SmsMessage();
+
+ msg.mIndexOnIcc = index;
+
+ // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+ // or STORED_UNSENT
+ // See 3GPP2 C.S0023 3.4.27
+ if ((data[0] & 1) == 0) {
+ Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
+ return null;
+ } else {
+ msg.mStatusOnIcc = data[0] & 0x07;
+ }
+
+ // Second byte is the MSG_LEN, length of the message
+ // See 3GPP2 C.S0023 3.4.27
+ int size = data[1];
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] pdu = new byte[size];
+ System.arraycopy(data, 2, pdu, 0, size);
+ // the message has to be parsed before it can be displayed
+ // see gsm.SmsMessage
+ msg.parsePduFromEfRecord(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
+ return 0;
+ }
+
+ /**
+ * TODO(cleanup): why do getSubmitPdu methods take an scAddr input
+ * and do nothing with it? GSM allows us to specify a SC (eg,
+ * when responding to an SMS that explicitly requests the response
+ * is sent to a specific SC), or pass null to use the default
+ * value. Is there no similar notion in CDMA? Or do we just not
+ * have it hooked up?
+ */
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddr Service Centre address. Null means use default.
+ * @param destAddr Address of the recipient.
+ * @param message String representation of the message payload.
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param smsHeader Array containing the data for the User Data Header, preceded
+ * by the Element Identifiers.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+ boolean statusReportRequested, SmsHeader smsHeader) {
+
+ /**
+ * TODO(cleanup): Do we really want silent failure like this?
+ * Would it not be much more reasonable to make sure we don't
+ * call this function if we really want nothing done?
+ */
+ if (message == null || destAddr == null) {
+ return null;
+ }
+
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
+ *
+ * @param scAddr Service Centre address. null == use default
+ * @param destAddr the address of the destination for the message
+ * @param destPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
+ byte[] data, boolean statusReportRequested) {
+
+ /**
+ * TODO(cleanup): this is not a general-purpose SMS creation
+ * method, but rather something specialized to messages
+ * containing OCTET encoded (meaning non-human-readable) user
+ * data. The name should reflect that, and not just overload.
+ */
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ UserData uData = new UserData();
+ uData.userDataHeader = smsHeader;
+ uData.msgEncoding = UserData.ENCODING_OCTET;
+ uData.msgEncodingSet = true;
+ uData.payload = data;
+
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param destAddr the address of the destination for the message
+ * @param userData the data for the message
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+ boolean statusReportRequested) {
+ return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public int getProtocolIdentifier() {
+ Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
+ // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
+ return 0;
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isReplace() {
+ Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isCphsMwiMessage() {
+ Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMWIClearMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMWISetMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMwiDontStore() {
+ return ((mBearerData != null) &&
+ (mBearerData.numberOfMessages > 0) &&
+ (mBearerData.userData == null));
+ }
+
+ /**
+ * Returns the status for a previously submitted message.
+ * For not interfering with status codes from GSM, this status code is
+ * shifted to the bits 31-16.
+ */
+ @Override
+ public int getStatus() {
+ return (status << 16);
+ }
+
+ /** Return true iff the bearer data message type is DELIVERY_ACK. */
+ @Override
+ public boolean isStatusReportMessage() {
+ return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isReplyPathPresent() {
+ Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * Calculate the number of septets needed to encode the message.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly, boolean isEntireMsg) {
+ CharSequence newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(messageBody);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = messageBody;
+ }
+ return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg);
+ }
+
+ /**
+ * Returns the teleservice type of the message.
+ * @return the teleservice:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
+ */
+ public int getTeleService() {
+ return mEnvelope.teleService;
+ }
+
+ /**
+ * Returns the message type of the message.
+ * @return the message type:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
+ */
+ public int getMessageType() {
+ // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
+ // Use the service category parameter to detect CMAS and other cell broadcast messages.
+ if (mEnvelope.serviceCategory != 0) {
+ return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+ } else {
+ return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ }
+ }
+
+ /**
+ * Decodes pdu to an empty SMS object.
+ * In the CDMA case the pdu is just an internal byte stream representation
+ * of the SMS Java-object.
+ * @see #createPdu()
+ */
+ private void parsePdu(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ int length;
+ int bearerDataLength;
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+
+ try {
+ env.messageType = dis.readInt();
+ env.teleService = dis.readInt();
+ env.serviceCategory = dis.readInt();
+
+ addr.digitMode = dis.readByte();
+ addr.numberMode = dis.readByte();
+ addr.ton = dis.readByte();
+ addr.numberPlan = dis.readByte();
+
+ length = dis.readUnsignedByte();
+ addr.numberOfDigits = length;
+
+ // sanity check on the length
+ if (length > pdu.length) {
+ throw new RuntimeException(
+ "createFromPdu: Invalid pdu, addr.numberOfDigits " + length
+ + " > pdu len " + pdu.length);
+ }
+ addr.origBytes = new byte[length];
+ dis.read(addr.origBytes, 0, length); // digits
+
+ env.bearerReply = dis.readInt();
+ // CauseCode values:
+ env.replySeqNo = dis.readByte();
+ env.errorClass = dis.readByte();
+ env.causeCode = dis.readByte();
+
+ //encoded BearerData:
+ bearerDataLength = dis.readInt();
+ // sanity check on the length
+ if (bearerDataLength > pdu.length) {
+ throw new RuntimeException(
+ "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
+ + " > pdu len " + pdu.length);
+ }
+ env.bearerData = new byte[bearerDataLength];
+ dis.read(env.bearerData, 0, bearerDataLength);
+ dis.close();
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "createFromPdu: conversion from byte array to object failed: " + ex, ex);
+ } catch (Exception ex) {
+ Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
+ }
+
+ // link the filled objects to this SMS
+ mOriginatingAddress = addr;
+ env.origAddress = addr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
+ */
+ private void parsePduFromEfRecord(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
+
+ try {
+ env.messageType = dis.readByte();
+
+ while (dis.available() > 0) {
+ int parameterId = dis.readByte();
+ int parameterLen = dis.readUnsignedByte();
+ byte[] parameterData = new byte[parameterLen];
+
+ switch (parameterId) {
+ case TELESERVICE_IDENTIFIER:
+ /*
+ * 16 bit parameter that identifies which upper layer
+ * service access point is sending or should receive
+ * this message
+ */
+ env.teleService = dis.readUnsignedShort();
+ Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
+ break;
+ case SERVICE_CATEGORY:
+ /*
+ * 16 bit parameter that identifies type of service as
+ * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
+ */
+ env.serviceCategory = dis.readUnsignedShort();
+ break;
+ case ORIGINATING_ADDRESS:
+ case DESTINATION_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
+ addr.digitMode = addrBis.read(1);
+ addr.numberMode = addrBis.read(1);
+ int numberType = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ numberType = addrBis.read(3);
+ addr.ton = numberType;
+
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
+ addr.numberPlan = addrBis.read(4);
+ }
+
+ addr.numberOfDigits = addrBis.read(8);
+
+ byte[] data = new byte[addr.numberOfDigits];
+ byte b = 0x00;
+
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+ /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xF & addrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8
+ // bit
+ data[index] = convertDtmfToAscii(b);
+ }
+ } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xFF & addrBis.read(8));
+ data[index] = b;
+ }
+
+ } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
+ if (numberType == 2)
+ Rlog.e(LOG_TAG, "TODO: Originating Addr is email id");
+ else
+ Rlog.e(LOG_TAG,
+ "TODO: Originating Addr is data network address");
+ } else {
+ Rlog.e(LOG_TAG, "Originating Addr is of incorrect type");
+ }
+ } else {
+ Rlog.e(LOG_TAG, "Incorrect Digit mode");
+ }
+ addr.origBytes = data;
+ Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString());
+ break;
+ case ORIGINATING_SUB_ADDRESS:
+ case DESTINATION_SUB_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
+ subAddr.type = subAddrBis.read(3);
+ subAddr.odd = subAddrBis.readByteArray(1)[0];
+ int subAddrLen = subAddrBis.read(8);
+ byte[] subdata = new byte[subAddrLen];
+ for (int index = 0; index < subAddrLen; index++) {
+ b = (byte) (0xFF & subAddrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8 bit
+ subdata[index] = convertDtmfToAscii(b);
+ }
+ subAddr.origBytes = subdata;
+ break;
+ case BEARER_REPLY_OPTION:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
+ env.bearerReply = replyOptBis.read(6);
+ break;
+ case CAUSE_CODES:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
+ env.replySeqNo = ccBis.readByteArray(6)[0];
+ env.errorClass = ccBis.readByteArray(2)[0];
+ if (env.errorClass != 0x00)
+ env.causeCode = ccBis.readByteArray(8)[0];
+ break;
+ case BEARER_DATA:
+ dis.read(parameterData, 0, parameterLen);
+ env.bearerData = parameterData;
+ break;
+ default:
+ throw new Exception("unsupported parameterId (" + parameterId + ")");
+ }
+ }
+ bais.close();
+ dis.close();
+ } catch (Exception ex) {
+ Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
+ }
+
+ // link the filled objects to this SMS
+ mOriginatingAddress = addr;
+ env.origAddress = addr;
+ env.origSubaddress = subAddr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Parses a SMS message from its BearerData stream. (mobile-terminated only)
+ */
+ public void parseSms() {
+ // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
+ // It contains only an 8-bit number with the number of messages waiting
+ if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
+ mBearerData = new BearerData();
+ if (mEnvelope.bearerData != null) {
+ mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
+ }
+ if (VDBG) {
+ Rlog.d(LOG_TAG, "parseSms: get MWI " +
+ Integer.toString(mBearerData.numberOfMessages));
+ }
+ return;
+ }
+ mBearerData = BearerData.decode(mEnvelope.bearerData);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MT raw BearerData = '" +
+ HexDump.toHexString(mEnvelope.bearerData) + "'");
+ Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
+ }
+ mMessageRef = mBearerData.messageId;
+ if (mBearerData.userData != null) {
+ mUserData = mBearerData.userData.payload;
+ mUserDataHeader = mBearerData.userData.userDataHeader;
+ mMessageBody = mBearerData.userData.payloadStr;
+ }
+
+ if (mOriginatingAddress != null) {
+ mOriginatingAddress.address = new String(mOriginatingAddress.origBytes);
+ if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
+ if (mOriginatingAddress.address.charAt(0) != '+') {
+ mOriginatingAddress.address = "+" + mOriginatingAddress.address;
+ }
+ }
+ if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ + mOriginatingAddress.address);
+ }
+
+ if (mBearerData.msgCenterTimeStamp != null) {
+ mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
+ }
+
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+ // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
+ // The BearerData MsgStatus subparameter should only be
+ // included for DELIVERY_ACK messages. If it occurred for
+ // other messages, it would be unclear what the status
+ // being reported refers to. The MsgStatus subparameter
+ // is primarily useful to indicate error conditions -- a
+ // message without this subparameter is assumed to
+ // indicate successful delivery (status == 0).
+ if (! mBearerData.messageStatusSet) {
+ Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
+ (mUserData == null ? "also missing" : "does have") +
+ " userData).");
+ status = 0;
+ } else {
+ status = mBearerData.errorClass << 8;
+ status |= mBearerData.messageStatus;
+ }
+ } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
+ throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
+ }
+
+ if (mMessageBody != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
+ parseMessageBody();
+ } else if ((mUserData != null) && VDBG) {
+ Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
+ }
+ }
+
+ /**
+ * Parses a broadcast SMS, possibly containing a CMAS alert.
+ */
+ public SmsCbMessage parseBroadcastSms() {
+ BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
+ if (bData == null) {
+ Rlog.w(LOG_TAG, "BearerData.decode() returned null");
+ return null;
+ }
+
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
+ }
+
+ String plmn = TelephonyManager.getDefault().getNetworkOperator();
+ SmsCbLocation location = new SmsCbLocation(plmn);
+
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+ SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
+ mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
+ bData.priority, null, bData.cmasWarningInfo);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SmsConstants.MessageClass getMessageClass() {
+ if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
+ return SmsConstants.MessageClass.CLASS_0;
+ } else {
+ return SmsConstants.MessageClass.UNKNOWN;
+ }
+ }
+
+ /**
+ * Calculate the next message id, starting at 1 and iteratively
+ * incrementing within the range 1..65535 remembering the state
+ * via a persistent system property. (See C.S0015-B, v2.0,
+ * 4.3.1.5) Since this routine is expected to be accessed via via
+ * binder-call, and hence should be thread-safe, it has been
+ * synchronized.
+ */
+ public synchronized static int getNextMessageId() {
+ // Testing and dialog with partners has indicated that
+ // msgId==0 is (sometimes?) treated specially by lower levels.
+ // Specifically, the ID is not preserved for delivery ACKs.
+ // Hence, avoid 0 -- constraining the range to 1..65535.
+ int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
+ String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
+ try{
+ SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
+ Rlog.d(LOG_TAG, "readback gets " +
+ SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
+ }
+ } catch(RuntimeException ex) {
+ Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
+ }
+ return msgId;
+ }
+
+ /**
+ * Creates BearerData and Envelope from parameters for a Submit SMS.
+ * @return byte stream for SubmitPdu.
+ */
+ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+ UserData userData) {
+
+ /**
+ * TODO(cleanup): give this function a more meaningful name.
+ */
+
+ /**
+ * TODO(cleanup): Make returning null from the getSubmitPdu
+ * variations meaningful -- clean up the error feedback
+ * mechanism, and avoid null pointer exceptions.
+ */
+
+ /**
+ * North America Plus Code :
+ * Convert + code to 011 and dial out for international SMS
+ */
+ CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
+ if (destAddr == null) return null;
+
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+
+ bearerData.messageId = getNextMessageId();
+
+ bearerData.deliveryAckReq = statusReportRequested;
+ bearerData.userAckReq = false;
+ bearerData.readAckReq = false;
+ bearerData.reportReq = false;
+
+ bearerData.userData = userData;
+
+ byte[] encodedBearerData = BearerData.encode(bearerData);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
+ Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
+ }
+ if (encodedBearerData == null) return null;
+
+ int teleservice = bearerData.hasUserDataHeader ?
+ SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
+
+ SmsEnvelope envelope = new SmsEnvelope();
+ envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ envelope.teleService = teleservice;
+ envelope.destAddress = destAddr;
+ envelope.bearerReply = RETURN_ACK;
+ envelope.bearerData = encodedBearerData;
+
+ /**
+ * TODO(cleanup): envelope looks to be a pointless class, get
+ * rid of it. Also -- most of the envelope fields set here
+ * are ignored, why?
+ */
+
+ try {
+ /**
+ * TODO(cleanup): reference a spec and get rid of the ugly comments
+ */
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeInt(envelope.teleService);
+ dos.writeInt(0); //servicePresent
+ dos.writeInt(0); //serviceCategory
+ dos.write(destAddr.digitMode);
+ dos.write(destAddr.numberMode);
+ dos.write(destAddr.ton); // number_type
+ dos.write(destAddr.numberPlan);
+ dos.write(destAddr.numberOfDigits);
+ dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+ // Subaddress is not supported.
+ dos.write(0); //subaddressType
+ dos.write(0); //subaddr_odd
+ dos.write(0); //subaddr_nbr_of_digits
+ dos.write(encodedBearerData.length);
+ dos.write(encodedBearerData, 0, encodedBearerData.length);
+ dos.close();
+
+ SubmitPdu pdu = new SubmitPdu();
+ pdu.encodedMessage = baos.toByteArray();
+ pdu.encodedScAddress = null;
+ return pdu;
+ } catch(IOException ex) {
+ Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
+ }
+ return null;
+ }
+
+ /**
+ * Creates byte array (pseudo pdu) from SMS object.
+ * Note: Do not call this method more than once per object!
+ * @hide
+ */
+ public void createPdu() {
+ SmsEnvelope env = mEnvelope;
+ CdmaSmsAddress addr = env.origAddress;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
+
+ try {
+ dos.writeInt(env.messageType);
+ dos.writeInt(env.teleService);
+ dos.writeInt(env.serviceCategory);
+
+ dos.writeByte(addr.digitMode);
+ dos.writeByte(addr.numberMode);
+ dos.writeByte(addr.ton);
+ dos.writeByte(addr.numberPlan);
+ dos.writeByte(addr.numberOfDigits);
+ dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
+
+ dos.writeInt(env.bearerReply);
+ // CauseCode values:
+ dos.writeByte(env.replySeqNo);
+ dos.writeByte(env.errorClass);
+ dos.writeByte(env.causeCode);
+ //encoded BearerData:
+ dos.writeInt(env.bearerData.length);
+ dos.write(env.bearerData, 0, env.bearerData.length);
+ dos.close();
+
+ /**
+ * TODO(cleanup) -- The mPdu field is managed in
+ * a fragile manner, and it would be much nicer if
+ * accessing the serialized representation used a less
+ * fragile mechanism. Maybe the getPdu method could
+ * generate a representation if there was not yet one?
+ */
+
+ mPdu = baos.toByteArray();
+ } catch (IOException ex) {
+ Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
+ }
+ }
+
+ /**
+ * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
+ * @hide
+ */
+ public static byte convertDtmfToAscii(byte dtmfDigit) {
+ byte asciiDigit;
+
+ switch (dtmfDigit) {
+ case 0: asciiDigit = 68; break; // 'D'
+ case 1: asciiDigit = 49; break; // '1'
+ case 2: asciiDigit = 50; break; // '2'
+ case 3: asciiDigit = 51; break; // '3'
+ case 4: asciiDigit = 52; break; // '4'
+ case 5: asciiDigit = 53; break; // '5'
+ case 6: asciiDigit = 54; break; // '6'
+ case 7: asciiDigit = 55; break; // '7'
+ case 8: asciiDigit = 56; break; // '8'
+ case 9: asciiDigit = 57; break; // '9'
+ case 10: asciiDigit = 48; break; // '0'
+ case 11: asciiDigit = 42; break; // '*'
+ case 12: asciiDigit = 35; break; // '#'
+ case 13: asciiDigit = 65; break; // 'A'
+ case 14: asciiDigit = 66; break; // 'B'
+ case 15: asciiDigit = 67; break; // 'C'
+ default:
+ asciiDigit = 32; // Invalid DTMF code
+ break;
+ }
+
+ return asciiDigit;
+ }
+
+ /** This function shall be called to get the number of voicemails.
+ * @hide
+ */
+ public int getNumOfVoicemails() {
+ return mBearerData.numberOfMessages;
+ }
+
+ /**
+ * Returns a byte array that can be use to uniquely identify a received SMS message.
+ * C.S0015-B 4.3.1.6 Unique Message Identification.
+ *
+ * @return byte array uniquely identifying the message.
+ * @hide
+ */
+ public byte[] getIncomingSmsFingerprint() {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ output.write(mEnvelope.serviceCategory);
+ output.write(mEnvelope.teleService);
+ output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
+ output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
+ output.write(mEnvelope.origSubaddress.origBytes, 0,
+ mEnvelope.origSubaddress.origBytes.length);
+
+ return output.toByteArray();
+ }
+
+ /**
+ * Returns the list of service category program data, if present.
+ * @return a list of CdmaSmsCbProgramData objects, or null if not present
+ * @hide
+ */
+ public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
+ return mBearerData.serviceCategoryProgramData;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/UserData.java b/telephony/java/com/android/internal/telephony/cdma/UserData.java
new file mode 100644
index 0000000..599c2b3
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/UserData.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2008 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.cdma.sms;
+
+import android.util.SparseIntArray;
+
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.util.HexDump;
+
+public class UserData {
+
+ /**
+ * User data encoding types.
+ * (See 3GPP2 C.R1001-F, v1.0, table 9.1-1)
+ */
+ public static final int ENCODING_OCTET = 0x00;
+ public static final int ENCODING_IS91_EXTENDED_PROTOCOL = 0x01;
+ public static final int ENCODING_7BIT_ASCII = 0x02;
+ public static final int ENCODING_IA5 = 0x03;
+ public static final int ENCODING_UNICODE_16 = 0x04;
+ public static final int ENCODING_SHIFT_JIS = 0x05;
+ public static final int ENCODING_KOREAN = 0x06;
+ public static final int ENCODING_LATIN_HEBREW = 0x07;
+ public static final int ENCODING_LATIN = 0x08;
+ public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09;
+ public static final int ENCODING_GSM_DCS = 0x0A;
+
+ /**
+ * IS-91 message types.
+ * (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3)
+ */
+ public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS = 0x82;
+ public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83;
+ public static final int IS91_MSG_TYPE_CLI = 0x84;
+ public static final int IS91_MSG_TYPE_SHORT_MESSAGE = 0x85;
+
+ /**
+ * US ASCII character mapping table.
+ *
+ * This table contains only the printable ASCII characters, with a
+ * 0x20 offset, meaning that the ASCII SPACE character is at index
+ * 0, with the resulting code of 0x20.
+ *
+ * Note this mapping is also equivalent to that used by both the
+ * IA5 and the IS-91 encodings. For the former this is defined
+ * using CCITT Rec. T.50 Tables 1 and 3. For the latter IS 637 B,
+ * Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
+ * and hence only maps entries up to the '_' character.
+ *
+ */
+ public static final char[] ASCII_MAP = {
+ ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'};
+
+ /**
+ * Character to use when forced to encode otherwise unencodable
+ * characters, meaning those not in the respective ASCII or GSM
+ * 7-bit encoding tables. Current choice is SPACE, which is 0x20
+ * in both the GSM-7bit and ASCII-7bit encodings.
+ */
+ static final byte UNENCODABLE_7_BIT_CHAR = 0x20;
+
+ /**
+ * Only elements between these indices in the ASCII table are printable.
+ */
+ public static final int PRINTABLE_ASCII_MIN_INDEX = 0x20;
+ public static final int ASCII_NL_INDEX = 0x0A;
+ public static final int ASCII_CR_INDEX = 0x0D;
+ public static final SparseIntArray charToAscii = new SparseIntArray();
+ static {
+ for (int i = 0; i < ASCII_MAP.length; i++) {
+ charToAscii.put(ASCII_MAP[i], PRINTABLE_ASCII_MIN_INDEX + i);
+ }
+ charToAscii.put('\n', ASCII_NL_INDEX);
+ charToAscii.put('\r', ASCII_CR_INDEX);
+ }
+
+ /*
+ * TODO(cleanup): Move this very generic functionality somewhere
+ * more general.
+ */
+ /**
+ * Given a string generate a corresponding ASCII-encoded byte
+ * array, but limited to printable characters. If the input
+ * contains unprintable characters, return null.
+ */
+ public static byte[] stringToAscii(String str) {
+ int len = str.length();
+ byte[] result = new byte[len];
+ for (int i = 0; i < len; i++) {
+ int charCode = charToAscii.get(str.charAt(i), -1);
+ if (charCode == -1) return null;
+ result[i] = (byte)charCode;
+ }
+ return result;
+ }
+
+ /**
+ * Mapping for ASCII values less than 32 are flow control signals
+ * and not used here.
+ */
+ public static final int ASCII_MAP_BASE_INDEX = 0x20;
+ public static final int ASCII_MAP_MAX_INDEX = ASCII_MAP_BASE_INDEX + ASCII_MAP.length - 1;
+
+ /**
+ * Contains the data header of the user data
+ */
+ public SmsHeader userDataHeader;
+
+ /**
+ * Contains the data encoding type for the SMS message
+ */
+ public int msgEncoding;
+ public boolean msgEncodingSet = false;
+
+ public int msgType;
+
+ /**
+ * Number of invalid bits in the last byte of data.
+ */
+ public int paddingBits;
+
+ public int numFields;
+
+ /**
+ * Contains the user data of a SMS message
+ * (See 3GPP2 C.S0015-B, v2, 4.5.2)
+ */
+ public byte[] payload;
+ public String payloadStr;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UserData ");
+ builder.append("{ msgEncoding=" + (msgEncodingSet ? msgEncoding : "unset"));
+ builder.append(", msgType=" + msgType);
+ builder.append(", paddingBits=" + paddingBits);
+ builder.append(", numFields=" + numFields);
+ builder.append(", userDataHeader=" + userDataHeader);
+ builder.append(", payload='" + HexDump.toHexString(payload) + "'");
+ builder.append(", payloadStr='" + payloadStr + "'");
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/package.html b/telephony/java/com/android/internal/telephony/cdma/package.html
new file mode 100644
index 0000000..b2bc736
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides CDMA-specific features for text/data/PDU SMS messages
+@hide
+</BODY>
+</HTML>
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
new file mode 100644
index 0000000..2fbf7ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2006 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.gsm;
+
+import android.telephony.PhoneNumberUtils;
+import java.text.ParseException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsAddress;
+
+public class GsmSmsAddress extends SmsAddress {
+
+ static final int OFFSET_ADDRESS_LENGTH = 0;
+
+ static final int OFFSET_TOA = 1;
+
+ static final int OFFSET_ADDRESS_VALUE = 2;
+
+ /**
+ * New GsmSmsAddress from TS 23.040 9.1.2.5 Address Field
+ *
+ * @param offset the offset of the Address-Length byte
+ * @param length the length in bytes rounded up, e.g. "2 +
+ * (addressLength + 1) / 2"
+ * @throws ParseException
+ */
+
+ public GsmSmsAddress(byte[] data, int offset, int length) throws ParseException {
+ origBytes = new byte[length];
+ System.arraycopy(data, offset, origBytes, 0, length);
+
+ // addressLength is the count of semi-octets, not bytes
+ int addressLength = origBytes[OFFSET_ADDRESS_LENGTH] & 0xff;
+
+ int toa = origBytes[OFFSET_TOA] & 0xff;
+ ton = 0x7 & (toa >> 4);
+
+ // TOA must have its high bit set
+ if ((toa & 0x80) != 0x80) {
+ throw new ParseException("Invalid TOA - high bit must be set. toa = " + toa,
+ offset + OFFSET_TOA);
+ }
+
+ if (isAlphanumeric()) {
+ // An alphanumeric address
+ int countSeptets = addressLength * 4 / 7;
+
+ address = GsmAlphabet.gsm7BitPackedToString(origBytes,
+ OFFSET_ADDRESS_VALUE, countSeptets);
+ } else {
+ // TS 23.040 9.1.2.5 says
+ // that "the MS shall interpret reserved values as 'Unknown'
+ // but shall store them exactly as received"
+
+ byte lastByte = origBytes[length - 1];
+
+ if ((addressLength & 1) == 1) {
+ // Make sure the final unused BCD digit is 0xf
+ origBytes[length - 1] |= 0xf0;
+ }
+ address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
+ OFFSET_TOA, length - OFFSET_TOA);
+
+ // And restore origBytes
+ origBytes[length - 1] = lastByte;
+ }
+ }
+
+ @Override
+ public String getAddressString() {
+ return address;
+ }
+
+ /**
+ * Returns true if this is an alphanumeric address
+ */
+ @Override
+ public boolean isAlphanumeric() {
+ return ton == TON_ALPHANUMERIC;
+ }
+
+ @Override
+ public boolean isNetworkSpecific() {
+ return ton == TON_NETWORK;
+ }
+
+ /**
+ * Returns true of this is a valid CPHS voice message waiting indicator
+ * address
+ */
+ public boolean isCphsVoiceMessageIndicatorAddress() {
+ // CPHS-style MWI message
+ // See CPHS 4.7 B.4.2.1
+ //
+ // Basically:
+ //
+ // - Originating address should be 4 bytes long and alphanumeric
+ // - Decode will result with two chars:
+ // - Char 1
+ // 76543210
+ // ^ set/clear indicator (0 = clear)
+ // ^^^ type of indicator (000 = voice)
+ // ^^^^ must be equal to 0001
+ // - Char 2:
+ // 76543210
+ // ^ line number (0 = line 1)
+ // ^^^^^^^ set to 0
+ //
+ // Remember, since the alpha address is stored in 7-bit compact form,
+ // the "line number" is really the top bit of the first address value
+ // byte
+
+ return (origBytes[OFFSET_ADDRESS_LENGTH] & 0xff) == 4
+ && isAlphanumeric() && (origBytes[OFFSET_TOA] & 0x0f) == 0;
+ }
+
+ /**
+ * Returns true if this is a valid CPHS voice message waiting indicator
+ * address indicating a "set" of "indicator 1" of type "voice message
+ * waiting"
+ */
+ public boolean isCphsVoiceMessageSet() {
+ // 0x11 means "set" "voice message waiting" "indicator 1"
+ return isCphsVoiceMessageIndicatorAddress()
+ && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x11;
+
+ }
+
+ /**
+ * Returns true if this is a valid CPHS voice message waiting indicator
+ * address indicating a "clear" of "indicator 1" of type "voice message
+ * waiting"
+ */
+ public boolean isCphsVoiceMessageClear() {
+ // 0x10 means "clear" "voice message waiting" "indicator 1"
+ return isCphsVoiceMessageIndicatorAddress()
+ && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x10;
+
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
new file mode 100644
index 0000000..6bf22a0
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2012 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.gsm;
+
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
+import static android.telephony.SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsConstants;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
+ * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
+ */
+public class GsmSmsCbMessage {
+
+ /**
+ * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+ */
+ private static final String[] LANGUAGE_CODES_GROUP_0 = {
+ "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
+ "pl", null
+ };
+
+ /**
+ * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+ */
+ private static final String[] LANGUAGE_CODES_GROUP_2 = {
+ "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
+ null, null
+ };
+
+ private static final char CARRIAGE_RETURN = 0x0d;
+
+ private static final int PDU_BODY_PAGE_LENGTH = 82;
+
+ /** Utility class with only static methods. */
+ private GsmSmsCbMessage() { }
+
+ /**
+ * Get built-in ETWS primary messages by category. ETWS primary message does not contain text,
+ * so we have to show the pre-built messages to the user.
+ *
+ * @param context Device context
+ * @param category ETWS message category defined in SmsCbConstants
+ * @return ETWS text message in string. Return an empty string if no match.
+ */
+ private static String getEtwsPrimaryMessage(Context context, int category) {
+ final Resources r = context.getResources();
+ switch (category) {
+ case ETWS_WARNING_TYPE_EARTHQUAKE:
+ return r.getString(R.string.etws_primary_default_message_earthquake);
+ case ETWS_WARNING_TYPE_TSUNAMI:
+ return r.getString(R.string.etws_primary_default_message_tsunami);
+ case ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI:
+ return r.getString(R.string.etws_primary_default_message_earthquake_and_tsunami);
+ case ETWS_WARNING_TYPE_TEST_MESSAGE:
+ return r.getString(R.string.etws_primary_default_message_test);
+ case ETWS_WARNING_TYPE_OTHER_EMERGENCY:
+ return r.getString(R.string.etws_primary_default_message_others);
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
+ *
+ * @param pdus PDU bytes
+ */
+ public static SmsCbMessage createSmsCbMessage(Context context, SmsCbHeader header,
+ SmsCbLocation location, byte[][] pdus)
+ throws IllegalArgumentException {
+ if (header.isEtwsPrimaryNotification()) {
+ // ETSI TS 23.041 ETWS Primary Notification message
+ // ETWS primary message only contains 4 fields including serial number,
+ // message identifier, warning type, and warning security information.
+ // There is no field for the content/text so we get the text from the resources.
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, header.getGeographicalScope(),
+ header.getSerialNumber(), location, header.getServiceCategory(), null,
+ getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
+ SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
+ header.getCmasInfo());
+ } else {
+ String language = null;
+ StringBuilder sb = new StringBuilder();
+ for (byte[] pdu : pdus) {
+ Pair<String, String> p = parseBody(header, pdu);
+ language = p.first;
+ sb.append(p.second);
+ }
+ int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
+ : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
+
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+ header.getGeographicalScope(), header.getSerialNumber(), location,
+ header.getServiceCategory(), language, sb.toString(), priority,
+ header.getEtwsInfo(), header.getCmasInfo());
+ }
+ }
+
+ /**
+ * Parse and unpack the body text according to the encoding in the DCS.
+ * After completing successfully this method will have assigned the body
+ * text into mBody, and optionally the language code into mLanguage
+ *
+ * @param header the message header to use
+ * @param pdu the PDU to decode
+ * @return a Pair of Strings containing the language and body of the message
+ */
+ private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
+ int encoding;
+ String language = null;
+ boolean hasLanguageIndicator = false;
+ int dataCodingScheme = header.getDataCodingScheme();
+
+ // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
+ // section 5.
+ switch ((dataCodingScheme & 0xf0) >> 4) {
+ case 0x00:
+ encoding = SmsConstants.ENCODING_7BIT;
+ language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
+ break;
+
+ case 0x01:
+ hasLanguageIndicator = true;
+ if ((dataCodingScheme & 0x0f) == 0x01) {
+ encoding = SmsConstants.ENCODING_16BIT;
+ } else {
+ encoding = SmsConstants.ENCODING_7BIT;
+ }
+ break;
+
+ case 0x02:
+ encoding = SmsConstants.ENCODING_7BIT;
+ language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
+ break;
+
+ case 0x03:
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+
+ case 0x04:
+ case 0x05:
+ switch ((dataCodingScheme & 0x0c) >> 2) {
+ case 0x01:
+ encoding = SmsConstants.ENCODING_8BIT;
+ break;
+
+ case 0x02:
+ encoding = SmsConstants.ENCODING_16BIT;
+ break;
+
+ case 0x00:
+ default:
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+ }
+ break;
+
+ case 0x06:
+ case 0x07:
+ // Compression not supported
+ case 0x09:
+ // UDH structure not supported
+ case 0x0e:
+ // Defined by the WAP forum not supported
+ throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
+ + dataCodingScheme);
+
+ case 0x0f:
+ if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
+ encoding = SmsConstants.ENCODING_8BIT;
+ } else {
+ encoding = SmsConstants.ENCODING_7BIT;
+ }
+ break;
+
+ default:
+ // Reserved values are to be treated as 7-bit
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+ }
+
+ if (header.isUmtsFormat()) {
+ // Payload may contain multiple pages
+ int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+
+ if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
+ * nrPages) {
+ throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
+ + nrPages + " pages");
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < nrPages; i++) {
+ // Each page is 82 bytes followed by a length octet indicating
+ // the number of useful octets within those 82
+ int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
+ int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
+
+ if (length > PDU_BODY_PAGE_LENGTH) {
+ throw new IllegalArgumentException("Page length " + length
+ + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
+ }
+
+ Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
+ hasLanguageIndicator, language);
+ language = p.first;
+ sb.append(p.second);
+ }
+ return new Pair<String, String>(language, sb.toString());
+ } else {
+ // Payload is one single page
+ int offset = SmsCbHeader.PDU_HEADER_LENGTH;
+ int length = pdu.length - offset;
+
+ return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
+ }
+ }
+
+ /**
+ * Unpack body text from the pdu using the given encoding, position and
+ * length within the pdu
+ *
+ * @param pdu The pdu
+ * @param encoding The encoding, as derived from the DCS
+ * @param offset Position of the first byte to unpack
+ * @param length Number of bytes to unpack
+ * @param hasLanguageIndicator true if the body text is preceded by a
+ * language indicator. If so, this method will as a side-effect
+ * assign the extracted language code into mLanguage
+ * @param language the language to return if hasLanguageIndicator is false
+ * @return a Pair of Strings containing the language and body of the message
+ */
+ private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
+ boolean hasLanguageIndicator, String language) {
+ String body = null;
+
+ switch (encoding) {
+ case SmsConstants.ENCODING_7BIT:
+ body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
+
+ if (hasLanguageIndicator && body != null && body.length() > 2) {
+ // Language is two GSM characters followed by a CR.
+ // The actual body text is offset by 3 characters.
+ language = body.substring(0, 2);
+ body = body.substring(3);
+ }
+ break;
+
+ case SmsConstants.ENCODING_16BIT:
+ if (hasLanguageIndicator && pdu.length >= offset + 2) {
+ // Language is two GSM characters.
+ // The actual body text is offset by 2 bytes.
+ language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
+ offset += 2;
+ length -= 2;
+ }
+
+ try {
+ body = new String(pdu, offset, (length & 0xfffe), "utf-16");
+ } catch (UnsupportedEncodingException e) {
+ // Apparently it wasn't valid UTF-16.
+ throw new IllegalArgumentException("Error decoding UTF-16 message", e);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (body != null) {
+ // Remove trailing carriage return
+ for (int i = body.length() - 1; i >= 0; i--) {
+ if (body.charAt(i) != CARRIAGE_RETURN) {
+ body = body.substring(0, i + 1);
+ break;
+ }
+ }
+ } else {
+ body = "";
+ }
+
+ return new Pair<String, String>(language, body);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
new file mode 100644
index 0000000..f4f4036
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 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.gsm;
+
+/**
+ * SmsBroadcastConfigInfo defines one configuration of Cell Broadcast
+ * Message (CBM) to be received by the ME
+ *
+ * fromServiceId - toServiceId defines a range of CBM message identifiers
+ * whose value is 0x0000 - 0xFFFF as defined in TS 23.041 9.4.1.2.2 for GMS
+ * and 9.4.4.2.2 for UMTS. All other values can be treated as empty
+ * CBM message ID.
+ *
+ * fromCodeScheme - toCodeScheme defines a range of CBM data coding schemes
+ * whose value is 0x00 - 0xFF as defined in TS 23.041 9.4.1.2.3 for GMS
+ * and 9.4.4.2.3 for UMTS.
+ * All other values can be treated as empty CBM data coding scheme.
+ *
+ * selected false means message types specified in {@code <fromServiceId, toServiceId>}
+ * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
+ *
+ */
+public final class SmsBroadcastConfigInfo {
+ private int mFromServiceId;
+ private int mToServiceId;
+ private int mFromCodeScheme;
+ private int mToCodeScheme;
+ private boolean mSelected;
+
+ /**
+ * Initialize the object from rssi and cid.
+ */
+ public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
+ int toScheme, boolean selected) {
+ mFromServiceId = fromId;
+ mToServiceId = toId;
+ mFromCodeScheme = fromScheme;
+ mToCodeScheme = toScheme;
+ mSelected = selected;
+ }
+
+ /**
+ * @param fromServiceId the fromServiceId to set
+ */
+ public void setFromServiceId(int fromServiceId) {
+ mFromServiceId = fromServiceId;
+ }
+
+ /**
+ * @return the fromServiceId
+ */
+ public int getFromServiceId() {
+ return mFromServiceId;
+ }
+
+ /**
+ * @param toServiceId the toServiceId to set
+ */
+ public void setToServiceId(int toServiceId) {
+ mToServiceId = toServiceId;
+ }
+
+ /**
+ * @return the toServiceId
+ */
+ public int getToServiceId() {
+ return mToServiceId;
+ }
+
+ /**
+ * @param fromCodeScheme the fromCodeScheme to set
+ */
+ public void setFromCodeScheme(int fromCodeScheme) {
+ mFromCodeScheme = fromCodeScheme;
+ }
+
+ /**
+ * @return the fromCodeScheme
+ */
+ public int getFromCodeScheme() {
+ return mFromCodeScheme;
+ }
+
+ /**
+ * @param toCodeScheme the toCodeScheme to set
+ */
+ public void setToCodeScheme(int toCodeScheme) {
+ mToCodeScheme = toCodeScheme;
+ }
+
+ /**
+ * @return the toCodeScheme
+ */
+ public int getToCodeScheme() {
+ return mToCodeScheme;
+ }
+
+ /**
+ * @param selected the selected to set
+ */
+ public void setSelected(boolean selected) {
+ mSelected = selected;
+ }
+
+ /**
+ * @return the selected
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsBroadcastConfigInfo: Id [" +
+ mFromServiceId + ',' + mToServiceId + "] Code [" +
+ mFromCodeScheme + ',' + mToCodeScheme + "] " +
+ (mSelected ? "ENABLED" : "DISABLED");
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
new file mode 100644
index 0000000..bce5680
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2012 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.gsm;
+
+/**
+ * Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the
+ * boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it
+ * is public, but should be avoided in favor of the radio technology independent constants in
+ * {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and
+ * {@link android.telephony.SmsCbCmasInfo} classes.
+ *
+ * {@hide}
+ */
+public class SmsCbConstants {
+
+ /** Private constructor for utility class. */
+ private SmsCbConstants() { }
+
+ /** Channel 50 required by Brazil. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_50
+ = 0x0032;
+
+ /** Channel 911 required by Taiwan NCC. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_911
+ = 0x038F; // 911
+
+ /** Channel 919 required by Taiwan NCC and Israel. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_919
+ = 0x0397; // 919
+
+ /** Channel 928 required by Israel. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_928
+ = 0x03A0; // 928
+
+ /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
+ public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER
+ = 0x1100; // 4352
+
+ /** Bitmask for messages of ETWS type (including future extensions). */
+ public static final int MESSAGE_ID_ETWS_TYPE_MASK
+ = 0xFFF8;
+
+ /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
+ public static final int MESSAGE_ID_ETWS_TYPE
+ = 0x1100; // 4352
+
+ /** ETWS Message Identifier for earthquake warning message. */
+ public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING
+ = 0x1100; // 4352
+
+ /** ETWS Message Identifier for tsunami warning message. */
+ public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING
+ = 0x1101; // 4353
+
+ /** ETWS Message Identifier for earthquake and tsunami combined warning message. */
+ public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING
+ = 0x1102; // 4354
+
+ /** ETWS Message Identifier for test message. */
+ public static final int MESSAGE_ID_ETWS_TEST_MESSAGE
+ = 0x1103; // 4355
+
+ /** ETWS Message Identifier for messages related to other emergency types. */
+ public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE
+ = 0x1104; // 4356
+
+ /** Start of CMAS Message Identifier range. */
+ public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+ = 0x1112; // 4370
+
+ /** CMAS Message Identifier for Presidential Level alerts. */
+ public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL
+ = 0x1112; // 4370
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED
+ = 0x1113; // 4371
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY
+ = 0x1114; // 4372
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED
+ = 0x1115; // 4373
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY
+ = 0x1116; // 4374
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED
+ = 0x1117; // 4375
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY
+ = 0x1118; // 4376
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED
+ = 0x1119; // 4377
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY
+ = 0x111A; // 4378
+
+ /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
+ public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY
+ = 0x111B; // 4379
+
+ /** CMAS Message Identifier for the Required Monthly Test. */
+ public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST
+ = 0x111C; // 4380
+
+ /** CMAS Message Identifier for CMAS Exercise. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE
+ = 0x111D; // 4381
+
+ /** CMAS Message Identifier for operator defined use. */
+ public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE
+ = 0x111E; // 4382
+
+ /** CMAS Message Identifier for Presidential Level alerts for additional languages
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE
+ = 0x111F; // 4383
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE
+ = 0x1120; // 4384
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE
+ = 0x1121; // 4385
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE
+ = 0x1122; // 4386
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE
+ = 0x1123; // 4387
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE
+ = 0x1124; // 4388
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely
+ * for additional languages.*/
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE
+ = 0x1125; // 4389
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE
+ = 0x1126; // 4390
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely
+ * for additional languages.*/
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE
+ = 0x1127; // 4391
+
+ /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert)
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE
+ = 0x1128; // 4392
+
+ /** CMAS Message Identifier for the Required Monthly Test
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE
+ = 0x1129; // 4393
+
+ /** CMAS Message Identifier for CMAS Exercise
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE
+ = 0x112A; // 4394
+
+ /** CMAS Message Identifier for operator defined use
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE
+ = 0x112B; // 4395
+
+ /** End of CMAS Message Identifier range (including future extensions). */
+ public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER
+ = 0x112F; // 4399
+
+ /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
+ public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER
+ = 0x18FF; // 6399
+
+ /** ETWS serial number flag to activate the popup display. */
+ public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP
+ = 0x1000; // 4096
+
+ /** ETWS serial number flag to activate the emergency user alert. */
+ public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT
+ = 0x2000; // 8192
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
new file mode 100644
index 0000000..d267ad2
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2010 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.gsm;
+
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
+
+import java.util.Arrays;
+
+/**
+ * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
+ * CellBroadcastReceiver test cases, but should not be used by applications.
+ *
+ * All relevant header information is now sent as a Parcelable
+ * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
+ * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent.
+ * The raw PDU is no longer sent to SMS CB applications.
+ */
+public class SmsCbHeader {
+
+ /**
+ * Length of SMS-CB header
+ */
+ static final int PDU_HEADER_LENGTH = 6;
+
+ /**
+ * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
+ */
+ static final int FORMAT_GSM = 1;
+
+ /**
+ * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
+ */
+ static final int FORMAT_UMTS = 2;
+
+ /**
+ * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
+ */
+ static final int FORMAT_ETWS_PRIMARY = 3;
+
+ /**
+ * Message type value as defined in 3gpp TS 25.324, section 11.1.
+ */
+ private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
+
+ /**
+ * Length of GSM pdus
+ */
+ private static final int PDU_LENGTH_GSM = 88;
+
+ /**
+ * Maximum length of ETWS primary message GSM pdus
+ */
+ private static final int PDU_LENGTH_ETWS = 56;
+
+ private final int mGeographicalScope;
+
+ /** The serial number combines geographical scope, message code, and update number. */
+ private final int mSerialNumber;
+
+ /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
+ private final int mMessageIdentifier;
+
+ private final int mDataCodingScheme;
+
+ private final int mPageIndex;
+
+ private final int mNrOfPages;
+
+ private final int mFormat;
+
+ /** ETWS warning notification info. */
+ private final SmsCbEtwsInfo mEtwsInfo;
+
+ /** CMAS warning notification info. */
+ private final SmsCbCmasInfo mCmasInfo;
+
+ public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
+ if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
+ throw new IllegalArgumentException("Illegal PDU");
+ }
+
+ if (pdu.length <= PDU_LENGTH_GSM) {
+ // can be ETWS or GSM format.
+ // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
+ // contain serial number which contains GS, Message Code, and Update Number
+ // per 9.4.1.2.1, and message identifier in same octets
+ mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
+ mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
+ mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
+ if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
+ mFormat = FORMAT_ETWS_PRIMARY;
+ mDataCodingScheme = -1;
+ mPageIndex = -1;
+ mNrOfPages = -1;
+ boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
+ boolean activatePopup = (pdu[5] & 0x80) != 0;
+ int warningType = (pdu[4] & 0xfe) >>> 1;
+ byte[] warningSecurityInfo;
+ // copy the Warning-Security-Information, if present
+ if (pdu.length > PDU_HEADER_LENGTH) {
+ warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
+ } else {
+ warningSecurityInfo = null;
+ }
+ mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+ true, warningSecurityInfo);
+ mCmasInfo = null;
+ return; // skip the ETWS/CMAS initialization code for regular notifications
+ } else {
+ // GSM pdus are no more than 88 bytes
+ mFormat = FORMAT_GSM;
+ mDataCodingScheme = pdu[4] & 0xff;
+
+ // Check for invalid page parameter
+ int pageIndex = (pdu[5] & 0xf0) >>> 4;
+ int nrOfPages = pdu[5] & 0x0f;
+
+ if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
+ pageIndex = 1;
+ nrOfPages = 1;
+ }
+
+ mPageIndex = pageIndex;
+ mNrOfPages = nrOfPages;
+ }
+ } else {
+ // UMTS pdus are always at least 90 bytes since the payload includes
+ // a number-of-pages octet and also one length octet per page
+ mFormat = FORMAT_UMTS;
+
+ int messageType = pdu[0];
+
+ if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
+ throw new IllegalArgumentException("Unsupported message type " + messageType);
+ }
+
+ mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
+ mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
+ mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
+ mDataCodingScheme = pdu[5] & 0xff;
+
+ // We will always consider a UMTS message as having one single page
+ // since there's only one instance of the header, even though the
+ // actual payload may contain several pages.
+ mPageIndex = 1;
+ mNrOfPages = 1;
+ }
+
+ if (isEtwsMessage()) {
+ boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
+ boolean activatePopup = isEtwsPopupAlert();
+ int warningType = getEtwsWarningType();
+ mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+ false, null);
+ mCmasInfo = null;
+ } else if (isCmasMessage()) {
+ int messageClass = getCmasMessageClass();
+ int severity = getCmasSeverity();
+ int urgency = getCmasUrgency();
+ int certainty = getCmasCertainty();
+ mEtwsInfo = null;
+ mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
+ SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
+ } else {
+ mEtwsInfo = null;
+ mCmasInfo = null;
+ }
+ }
+
+ int getGeographicalScope() {
+ return mGeographicalScope;
+ }
+
+ int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ int getServiceCategory() {
+ return mMessageIdentifier;
+ }
+
+ int getDataCodingScheme() {
+ return mDataCodingScheme;
+ }
+
+ int getPageIndex() {
+ return mPageIndex;
+ }
+
+ int getNumberOfPages() {
+ return mNrOfPages;
+ }
+
+ SmsCbEtwsInfo getEtwsInfo() {
+ return mEtwsInfo;
+ }
+
+ SmsCbCmasInfo getCmasInfo() {
+ return mCmasInfo;
+ }
+
+ /**
+ * Return whether this broadcast is an emergency (PWS) message type.
+ * @return true if this message is emergency type; false otherwise
+ */
+ boolean isEmergencyMessage() {
+ return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
+ && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
+ }
+
+ /**
+ * Return whether this broadcast is an ETWS emergency message type.
+ * @return true if this message is ETWS emergency type; false otherwise
+ */
+ private boolean isEtwsMessage() {
+ return (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
+ == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
+ }
+
+ /**
+ * Return whether this broadcast is an ETWS primary notification.
+ * @return true if this message is an ETWS primary notification; false otherwise
+ */
+ boolean isEtwsPrimaryNotification() {
+ return mFormat == FORMAT_ETWS_PRIMARY;
+ }
+
+ /**
+ * Return whether this broadcast is in UMTS format.
+ * @return true if this message is in UMTS format; false otherwise
+ */
+ boolean isUmtsFormat() {
+ return mFormat == FORMAT_UMTS;
+ }
+
+ /**
+ * Return whether this message is a CMAS emergency message type.
+ * @return true if this message is CMAS emergency type; false otherwise
+ */
+ private boolean isCmasMessage() {
+ return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+ && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+ }
+
+ /**
+ * Return whether the popup alert flag is set for an ETWS warning notification.
+ * This method assumes that the message ID has already been checked for ETWS type.
+ *
+ * @return true if the message code indicates a popup alert should be displayed
+ */
+ private boolean isEtwsPopupAlert() {
+ return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
+ }
+
+ /**
+ * Return whether the emergency user alert flag is set for an ETWS warning notification.
+ * This method assumes that the message ID has already been checked for ETWS type.
+ *
+ * @return true if the message code indicates an emergency user alert
+ */
+ private boolean isEtwsEmergencyUserAlert() {
+ return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
+ }
+
+ /**
+ * Returns the warning type for an ETWS warning notification.
+ * This method assumes that the message ID has already been checked for ETWS type.
+ *
+ * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
+ */
+ private int getEtwsWarningType() {
+ return mMessageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
+ }
+
+ /**
+ * Returns the message class for a CMAS warning notification.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasMessageClass() {
+ switch (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the severity for a CMAS warning notification. This is only available for extreme
+ * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasSeverity() {
+ switch (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+
+ default:
+ return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the urgency for a CMAS warning notification. This is only available for extreme
+ * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasUrgency() {
+ switch (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+
+ default:
+ return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+ }
+ }
+
+ /**
+ * Returns the certainty for a CMAS warning notification. This is only available for extreme
+ * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+ * This method assumes that the message ID has already been checked for CMAS type.
+ * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
+ */
+ private int getCmasCertainty() {
+ switch (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x" +
+ Integer.toHexString(mSerialNumber) +
+ ", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier) +
+ ", DCS=0x" + Integer.toHexString(mDataCodingScheme) +
+ ", page " + mPageIndex + " of " + mNrOfPages + '}';
+ }
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
new file mode 100644
index 0000000..582506a
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -0,0 +1,1369 @@
+/*
+ * Copyright (C) 2006 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.gsm;
+
+import android.telephony.PhoneNumberUtils;
+import android.text.format.Time;
+import android.telephony.Rlog;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+
+import static com.android.internal.telephony.SmsConstants.MessageClass;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+ static final String LOG_TAG = "SmsMessage";
+ private static final boolean VDBG = false;
+
+ private MessageClass messageClass;
+
+ /**
+ * TP-Message-Type-Indicator
+ * 9.2.3
+ */
+ private int mMti;
+
+ /** TP-Protocol-Identifier (TP-PID) */
+ private int mProtocolIdentifier;
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ private int mDataCodingScheme;
+
+ // TP-Reply-Path
+ // e.g. 23.040 9.2.2.1
+ private boolean mReplyPathPresent = false;
+
+ /** The address of the receiver. */
+ private GsmSmsAddress mRecipientAddress;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field applies to SMS-STATUS-REPORT messages. 0 indicates success;
+ * see TS 23.040, 9.2.3.15 for description of other possible values.
+ */
+ private int mStatus;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field is true iff the message is a SMS-STATUS-REPORT message.
+ */
+ private boolean mIsStatusReportMessage = false;
+
+ private int mVoiceMailCount = 0;
+
+ public static class SubmitPdu extends SubmitPduBase {
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ } catch (OutOfMemoryError e) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
+ return null;
+ }
+ }
+
+ /**
+ * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
+ * by TP_PID field set to value 0x40
+ */
+ public boolean isTypeZero() {
+ return (mProtocolIdentifier == 0x40);
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [<alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging
+ *
+ * {@hide}
+ */
+ public static SmsMessage newFromCMT(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /** @hide */
+ public static SmsMessage newFromCDS(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ try {
+ SmsMessage msg = new SmsMessage();
+
+ msg.mIndexOnIcc = index;
+
+ // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+ // or STORED_UNSENT
+ // See TS 51.011 10.5.3
+ if ((data[0] & 1) == 0) {
+ Rlog.w(LOG_TAG,
+ "SMS parsing failed: Trying to parse a free record");
+ return null;
+ } else {
+ msg.mStatusOnIcc = data[0] & 0x07;
+ }
+
+ int size = data.length - 1;
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] pdu = new byte[size];
+ System.arraycopy(data, 1, pdu, 0, size);
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ int len = pdu.length() / 2;
+ int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
+
+ return len - smscLen - 1;
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
+ ENCODING_UNKNOWN, 0, 0);
+ }
+
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message using the
+ * specified encoding.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param encoding Encoding defined by constants in
+ * com.android.internal.telephony.SmsConstants.ENCODING_*
+ * @param languageTable
+ * @param languageShiftTable
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header, int encoding,
+ int languageTable, int languageShiftTable) {
+
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ return null;
+ }
+
+ if (encoding == ENCODING_UNKNOWN) {
+ // Find the best encoding to use
+ TextEncodingDetails ted = calculateLength(message, false);
+ encoding = ted.codeUnitSize;
+ languageTable = ted.languageTable;
+ languageShiftTable = ted.languageShiftTable;
+
+ if (encoding == ENCODING_7BIT &&
+ (languageTable != 0 || languageShiftTable != 0)) {
+ if (header != null) {
+ SmsHeader smsHeader = SmsHeader.fromByteArray(header);
+ if (smsHeader.languageTable != languageTable
+ || smsHeader.languageShiftTable != languageShiftTable) {
+ Rlog.w(LOG_TAG, "Updating language table in SMS header: "
+ + smsHeader.languageTable + " -> " + languageTable + ", "
+ + smsHeader.languageShiftTable + " -> " + languageShiftTable);
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ } else {
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ }
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ // MTI = SMS-SUBMIT, UDHI = header != null
+ byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, mtiByte,
+ statusReportRequested, ret);
+
+ // User Data (and length)
+ byte[] userData;
+ try {
+ if (encoding == ENCODING_7BIT) {
+ userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
+ languageTable, languageShiftTable);
+ } else { //assume UCS-2
+ try {
+ userData = encodeUCS2(message, header);
+ } catch(UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+ } catch (EncodeException ex) {
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // send it as a UCS-2 encoded message
+ try {
+ userData = encodeUCS2(message, header);
+ encoding = ENCODING_16BIT;
+ } catch(UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+
+ if (encoding == ENCODING_7BIT) {
+ if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+ // Message too long
+ Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // Default encoding, uncompressed
+ // To test writing messages to the SIM card, change this value 0x00
+ // to 0x12, which means "bits 1 and 0 contain message class, and the
+ // class is 2". Note that this takes effect for the sender. In other
+ // words, messages sent by the phone with this change will end up on
+ // the receiver's SIM card. You can then send messages to yourself
+ // (on a phone with this change) and they'll end up on the SIM card.
+ bo.write(0x00);
+ } else { // assume UCS-2
+ if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+ // Message too long
+ Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // UCS-2 encoding, uncompressed
+ bo.write(0x08);
+ }
+
+ // (no TP-Validity-Period)
+ bo.write(userData, 0, userData.length);
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
+ *
+ * @return encoded message as UCS2
+ * @throws UnsupportedEncodingException
+ */
+ private static byte[] encodeUCS2(String message, byte[] header)
+ throws UnsupportedEncodingException {
+ byte[] userData, textPart;
+ textPart = message.getBytes("utf-16be");
+
+ if (header != null) {
+ // Need 1 byte for UDHL
+ userData = new byte[header.length + textPart.length + 1];
+
+ userData[0] = (byte)header.length;
+ System.arraycopy(header, 0, userData, 1, header.length);
+ System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+ }
+ else {
+ userData = textPart;
+ }
+ byte[] ret = new byte[userData.length+1];
+ ret[0] = (byte) (userData.length & 0xff );
+ System.arraycopy(userData, 0, ret, 1, userData.length);
+ return ret;
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested) {
+
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, int destinationPort, byte[] data,
+ boolean statusReportRequested) {
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destinationPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
+
+ if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
+ Rlog.e(LOG_TAG, "SMS data message may only contain "
+ + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
+ return null;
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
+ // TP-UDHI = true
+ statusReportRequested, ret);
+
+ // TP-Data-Coding-Scheme
+ // No class, 8 bit data
+ bo.write(0x04);
+
+ // (no TP-Validity-Period)
+
+ // Total size
+ bo.write(data.length + smsHeaderData.length + 1);
+
+ // User data header
+ bo.write(smsHeaderData.length);
+ bo.write(smsHeaderData, 0, smsHeaderData.length);
+
+ // User data
+ bo.write(data, 0, data.length);
+
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Create the beginning of a SUBMIT PDU. This is the part of the
+ * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
+ * one of which takes a byte array and the other of which takes a
+ * <code>String</code>.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param mtiByte
+ * @param ret <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message
+ */
+ private static ByteArrayOutputStream getSubmitPduHead(
+ String scAddress, String destinationAddress, byte mtiByte,
+ boolean statusReportRequested, SubmitPdu ret) {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream(
+ MAX_USER_DATA_BYTES + 40);
+
+ // SMSC address with length octet, or 0
+ if (scAddress == null) {
+ ret.encodedScAddress = null;
+ } else {
+ ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+ scAddress);
+ }
+
+ // TP-Message-Type-Indicator (and friends)
+ if (statusReportRequested) {
+ // Set TP-Status-Report-Request bit.
+ mtiByte |= 0x20;
+ if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
+ }
+ bo.write(mtiByte);
+
+ // space for TP-Message-Reference
+ bo.write(0);
+
+ byte[] daBytes;
+
+ daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+ // destination address length in BCD digits, ignoring TON byte and pad
+ // TODO Should be better.
+ bo.write((daBytes.length - 1) * 2
+ - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+
+ // destination address
+ bo.write(daBytes, 0, daBytes.length);
+
+ // TP-Protocol-Identifier
+ bo.write(0);
+ return bo;
+ }
+
+ private static class PduParser {
+ byte mPdu[];
+ int mCur;
+ SmsHeader mUserDataHeader;
+ byte[] mUserData;
+ int mUserDataSeptetPadding;
+
+ PduParser(byte[] pdu) {
+ mPdu = pdu;
+ mCur = 0;
+ mUserDataSeptetPadding = 0;
+ }
+
+ /**
+ * Parse and return the SC address prepended to SMS messages coming via
+ * the TS 27.005 / AT interface. Returns null on invalid address
+ */
+ String getSCAddress() {
+ int len;
+ String ret;
+
+ // length of SC Address
+ len = getByte();
+
+ if (len == 0) {
+ // no SC address
+ ret = null;
+ } else {
+ // SC address
+ try {
+ ret = PhoneNumberUtils
+ .calledPartyBCDToString(mPdu, mCur, len);
+ } catch (RuntimeException tr) {
+ Rlog.d(LOG_TAG, "invalid SC address: ", tr);
+ ret = null;
+ }
+ }
+
+ mCur += len;
+
+ return ret;
+ }
+
+ /**
+ * returns non-sign-extended byte value
+ */
+ int getByte() {
+ return mPdu[mCur++] & 0xff;
+ }
+
+ /**
+ * Any address except the SC address (eg, originating address) See TS
+ * 23.040 9.1.2.5
+ */
+ GsmSmsAddress getAddress() {
+ GsmSmsAddress ret;
+
+ // "The Address-Length field is an integer representation of
+ // the number field, i.e. excludes any semi-octet containing only
+ // fill bits."
+ // The TOA field is not included as part of this
+ int addressLength = mPdu[mCur] & 0xff;
+ int lengthBytes = 2 + (addressLength + 1) / 2;
+
+ try {
+ ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
+ } catch (ParseException e) {
+ ret = null;
+ //This is caught by createFromPdu(byte[] pdu)
+ throw new RuntimeException(e.getMessage());
+ }
+
+ mCur += lengthBytes;
+
+ return ret;
+ }
+
+ /**
+ * Parses an SC timestamp and returns a currentTimeMillis()-style
+ * timestamp
+ */
+
+ long getSCTimestampMillis() {
+ // TP-Service-Centre-Time-Stamp
+ int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+
+ // For the timezone, the most significant bit of the
+ // least significant nibble is the sign byte
+ // (meaning the max range of this field is 79 quarter-hours,
+ // which is more than enough)
+
+ byte tzByte = mPdu[mCur++];
+
+ // Mask out sign bit.
+ int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+ timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // It's 2006. Should I really support years < 2000?
+ time.year = year >= 90 ? year + 1900 : year + 2000;
+ time.month = month - 1;
+ time.monthDay = day;
+ time.hour = hour;
+ time.minute = minute;
+ time.second = second;
+
+ // Timezone offset is in quarter hours.
+ return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+ }
+
+ /**
+ * Pulls the user data out of the PDU, and separates the payload from
+ * the header if there is one.
+ *
+ * @param hasUserDataHeader true if there is a user data header
+ * @param dataInSeptets true if the data payload is in septets instead
+ * of octets
+ * @return the number of septets or octets in the user data payload
+ */
+ int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
+ int offset = mCur;
+ int userDataLength = mPdu[offset++] & 0xff;
+ int headerSeptets = 0;
+ int userDataHeaderLength = 0;
+
+ if (hasUserDataHeader) {
+ userDataHeaderLength = mPdu[offset++] & 0xff;
+
+ byte[] udh = new byte[userDataHeaderLength];
+ System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
+ mUserDataHeader = SmsHeader.fromByteArray(udh);
+ offset += userDataHeaderLength;
+
+ int headerBits = (userDataHeaderLength + 1) * 8;
+ headerSeptets = headerBits / 7;
+ headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+ mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
+ }
+
+ int bufferLen;
+ if (dataInSeptets) {
+ /*
+ * Here we just create the user data length to be the remainder of
+ * the pdu minus the user data header, since userDataLength means
+ * the number of uncompressed septets.
+ */
+ bufferLen = mPdu.length - offset;
+ } else {
+ /*
+ * userDataLength is the count of octets, so just subtract the
+ * user data header.
+ */
+ bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
+ if (bufferLen < 0) {
+ bufferLen = 0;
+ }
+ }
+
+ mUserData = new byte[bufferLen];
+ System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
+ mCur = offset;
+
+ if (dataInSeptets) {
+ // Return the number of septets
+ int count = userDataLength - headerSeptets;
+ // If count < 0, return 0 (means UDL was probably incorrect)
+ return count < 0 ? 0 : count;
+ } else {
+ // Return the number of octets
+ return mUserData.length;
+ }
+ }
+
+ /**
+ * Returns the user data payload, not including the headers
+ *
+ * @return the user data payload, not including the headers
+ */
+ byte[] getUserData() {
+ return mUserData;
+ }
+
+ /**
+ * Returns an object representing the user data headers
+ *
+ * {@hide}
+ */
+ SmsHeader getUserDataHeader() {
+ return mUserDataHeader;
+ }
+
+ /**
+ * Interprets the user data payload as packed GSM 7bit characters, and
+ * decodes them into a String.
+ *
+ * @param septetCount the number of septets in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM7Bit(int septetCount, int languageTable,
+ int languageShiftTable) {
+ String ret;
+
+ ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
+ mUserDataSeptetPadding, languageTable, languageShiftTable);
+
+ mCur += (septetCount * 7) / 8;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's
+ * stored in 8-bit unpacked format) characters, and decodes them into a String.
+ *
+ * @param byteCount the number of byest in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM8bit(int byteCount) {
+ String ret;
+
+ ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount);
+
+ mCur += byteCount;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as UCS2 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataUCS2(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(mPdu, mCur, byteCount, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ mCur += byteCount;
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as KSC-5601 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataKSC5601(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(mPdu, mCur, byteCount, "KSC5601");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ mCur += byteCount;
+ return ret;
+ }
+
+ boolean moreDataPresent() {
+ return (mPdu.length > mCur);
+ }
+ }
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence msgBody,
+ boolean use7bitOnly) {
+ CharSequence newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(msgBody);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = msgBody;
+ }
+ TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
+ if (ted == null) {
+ return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
+ }
+ return ted;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getProtocolIdentifier() {
+ return mProtocolIdentifier;
+ }
+
+ /**
+ * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
+ * @return the TP-DCS field of the SMS header
+ */
+ int getDataCodingScheme() {
+ return mDataCodingScheme;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplace() {
+ return (mProtocolIdentifier & 0xc0) == 0x40
+ && (mProtocolIdentifier & 0x3f) > 0
+ && (mProtocolIdentifier & 0x3f) < 8;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isCphsMwiMessage() {
+ return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
+ || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWIClearMessage() {
+ if (mIsMwi && !mMwiSense) {
+ return true;
+ }
+
+ return mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWISetMessage() {
+ if (mIsMwi && mMwiSense) {
+ return true;
+ }
+
+ return mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMwiDontStore() {
+ if (mIsMwi && mMwiDontStore) {
+ return true;
+ }
+
+ if (isCphsMwiMessage()) {
+ // See CPHS 4.2 Section B.4.2.1
+ // If the user data is a single space char, do not store
+ // the message. Otherwise, store and display as usual
+ if (" ".equals(getMessageBody())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isStatusReportMessage() {
+ return mIsStatusReportMessage;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplyPathPresent() {
+ return mReplyPathPresent;
+ }
+
+ /**
+ * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
+ * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
+ * ME/TA converts each octet of TP data unit into two IRA character long
+ * hex number (e.g. octet with integer value 42 is presented to TE as two
+ * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
+ * something else...
+ */
+ private void parsePdu(byte[] pdu) {
+ mPdu = pdu;
+ // Rlog.d(LOG_TAG, "raw sms message:");
+ // Rlog.d(LOG_TAG, s);
+
+ PduParser p = new PduParser(pdu);
+
+ mScAddress = p.getSCAddress();
+
+ if (mScAddress != null) {
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
+ }
+
+ // TODO(mkf) support reply path, user data header indicator
+
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ int firstByte = p.getByte();
+
+ mMti = firstByte & 0x3;
+ switch (mMti) {
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ case 0:
+ case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
+ //This should be processed in the same way as MTI == 0 (Deliver)
+ parseSmsDeliver(p, firstByte);
+ break;
+ case 1:
+ parseSmsSubmit(p, firstByte);
+ break;
+ case 2:
+ parseSmsStatusReport(p, firstByte);
+ break;
+ default:
+ // TODO(mkf) the rest of these
+ throw new RuntimeException("Unsupported message type");
+ }
+ }
+
+ /**
+ * Parses a SMS-STATUS-REPORT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsStatusReport(PduParser p, int firstByte) {
+ mIsStatusReportMessage = true;
+
+ // TP-Message-Reference
+ mMessageRef = p.getByte();
+ // TP-Recipient-Address
+ mRecipientAddress = p.getAddress();
+ // TP-Service-Centre-Time-Stamp
+ mScTimeMillis = p.getSCTimestampMillis();
+ p.getSCTimestampMillis();
+ // TP-Status
+ mStatus = p.getByte();
+
+ // The following are optional fields that may or may not be present.
+ if (p.moreDataPresent()) {
+ // TP-Parameter-Indicator
+ int extraParams = p.getByte();
+ int moreExtraParams = extraParams;
+ while ((moreExtraParams & 0x80) != 0) {
+ // We only know how to parse a few extra parameters, all
+ // indicated in the first TP-PI octet, so skip over any
+ // additional TP-PI octets.
+ moreExtraParams = p.getByte();
+ }
+ // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
+ // only process the byte if the reserved bits (bits3 to 6) are zero.
+ if ((extraParams & 0x78) == 0) {
+ // TP-Protocol-Identifier
+ if ((extraParams & 0x01) != 0) {
+ mProtocolIdentifier = p.getByte();
+ }
+ // TP-Data-Coding-Scheme
+ if ((extraParams & 0x02) != 0) {
+ mDataCodingScheme = p.getByte();
+ }
+ // TP-User-Data-Length (implies existence of TP-User-Data)
+ if ((extraParams & 0x04) != 0) {
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+ parseUserData(p, hasUserDataHeader);
+ }
+ }
+ }
+ }
+
+ private void parseSmsDeliver(PduParser p, int firstByte) {
+ mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+ mOriginatingAddress = p.getAddress();
+
+ if (mOriginatingAddress != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ + mOriginatingAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ mProtocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ mDataCodingScheme = p.getByte();
+
+ if (VDBG) {
+ Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ + " data coding scheme: " + mDataCodingScheme);
+ }
+
+ mScTimeMillis = p.getSCTimestampMillis();
+
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses a SMS-SUBMIT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsSubmit(PduParser p, int firstByte) {
+ mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+ // TP-MR (TP-Message Reference)
+ mMessageRef = p.getByte();
+
+ mRecipientAddress = p.getAddress();
+
+ if (mRecipientAddress != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ mProtocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ mDataCodingScheme = p.getByte();
+
+ if (VDBG) {
+ Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ + " data coding scheme: " + mDataCodingScheme);
+ }
+
+ // TP-Validity-Period-Format
+ int validityPeriodLength = 0;
+ int validityPeriodFormat = ((firstByte>>3) & 0x3);
+ if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
+ {
+ validityPeriodLength = 0;
+ }
+ else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
+ {
+ validityPeriodLength = 1;
+ }
+ else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
+ {
+ validityPeriodLength = 7;
+ }
+
+ // TP-Validity-Period is not used on phone, so just ignore it for now.
+ while (validityPeriodLength-- > 0)
+ {
+ p.getByte();
+ }
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses the User Data of an SMS.
+ *
+ * @param p The current PduParser.
+ * @param hasUserDataHeader Indicates whether a header is present in the
+ * User Data.
+ */
+ private void parseUserData(PduParser p, boolean hasUserDataHeader) {
+ boolean hasMessageClass = false;
+ boolean userDataCompressed = false;
+
+ int encodingType = ENCODING_UNKNOWN;
+
+ // Look up the data encoding scheme
+ if ((mDataCodingScheme & 0x80) == 0) {
+ userDataCompressed = (0 != (mDataCodingScheme & 0x20));
+ hasMessageClass = (0 != (mDataCodingScheme & 0x10));
+
+ if (userDataCompressed) {
+ Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+ + "(compression) " + (mDataCodingScheme & 0xff));
+ } else {
+ switch ((mDataCodingScheme >> 2) & 0x3) {
+ case 0: // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ break;
+
+ case 2: // UCS 2 (16bit)
+ encodingType = ENCODING_16BIT;
+ break;
+
+ case 1: // 8 bit data
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.
+ R.bool.config_sms_decode_gsm_8bit_data)) {
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+
+ case 3: // reserved
+ Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+ }
+ } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
+ hasMessageClass = true;
+ userDataCompressed = false;
+
+ if (0 == (mDataCodingScheme & 0x04)) {
+ // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ } else {
+ // 8 bit data
+ encodingType = ENCODING_8BIT;
+ }
+ } else if ((mDataCodingScheme & 0xF0) == 0xC0
+ || (mDataCodingScheme & 0xF0) == 0xD0
+ || (mDataCodingScheme & 0xF0) == 0xE0) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+ // 0xC0 == 7 bit, don't store
+ // 0xD0 == 7 bit, store
+ // 0xE0 == UCS-2, store
+
+ if ((mDataCodingScheme & 0xF0) == 0xE0) {
+ encodingType = ENCODING_16BIT;
+ } else {
+ encodingType = ENCODING_7BIT;
+ }
+
+ userDataCompressed = false;
+ boolean active = ((mDataCodingScheme & 0x08) == 0x08);
+ // bit 0x04 reserved
+
+ // VM - If TP-UDH is present, these values will be overwritten
+ if ((mDataCodingScheme & 0x03) == 0x00) {
+ mIsMwi = true; /* Indicates vmail */
+ mMwiSense = active;/* Indicates vmail notification set/clear */
+ mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
+
+ /* Set voice mail count based on notification bit */
+ if (active == true) {
+ mVoiceMailCount = -1; // unknown number of messages waiting
+ } else {
+ mVoiceMailCount = 0; // no unread messages
+ }
+
+ Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = "
+ + (mDataCodingScheme & 0xff) + " Dont store = "
+ + mMwiDontStore + " vmail count = " + mVoiceMailCount);
+
+ } else {
+ mIsMwi = false;
+ Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: "
+ + (mDataCodingScheme & 0xff));
+ }
+ } else if ((mDataCodingScheme & 0xC0) == 0x80) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+ // 0x80..0xBF == Reserved coding groups
+ if (mDataCodingScheme == 0x84) {
+ // This value used for KSC5601 by carriers in Korea.
+ encodingType = ENCODING_KSC5601;
+ } else {
+ Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ }
+ } else {
+ Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ }
+
+ // set both the user data and the user data header.
+ int count = p.constructUserData(hasUserDataHeader,
+ encodingType == ENCODING_7BIT);
+ this.mUserData = p.getUserData();
+ this.mUserDataHeader = p.getUserDataHeader();
+
+ /*
+ * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
+ * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND)
+ * ieidl =2 octets
+ * ieda msg_ind_type = 0x00 (voice mail; discard sms )or
+ * = 0x80 (voice mail; store sms)
+ * msg_count = 0x00 ..0xFF
+ */
+ if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) {
+ for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) {
+ int msgInd = msg.msgIndType & 0xff;
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id bit 7 storage type
+ */
+ if ((msgInd == 0) || (msgInd == 0x80)) {
+ mIsMwi = true;
+ if (msgInd == 0x80) {
+ /* Store message because TP_UDH indicates so*/
+ mMwiDontStore = false;
+ } else if (mMwiDontStore == false) {
+ /* Storage bit is not set by TP_UDH
+ * Check for conflict
+ * between message storage bit in TP_UDH
+ * & DCS. The message shall be stored if either of
+ * the one indicates so.
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ */
+ if (!((((mDataCodingScheme & 0xF0) == 0xD0)
+ || ((mDataCodingScheme & 0xF0) == 0xE0))
+ && ((mDataCodingScheme & 0x03) == 0x00))) {
+ /* Even DCS did not have voice mail with Storage bit
+ * 3GPP TS 23.038 V7.0.0 section 4
+ * So clear this flag*/
+ mMwiDontStore = true;
+ }
+ }
+
+ mVoiceMailCount = msg.msgCount & 0xff;
+
+ /*
+ * In the event of a conflict between message count setting
+ * and DCS then the Message Count in the TP-UDH shall
+ * override the indication in the TP-DCS. Set voice mail
+ * notification based on count in TP-UDH
+ */
+ if (mVoiceMailCount > 0)
+ mMwiSense = true;
+ else
+ mMwiSense = false;
+
+ Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd
+ + " Dont store = " + mMwiDontStore + " Vmail count = "
+ + mVoiceMailCount);
+
+ /*
+ * There can be only one IE for each type of message
+ * indication in TP_UDH. In the event they are duplicated
+ * last occurence will be used. Hence the for loop
+ */
+ } else {
+ Rlog.w(LOG_TAG, "TP_UDH fax/email/"
+ + "extended msg/multisubscriber profile. Msg Ind = " + msgInd);
+ }
+ } // end of for
+ } // end of if UDH
+
+ switch (encodingType) {
+ case ENCODING_UNKNOWN:
+ mMessageBody = null;
+ break;
+
+ case ENCODING_8BIT:
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.
+ R.bool.config_sms_decode_gsm_8bit_data)) {
+ mMessageBody = p.getUserDataGSM8bit(count);
+ } else {
+ mMessageBody = null;
+ }
+ break;
+
+ case ENCODING_7BIT:
+ mMessageBody = p.getUserDataGSM7Bit(count,
+ hasUserDataHeader ? mUserDataHeader.languageTable : 0,
+ hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
+ break;
+
+ case ENCODING_16BIT:
+ mMessageBody = p.getUserDataUCS2(count);
+ break;
+
+ case ENCODING_KSC5601:
+ mMessageBody = p.getUserDataKSC5601(count);
+ break;
+ }
+
+ if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
+
+ if (mMessageBody != null) {
+ parseMessageBody();
+ }
+
+ if (!hasMessageClass) {
+ messageClass = MessageClass.UNKNOWN;
+ } else {
+ switch (mDataCodingScheme & 0x3) {
+ case 0:
+ messageClass = MessageClass.CLASS_0;
+ break;
+ case 1:
+ messageClass = MessageClass.CLASS_1;
+ break;
+ case 2:
+ messageClass = MessageClass.CLASS_2;
+ break;
+ case 3:
+ messageClass = MessageClass.CLASS_3;
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MessageClass getMessageClass() {
+ return messageClass;
+ }
+
+ /**
+ * Returns true if this is a (U)SIM data download type SM.
+ * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
+ *
+ * @return true if this is a USIM data download message; false otherwise
+ */
+ boolean isUsimDataDownload() {
+ return messageClass == MessageClass.CLASS_2 &&
+ (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
+ }
+
+ public int getNumOfVoicemails() {
+ /*
+ * Order of priority if multiple indications are present is 1.UDH,
+ * 2.DCS, 3.CPHS.
+ * Voice mail count if voice mail present indication is
+ * received
+ * 1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040]
+ * 2. DCS only: count is unknown mVoiceMailCount= -1
+ * 3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700]
+ * Voice mail clear, mVoiceMailCount = 0.
+ */
+ if ((!mIsMwi) && isCphsMwiMessage()) {
+ if (mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) {
+ mVoiceMailCount = 0xff;
+ } else {
+ mVoiceMailCount = 0;
+ }
+ Rlog.v(LOG_TAG, "CPHS voice mail message");
+ }
+ return mVoiceMailCount;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
new file mode 100644
index 0000000..67de87f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2006 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.uicc;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.GsmAlphabet;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Various methods, useful for dealing with SIM data.
+ */
+public class IccUtils {
+ static final String LOG_TAG="IccUtils";
+
+ /**
+ * Many fields in GSM SIM's are stored as nibble-swizzled BCD
+ *
+ * Assumes left-justified field that may be padded right with 0xf
+ * values.
+ *
+ * Stops on invalid BCD value, returning string so far
+ */
+ public static String
+ bcdToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length*2);
+
+ for (int i = offset ; i < offset + length ; i++) {
+ int v;
+
+ v = data[i] & 0xf;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+
+ v = (data[i] >> 4) & 0xf;
+ // Some PLMNs have 'f' as high nibble, ignore it
+ if (v == 0xf) continue;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
+ * Returns a concatenated string of MCC+MNC, stripping
+ * a trailing character for a 2-digit MNC
+ */
+ public static String bcdPlmnToString(byte[] data, int offset) {
+ if (offset + 3 > data.length) {
+ return null;
+ }
+ byte[] trans = new byte[3];
+ trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF));
+ trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF));
+ trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF));
+ String ret = bytesToHexString(trans);
+
+ // For a 2-digit MNC we trim the trailing 'f'
+ if (ret.endsWith("f")) {
+ ret = ret.substring(0, ret.length() - 1);
+ }
+ return ret;
+ }
+
+ /**
+ * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH
+ */
+ public static String
+ bchToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length*2);
+
+ for (int i = offset ; i < offset + length ; i++) {
+ int v;
+
+ v = data[i] & 0xf;
+ ret.append("0123456789abcdef".charAt(v));
+
+ v = (data[i] >> 4) & 0xf;
+ ret.append("0123456789abcdef".charAt(v));
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Decode cdma byte into String.
+ */
+ public static String
+ cdmaBcdToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length);
+
+ int count = 0;
+ for (int i = offset; count < length; i++) {
+ int v;
+ v = data[i] & 0xf;
+ if (v > 9) v = 0;
+ ret.append((char)('0' + v));
+
+ if (++count == length) break;
+
+ v = (data[i] >> 4) & 0xf;
+ if (v > 9) v = 0;
+ ret.append((char)('0' + v));
+ ++count;
+ }
+ return ret.toString();
+ }
+
+ /**
+ * Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
+ *
+ * In GSM land, the least significant BCD digit is stored in the most
+ * significant nibble.
+ *
+ * Out-of-range digits are treated as 0 for the sake of the time stamp,
+ * because of this:
+ *
+ * TS 23.040 section 9.2.3.11
+ * "if the MS receives a non-integer value in the SCTS, it shall
+ * assume the digit is set to 0 but shall store the entire field
+ * exactly as received"
+ */
+ public static int
+ gsmBcdByteToInt(byte b) {
+ int ret = 0;
+
+ // treat out-of-range BCD values as 0
+ if ((b & 0xf0) <= 0x90) {
+ ret = (b >> 4) & 0xf;
+ }
+
+ if ((b & 0x0f) <= 0x09) {
+ ret += (b & 0xf) * 10;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but
+ * opposite nibble format. The least significant BCD digit
+ * is in the least significant nibble and the most significant
+ * is in the most significant nibble.
+ */
+ public static int
+ cdmaBcdByteToInt(byte b) {
+ int ret = 0;
+
+ // treat out-of-range BCD values as 0
+ if ((b & 0xf0) <= 0x90) {
+ ret = ((b >> 4) & 0xf) * 10;
+ }
+
+ if ((b & 0x0f) <= 0x09) {
+ ret += (b & 0xf);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Decodes a string field that's formatted like the EF[ADN] alpha
+ * identifier
+ *
+ * From TS 51.011 10.5.1:
+ * Coding:
+ * this alpha tagging shall use either
+ * - the SMS default 7 bit coded alphabet as defined in
+ * TS 23.038 [12] with bit 8 set to 0. The alpha identifier
+ * shall be left justified. Unused bytes shall be set to 'FF'; or
+ * - one of the UCS2 coded options as defined in annex B.
+ *
+ * Annex B from TS 11.11 V8.13.0:
+ * 1) If the first octet in the alpha string is '80', then the
+ * remaining octets are 16 bit UCS2 characters ...
+ * 2) if the first octet in the alpha string is '81', then the
+ * second octet contains a value indicating the number of
+ * characters in the string, and the third octet contains an
+ * 8 bit number which defines bits 15 to 8 of a 16 bit
+ * base pointer, where bit 16 is set to zero and bits 7 to 1
+ * are also set to zero. These sixteen bits constitute a
+ * base pointer to a "half page" in the UCS2 code space, to be
+ * used with some or all of the remaining octets in the string.
+ * The fourth and subsequent octets contain codings as follows:
+ * If bit 8 of the octet is set to zero, the remaining 7 bits
+ * of the octet contain a GSM Default Alphabet character,
+ * whereas if bit 8 of the octet is set to one, then the
+ * remaining seven bits are an offset value added to the
+ * 16 bit base pointer defined earlier...
+ * 3) If the first octet of the alpha string is set to '82', then
+ * the second octet contains a value indicating the number of
+ * characters in the string, and the third and fourth octets
+ * contain a 16 bit number which defines the complete 16 bit
+ * base pointer to a "half page" in the UCS2 code space...
+ */
+ public static String
+ adnStringFieldToString(byte[] data, int offset, int length) {
+ if (length == 0) {
+ return "";
+ }
+ if (length >= 1) {
+ if (data[offset] == (byte) 0x80) {
+ int ucslen = (length - 1) / 2;
+ String ret = null;
+
+ try {
+ ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
+ } catch (UnsupportedEncodingException ex) {
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
+ ex);
+ }
+
+ if (ret != null) {
+ // trim off trailing FFFF characters
+
+ ucslen = ret.length();
+ while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
+ ucslen--;
+
+ return ret.substring(0, ucslen);
+ }
+ }
+ }
+
+ boolean isucs2 = false;
+ char base = '\0';
+ int len = 0;
+
+ if (length >= 3 && data[offset] == (byte) 0x81) {
+ len = data[offset + 1] & 0xFF;
+ if (len > length - 3)
+ len = length - 3;
+
+ base = (char) ((data[offset + 2] & 0xFF) << 7);
+ offset += 3;
+ isucs2 = true;
+ } else if (length >= 4 && data[offset] == (byte) 0x82) {
+ len = data[offset + 1] & 0xFF;
+ if (len > length - 4)
+ len = length - 4;
+
+ base = (char) (((data[offset + 2] & 0xFF) << 8) |
+ (data[offset + 3] & 0xFF));
+ offset += 4;
+ isucs2 = true;
+ }
+
+ if (isucs2) {
+ StringBuilder ret = new StringBuilder();
+
+ while (len > 0) {
+ // UCS2 subset case
+
+ if (data[offset] < 0) {
+ ret.append((char) (base + (data[offset] & 0x7F)));
+ offset++;
+ len--;
+ }
+
+ // GSM character set case
+
+ int count = 0;
+ while (count < len && data[offset + count] >= 0)
+ count++;
+
+ ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
+ offset, count));
+
+ offset += count;
+ len -= count;
+ }
+
+ return ret.toString();
+ }
+
+ Resources resource = Resources.getSystem();
+ String defaultCharset = "";
+ try {
+ defaultCharset =
+ resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+ } catch (NotFoundException e) {
+ // Ignore Exception and defaultCharset is set to a empty string.
+ }
+ return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
+ }
+
+ static int
+ hexCharToInt(char c) {
+ if (c >= '0' && c <= '9') return (c - '0');
+ if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+ if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+ throw new RuntimeException ("invalid hex char '" + c + "'");
+ }
+
+ /**
+ * Converts a hex String to a byte array.
+ *
+ * @param s A string of hexadecimal characters, must be an even number of
+ * chars long
+ *
+ * @return byte array representation
+ *
+ * @throws RuntimeException on invalid format
+ */
+ public static byte[]
+ hexStringToBytes(String s) {
+ byte[] ret;
+
+ if (s == null) return null;
+
+ int sz = s.length();
+
+ ret = new byte[sz/2];
+
+ for (int i=0 ; i <sz ; i+=2) {
+ ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
+ | hexCharToInt(s.charAt(i+1)));
+ }
+
+ return ret;
+ }
+
+
+ /**
+ * Converts a byte array into a String of hexadecimal characters.
+ *
+ * @param bytes an array of bytes
+ *
+ * @return hex string representation of bytes array
+ */
+ public static String
+ bytesToHexString(byte[] bytes) {
+ if (bytes == null) return null;
+
+ StringBuilder ret = new StringBuilder(2*bytes.length);
+
+ for (int i = 0 ; i < bytes.length ; i++) {
+ int b;
+
+ b = 0x0f & (bytes[i] >> 4);
+
+ ret.append("0123456789abcdef".charAt(b));
+
+ b = 0x0f & bytes[i];
+
+ ret.append("0123456789abcdef".charAt(b));
+ }
+
+ return ret.toString();
+ }
+
+
+ /**
+ * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
+ * "offset" points to "octet 3", the coding scheme byte
+ * empty string returned on decode error
+ */
+ public static String
+ networkNameToString(byte[] data, int offset, int length) {
+ String ret;
+
+ if ((data[offset] & 0x80) != 0x80 || length < 1) {
+ return "";
+ }
+
+ switch ((data[offset] >>> 4) & 0x7) {
+ case 0:
+ // SMS character set
+ int countSeptets;
+ int unusedBits = data[offset] & 7;
+ countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
+ ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets);
+ break;
+ case 1:
+ // UCS2
+ try {
+ ret = new String(data,
+ offset + 1, length - 1, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
+ }
+ break;
+
+ // unsupported encoding
+ default:
+ ret = "";
+ break;
+ }
+
+ // "Add CI"
+ // "The MS should add the letters for the Country's Initials and
+ // a separator (e.g. a space) to the text string"
+
+ if ((data[offset] & 0x40) != 0) {
+ // FIXME(mkf) add country initials here
+
+ }
+
+ return ret;
+ }
+
+ /**
+ * Convert a TS 131.102 image instance of code scheme '11' into Bitmap
+ * @param data The raw data
+ * @param length The length of image body
+ * @return The bitmap
+ */
+ public static Bitmap parseToBnW(byte[] data, int length){
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int numOfPixels = width*height;
+
+ int[] pixels = new int[numOfPixels];
+
+ int pixelIndex = 0;
+ int bitIndex = 7;
+ byte currentByte = 0x00;
+ while (pixelIndex < numOfPixels) {
+ // reassign data and index for every byte (8 bits).
+ if (pixelIndex % 8 == 0) {
+ currentByte = data[valueIndex++];
+ bitIndex = 7;
+ }
+ pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
+ }
+
+ if (pixelIndex != numOfPixels) {
+ Rlog.e(LOG_TAG, "parse end and size error");
+ }
+ return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ private static int bitToRGB(int bit){
+ if(bit == 1){
+ return Color.WHITE;
+ } else {
+ return Color.BLACK;
+ }
+ }
+
+ /**
+ * a TS 131.102 image instance of code scheme '11' into color Bitmap
+ *
+ * @param data The raw data
+ * @param length the length of image body
+ * @param transparency with or without transparency
+ * @return The color bitmap
+ */
+ public static Bitmap parseToRGB(byte[] data, int length,
+ boolean transparency) {
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int bits = data[valueIndex++] & 0xFF;
+ int colorNumber = data[valueIndex++] & 0xFF;
+ int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
+ | (data[valueIndex++] & 0xFF);
+
+ int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
+ if (true == transparency) {
+ colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
+ }
+
+ int[] resultArray = null;
+ if (0 == (8 % bits)) {
+ resultArray = mapTo2OrderBitColor(data, valueIndex,
+ (width * height), colorIndexArray, bits);
+ } else {
+ resultArray = mapToNon2OrderBitColor(data, valueIndex,
+ (width * height), colorIndexArray, bits);
+ }
+
+ return Bitmap.createBitmap(resultArray, width, height,
+ Bitmap.Config.RGB_565);
+ }
+
+ private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
+ int length, int[] colorArray, int bits) {
+ if (0 != (8 % bits)) {
+ Rlog.e(LOG_TAG, "not event number of color");
+ return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
+ bits);
+ }
+
+ int mask = 0x01;
+ switch (bits) {
+ case 1:
+ mask = 0x01;
+ break;
+ case 2:
+ mask = 0x03;
+ break;
+ case 4:
+ mask = 0x0F;
+ break;
+ case 8:
+ mask = 0xFF;
+ break;
+ }
+
+ int[] resultArray = new int[length];
+ int resultIndex = 0;
+ int run = 8 / bits;
+ while (resultIndex < length) {
+ byte tempByte = data[valueIndex++];
+ for (int runIndex = 0; runIndex < run; ++runIndex) {
+ int offset = run - runIndex - 1;
+ resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
+ & mask];
+ }
+ }
+ return resultArray;
+ }
+
+ private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
+ int length, int[] colorArray, int bits) {
+ if (0 == (8 % bits)) {
+ Rlog.e(LOG_TAG, "not odd number of color");
+ return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
+ bits);
+ }
+
+ int[] resultArray = new int[length];
+ // TODO fix me:
+ return resultArray;
+ }
+
+ private static int[] getCLUT(byte[] rawData, int offset, int number) {
+ if (null == rawData) {
+ return null;
+ }
+
+ int[] result = new int[number];
+ int endIndex = offset + (number * 3); // 1 color use 3 bytes
+ int valueIndex = offset;
+ int colorIndex = 0;
+ int alpha = 0xff << 24;
+ do {
+ result[colorIndex++] = alpha
+ | ((rawData[valueIndex++] & 0xFF) << 16)
+ | ((rawData[valueIndex++] & 0xFF) << 8)
+ | ((rawData[valueIndex++] & 0xFF));
+ } while (valueIndex < endIndex);
+ return result;
+ }
+}
diff --git a/tests/net/java/android/net/nsd/NsdManagerTest.java b/tests/net/java/android/net/nsd/NsdManagerTest.java
index 2418450..063cd5dc 100644
--- a/tests/net/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/net/java/android/net/nsd/NsdManagerTest.java
@@ -18,14 +18,16 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.when;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.mock;
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 android.os.HandlerThread;
import android.os.Handler;
@@ -42,10 +44,14 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NsdManagerTest {
+ static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
+
@Mock Context mContext;
@Mock INsdManager mService;
MockServiceHandler mServiceHandler;
@@ -101,7 +107,206 @@
verify(listener1, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
verify(listener2, timeout(mTimeoutMs).times(1)).onServiceResolved(reply);
- }
+ }
+
+ @Test
+ public void testRegisterService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo request1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo request2 = new NsdServiceInfo("another_name", "another_type");
+ request1.setPort(2201);
+ request2.setPort(2202);
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.RegistrationListener listener2 = mock(NsdManager.RegistrationListener.class);
+
+ // Register two services
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key1 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ manager.registerService(request2, PROTOCOL, listener2);
+ int key2 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ // First reques fails, second request succeeds
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key2, request2);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onServiceRegistered(request2);
+
+ int err = 1;
+ sendResponse(NsdManager.REGISTER_SERVICE_FAILED, err, key1, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onRegistrationFailed(request1, err);
+
+ // Client retries first request, it succeeds
+ manager.registerService(request1, PROTOCOL, listener1);
+ int key3 = verifyRequest(NsdManager.REGISTER_SERVICE);
+
+ sendResponse(NsdManager.REGISTER_SERVICE_SUCCEEDED, 0, key3, request1);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceRegistered(request1);
+
+ // First request is unregistered, it succeeds
+ manager.unregisterService(listener1);
+ int key3again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key3, key3again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key3again, null);
+ verify(listener1, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request1);
+
+ // Second request is unregistered, it fails
+ manager.unregisterService(listener2);
+ int key2again = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ assertEquals(key2, key2again);
+
+ sendResponse(NsdManager.UNREGISTER_SERVICE_FAILED, err, key2again, null);
+ verify(listener2, timeout(mTimeoutMs).times(1)).onUnregistrationFailed(request2, err);
+
+ // TODO: do not unregister listener until service is unregistered
+ // Client retries unregistration of second request, it succeeds
+ //manager.unregisterService(listener2);
+ //int key2yetAgain = verifyRequest(NsdManager.UNREGISTER_SERVICE);
+ //assertEquals(key2, key2yetAgain);
+
+ //sendResponse(NsdManager.UNREGISTER_SERVICE_SUCCEEDED, 0, key2yetAgain, null);
+ //verify(listener2, timeout(mTimeoutMs).times(1)).onServiceUnregistered(request2);
+ }
+
+ @Test
+ public void testDiscoverService() {
+ NsdManager manager = makeManager();
+
+ NsdServiceInfo reply1 = new NsdServiceInfo("a_name", "a_type");
+ NsdServiceInfo reply2 = new NsdServiceInfo("another_name", "a_type");
+ NsdServiceInfo reply3 = new NsdServiceInfo("a_third_name", "a_type");
+
+ NsdManager.DiscoveryListener listener = mock(NsdManager.DiscoveryListener.class);
+
+ // Client registers for discovery, request fails
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key1 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ int err = 1;
+ sendResponse(NsdManager.DISCOVER_SERVICES_FAILED, err, key1, null);
+ verify(listener, timeout(mTimeoutMs).times(1)).onStartDiscoveryFailed("a_type", err);
+
+ // Client retries, request succeeds
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key2 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+
+ // mdns notifies about services
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply1);
+
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceFound(reply2);
+
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply2);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply2);
+
+
+ // Client unregisters its listener
+ manager.stopServiceDiscovery(listener);
+ int key2again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key2, key2again);
+
+ // TODO: unregister listener immediately and stop notifying it about services
+ // Notifications are still passed to the client's listener
+ sendResponse(NsdManager.SERVICE_LOST, 0, key2, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onServiceLost(reply1);
+
+ // Client is notified of complete unregistration
+ sendResponse(NsdManager.STOP_DISCOVERY_SUCCEEDED, 0, key2again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStopped("a_type");
+
+ // Notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key2, reply3);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceLost(reply3);
+
+
+ // Client registers for service discovery
+ reset(listener);
+ manager.discoverServices("a_type", PROTOCOL, listener);
+ int key3 = verifyRequest(NsdManager.DISCOVER_SERVICES);
+
+ sendResponse(NsdManager.DISCOVER_SERVICES_STARTED, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(1)).onDiscoveryStarted("a_type");
+
+ // Client unregisters immediately, it fails
+ manager.stopServiceDiscovery(listener);
+ int key3again = verifyRequest(NsdManager.STOP_DISCOVERY);
+ assertEquals(key3, key3again);
+
+ err = 2;
+ sendResponse(NsdManager.STOP_DISCOVERY_FAILED, err, key3again, "a_type");
+ verify(listener, timeout(mTimeoutMs).times(1)).onStopDiscoveryFailed("a_type", err);
+
+ // New notifications are not passed to the client anymore
+ sendResponse(NsdManager.SERVICE_FOUND, 0, key3, reply1);
+ verify(listener, timeout(mTimeoutMs).times(0)).onServiceFound(reply1);
+ }
+
+ @Test
+ public void testInvalidCalls() {
+ NsdManager manager = new NsdManager(mContext, mService);
+
+ NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
+ NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
+ NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
+
+ NsdServiceInfo invalidService = new NsdServiceInfo(null, null);
+ NsdServiceInfo validService = new NsdServiceInfo("a_name", "a_type");
+ validService.setPort(2222);
+
+ // Service registration
+ // - invalid arguments
+ mustFail(() -> { manager.unregisterService(null); });
+ mustFail(() -> { manager.registerService(null, -1, null); });
+ mustFail(() -> { manager.registerService(null, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(invalidService, PROTOCOL, listener1); });
+ mustFail(() -> { manager.registerService(validService, -1, listener1); });
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, null); });
+ manager.registerService(validService, PROTOCOL, listener1);
+ // - listener already registered
+ mustFail(() -> { manager.registerService(validService, PROTOCOL, listener1); });
+ manager.unregisterService(listener1);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.unregisterService(listener1); });
+ //manager.registerService(validService, PROTOCOL, listener1);
+
+ // Discover service
+ // - invalid arguments
+ mustFail(() -> { manager.stopServiceDiscovery(null); });
+ mustFail(() -> { manager.discoverServices(null, -1, null); });
+ mustFail(() -> { manager.discoverServices(null, PROTOCOL, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", -1, listener2); });
+ mustFail(() -> { manager.discoverServices("a_service", PROTOCOL, null); });
+ manager.discoverServices("a_service", PROTOCOL, listener2);
+ // - listener already registered
+ mustFail(() -> { manager.discoverServices("another_service", PROTOCOL, listener2); });
+ manager.stopServiceDiscovery(listener2);
+ // TODO: make listener immediately reusable
+ //mustFail(() -> { manager.stopServiceDiscovery(listener2); });
+ //manager.discoverServices("another_service", PROTOCOL, listener2);
+
+ // Resolver service
+ // - invalid arguments
+ mustFail(() -> { manager.resolveService(null, null); });
+ mustFail(() -> { manager.resolveService(null, listener3); });
+ mustFail(() -> { manager.resolveService(invalidService, listener3); });
+ mustFail(() -> { manager.resolveService(validService, null); });
+ manager.resolveService(validService, listener3);
+ // - listener already registered:w
+ mustFail(() -> { manager.resolveService(validService, listener3); });
+ }
+
+ public void mustFail(Runnable fn) {
+ try {
+ fn.run();
+ fail();
+ } catch (Exception expected) {
+ }
+ }
NsdManager makeManager() {
NsdManager manager = new NsdManager(mContext, mService);
diff --git a/tests/net/java/android/net/nsd/NsdServiceTest.java b/tests/net/java/android/net/nsd/NsdServiceTest.java
new file mode 100644
index 0000000..acc390c
--- /dev/null
+++ b/tests/net/java/android/net/nsd/NsdServiceTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+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 android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.net.nsd.NsdManager;
+import com.android.server.NsdService.DaemonConnection;
+import com.android.server.NsdService.DaemonConnectionSupplier;
+import com.android.server.NsdService.NativeCallbackReceiver;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+// TODOs:
+// - test client disconnects
+// - test client can send requests and receive replies
+// - test NSD_ON ENABLE/DISABLED listening
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NsdServiceTest {
+
+ @Mock Context mContext;
+ @Mock ContentResolver mResolver;
+ @Mock NsdService.NsdSettings mSettings;
+ @Mock DaemonConnection mDaemon;
+ NativeCallbackReceiver mDaemonCallback;
+ TestLooper mLooper;
+ TestHandler mHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mLooper = new TestLooper();
+ mHandler = new TestHandler(mLooper.getLooper());
+ when(mContext.getContentResolver()).thenReturn(mResolver);
+ }
+
+ @Test
+ public void testClientsCanConnect() {
+ when(mSettings.isEnabled()).thenReturn(true);
+
+ NsdService service = makeService();
+
+ NsdManager client1 = connectClient(service);
+ verify(mDaemon, timeout(100).times(1)).execute("start-service");
+
+ NsdManager client2 = connectClient(service);
+
+ // TODO: disconnect client1
+ // TODO: disconnect client2
+ }
+
+ NsdService makeService() {
+ DaemonConnectionSupplier supplier = (callback) -> {
+ mDaemonCallback = callback;
+ return mDaemon;
+ };
+ NsdService service = new NsdService(mContext, mSettings, mHandler, supplier);
+ verify(mDaemon, never()).execute(any(String.class));
+ return service;
+ }
+
+ NsdManager connectClient(NsdService service) {
+ mLooper.startAutoDispatch();
+ NsdManager client = new NsdManager(mContext, service);
+ mLooper.stopAutoDispatch();
+ return client;
+ }
+
+ public static class TestHandler extends Handler {
+ public Message lastMessage;
+
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ lastMessage = obtainMessage();
+ lastMessage.copyFrom(msg);
+ }
+ }
+}
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index b2c1244..7a1c239 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -236,6 +236,9 @@
verify(mNMService, times(1)).setIpForwardingEnabled(true);
verify(mNMService, times(1)).startTethering(any(String[].class));
verifyNoMoreInteractions(mNMService);
+ verify(mWifiManager).updateInterfaceIpState(
+ mTestIfname, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+ verifyNoMoreInteractions(mWifiManager);
verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY);
// UpstreamNetworkMonitor will be started, and will register two callbacks:
// a "listen all" and a "track default".
@@ -261,6 +264,7 @@
verify(mNMService, times(1)).stopTethering();
verify(mNMService, times(1)).setIpForwardingEnabled(false);
verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mWifiManager);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
@@ -292,6 +296,9 @@
verify(mNMService, times(1)).setIpForwardingEnabled(true);
verify(mNMService, times(1)).startTethering(any(String[].class));
verifyNoMoreInteractions(mNMService);
+ verify(mWifiManager).updateInterfaceIpState(
+ mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
+ verifyNoMoreInteractions(mWifiManager);
verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_TETHER);
// UpstreamNetworkMonitor will be started, and will register two callbacks:
// a "listen all" and a "track default".
@@ -338,6 +345,7 @@
verify(mNMService, times(1)).stopTethering();
verify(mNMService, times(1)).setIpForwardingEnabled(false);
verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mWifiManager);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index a84c306..e2ee603 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -802,7 +802,7 @@
}
struct SymbolComparator {
- bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+ bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) const {
return a.symbol.name.value() < b.symbol.name.value();
}
};
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index bbf7f41..65c4eb8 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -1080,6 +1080,10 @@
newRows[i] = image->rows[i + 1];
memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
}
+ delete[] image->rows[0];
+ if (H - 1 > 0) {
+ delete[] image->rows[H - 1];
+ }
image->rows.swap(newRows);
image->width -= 2;
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 69e1029..ad9f9b8 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -125,6 +125,8 @@
void setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable);
+ void updateInterfaceIpState(String ifaceName, int mode);
+
boolean startSoftAp(in WifiConfiguration wifiConfig);
boolean stopSoftAp();
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index ae8a224..b7fe9c7 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -20,7 +20,6 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.net.ConnectivityManager;
@@ -46,9 +45,9 @@
import com.android.server.net.NetworkPinner;
import java.net.InetAddress;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.Collections;
/**
* This class provides the primary API for managing all aspects of Wi-Fi
@@ -446,6 +445,35 @@
* @hide
*/
public static final int SAP_START_FAILURE_NO_CHANNEL = 1;
+
+ /**
+ * Interface IP mode for configuration error.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_CONFIGURATION_ERROR = 0;
+
+ /**
+ * Interface IP mode for tethering.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_TETHERED = 1;
+
+ /**
+ * Interface IP mode for Local Only Hotspot.
+ *
+ * @see updateInterfaceIpState(String, int)
+ *
+ * @hide
+ */
+ public static final int IFACE_IP_MODE_LOCAL_ONLY = 2;
+
+
/**
* Broadcast intent action indicating that a connection to the supplicant has
* been established (and it is now possible
@@ -1718,6 +1746,26 @@
}
/**
+ * Call allowing ConnectivityService to update WifiService with interface mode changes.
+ *
+ * The possible modes include: {@link IFACE_IP_MODE_TETHERED},
+ * {@link IFACE_IP_MODE_LOCAL_ONLY},
+ * {@link IFACE_IP_MODE_CONFIGURATION_ERROR}
+ *
+ * @param ifaceName String name of the updated interface
+ * @param mode int representing the new mode
+ *
+ * @hide
+ */
+ public void updateInterfaceIpState(String ifaceName, int mode) {
+ try {
+ mService.updateInterfaceIpState(ifaceName, mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Start SoftAp mode with the specified configuration.
* Note that starting in access point mode disables station
* mode operation