From 9ee98103a9015fbabe813218915b2892b599ae16 Mon Sep 17 00:00:00 2001 From: Wink Saville Date: Thu, 14 Jun 2012 12:11:20 -0700 Subject: Create mms-common - DO NOT MERGE mms-common was created by moving all of frameworks/base/core/java/com/google/android/mms to: frameworks/opt/mms Change-Id: I7b9f491e19161d86ccfffc01ace826de23ef167e --- src/java/com/google/android/mms/ContentType.java | 229 +++ .../android/mms/InvalidHeaderValueException.java | 41 + src/java/com/google/android/mms/MmsException.java | 60 + src/java/com/google/android/mms/package.html | 5 + .../com/google/android/mms/pdu/AcknowledgeInd.java | 89 + src/java/com/google/android/mms/pdu/Base64.java | 167 ++ .../com/google/android/mms/pdu/CharacterSets.java | 172 ++ .../com/google/android/mms/pdu/DeliveryInd.java | 138 ++ .../google/android/mms/pdu/EncodedStringValue.java | 283 +++ .../com/google/android/mms/pdu/GenericPdu.java | 113 ++ .../android/mms/pdu/MultimediaMessagePdu.java | 150 ++ .../google/android/mms/pdu/NotificationInd.java | 285 +++ .../com/google/android/mms/pdu/NotifyRespInd.java | 114 ++ src/java/com/google/android/mms/pdu/PduBody.java | 191 ++ .../com/google/android/mms/pdu/PduComposer.java | 1179 ++++++++++++ .../google/android/mms/pdu/PduContentTypes.java | 110 ++ .../com/google/android/mms/pdu/PduHeaders.java | 721 ++++++++ src/java/com/google/android/mms/pdu/PduParser.java | 1912 ++++++++++++++++++++ src/java/com/google/android/mms/pdu/PduPart.java | 402 ++++ .../com/google/android/mms/pdu/PduPersister.java | 1477 +++++++++++++++ .../google/android/mms/pdu/QuotedPrintable.java | 68 + .../com/google/android/mms/pdu/ReadOrigInd.java | 153 ++ .../com/google/android/mms/pdu/ReadRecInd.java | 144 ++ .../com/google/android/mms/pdu/RetrieveConf.java | 300 +++ src/java/com/google/android/mms/pdu/SendConf.java | 117 ++ src/java/com/google/android/mms/pdu/SendReq.java | 345 ++++ src/java/com/google/android/mms/pdu/package.html | 5 + .../com/google/android/mms/util/AbstractCache.java | 112 ++ .../google/android/mms/util/DownloadDrmHelper.java | 111 ++ .../google/android/mms/util/DrmConvertSession.java | 172 ++ src/java/com/google/android/mms/util/PduCache.java | 261 +++ .../com/google/android/mms/util/PduCacheEntry.java | 44 + .../com/google/android/mms/util/SqliteWrapper.java | 120 ++ src/java/com/google/android/mms/util/package.html | 5 + 34 files changed, 9795 insertions(+) create mode 100644 src/java/com/google/android/mms/ContentType.java create mode 100644 src/java/com/google/android/mms/InvalidHeaderValueException.java create mode 100644 src/java/com/google/android/mms/MmsException.java create mode 100755 src/java/com/google/android/mms/package.html create mode 100644 src/java/com/google/android/mms/pdu/AcknowledgeInd.java create mode 100644 src/java/com/google/android/mms/pdu/Base64.java create mode 100644 src/java/com/google/android/mms/pdu/CharacterSets.java create mode 100644 src/java/com/google/android/mms/pdu/DeliveryInd.java create mode 100644 src/java/com/google/android/mms/pdu/EncodedStringValue.java create mode 100644 src/java/com/google/android/mms/pdu/GenericPdu.java create mode 100644 src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java create mode 100644 src/java/com/google/android/mms/pdu/NotificationInd.java create mode 100644 src/java/com/google/android/mms/pdu/NotifyRespInd.java create mode 100644 src/java/com/google/android/mms/pdu/PduBody.java create mode 100644 src/java/com/google/android/mms/pdu/PduComposer.java create mode 100644 src/java/com/google/android/mms/pdu/PduContentTypes.java create mode 100644 src/java/com/google/android/mms/pdu/PduHeaders.java create mode 100755 src/java/com/google/android/mms/pdu/PduParser.java create mode 100644 src/java/com/google/android/mms/pdu/PduPart.java create mode 100644 src/java/com/google/android/mms/pdu/PduPersister.java create mode 100644 src/java/com/google/android/mms/pdu/QuotedPrintable.java create mode 100644 src/java/com/google/android/mms/pdu/ReadOrigInd.java create mode 100644 src/java/com/google/android/mms/pdu/ReadRecInd.java create mode 100644 src/java/com/google/android/mms/pdu/RetrieveConf.java create mode 100644 src/java/com/google/android/mms/pdu/SendConf.java create mode 100644 src/java/com/google/android/mms/pdu/SendReq.java create mode 100755 src/java/com/google/android/mms/pdu/package.html create mode 100644 src/java/com/google/android/mms/util/AbstractCache.java create mode 100644 src/java/com/google/android/mms/util/DownloadDrmHelper.java create mode 100644 src/java/com/google/android/mms/util/DrmConvertSession.java create mode 100644 src/java/com/google/android/mms/util/PduCache.java create mode 100644 src/java/com/google/android/mms/util/PduCacheEntry.java create mode 100644 src/java/com/google/android/mms/util/SqliteWrapper.java create mode 100755 src/java/com/google/android/mms/util/package.html (limited to 'src') diff --git a/src/java/com/google/android/mms/ContentType.java b/src/java/com/google/android/mms/ContentType.java new file mode 100644 index 0000000..12a1343 --- /dev/null +++ b/src/java/com/google/android/mms/ContentType.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms; + +import java.util.ArrayList; + +public class ContentType { + public static final String MMS_MESSAGE = "application/vnd.wap.mms-message"; + // The phony content type for generic PDUs (e.g. ReadOrig.ind, + // Notification.ind, Delivery.ind). + public static final String MMS_GENERIC = "application/vnd.wap.mms-generic"; + public static final String MULTIPART_MIXED = "application/vnd.wap.multipart.mixed"; + public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related"; + public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative"; + + public static final String TEXT_PLAIN = "text/plain"; + public static final String TEXT_HTML = "text/html"; + public static final String TEXT_VCALENDAR = "text/x-vCalendar"; + public static final String TEXT_VCARD = "text/x-vCard"; + + public static final String IMAGE_UNSPECIFIED = "image/*"; + public static final String IMAGE_JPEG = "image/jpeg"; + public static final String IMAGE_JPG = "image/jpg"; + public static final String IMAGE_GIF = "image/gif"; + public static final String IMAGE_WBMP = "image/vnd.wap.wbmp"; + public static final String IMAGE_PNG = "image/png"; + public static final String IMAGE_X_MS_BMP = "image/x-ms-bmp"; + + public static final String AUDIO_UNSPECIFIED = "audio/*"; + public static final String AUDIO_AAC = "audio/aac"; + public static final String AUDIO_AMR = "audio/amr"; + public static final String AUDIO_IMELODY = "audio/imelody"; + public static final String AUDIO_MID = "audio/mid"; + public static final String AUDIO_MIDI = "audio/midi"; + public static final String AUDIO_MP3 = "audio/mp3"; + public static final String AUDIO_MPEG3 = "audio/mpeg3"; + public static final String AUDIO_MPEG = "audio/mpeg"; + public static final String AUDIO_MPG = "audio/mpg"; + public static final String AUDIO_MP4 = "audio/mp4"; + public static final String AUDIO_X_MID = "audio/x-mid"; + public static final String AUDIO_X_MIDI = "audio/x-midi"; + public static final String AUDIO_X_MP3 = "audio/x-mp3"; + public static final String AUDIO_X_MPEG3 = "audio/x-mpeg3"; + public static final String AUDIO_X_MPEG = "audio/x-mpeg"; + public static final String AUDIO_X_MPG = "audio/x-mpg"; + public static final String AUDIO_3GPP = "audio/3gpp"; + public static final String AUDIO_X_WAV = "audio/x-wav"; + public static final String AUDIO_OGG = "application/ogg"; + + public static final String VIDEO_UNSPECIFIED = "video/*"; + public static final String VIDEO_3GPP = "video/3gpp"; + public static final String VIDEO_3G2 = "video/3gpp2"; + public static final String VIDEO_H263 = "video/h263"; + public static final String VIDEO_MP4 = "video/mp4"; + + public static final String APP_SMIL = "application/smil"; + public static final String APP_WAP_XHTML = "application/vnd.wap.xhtml+xml"; + public static final String APP_XHTML = "application/xhtml+xml"; + + public static final String APP_DRM_CONTENT = "application/vnd.oma.drm.content"; + public static final String APP_DRM_MESSAGE = "application/vnd.oma.drm.message"; + + private static final ArrayList sSupportedContentTypes = new ArrayList(); + private static final ArrayList sSupportedImageTypes = new ArrayList(); + private static final ArrayList sSupportedAudioTypes = new ArrayList(); + private static final ArrayList sSupportedVideoTypes = new ArrayList(); + + static { + sSupportedContentTypes.add(TEXT_PLAIN); + sSupportedContentTypes.add(TEXT_HTML); + sSupportedContentTypes.add(TEXT_VCALENDAR); + sSupportedContentTypes.add(TEXT_VCARD); + + sSupportedContentTypes.add(IMAGE_JPEG); + sSupportedContentTypes.add(IMAGE_GIF); + sSupportedContentTypes.add(IMAGE_WBMP); + sSupportedContentTypes.add(IMAGE_PNG); + sSupportedContentTypes.add(IMAGE_JPG); + sSupportedContentTypes.add(IMAGE_X_MS_BMP); + //supportedContentTypes.add(IMAGE_SVG); not yet supported. + + sSupportedContentTypes.add(AUDIO_AAC); + sSupportedContentTypes.add(AUDIO_AMR); + sSupportedContentTypes.add(AUDIO_IMELODY); + sSupportedContentTypes.add(AUDIO_MID); + sSupportedContentTypes.add(AUDIO_MIDI); + sSupportedContentTypes.add(AUDIO_MP3); + sSupportedContentTypes.add(AUDIO_MPEG3); + sSupportedContentTypes.add(AUDIO_MPEG); + sSupportedContentTypes.add(AUDIO_MPG); + sSupportedContentTypes.add(AUDIO_X_MID); + sSupportedContentTypes.add(AUDIO_X_MIDI); + sSupportedContentTypes.add(AUDIO_X_MP3); + sSupportedContentTypes.add(AUDIO_X_MPEG3); + sSupportedContentTypes.add(AUDIO_X_MPEG); + sSupportedContentTypes.add(AUDIO_X_MPG); + sSupportedContentTypes.add(AUDIO_X_WAV); + sSupportedContentTypes.add(AUDIO_3GPP); + sSupportedContentTypes.add(AUDIO_OGG); + + sSupportedContentTypes.add(VIDEO_3GPP); + sSupportedContentTypes.add(VIDEO_3G2); + sSupportedContentTypes.add(VIDEO_H263); + sSupportedContentTypes.add(VIDEO_MP4); + + sSupportedContentTypes.add(APP_SMIL); + sSupportedContentTypes.add(APP_WAP_XHTML); + sSupportedContentTypes.add(APP_XHTML); + + sSupportedContentTypes.add(APP_DRM_CONTENT); + sSupportedContentTypes.add(APP_DRM_MESSAGE); + + // add supported image types + sSupportedImageTypes.add(IMAGE_JPEG); + sSupportedImageTypes.add(IMAGE_GIF); + sSupportedImageTypes.add(IMAGE_WBMP); + sSupportedImageTypes.add(IMAGE_PNG); + sSupportedImageTypes.add(IMAGE_JPG); + sSupportedImageTypes.add(IMAGE_X_MS_BMP); + + // add supported audio types + sSupportedAudioTypes.add(AUDIO_AAC); + sSupportedAudioTypes.add(AUDIO_AMR); + sSupportedAudioTypes.add(AUDIO_IMELODY); + sSupportedAudioTypes.add(AUDIO_MID); + sSupportedAudioTypes.add(AUDIO_MIDI); + sSupportedAudioTypes.add(AUDIO_MP3); + sSupportedAudioTypes.add(AUDIO_MPEG3); + sSupportedAudioTypes.add(AUDIO_MPEG); + sSupportedAudioTypes.add(AUDIO_MPG); + sSupportedAudioTypes.add(AUDIO_MP4); + sSupportedAudioTypes.add(AUDIO_X_MID); + sSupportedAudioTypes.add(AUDIO_X_MIDI); + sSupportedAudioTypes.add(AUDIO_X_MP3); + sSupportedAudioTypes.add(AUDIO_X_MPEG3); + sSupportedAudioTypes.add(AUDIO_X_MPEG); + sSupportedAudioTypes.add(AUDIO_X_MPG); + sSupportedAudioTypes.add(AUDIO_X_WAV); + sSupportedAudioTypes.add(AUDIO_3GPP); + sSupportedAudioTypes.add(AUDIO_OGG); + + // add supported video types + sSupportedVideoTypes.add(VIDEO_3GPP); + sSupportedVideoTypes.add(VIDEO_3G2); + sSupportedVideoTypes.add(VIDEO_H263); + sSupportedVideoTypes.add(VIDEO_MP4); + } + + // This class should never be instantiated. + private ContentType() { + } + + public static boolean isSupportedType(String contentType) { + return (null != contentType) && sSupportedContentTypes.contains(contentType); + } + + public static boolean isSupportedImageType(String contentType) { + return isImageType(contentType) && isSupportedType(contentType); + } + + public static boolean isSupportedAudioType(String contentType) { + return isAudioType(contentType) && isSupportedType(contentType); + } + + public static boolean isSupportedVideoType(String contentType) { + return isVideoType(contentType) && isSupportedType(contentType); + } + + public static boolean isTextType(String contentType) { + return (null != contentType) && contentType.startsWith("text/"); + } + + public static boolean isImageType(String contentType) { + return (null != contentType) && contentType.startsWith("image/"); + } + + public static boolean isAudioType(String contentType) { + return (null != contentType) && contentType.startsWith("audio/"); + } + + public static boolean isVideoType(String contentType) { + return (null != contentType) && contentType.startsWith("video/"); + } + + public static boolean isDrmType(String contentType) { + return (null != contentType) + && (contentType.equals(APP_DRM_CONTENT) + || contentType.equals(APP_DRM_MESSAGE)); + } + + public static boolean isUnspecified(String contentType) { + return (null != contentType) && contentType.endsWith("*"); + } + + @SuppressWarnings("unchecked") + public static ArrayList getImageTypes() { + return (ArrayList) sSupportedImageTypes.clone(); + } + + @SuppressWarnings("unchecked") + public static ArrayList getAudioTypes() { + return (ArrayList) sSupportedAudioTypes.clone(); + } + + @SuppressWarnings("unchecked") + public static ArrayList getVideoTypes() { + return (ArrayList) sSupportedVideoTypes.clone(); + } + + @SuppressWarnings("unchecked") + public static ArrayList getSupportedTypes() { + return (ArrayList) sSupportedContentTypes.clone(); + } +} diff --git a/src/java/com/google/android/mms/InvalidHeaderValueException.java b/src/java/com/google/android/mms/InvalidHeaderValueException.java new file mode 100644 index 0000000..73d7832 --- /dev/null +++ b/src/java/com/google/android/mms/InvalidHeaderValueException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms; + +/** + * Thrown when an invalid header value was set. + */ +public class InvalidHeaderValueException extends MmsException { + private static final long serialVersionUID = -2053384496042052262L; + + /** + * Constructs an InvalidHeaderValueException with no detailed message. + */ + public InvalidHeaderValueException() { + super(); + } + + /** + * Constructs an InvalidHeaderValueException with the specified detailed message. + * + * @param message the detailed message. + */ + public InvalidHeaderValueException(String message) { + super(message); + } +} diff --git a/src/java/com/google/android/mms/MmsException.java b/src/java/com/google/android/mms/MmsException.java new file mode 100644 index 0000000..6ca0c7e --- /dev/null +++ b/src/java/com/google/android/mms/MmsException.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms; + +/** + * A generic exception that is thrown by the Mms client. + */ +public class MmsException extends Exception { + private static final long serialVersionUID = -7323249827281485390L; + + /** + * Creates a new MmsException. + */ + public MmsException() { + super(); + } + + /** + * Creates a new MmsException with the specified detail message. + * + * @param message the detail message. + */ + public MmsException(String message) { + super(message); + } + + /** + * Creates a new MmsException with the specified cause. + * + * @param cause the cause. + */ + public MmsException(Throwable cause) { + super(cause); + } + + /** + * Creates a new MmsException with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause. + */ + public MmsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/java/com/google/android/mms/package.html b/src/java/com/google/android/mms/package.html new file mode 100755 index 0000000..c9f96a6 --- /dev/null +++ b/src/java/com/google/android/mms/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/src/java/com/google/android/mms/pdu/AcknowledgeInd.java b/src/java/com/google/android/mms/pdu/AcknowledgeInd.java new file mode 100644 index 0000000..0e96c60 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/AcknowledgeInd.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Acknowledge.ind PDU. + */ +public class AcknowledgeInd extends GenericPdu { + /** + * Constructor, used when composing a M-Acknowledge.ind pdu. + * + * @param mmsVersion current viersion of mms + * @param transactionId the transaction-id value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if transactionId is null. + */ + public AcknowledgeInd(int mmsVersion, byte[] transactionId) + throws InvalidHeaderValueException { + super(); + + setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); + setMmsVersion(mmsVersion); + setTransactionId(transactionId); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + AcknowledgeInd(PduHeaders headers) { + super(headers); + } + + /** + * Get X-Mms-Report-Allowed field value. + * + * @return the X-Mms-Report-Allowed value + */ + public int getReportAllowed() { + return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); + } + + /** + * Set X-Mms-Report-Allowed field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReportAllowed(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } +} diff --git a/src/java/com/google/android/mms/pdu/Base64.java b/src/java/com/google/android/mms/pdu/Base64.java new file mode 100644 index 0000000..604bee0 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/Base64.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +public class Base64 { + /** + * Used to get the number of Quadruples. + */ + static final int FOURBYTE = 4; + + /** + * Byte used to pad output. + */ + static final byte PAD = (byte) '='; + + /** + * The base length. + */ + static final int BASELENGTH = 255; + + // Create arrays to hold the base64 characters + private static byte[] base64Alphabet = new byte[BASELENGTH]; + + // Populating the character arrays + static { + for (int i = 0; i < BASELENGTH; i++) { + base64Alphabet[i] = (byte) -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + } + + /** + * Decodes Base64 data into octects + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + // RFC 2045 requires that we discard ALL non-Base64 characters + base64Data = discardNonBase64(base64Data); + + // handle the edge case, so we don't have to worry about it later + if (base64Data.length == 0) { + return new byte[0]; + } + + int numberQuadruple = base64Data.length / FOURBYTE; + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; + + // Throw away anything not in base64Data + + int encodedIndex = 0; + int dataIndex = 0; + { + // this sizes the output array properly - rlw + int lastData = base64Data.length; + // ignore the '=' padding + while (base64Data[lastData - 1] == PAD) { + if (--lastData == 0) { + return new byte[0]; + } + } + decodedData = new byte[lastData - numberQuadruple]; + } + + for (int i = 0; i < numberQuadruple; i++) { + dataIndex = i * 4; + marker0 = base64Data[dataIndex + 2]; + marker1 = base64Data[dataIndex + 3]; + + b1 = base64Alphabet[base64Data[dataIndex]]; + b2 = base64Alphabet[base64Data[dataIndex + 1]]; + + if (marker0 != PAD && marker1 != PAD) { + //No PAD e.g 3cQl + b3 = base64Alphabet[marker0]; + b4 = base64Alphabet[marker1]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); + } else if (marker0 == PAD) { + //Two PAD e.g. 3c[Pad][Pad] + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + } else if (marker1 == PAD) { + //One PAD e.g. 3cQ[Pad] + b3 = base64Alphabet[marker0]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + } + encodedIndex += 3; + } + return decodedData; + } + + /** + * Check octect wheter it is a base64 encoding. + * + * @param octect to be checked byte + * @return ture if it is base64 encoding, false otherwise. + */ + private static boolean isBase64(byte octect) { + if (octect == PAD) { + return true; + } else if (base64Alphabet[octect] == -1) { + return false; + } else { + return true; + } + } + + /** + * Discards any characters outside of the base64 alphabet, per + * the requirements on page 25 of RFC 2045 - "Any characters + * outside of the base64 alphabet are to be ignored in base64 + * encoded data." + * + * @param data The base-64 encoded data to groom + * @return The data, less non-base64 characters (see RFC 2045). + */ + static byte[] discardNonBase64(byte[] data) { + byte groomedData[] = new byte[data.length]; + int bytesCopied = 0; + + for (int i = 0; i < data.length; i++) { + if (isBase64(data[i])) { + groomedData[bytesCopied++] = data[i]; + } + } + + byte packedData[] = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } +} diff --git a/src/java/com/google/android/mms/pdu/CharacterSets.java b/src/java/com/google/android/mms/pdu/CharacterSets.java new file mode 100644 index 0000000..4e22ca5 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/CharacterSets.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import java.io.UnsupportedEncodingException; +import java.util.HashMap; + +public class CharacterSets { + /** + * IANA assigned MIB enum numbers. + * + * From wap-230-wsp-20010705-a.pdf + * Any-charset = + * Equivalent to the special RFC2616 charset value "*" + */ + public static final int ANY_CHARSET = 0x00; + public static final int US_ASCII = 0x03; + public static final int ISO_8859_1 = 0x04; + public static final int ISO_8859_2 = 0x05; + public static final int ISO_8859_3 = 0x06; + public static final int ISO_8859_4 = 0x07; + public static final int ISO_8859_5 = 0x08; + public static final int ISO_8859_6 = 0x09; + public static final int ISO_8859_7 = 0x0A; + public static final int ISO_8859_8 = 0x0B; + public static final int ISO_8859_9 = 0x0C; + public static final int SHIFT_JIS = 0x11; + public static final int UTF_8 = 0x6A; + public static final int BIG5 = 0x07EA; + public static final int UCS2 = 0x03E8; + public static final int UTF_16 = 0x03F7; + + /** + * If the encoding of given data is unsupported, use UTF_8 to decode it. + */ + public static final int DEFAULT_CHARSET = UTF_8; + + /** + * Array of MIB enum numbers. + */ + private static final int[] MIBENUM_NUMBERS = { + ANY_CHARSET, + US_ASCII, + ISO_8859_1, + ISO_8859_2, + ISO_8859_3, + ISO_8859_4, + ISO_8859_5, + ISO_8859_6, + ISO_8859_7, + ISO_8859_8, + ISO_8859_9, + SHIFT_JIS, + UTF_8, + BIG5, + UCS2, + UTF_16, + }; + + /** + * The Well-known-charset Mime name. + */ + public static final String MIMENAME_ANY_CHARSET = "*"; + public static final String MIMENAME_US_ASCII = "us-ascii"; + public static final String MIMENAME_ISO_8859_1 = "iso-8859-1"; + public static final String MIMENAME_ISO_8859_2 = "iso-8859-2"; + public static final String MIMENAME_ISO_8859_3 = "iso-8859-3"; + public static final String MIMENAME_ISO_8859_4 = "iso-8859-4"; + public static final String MIMENAME_ISO_8859_5 = "iso-8859-5"; + public static final String MIMENAME_ISO_8859_6 = "iso-8859-6"; + public static final String MIMENAME_ISO_8859_7 = "iso-8859-7"; + public static final String MIMENAME_ISO_8859_8 = "iso-8859-8"; + public static final String MIMENAME_ISO_8859_9 = "iso-8859-9"; + public static final String MIMENAME_SHIFT_JIS = "shift_JIS"; + public static final String MIMENAME_UTF_8 = "utf-8"; + public static final String MIMENAME_BIG5 = "big5"; + public static final String MIMENAME_UCS2 = "iso-10646-ucs-2"; + public static final String MIMENAME_UTF_16 = "utf-16"; + + public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8; + + /** + * Array of the names of character sets. + */ + private static final String[] MIME_NAMES = { + MIMENAME_ANY_CHARSET, + MIMENAME_US_ASCII, + MIMENAME_ISO_8859_1, + MIMENAME_ISO_8859_2, + MIMENAME_ISO_8859_3, + MIMENAME_ISO_8859_4, + MIMENAME_ISO_8859_5, + MIMENAME_ISO_8859_6, + MIMENAME_ISO_8859_7, + MIMENAME_ISO_8859_8, + MIMENAME_ISO_8859_9, + MIMENAME_SHIFT_JIS, + MIMENAME_UTF_8, + MIMENAME_BIG5, + MIMENAME_UCS2, + MIMENAME_UTF_16, + }; + + private static final HashMap MIBENUM_TO_NAME_MAP; + private static final HashMap NAME_TO_MIBENUM_MAP; + + static { + // Create the HashMaps. + MIBENUM_TO_NAME_MAP = new HashMap(); + NAME_TO_MIBENUM_MAP = new HashMap(); + assert(MIBENUM_NUMBERS.length == MIME_NAMES.length); + int count = MIBENUM_NUMBERS.length - 1; + for(int i = 0; i <= count; i++) { + MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]); + NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]); + } + } + + private CharacterSets() {} // Non-instantiatable + + /** + * Map an MIBEnum number to the name of the charset which this number + * is assigned to by IANA. + * + * @param mibEnumValue An IANA assigned MIBEnum number. + * @return The name string of the charset. + * @throws UnsupportedEncodingException + */ + public static String getMimeName(int mibEnumValue) + throws UnsupportedEncodingException { + String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue); + if (name == null) { + throw new UnsupportedEncodingException(); + } + return name; + } + + /** + * Map a well-known charset name to its assigned MIBEnum number. + * + * @param mimeName The charset name. + * @return The MIBEnum number assigned by IANA for this charset. + * @throws UnsupportedEncodingException + */ + public static int getMibEnumValue(String mimeName) + throws UnsupportedEncodingException { + if(null == mimeName) { + return -1; + } + + Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName); + if (mibEnumValue == null) { + throw new UnsupportedEncodingException(); + } + return mibEnumValue; + } +} diff --git a/src/java/com/google/android/mms/pdu/DeliveryInd.java b/src/java/com/google/android/mms/pdu/DeliveryInd.java new file mode 100644 index 0000000..dafa8d1 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/DeliveryInd.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Delivery.Ind Pdu. + */ +public class DeliveryInd extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + public DeliveryInd() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + DeliveryInd(PduHeaders headers) { + super(headers); + } + + /** + * Get Date value. + * + * @return the value + */ + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value. + * + * @param value the value + */ + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value, should not be null + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get Status value. + * + * @return the value + */ + public int getStatus() { + return mPduHeaders.getOctet(PduHeaders.STATUS); + } + + /** + * Set Status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.STATUS); + } + + /** + * Get To value. + * + * @return the value + */ + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * set To value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public EncodedStringValue getStatusText() {return null;} + * public void setStatusText(EncodedStringValue value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/EncodedStringValue.java b/src/java/com/google/android/mms/pdu/EncodedStringValue.java new file mode 100644 index 0000000..9495c1c --- /dev/null +++ b/src/java/com/google/android/mms/pdu/EncodedStringValue.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; + +/** + * Encoded-string-value = Text-string | Value-length Char-set Text-string + */ +public class EncodedStringValue implements Cloneable { + private static final String TAG = "EncodedStringValue"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + /** + * The Char-set value. + */ + private int mCharacterSet; + + /** + * The Text-string value. + */ + private byte[] mData; + + /** + * Constructor. + * + * @param charset the Char-set value + * @param data the Text-string value + * @throws NullPointerException if Text-string value is null. + */ + public EncodedStringValue(int charset, byte[] data) { + // TODO: CharSet needs to be validated against MIBEnum. + if(null == data) { + throw new NullPointerException("EncodedStringValue: Text-string is null."); + } + + mCharacterSet = charset; + mData = new byte[data.length]; + System.arraycopy(data, 0, mData, 0, data.length); + } + + /** + * Constructor. + * + * @param data the Text-string value + * @throws NullPointerException if Text-string value is null. + */ + public EncodedStringValue(byte[] data) { + this(CharacterSets.DEFAULT_CHARSET, data); + } + + public EncodedStringValue(String data) { + try { + mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME); + mCharacterSet = CharacterSets.DEFAULT_CHARSET; + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Default encoding must be supported.", e); + } + } + + /** + * Get Char-set value. + * + * @return the value + */ + public int getCharacterSet() { + return mCharacterSet; + } + + /** + * Set Char-set value. + * + * @param charset the Char-set value + */ + public void setCharacterSet(int charset) { + // TODO: CharSet needs to be validated against MIBEnum. + mCharacterSet = charset; + } + + /** + * Get Text-string value. + * + * @return the value + */ + public byte[] getTextString() { + byte[] byteArray = new byte[mData.length]; + + System.arraycopy(mData, 0, byteArray, 0, mData.length); + return byteArray; + } + + /** + * Set Text-string value. + * + * @param textString the Text-string value + * @throws NullPointerException if Text-string value is null. + */ + public void setTextString(byte[] textString) { + if(null == textString) { + throw new NullPointerException("EncodedStringValue: Text-string is null."); + } + + mData = new byte[textString.length]; + System.arraycopy(textString, 0, mData, 0, textString.length); + } + + /** + * Convert this object to a {@link java.lang.String}. If the encoding of + * the EncodedStringValue is null or unsupported, it will be + * treated as iso-8859-1 encoding. + * + * @return The decoded String. + */ + public String getString() { + if (CharacterSets.ANY_CHARSET == mCharacterSet) { + return new String(mData); // system default encoding. + } else { + try { + String name = CharacterSets.getMimeName(mCharacterSet); + return new String(mData, name); + } catch (UnsupportedEncodingException e) { + if (LOCAL_LOGV) { + Log.v(TAG, e.getMessage(), e); + } + try { + return new String(mData, CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException _) { + return new String(mData); // system default encoding. + } + } + } + } + + /** + * Append to Text-string. + * + * @param textString the textString to append + * @throws NullPointerException if the text String is null + * or an IOException occured. + */ + public void appendTextString(byte[] textString) { + if(null == textString) { + throw new NullPointerException("Text-string is null."); + } + + if(null == mData) { + mData = new byte[textString.length]; + System.arraycopy(textString, 0, mData, 0, textString.length); + } else { + ByteArrayOutputStream newTextString = new ByteArrayOutputStream(); + try { + newTextString.write(mData); + newTextString.write(textString); + } catch (IOException e) { + e.printStackTrace(); + throw new NullPointerException( + "appendTextString: failed when write a new Text-string"); + } + + mData = newTextString.toByteArray(); + } + } + + /* + * (non-Javadoc) + * @see java.lang.Object#clone() + */ + @Override + public Object clone() throws CloneNotSupportedException { + super.clone(); + int len = mData.length; + byte[] dstBytes = new byte[len]; + System.arraycopy(mData, 0, dstBytes, 0, len); + + try { + return new EncodedStringValue(mCharacterSet, dstBytes); + } catch (Exception e) { + Log.e(TAG, "failed to clone an EncodedStringValue: " + this); + e.printStackTrace(); + throw new CloneNotSupportedException(e.getMessage()); + } + } + + /** + * Split this encoded string around matches of the given pattern. + * + * @param pattern the delimiting pattern + * @return the array of encoded strings computed by splitting this encoded + * string around matches of the given pattern + */ + public EncodedStringValue[] split(String pattern) { + String[] temp = getString().split(pattern); + EncodedStringValue[] ret = new EncodedStringValue[temp.length]; + for (int i = 0; i < ret.length; ++i) { + try { + ret[i] = new EncodedStringValue(mCharacterSet, + temp[i].getBytes()); + } catch (NullPointerException _) { + // Can't arrive here + return null; + } + } + return ret; + } + + /** + * Extract an EncodedStringValue[] from a given String. + */ + public static EncodedStringValue[] extract(String src) { + String[] values = src.split(";"); + + ArrayList list = new ArrayList(); + for (int i = 0; i < values.length; i++) { + if (values[i].length() > 0) { + list.add(new EncodedStringValue(values[i])); + } + } + + int len = list.size(); + if (len > 0) { + return list.toArray(new EncodedStringValue[len]); + } else { + return null; + } + } + + /** + * Concatenate an EncodedStringValue[] into a single String. + */ + public static String concat(EncodedStringValue[] addr) { + StringBuilder sb = new StringBuilder(); + int maxIndex = addr.length - 1; + for (int i = 0; i <= maxIndex; i++) { + sb.append(addr[i].getString()); + if (i < maxIndex) { + sb.append(";"); + } + } + + return sb.toString(); + } + + public static EncodedStringValue copy(EncodedStringValue value) { + if (value == null) { + return null; + } + + return new EncodedStringValue(value.mCharacterSet, value.mData); + } + + public static EncodedStringValue[] encodeStrings(String[] array) { + int count = array.length; + if (count > 0) { + EncodedStringValue[] encodedArray = new EncodedStringValue[count]; + for (int i = 0; i < count; i++) { + encodedArray[i] = new EncodedStringValue(array[i]); + } + return encodedArray; + } + return null; + } +} diff --git a/src/java/com/google/android/mms/pdu/GenericPdu.java b/src/java/com/google/android/mms/pdu/GenericPdu.java new file mode 100644 index 0000000..705de6a --- /dev/null +++ b/src/java/com/google/android/mms/pdu/GenericPdu.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +public class GenericPdu { + /** + * The headers of pdu. + */ + PduHeaders mPduHeaders = null; + + /** + * Constructor. + */ + public GenericPdu() { + mPduHeaders = new PduHeaders(); + } + + /** + * Constructor. + * + * @param headers Headers for this PDU. + */ + GenericPdu(PduHeaders headers) { + mPduHeaders = headers; + } + + /** + * Get the headers of this PDU. + * + * @return A PduHeaders of this PDU. + */ + PduHeaders getPduHeaders() { + return mPduHeaders; + } + + /** + * Get X-Mms-Message-Type field value. + * + * @return the X-Mms-Report-Allowed value + */ + public int getMessageType() { + return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE); + } + + /** + * Set X-Mms-Message-Type field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if field's value is not Octet. + */ + public void setMessageType(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE); + } + + /** + * Get X-Mms-MMS-Version field value. + * + * @return the X-Mms-MMS-Version value + */ + public int getMmsVersion() { + return mPduHeaders.getOctet(PduHeaders.MMS_VERSION); + } + + /** + * Set X-Mms-MMS-Version field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if field's value is not Octet. + */ + public void setMmsVersion(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } +} diff --git a/src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java b/src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java new file mode 100644 index 0000000..5a85e0e --- /dev/null +++ b/src/java/com/google/android/mms/pdu/MultimediaMessagePdu.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * Multimedia message PDU. + */ +public class MultimediaMessagePdu extends GenericPdu{ + /** + * The body. + */ + private PduBody mMessageBody; + + /** + * Constructor. + */ + public MultimediaMessagePdu() { + super(); + } + + /** + * Constructor. + * + * @param header the header of this PDU + * @param body the body of this PDU + */ + public MultimediaMessagePdu(PduHeaders header, PduBody body) { + super(header); + mMessageBody = body; + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + MultimediaMessagePdu(PduHeaders headers) { + super(headers); + } + + /** + * Get body of the PDU. + * + * @return the body + */ + public PduBody getBody() { + return mMessageBody; + } + + /** + * Set body of the PDU. + * + * @param body the body + */ + public void setBody(PduBody body) { + mMessageBody = body; + } + + /** + * Get subject. + * + * @return the value + */ + public EncodedStringValue getSubject() { + return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); + } + + /** + * Set subject. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setSubject(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); + } + + /** + * Get To value. + * + * @return the value + */ + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * Add a "To" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void addTo(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO); + } + + /** + * Get X-Mms-Priority value. + * + * @return the value + */ + public int getPriority() { + return mPduHeaders.getOctet(PduHeaders.PRIORITY); + } + + /** + * Set X-Mms-Priority value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setPriority(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.PRIORITY); + } + + /** + * Get Date value. + * + * @return the value + */ + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value in seconds. + * + * @param value the value + */ + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } +} diff --git a/src/java/com/google/android/mms/pdu/NotificationInd.java b/src/java/com/google/android/mms/pdu/NotificationInd.java new file mode 100644 index 0000000..c56cba6 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/NotificationInd.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Notification.ind PDU. + */ +public class NotificationInd extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + * RuntimeException if an undeclared error occurs. + */ + public NotificationInd() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + NotificationInd(PduHeaders headers) { + super(headers); + } + + /** + * Get X-Mms-Content-Class Value. + * + * @return the value + */ + public int getContentClass() { + return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS); + } + + /** + * Set X-Mms-Content-Class Value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + public void setContentClass(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS); + } + + /** + * Get X-Mms-Content-Location value. + * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf: + * Content-location-value = Uri-value + * + * @return the value + */ + public byte[] getContentLocation() { + return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION); + } + + /** + * Set X-Mms-Content-Location value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + public void setContentLocation(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION); + } + + /** + * Get X-Mms-Expiry value. + * + * Expiry-value = Value-length + * (Absolute-token Date-value | Relative-token Delta-seconds-value) + * + * @return the value + */ + public long getExpiry() { + return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); + } + + /** + * Set X-Mms-Expiry value. + * + * @param value the value + * @throws RuntimeException if an undeclared error occurs. + */ + public void setExpiry(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } + + /** + * Get X-Mms-Message-Class value. + * Message-class-value = Class-identifier | Token-text + * Class-identifier = Personal | Advertisement | Informational | Auto + * + * @return the value + */ + public byte[] getMessageClass() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); + } + + /** + * Set X-Mms-Message-Class value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + public void setMessageClass(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); + } + + /** + * Get X-Mms-Message-Size value. + * Message-size-value = Long-integer + * + * @return the value + */ + public long getMessageSize() { + return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); + } + + /** + * Set X-Mms-Message-Size value. + * + * @param value the value + * @throws RuntimeException if an undeclared error occurs. + */ + public void setMessageSize(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); + } + + /** + * Get subject. + * + * @return the value + */ + public EncodedStringValue getSubject() { + return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT); + } + + /** + * Set subject. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + public void setSubject(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT); + } + + /** + * Get X-Mms-Transaction-Id. + * + * @return the value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /** + * Get X-Mms-Delivery-Report Value. + * + * @return the value + */ + public int getDeliveryReport() { + return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); + } + + /** + * Set X-Mms-Delivery-Report Value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + public void setDeliveryReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte getDrmContent() {return 0x00;} + * public void setDrmContent(byte value) {} + * + * public byte getDistributionIndicator() {return 0x00;} + * public void setDistributionIndicator(byte value) {} + * + * public ElementDescriptorValue getElementDescriptor() {return null;} + * public void getElementDescriptor(ElementDescriptorValue value) {} + * + * public byte getPriority() {return 0x00;} + * public void setPriority(byte value) {} + * + * public byte getRecommendedRetrievalMode() {return 0x00;} + * public void setRecommendedRetrievalMode(byte value) {} + * + * public byte getRecommendedRetrievalModeText() {return 0x00;} + * public void setRecommendedRetrievalModeText(byte value) {} + * + * public byte[] getReplaceId() {return 0x00;} + * public void setReplaceId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getReplyCharging() {return 0x00;} + * public void setReplyCharging(byte value) {} + * + * public byte getReplyChargingDeadline() {return 0x00;} + * public void setReplyChargingDeadline(byte value) {} + * + * public byte[] getReplyChargingId() {return 0x00;} + * public void setReplyChargingId(byte[] value) {} + * + * public long getReplyChargingSize() {return 0;} + * public void setReplyChargingSize(long value) {} + * + * public byte getStored() {return 0x00;} + * public void setStored(byte value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/NotifyRespInd.java b/src/java/com/google/android/mms/pdu/NotifyRespInd.java new file mode 100644 index 0000000..2cc2fce --- /dev/null +++ b/src/java/com/google/android/mms/pdu/NotifyRespInd.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-NofifyResp.ind PDU. + */ +public class NotifyRespInd extends GenericPdu { + /** + * Constructor, used when composing a M-NotifyResp.ind pdu. + * + * @param mmsVersion current version of mms + * @param transactionId the transaction-id value + * @param status the status value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if transactionId is null. + * RuntimeException if an undeclared error occurs. + */ + public NotifyRespInd(int mmsVersion, + byte[] transactionId, + int status) throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); + setMmsVersion(mmsVersion); + setTransactionId(transactionId); + setStatus(status); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + NotifyRespInd(PduHeaders headers) { + super(headers); + } + + /** + * Get X-Mms-Report-Allowed field value. + * + * @return the X-Mms-Report-Allowed value + */ + public int getReportAllowed() { + return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED); + } + + /** + * Set X-Mms-Report-Allowed field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + public void setReportAllowed(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED); + } + + /** + * Set X-Mms-Status field value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + * RuntimeException if an undeclared error occurs. + */ + public void setStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.STATUS); + } + + /** + * GetX-Mms-Status field value. + * + * @return the X-Mms-Status value + */ + public int getStatus() { + return mPduHeaders.getOctet(PduHeaders.STATUS); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + * RuntimeException if an undeclared error occurs. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } +} diff --git a/src/java/com/google/android/mms/pdu/PduBody.java b/src/java/com/google/android/mms/pdu/PduBody.java new file mode 100644 index 0000000..fa0416c --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduBody.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +public class PduBody { + private Vector mParts = null; + + private Map mPartMapByContentId = null; + private Map mPartMapByContentLocation = null; + private Map mPartMapByName = null; + private Map mPartMapByFileName = null; + + /** + * Constructor. + */ + public PduBody() { + mParts = new Vector(); + + mPartMapByContentId = new HashMap(); + mPartMapByContentLocation = new HashMap(); + mPartMapByName = new HashMap(); + mPartMapByFileName = new HashMap(); + } + + private void putPartToMaps(PduPart part) { + // Put part to mPartMapByContentId. + byte[] contentId = part.getContentId(); + if(null != contentId) { + mPartMapByContentId.put(new String(contentId), part); + } + + // Put part to mPartMapByContentLocation. + byte[] contentLocation = part.getContentLocation(); + if(null != contentLocation) { + String clc = new String(contentLocation); + mPartMapByContentLocation.put(clc, part); + } + + // Put part to mPartMapByName. + byte[] name = part.getName(); + if(null != name) { + String clc = new String(name); + mPartMapByName.put(clc, part); + } + + // Put part to mPartMapByFileName. + byte[] fileName = part.getFilename(); + if(null != fileName) { + String clc = new String(fileName); + mPartMapByFileName.put(clc, part); + } + } + + /** + * Appends the specified part to the end of this body. + * + * @param part part to be appended + * @return true when success, false when fail + * @throws NullPointerException when part is null + */ + public boolean addPart(PduPart part) { + if(null == part) { + throw new NullPointerException(); + } + + putPartToMaps(part); + return mParts.add(part); + } + + /** + * Inserts the specified part at the specified position. + * + * @param index index at which the specified part is to be inserted + * @param part part to be inserted + * @throws NullPointerException when part is null + */ + public void addPart(int index, PduPart part) { + if(null == part) { + throw new NullPointerException(); + } + + putPartToMaps(part); + mParts.add(index, part); + } + + /** + * Removes the part at the specified position. + * + * @param index index of the part to return + * @return part at the specified index + */ + public PduPart removePart(int index) { + return mParts.remove(index); + } + + /** + * Remove all of the parts. + */ + public void removeAll() { + mParts.clear(); + } + + /** + * Get the part at the specified position. + * + * @param index index of the part to return + * @return part at the specified index + */ + public PduPart getPart(int index) { + return mParts.get(index); + } + + /** + * Get the index of the specified part. + * + * @param part the part object + * @return index the index of the first occurrence of the part in this body + */ + public int getPartIndex(PduPart part) { + return mParts.indexOf(part); + } + + /** + * Get the number of parts. + * + * @return the number of parts + */ + public int getPartsNum() { + return mParts.size(); + } + + /** + * Get pdu part by content id. + * + * @param cid the value of content id. + * @return the pdu part. + */ + public PduPart getPartByContentId(String cid) { + return mPartMapByContentId.get(cid); + } + + /** + * Get pdu part by Content-Location. Content-Location of part is + * the same as filename and name(param of content-type). + * + * @param fileName the value of filename. + * @return the pdu part. + */ + public PduPart getPartByContentLocation(String contentLocation) { + return mPartMapByContentLocation.get(contentLocation); + } + + /** + * Get pdu part by name. + * + * @param fileName the value of filename. + * @return the pdu part. + */ + public PduPart getPartByName(String name) { + return mPartMapByName.get(name); + } + + /** + * Get pdu part by filename. + * + * @param fileName the value of filename. + * @return the pdu part. + */ + public PduPart getPartByFileName(String filename) { + return mPartMapByFileName.get(filename); + } +} diff --git a/src/java/com/google/android/mms/pdu/PduComposer.java b/src/java/com/google/android/mms/pdu/PduComposer.java new file mode 100644 index 0000000..d426f89 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduComposer.java @@ -0,0 +1,1179 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.content.ContentResolver; +import android.content.Context; +import android.util.Log; +import android.text.TextUtils; + +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; + +public class PduComposer { + /** + * Address type. + */ + static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1; + static private final int PDU_EMAIL_ADDRESS_TYPE = 2; + static private final int PDU_IPV4_ADDRESS_TYPE = 3; + static private final int PDU_IPV6_ADDRESS_TYPE = 4; + static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5; + + /** + * Address regular expression string. + */ + static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+"; + static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" + + "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}"; + static final String REGEXP_IPV6_ADDRESS_TYPE = + "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + + "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" + + "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}"; + static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" + + "[0-9]{1,3}\\.{1}[0-9]{1,3}"; + + /** + * The postfix strings of address. + */ + static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN"; + static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4"; + static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6"; + + /** + * Error values. + */ + static private final int PDU_COMPOSE_SUCCESS = 0; + static private final int PDU_COMPOSE_CONTENT_ERROR = 1; + static private final int PDU_COMPOSE_FIELD_NOT_SET = 2; + static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3; + + /** + * WAP values defined in WSP spec. + */ + static private final int QUOTED_STRING_FLAG = 34; + static private final int END_STRING_FLAG = 0; + static private final int LENGTH_QUOTE = 31; + static private final int TEXT_MAX = 127; + static private final int SHORT_INTEGER_MAX = 127; + static private final int LONG_INTEGER_LENGTH_MAX = 8; + + /** + * Block size when read data from InputStream. + */ + static private final int PDU_COMPOSER_BLOCK_SIZE = 1024; + + /** + * The output message. + */ + protected ByteArrayOutputStream mMessage = null; + + /** + * The PDU. + */ + private GenericPdu mPdu = null; + + /** + * Current visiting position of the mMessage. + */ + protected int mPosition = 0; + + /** + * Message compose buffer stack. + */ + private BufferStack mStack = null; + + /** + * Content resolver. + */ + private final ContentResolver mResolver; + + /** + * Header of this pdu. + */ + private PduHeaders mPduHeader = null; + + /** + * Map of all content type + */ + private static HashMap mContentTypeMap = null; + + static { + mContentTypeMap = new HashMap(); + + int i; + for (i = 0; i < PduContentTypes.contentTypes.length; i++) { + mContentTypeMap.put(PduContentTypes.contentTypes[i], i); + } + } + + /** + * Constructor. + * + * @param context the context + * @param pdu the pdu to be composed + */ + public PduComposer(Context context, GenericPdu pdu) { + mPdu = pdu; + mResolver = context.getContentResolver(); + mPduHeader = pdu.getPduHeaders(); + mStack = new BufferStack(); + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + /** + * Make the message. No need to check whether mandatory fields are set, + * because the constructors of outgoing pdus are taking care of this. + * + * @return OutputStream of maked message. Return null if + * the PDU is invalid. + */ + public byte[] make() { + // Get Message-type. + int type = mPdu.getMessageType(); + + /* make the message */ + switch (type) { + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + if (makeSendReqPdu() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + if (makeAckInd() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) { + return null; + } + break; + default: + return null; + } + + return mMessage.toByteArray(); + } + + /** + * Copy buf to mMessage. + */ + protected void arraycopy(byte[] buf, int pos, int length) { + mMessage.write(buf, pos, length); + mPosition = mPosition + length; + } + + /** + * Append a byte to mMessage. + */ + protected void append(int value) { + mMessage.write(value); + mPosition ++; + } + + /** + * Append short integer value to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendShortInteger(int value) { + /* + * From WAP-230-WSP-20010705-a: + * Short-integer = OCTET + * ; Integers in range 0-127 shall be encoded as a one octet value + * ; with the most significant bit set to one (1xxx xxxx) and with + * ; the value in the remaining least significant bits. + * In our implementation, only low 7 bits are stored and otherwise + * bits are ignored. + */ + append((value | 0x80) & 0xff); + } + + /** + * Append an octet number between 128 and 255 into mMessage. + * NOTE: + * A value between 0 and 127 should be appended by using appendShortInteger. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendOctet(int number) { + append(number); + } + + /** + * Append a short length into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendShortLength(int value) { + /* + * From WAP-230-WSP-20010705-a: + * Short-length = + */ + append(value); + } + + /** + * Append long integer into mMessage. it's used for really long integers. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendLongInteger(long longInt) { + /* + * From WAP-230-WSP-20010705-a: + * Long-integer = Short-length Multi-octet-integer + * ; The Short-length indicates the length of the Multi-octet-integer + * Multi-octet-integer = 1*30 OCTET + * ; The content octets shall be an unsigned integer value with the + * ; most significant octet encoded first (big-endian representation). + * ; The minimum number of octets must be used to encode the value. + */ + int size; + long temp = longInt; + + // Count the length of the long integer. + for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) { + temp = (temp >>> 8); + } + + // Set Length. + appendShortLength(size); + + // Count and set the long integer. + int i; + int shift = (size -1) * 8; + + for (i = 0; i < size; i++) { + append((int)((longInt >>> shift) & 0xff)); + shift = shift - 8; + } + } + + /** + * Append text string into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendTextString(byte[] text) { + /* + * From WAP-230-WSP-20010705-a: + * Text-string = [Quote] *TEXT End-of-string + * ; If the first character in the TEXT is in the range of 128-255, + * ; a Quote character must precede it. Otherwise the Quote character + * ;must be omitted. The Quote is not part of the contents. + */ + if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255 + append(TEXT_MAX); + } + + arraycopy(text, 0, text.length); + append(0); + } + + /** + * Append text string into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendTextString(String str) { + /* + * From WAP-230-WSP-20010705-a: + * Text-string = [Quote] *TEXT End-of-string + * ; If the first character in the TEXT is in the range of 128-255, + * ; a Quote character must precede it. Otherwise the Quote character + * ;must be omitted. The Quote is not part of the contents. + */ + appendTextString(str.getBytes()); + } + + /** + * Append encoded string value to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendEncodedString(EncodedStringValue enStr) { + /* + * From OMA-TS-MMS-ENC-V1_3-20050927-C: + * Encoded-string-value = Text-string | Value-length Char-set Text-string + */ + assert(enStr != null); + + int charset = enStr.getCharacterSet(); + byte[] textString = enStr.getTextString(); + if (null == textString) { + return; + } + + /* + * In the implementation of EncodedStringValue, the charset field will + * never be 0. It will always be composed as + * Encoded-string-value = Value-length Char-set Text-string + */ + mStack.newbuf(); + PositionMarker start = mStack.mark(); + + appendShortInteger(charset); + appendTextString(textString); + + int len = start.getLength(); + mStack.pop(); + appendValueLength(len); + mStack.copy(); + } + + /** + * Append uintvar integer into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendUintvarInteger(long value) { + /* + * From WAP-230-WSP-20010705-a: + * To encode a large unsigned integer, split it into 7-bit fragments + * and place them in the payloads of multiple octets. The most significant + * bits are placed in the first octets with the least significant bits + * ending up in the last octet. All octets MUST set the Continue bit to 1 + * except the last octet, which MUST set the Continue bit to 0. + */ + int i; + long max = SHORT_INTEGER_MAX; + + for (i = 0; i < 5; i++) { + if (value < max) { + break; + } + + max = (max << 7) | 0x7fl; + } + + while(i > 0) { + long temp = value >>> (i * 7); + temp = temp & 0x7f; + + append((int)((temp | 0x80) & 0xff)); + + i--; + } + + append((int)(value & 0x7f)); + } + + /** + * Append date value into mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendDateValue(long date) { + /* + * From OMA-TS-MMS-ENC-V1_3-20050927-C: + * Date-value = Long-integer + */ + appendLongInteger(date); + } + + /** + * Append value length to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendValueLength(long value) { + /* + * From WAP-230-WSP-20010705-a: + * Value-length = Short-length | (Length-quote Length) + * ; Value length is used to indicate the length of the value to follow + * Short-length = + * Length-quote = + * Length = Uintvar-integer + */ + if (value < LENGTH_QUOTE) { + appendShortLength((int) value); + return; + } + + append(LENGTH_QUOTE); + appendUintvarInteger(value); + } + + /** + * Append quoted string to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendQuotedString(byte[] text) { + /* + * From WAP-230-WSP-20010705-a: + * Quoted-string = *TEXT End-of-string + * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing + * ;quotation-marks <"> removed. + */ + append(QUOTED_STRING_FLAG); + arraycopy(text, 0, text.length); + append(END_STRING_FLAG); + } + + /** + * Append quoted string to mMessage. + * This implementation doesn't check the validity of parameter, since it + * assumes that the values are validated in the GenericPdu setter methods. + */ + protected void appendQuotedString(String str) { + /* + * From WAP-230-WSP-20010705-a: + * Quoted-string = *TEXT End-of-string + * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing + * ;quotation-marks <"> removed. + */ + appendQuotedString(str.getBytes()); + } + + private EncodedStringValue appendAddressType(EncodedStringValue address) { + EncodedStringValue temp = null; + + try { + int addressType = checkAddressType(address.getString()); + temp = EncodedStringValue.copy(address); + if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) { + // Phone number. + temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes()); + } else if (PDU_IPV4_ADDRESS_TYPE == addressType) { + // Ipv4 address. + temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes()); + } else if (PDU_IPV6_ADDRESS_TYPE == addressType) { + // Ipv6 address. + temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes()); + } + } catch (NullPointerException e) { + return null; + } + + return temp; + } + + /** + * Append header to mMessage. + */ + private int appendHeader(int field) { + switch (field) { + case PduHeaders.MMS_VERSION: + appendOctet(field); + + int version = mPduHeader.getOctet(field); + if (0 == version) { + appendShortInteger(PduHeaders.CURRENT_MMS_VERSION); + } else { + appendShortInteger(version); + } + + break; + + case PduHeaders.MESSAGE_ID: + case PduHeaders.TRANSACTION_ID: + byte[] textString = mPduHeader.getTextString(field); + if (null == textString) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendTextString(textString); + break; + + case PduHeaders.TO: + case PduHeaders.BCC: + case PduHeaders.CC: + EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field); + + if (null == addr) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + EncodedStringValue temp; + for (int i = 0; i < addr.length; i++) { + temp = appendAddressType(addr[i]); + if (temp == null) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + appendOctet(field); + appendEncodedString(temp); + } + break; + + case PduHeaders.FROM: + // Value-length (Address-present-token Encoded-string-value | Insert-address-token) + appendOctet(field); + + EncodedStringValue from = mPduHeader.getEncodedStringValue(field); + if ((from == null) + || TextUtils.isEmpty(from.getString()) + || new String(from.getTextString()).equals( + PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) { + // Length of from = 1 + append(1); + // Insert-address-token = + append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN); + } else { + mStack.newbuf(); + PositionMarker fstart = mStack.mark(); + + // Address-present-token = + append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN); + + temp = appendAddressType(from); + if (temp == null) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + appendEncodedString(temp); + + int flen = fstart.getLength(); + mStack.pop(); + appendValueLength(flen); + mStack.copy(); + } + break; + + case PduHeaders.READ_STATUS: + case PduHeaders.STATUS: + case PduHeaders.REPORT_ALLOWED: + case PduHeaders.PRIORITY: + case PduHeaders.DELIVERY_REPORT: + case PduHeaders.READ_REPORT: + int octet = mPduHeader.getOctet(field); + if (0 == octet) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendOctet(octet); + break; + + case PduHeaders.DATE: + long date = mPduHeader.getLongInteger(field); + if (-1 == date) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendDateValue(date); + break; + + case PduHeaders.SUBJECT: + EncodedStringValue enString = + mPduHeader.getEncodedStringValue(field); + if (null == enString) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + appendEncodedString(enString); + break; + + case PduHeaders.MESSAGE_CLASS: + byte[] messageClass = mPduHeader.getTextString(field); + if (null == messageClass) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT); + } else if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_AUTO); + } else if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL); + } else if (Arrays.equals(messageClass, + PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) { + appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL); + } else { + appendTextString(messageClass); + } + break; + + case PduHeaders.EXPIRY: + long expiry = mPduHeader.getLongInteger(field); + if (-1 == expiry) { + return PDU_COMPOSE_FIELD_NOT_SET; + } + + appendOctet(field); + + mStack.newbuf(); + PositionMarker expiryStart = mStack.mark(); + + append(PduHeaders.VALUE_RELATIVE_TOKEN); + appendLongInteger(expiry); + + int expiryLength = expiryStart.getLength(); + mStack.pop(); + appendValueLength(expiryLength); + mStack.copy(); + break; + + default: + return PDU_COMPOSE_FIELD_NOT_SUPPORTED; + } + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make ReadRec.Ind. + */ + private int makeReadRecInd() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND); + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Message-ID + if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // To + if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // From + if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Date Optional + appendHeader(PduHeaders.DATE); + + // X-Mms-Read-Status + if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Applic-ID Optional(not support) + // X-Mms-Reply-Applic-ID Optional(not support) + // X-Mms-Aux-Applic-Info Optional(not support) + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make NotifyResp.Ind. + */ + private int makeNotifyResp() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND); + + // X-Mms-Transaction-ID + if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Status + if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Report-Allowed Optional (not support) + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make Acknowledge.Ind. + */ + private int makeAckInd() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND); + + // X-Mms-Transaction-ID + if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // X-Mms-Report-Allowed Optional + appendHeader(PduHeaders.REPORT_ALLOWED); + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Make Send.req. + */ + private int makeSendReqPdu() { + if (mMessage == null) { + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + // X-Mms-Message-Type + appendOctet(PduHeaders.MESSAGE_TYPE); + appendOctet(PduHeaders.MESSAGE_TYPE_SEND_REQ); + + // X-Mms-Transaction-ID + appendOctet(PduHeaders.TRANSACTION_ID); + + byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID); + if (trid == null) { + // Transaction-ID should be set(by Transaction) before make(). + throw new IllegalArgumentException("Transaction-ID is null."); + } + appendTextString(trid); + + // X-Mms-MMS-Version + if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Date Date-value Optional. + appendHeader(PduHeaders.DATE); + + // From + if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + boolean recipient = false; + + // To + if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) { + recipient = true; + } + + // Cc + if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) { + recipient = true; + } + + // Bcc + if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) { + recipient = true; + } + + // Need at least one of "cc", "bcc" and "to". + if (false == recipient) { + return PDU_COMPOSE_CONTENT_ERROR; + } + + // Subject Optional + appendHeader(PduHeaders.SUBJECT); + + // X-Mms-Message-Class Optional + // Message-class-value = Class-identifier | Token-text + appendHeader(PduHeaders.MESSAGE_CLASS); + + // X-Mms-Expiry Optional + appendHeader(PduHeaders.EXPIRY); + + // X-Mms-Priority Optional + appendHeader(PduHeaders.PRIORITY); + + // X-Mms-Delivery-Report Optional + appendHeader(PduHeaders.DELIVERY_REPORT); + + // X-Mms-Read-Report Optional + appendHeader(PduHeaders.READ_REPORT); + + // Content-Type + appendOctet(PduHeaders.CONTENT_TYPE); + + // Message body + return makeMessageBody(); + } + + /** + * Make message body. + */ + private int makeMessageBody() { + // 1. add body informations + mStack.newbuf(); // Switching buffer because we need to + + PositionMarker ctStart = mStack.mark(); + + // This contentTypeIdentifier should be used for type of attachment... + String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE)); + Integer contentTypeIdentifier = mContentTypeMap.get(contentType); + if (contentTypeIdentifier == null) { + // content type is mandatory + return PDU_COMPOSE_CONTENT_ERROR; + } + + appendShortInteger(contentTypeIdentifier.intValue()); + + // content-type parameter: start + PduBody body = ((SendReq) mPdu).getBody(); + if (null == body || body.getPartsNum() == 0) { + // empty message + appendUintvarInteger(0); + mStack.pop(); + mStack.copy(); + return PDU_COMPOSE_SUCCESS; + } + + PduPart part; + try { + part = body.getPart(0); + + byte[] start = part.getContentId(); + if (start != null) { + appendOctet(PduPart.P_DEP_START); + if (('<' == start[0]) && ('>' == start[start.length - 1])) { + appendTextString(start); + } else { + appendTextString("<" + new String(start) + ">"); + } + } + + // content-type parameter: type + appendOctet(PduPart.P_CT_MR_TYPE); + appendTextString(part.getContentType()); + } + catch (ArrayIndexOutOfBoundsException e){ + e.printStackTrace(); + } + + int ctLength = ctStart.getLength(); + mStack.pop(); + appendValueLength(ctLength); + mStack.copy(); + + // 3. add content + int partNum = body.getPartsNum(); + appendUintvarInteger(partNum); + for (int i = 0; i < partNum; i++) { + part = body.getPart(i); + mStack.newbuf(); // Leaving space for header lengh and data length + PositionMarker attachment = mStack.mark(); + + mStack.newbuf(); // Leaving space for Content-Type length + PositionMarker contentTypeBegin = mStack.mark(); + + byte[] partContentType = part.getContentType(); + + if (partContentType == null) { + // content type is mandatory + return PDU_COMPOSE_CONTENT_ERROR; + } + + // content-type value + Integer partContentTypeIdentifier = + mContentTypeMap.get(new String(partContentType)); + if (partContentTypeIdentifier == null) { + appendTextString(partContentType); + } else { + appendShortInteger(partContentTypeIdentifier.intValue()); + } + + /* Content-type parameter : name. + * The value of name, filename, content-location is the same. + * Just one of them is enough for this PDU. + */ + byte[] name = part.getName(); + + if (null == name) { + name = part.getFilename(); + + if (null == name) { + name = part.getContentLocation(); + + if (null == name) { + /* at lease one of name, filename, Content-location + * should be available. + */ + return PDU_COMPOSE_CONTENT_ERROR; + } + } + } + appendOctet(PduPart.P_DEP_NAME); + appendTextString(name); + + // content-type parameter : charset + int charset = part.getCharset(); + if (charset != 0) { + appendOctet(PduPart.P_CHARSET); + appendShortInteger(charset); + } + + int contentTypeLength = contentTypeBegin.getLength(); + mStack.pop(); + appendValueLength(contentTypeLength); + mStack.copy(); + + // content id + byte[] contentId = part.getContentId(); + + if (null != contentId) { + appendOctet(PduPart.P_CONTENT_ID); + if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) { + appendQuotedString(contentId); + } else { + appendQuotedString("<" + new String(contentId) + ">"); + } + } + + // content-location + byte[] contentLocation = part.getContentLocation(); + if (null != contentLocation) { + appendOctet(PduPart.P_CONTENT_LOCATION); + appendTextString(contentLocation); + } + + // content + int headerLength = attachment.getLength(); + + int dataLength = 0; // Just for safety... + byte[] partData = part.getData(); + + if (partData != null) { + arraycopy(partData, 0, partData.length); + dataLength = partData.length; + } else { + InputStream cr; + try { + byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE]; + cr = mResolver.openInputStream(part.getDataUri()); + int len = 0; + while ((len = cr.read(buffer)) != -1) { + mMessage.write(buffer, 0, len); + mPosition += len; + dataLength += len; + } + } catch (FileNotFoundException e) { + return PDU_COMPOSE_CONTENT_ERROR; + } catch (IOException e) { + return PDU_COMPOSE_CONTENT_ERROR; + } catch (RuntimeException e) { + return PDU_COMPOSE_CONTENT_ERROR; + } + } + + if (dataLength != (attachment.getLength() - headerLength)) { + throw new RuntimeException("BUG: Length sanity check failed"); + } + + mStack.pop(); + appendUintvarInteger(headerLength); + appendUintvarInteger(dataLength); + mStack.copy(); + } + + return PDU_COMPOSE_SUCCESS; + } + + /** + * Record current message informations. + */ + static private class LengthRecordNode { + ByteArrayOutputStream currentMessage = null; + public int currentPosition = 0; + + public LengthRecordNode next = null; + } + + /** + * Mark current message position and stact size. + */ + private class PositionMarker { + private int c_pos; // Current position + private int currentStackSize; // Current stack size + + int getLength() { + // If these assert fails, likely that you are finding the + // size of buffer that is deep in BufferStack you can only + // find the length of the buffer that is on top + if (currentStackSize != mStack.stackSize) { + throw new RuntimeException("BUG: Invalid call to getLength()"); + } + + return mPosition - c_pos; + } + } + + /** + * This implementation can be OPTIMIZED to use only + * 2 buffers. This optimization involves changing BufferStack + * only... Its usage (interface) will not change. + */ + private class BufferStack { + private LengthRecordNode stack = null; + private LengthRecordNode toCopy = null; + + int stackSize = 0; + + /** + * Create a new message buffer and push it into the stack. + */ + void newbuf() { + // You can't create a new buff when toCopy != null + // That is after calling pop() and before calling copy() + // If you do, it is a bug + if (toCopy != null) { + throw new RuntimeException("BUG: Invalid newbuf() before copy()"); + } + + LengthRecordNode temp = new LengthRecordNode(); + + temp.currentMessage = mMessage; + temp.currentPosition = mPosition; + + temp.next = stack; + stack = temp; + + stackSize = stackSize + 1; + + mMessage = new ByteArrayOutputStream(); + mPosition = 0; + } + + /** + * Pop the message before and record current message in the stack. + */ + void pop() { + ByteArrayOutputStream currentMessage = mMessage; + int currentPosition = mPosition; + + mMessage = stack.currentMessage; + mPosition = stack.currentPosition; + + toCopy = stack; + // Re using the top element of the stack to avoid memory allocation + + stack = stack.next; + stackSize = stackSize - 1; + + toCopy.currentMessage = currentMessage; + toCopy.currentPosition = currentPosition; + } + + /** + * Append current message to the message before. + */ + void copy() { + arraycopy(toCopy.currentMessage.toByteArray(), 0, + toCopy.currentPosition); + + toCopy = null; + } + + /** + * Mark current message position + */ + PositionMarker mark() { + PositionMarker m = new PositionMarker(); + + m.c_pos = mPosition; + m.currentStackSize = stackSize; + + return m; + } + } + + /** + * Check address type. + * + * @param address address string without the postfix stinng type, + * such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4" + * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number, + * PDU_EMAIL_ADDRESS_TYPE if it is email address, + * PDU_IPV4_ADDRESS_TYPE if it is ipv4 address, + * PDU_IPV6_ADDRESS_TYPE if it is ipv6 address, + * PDU_UNKNOWN_ADDRESS_TYPE if it is unknown. + */ + protected static int checkAddressType(String address) { + /** + * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8. + * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode) + * e-mail = mailbox; to the definition of mailbox as described in + * section 3.4 of [RFC2822], but excluding the + * obsolete definitions as indicated by the "obs-" prefix. + * device-address = ( global-phone-number "/TYPE=PLMN" ) + * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" ) + * / ( escaped-value "/TYPE=" address-type ) + * + * global-phone-number = ["+"] 1*( DIGIT / written-sep ) + * written-sep =("-"/".") + * + * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value + * + * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373 + */ + + if (null == address) { + return PDU_UNKNOWN_ADDRESS_TYPE; + } + + if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) { + // Ipv4 address. + return PDU_IPV4_ADDRESS_TYPE; + }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) { + // Phone number. + return PDU_PHONE_NUMBER_ADDRESS_TYPE; + } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) { + // Email address. + return PDU_EMAIL_ADDRESS_TYPE; + } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) { + // Ipv6 address. + return PDU_IPV6_ADDRESS_TYPE; + } else { + // Unknown address. + return PDU_UNKNOWN_ADDRESS_TYPE; + } + } +} diff --git a/src/java/com/google/android/mms/pdu/PduContentTypes.java b/src/java/com/google/android/mms/pdu/PduContentTypes.java new file mode 100644 index 0000000..7799e0e --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduContentTypes.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +public class PduContentTypes { + /** + * All content types. From: + * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm + */ + static final String[] contentTypes = { + "*/*", /* 0x00 */ + "text/*", /* 0x01 */ + "text/html", /* 0x02 */ + "text/plain", /* 0x03 */ + "text/x-hdml", /* 0x04 */ + "text/x-ttml", /* 0x05 */ + "text/x-vCalendar", /* 0x06 */ + "text/x-vCard", /* 0x07 */ + "text/vnd.wap.wml", /* 0x08 */ + "text/vnd.wap.wmlscript", /* 0x09 */ + "text/vnd.wap.wta-event", /* 0x0A */ + "multipart/*", /* 0x0B */ + "multipart/mixed", /* 0x0C */ + "multipart/form-data", /* 0x0D */ + "multipart/byterantes", /* 0x0E */ + "multipart/alternative", /* 0x0F */ + "application/*", /* 0x10 */ + "application/java-vm", /* 0x11 */ + "application/x-www-form-urlencoded", /* 0x12 */ + "application/x-hdmlc", /* 0x13 */ + "application/vnd.wap.wmlc", /* 0x14 */ + "application/vnd.wap.wmlscriptc", /* 0x15 */ + "application/vnd.wap.wta-eventc", /* 0x16 */ + "application/vnd.wap.uaprof", /* 0x17 */ + "application/vnd.wap.wtls-ca-certificate", /* 0x18 */ + "application/vnd.wap.wtls-user-certificate", /* 0x19 */ + "application/x-x509-ca-cert", /* 0x1A */ + "application/x-x509-user-cert", /* 0x1B */ + "image/*", /* 0x1C */ + "image/gif", /* 0x1D */ + "image/jpeg", /* 0x1E */ + "image/tiff", /* 0x1F */ + "image/png", /* 0x20 */ + "image/vnd.wap.wbmp", /* 0x21 */ + "application/vnd.wap.multipart.*", /* 0x22 */ + "application/vnd.wap.multipart.mixed", /* 0x23 */ + "application/vnd.wap.multipart.form-data", /* 0x24 */ + "application/vnd.wap.multipart.byteranges", /* 0x25 */ + "application/vnd.wap.multipart.alternative", /* 0x26 */ + "application/xml", /* 0x27 */ + "text/xml", /* 0x28 */ + "application/vnd.wap.wbxml", /* 0x29 */ + "application/x-x968-cross-cert", /* 0x2A */ + "application/x-x968-ca-cert", /* 0x2B */ + "application/x-x968-user-cert", /* 0x2C */ + "text/vnd.wap.si", /* 0x2D */ + "application/vnd.wap.sic", /* 0x2E */ + "text/vnd.wap.sl", /* 0x2F */ + "application/vnd.wap.slc", /* 0x30 */ + "text/vnd.wap.co", /* 0x31 */ + "application/vnd.wap.coc", /* 0x32 */ + "application/vnd.wap.multipart.related", /* 0x33 */ + "application/vnd.wap.sia", /* 0x34 */ + "text/vnd.wap.connectivity-xml", /* 0x35 */ + "application/vnd.wap.connectivity-wbxml", /* 0x36 */ + "application/pkcs7-mime", /* 0x37 */ + "application/vnd.wap.hashed-certificate", /* 0x38 */ + "application/vnd.wap.signed-certificate", /* 0x39 */ + "application/vnd.wap.cert-response", /* 0x3A */ + "application/xhtml+xml", /* 0x3B */ + "application/wml+xml", /* 0x3C */ + "text/css", /* 0x3D */ + "application/vnd.wap.mms-message", /* 0x3E */ + "application/vnd.wap.rollover-certificate", /* 0x3F */ + "application/vnd.wap.locc+wbxml", /* 0x40 */ + "application/vnd.wap.loc+xml", /* 0x41 */ + "application/vnd.syncml.dm+wbxml", /* 0x42 */ + "application/vnd.syncml.dm+xml", /* 0x43 */ + "application/vnd.syncml.notification", /* 0x44 */ + "application/vnd.wap.xhtml+xml", /* 0x45 */ + "application/vnd.wv.csp.cir", /* 0x46 */ + "application/vnd.oma.dd+xml", /* 0x47 */ + "application/vnd.oma.drm.message", /* 0x48 */ + "application/vnd.oma.drm.content", /* 0x49 */ + "application/vnd.oma.drm.rights+xml", /* 0x4A */ + "application/vnd.oma.drm.rights+wbxml", /* 0x4B */ + "application/vnd.wv.csp+xml", /* 0x4C */ + "application/vnd.wv.csp+wbxml", /* 0x4D */ + "application/vnd.syncml.ds.notification", /* 0x4E */ + "audio/*", /* 0x4F */ + "video/*", /* 0x50 */ + "application/vnd.oma.dd2+xml", /* 0x51 */ + "application/mikey" /* 0x52 */ + }; +} diff --git a/src/java/com/google/android/mms/pdu/PduHeaders.java b/src/java/com/google/android/mms/pdu/PduHeaders.java new file mode 100644 index 0000000..4313815 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduHeaders.java @@ -0,0 +1,721 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +import java.util.ArrayList; +import java.util.HashMap; + +public class PduHeaders { + /** + * All pdu header fields. + */ + public static final int BCC = 0x81; + public static final int CC = 0x82; + public static final int CONTENT_LOCATION = 0x83; + public static final int CONTENT_TYPE = 0x84; + public static final int DATE = 0x85; + public static final int DELIVERY_REPORT = 0x86; + public static final int DELIVERY_TIME = 0x87; + public static final int EXPIRY = 0x88; + public static final int FROM = 0x89; + public static final int MESSAGE_CLASS = 0x8A; + public static final int MESSAGE_ID = 0x8B; + public static final int MESSAGE_TYPE = 0x8C; + public static final int MMS_VERSION = 0x8D; + public static final int MESSAGE_SIZE = 0x8E; + public static final int PRIORITY = 0x8F; + + public static final int READ_REPLY = 0x90; + public static final int READ_REPORT = 0x90; + public static final int REPORT_ALLOWED = 0x91; + public static final int RESPONSE_STATUS = 0x92; + public static final int RESPONSE_TEXT = 0x93; + public static final int SENDER_VISIBILITY = 0x94; + public static final int STATUS = 0x95; + public static final int SUBJECT = 0x96; + public static final int TO = 0x97; + public static final int TRANSACTION_ID = 0x98; + public static final int RETRIEVE_STATUS = 0x99; + public static final int RETRIEVE_TEXT = 0x9A; + public static final int READ_STATUS = 0x9B; + public static final int REPLY_CHARGING = 0x9C; + public static final int REPLY_CHARGING_DEADLINE = 0x9D; + public static final int REPLY_CHARGING_ID = 0x9E; + public static final int REPLY_CHARGING_SIZE = 0x9F; + + public static final int PREVIOUSLY_SENT_BY = 0xA0; + public static final int PREVIOUSLY_SENT_DATE = 0xA1; + public static final int STORE = 0xA2; + public static final int MM_STATE = 0xA3; + public static final int MM_FLAGS = 0xA4; + public static final int STORE_STATUS = 0xA5; + public static final int STORE_STATUS_TEXT = 0xA6; + public static final int STORED = 0xA7; + public static final int ATTRIBUTES = 0xA8; + public static final int TOTALS = 0xA9; + public static final int MBOX_TOTALS = 0xAA; + public static final int QUOTAS = 0xAB; + public static final int MBOX_QUOTAS = 0xAC; + public static final int MESSAGE_COUNT = 0xAD; + public static final int CONTENT = 0xAE; + public static final int START = 0xAF; + + public static final int ADDITIONAL_HEADERS = 0xB0; + public static final int DISTRIBUTION_INDICATOR = 0xB1; + public static final int ELEMENT_DESCRIPTOR = 0xB2; + public static final int LIMIT = 0xB3; + public static final int RECOMMENDED_RETRIEVAL_MODE = 0xB4; + public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5; + public static final int STATUS_TEXT = 0xB6; + public static final int APPLIC_ID = 0xB7; + public static final int REPLY_APPLIC_ID = 0xB8; + public static final int AUX_APPLIC_ID = 0xB9; + public static final int CONTENT_CLASS = 0xBA; + public static final int DRM_CONTENT = 0xBB; + public static final int ADAPTATION_ALLOWED = 0xBC; + public static final int REPLACE_ID = 0xBD; + public static final int CANCEL_ID = 0xBE; + public static final int CANCEL_STATUS = 0xBF; + + /** + * X-Mms-Message-Type field types. + */ + public static final int MESSAGE_TYPE_SEND_REQ = 0x80; + public static final int MESSAGE_TYPE_SEND_CONF = 0x81; + public static final int MESSAGE_TYPE_NOTIFICATION_IND = 0x82; + public static final int MESSAGE_TYPE_NOTIFYRESP_IND = 0x83; + public static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84; + public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND = 0x85; + public static final int MESSAGE_TYPE_DELIVERY_IND = 0x86; + public static final int MESSAGE_TYPE_READ_REC_IND = 0x87; + public static final int MESSAGE_TYPE_READ_ORIG_IND = 0x88; + public static final int MESSAGE_TYPE_FORWARD_REQ = 0x89; + public static final int MESSAGE_TYPE_FORWARD_CONF = 0x8A; + public static final int MESSAGE_TYPE_MBOX_STORE_REQ = 0x8B; + public static final int MESSAGE_TYPE_MBOX_STORE_CONF = 0x8C; + public static final int MESSAGE_TYPE_MBOX_VIEW_REQ = 0x8D; + public static final int MESSAGE_TYPE_MBOX_VIEW_CONF = 0x8E; + public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ = 0x8F; + public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF = 0x90; + public static final int MESSAGE_TYPE_MBOX_DELETE_REQ = 0x91; + public static final int MESSAGE_TYPE_MBOX_DELETE_CONF = 0x92; + public static final int MESSAGE_TYPE_MBOX_DESCR = 0x93; + public static final int MESSAGE_TYPE_DELETE_REQ = 0x94; + public static final int MESSAGE_TYPE_DELETE_CONF = 0x95; + public static final int MESSAGE_TYPE_CANCEL_REQ = 0x96; + public static final int MESSAGE_TYPE_CANCEL_CONF = 0x97; + + /** + * X-Mms-Delivery-Report | + * X-Mms-Read-Report | + * X-Mms-Report-Allowed | + * X-Mms-Sender-Visibility | + * X-Mms-Store | + * X-Mms-Stored | + * X-Mms-Totals | + * X-Mms-Quotas | + * X-Mms-Distribution-Indicator | + * X-Mms-DRM-Content | + * X-Mms-Adaptation-Allowed | + * field types. + */ + public static final int VALUE_YES = 0x80; + public static final int VALUE_NO = 0x81; + + /** + * Delivery-Time | + * Expiry and Reply-Charging-Deadline | + * field type components. + */ + public static final int VALUE_ABSOLUTE_TOKEN = 0x80; + public static final int VALUE_RELATIVE_TOKEN = 0x81; + + /** + * X-Mms-MMS-Version field types. + */ + public static final int MMS_VERSION_1_3 = ((1 << 4) | 3); + public static final int MMS_VERSION_1_2 = ((1 << 4) | 2); + public static final int MMS_VERSION_1_1 = ((1 << 4) | 1); + public static final int MMS_VERSION_1_0 = ((1 << 4) | 0); + + // Current version is 1.2. + public static final int CURRENT_MMS_VERSION = MMS_VERSION_1_2; + + /** + * From field type components. + */ + public static final int FROM_ADDRESS_PRESENT_TOKEN = 0x80; + public static final int FROM_INSERT_ADDRESS_TOKEN = 0x81; + + public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token"; + public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token"; + + /** + * X-Mms-Status Field. + */ + public static final int STATUS_EXPIRED = 0x80; + public static final int STATUS_RETRIEVED = 0x81; + public static final int STATUS_REJECTED = 0x82; + public static final int STATUS_DEFERRED = 0x83; + public static final int STATUS_UNRECOGNIZED = 0x84; + public static final int STATUS_INDETERMINATE = 0x85; + public static final int STATUS_FORWARDED = 0x86; + public static final int STATUS_UNREACHABLE = 0x87; + + /** + * MM-Flags field type components. + */ + public static final int MM_FLAGS_ADD_TOKEN = 0x80; + public static final int MM_FLAGS_REMOVE_TOKEN = 0x81; + public static final int MM_FLAGS_FILTER_TOKEN = 0x82; + + /** + * X-Mms-Message-Class field types. + */ + public static final int MESSAGE_CLASS_PERSONAL = 0x80; + public static final int MESSAGE_CLASS_ADVERTISEMENT = 0x81; + public static final int MESSAGE_CLASS_INFORMATIONAL = 0x82; + public static final int MESSAGE_CLASS_AUTO = 0x83; + + public static final String MESSAGE_CLASS_PERSONAL_STR = "personal"; + public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement"; + public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational"; + public static final String MESSAGE_CLASS_AUTO_STR = "auto"; + + /** + * X-Mms-Priority field types. + */ + public static final int PRIORITY_LOW = 0x80; + public static final int PRIORITY_NORMAL = 0x81; + public static final int PRIORITY_HIGH = 0x82; + + /** + * X-Mms-Response-Status field types. + */ + public static final int RESPONSE_STATUS_OK = 0x80; + public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED = 0x81; + public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82; + + public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT = 0x83; + public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84; + + public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND = 0x85; + public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM = 0x86; + public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87; + public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE = 0x88; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; + + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC2; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC3; + public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS = 0xC4; + + public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED = 0xE3; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE4; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED = 0xE5; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET = 0xE6; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED = 0xE8; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED = 0xE9; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED = 0xEA; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID = 0xEB; + public static final int RESPONSE_STATUS_ERROR_PERMANENT_END = 0xFF; + + /** + * X-Mms-Retrieve-Status field types. + */ + public static final int RETRIEVE_STATUS_OK = 0x80; + public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; + public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND = 0xC1; + public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC2; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE2; + public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3; + public static final int RETRIEVE_STATUS_ERROR_END = 0xFF; + + /** + * X-Mms-Sender-Visibility field types. + */ + public static final int SENDER_VISIBILITY_HIDE = 0x80; + public static final int SENDER_VISIBILITY_SHOW = 0x81; + + /** + * X-Mms-Read-Status field types. + */ + public static final int READ_STATUS_READ = 0x80; + public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81; + + /** + * X-Mms-Cancel-Status field types. + */ + public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80; + public static final int CANCEL_STATUS_REQUEST_CORRUPTED = 0x81; + + /** + * X-Mms-Reply-Charging field types. + */ + public static final int REPLY_CHARGING_REQUESTED = 0x80; + public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81; + public static final int REPLY_CHARGING_ACCEPTED = 0x82; + public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY = 0x83; + + /** + * X-Mms-MM-State field types. + */ + public static final int MM_STATE_DRAFT = 0x80; + public static final int MM_STATE_SENT = 0x81; + public static final int MM_STATE_NEW = 0x82; + public static final int MM_STATE_RETRIEVED = 0x83; + public static final int MM_STATE_FORWARDED = 0x84; + + /** + * X-Mms-Recommended-Retrieval-Mode field types. + */ + public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80; + + /** + * X-Mms-Content-Class field types. + */ + public static final int CONTENT_CLASS_TEXT = 0x80; + public static final int CONTENT_CLASS_IMAGE_BASIC = 0x81; + public static final int CONTENT_CLASS_IMAGE_RICH = 0x82; + public static final int CONTENT_CLASS_VIDEO_BASIC = 0x83; + public static final int CONTENT_CLASS_VIDEO_RICH = 0x84; + public static final int CONTENT_CLASS_MEGAPIXEL = 0x85; + public static final int CONTENT_CLASS_CONTENT_BASIC = 0x86; + public static final int CONTENT_CLASS_CONTENT_RICH = 0x87; + + /** + * X-Mms-Store-Status field types. + */ + public static final int STORE_STATUS_SUCCESS = 0x80; + public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE = 0xC0; + public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM = 0xC1; + public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE = 0xE0; + public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED = 0xE1; + public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2; + public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND = 0xE3; + public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL = 0xE4; + public static final int STORE_STATUS_ERROR_END = 0xFF; + + /** + * The map contains the value of all headers. + */ + private HashMap mHeaderMap = null; + + /** + * Constructor of PduHeaders. + */ + public PduHeaders() { + mHeaderMap = new HashMap(); + } + + /** + * Get octet value by header field. + * + * @param field the field + * @return the octet value of the pdu header + * with specified header field. Return 0 if + * the value is not set. + */ + protected int getOctet(int field) { + Integer octet = (Integer) mHeaderMap.get(field); + if (null == octet) { + return 0; + } + + return octet; + } + + /** + * Set octet value to pdu header by header field. + * + * @param value the value + * @param field the field + * @throws InvalidHeaderValueException if the value is invalid. + */ + protected void setOctet(int value, int field) + throws InvalidHeaderValueException{ + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + switch (field) { + case REPORT_ALLOWED: + case ADAPTATION_ALLOWED: + case DELIVERY_REPORT: + case DRM_CONTENT: + case DISTRIBUTION_INDICATOR: + case QUOTAS: + case READ_REPORT: + case STORE: + case STORED: + case TOTALS: + case SENDER_VISIBILITY: + if ((VALUE_YES != value) && (VALUE_NO != value)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case READ_STATUS: + if ((READ_STATUS_READ != value) && + (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case CANCEL_STATUS: + if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) && + (CANCEL_STATUS_REQUEST_CORRUPTED != value)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case PRIORITY: + if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case STATUS: + if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case REPLY_CHARGING: + if ((value < REPLY_CHARGING_REQUESTED) + || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case MM_STATE: + if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case RECOMMENDED_RETRIEVAL_MODE: + if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case CONTENT_CLASS: + if ((value < CONTENT_CLASS_TEXT) + || (value > CONTENT_CLASS_CONTENT_RICH)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + case RETRIEVE_STATUS: + // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value. + if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && + (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) { + value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE; + } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) && + (value <= RETRIEVE_STATUS_ERROR_END)) { + value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; + } else if ((value < RETRIEVE_STATUS_OK) || + ((value > RETRIEVE_STATUS_OK) && + (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) || + (value > RETRIEVE_STATUS_ERROR_END)) { + value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE; + } + break; + case STORE_STATUS: + // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value. + if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) && + (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) { + value = STORE_STATUS_ERROR_TRANSIENT_FAILURE; + } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) && + (value <= STORE_STATUS_ERROR_END)) { + value = STORE_STATUS_ERROR_PERMANENT_FAILURE; + } else if ((value < STORE_STATUS_SUCCESS) || + ((value > STORE_STATUS_SUCCESS) && + (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) || + (value > STORE_STATUS_ERROR_END)) { + value = STORE_STATUS_ERROR_PERMANENT_FAILURE; + } + break; + case RESPONSE_STATUS: + // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value. + if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) && + (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) { + value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE; + } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) && + (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) || + (value < RESPONSE_STATUS_OK) || + ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) && + (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) || + (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) { + value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE; + } + break; + case MMS_VERSION: + if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) { + value = CURRENT_MMS_VERSION; // Current version is the default value. + } + break; + case MESSAGE_TYPE: + if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) { + // Invalid value. + throw new InvalidHeaderValueException("Invalid Octet value!"); + } + break; + default: + // This header value should not be Octect. + throw new RuntimeException("Invalid header field!"); + } + mHeaderMap.put(field, value); + } + + /** + * Get TextString value by header field. + * + * @param field the field + * @return the TextString value of the pdu header + * with specified header field + */ + protected byte[] getTextString(int field) { + return (byte[]) mHeaderMap.get(field); + } + + /** + * Set TextString value to pdu header by header field. + * + * @param value the value + * @param field the field + * @return the TextString value of the pdu header + * with specified header field + * @throws NullPointerException if the value is null. + */ + protected void setTextString(byte[] value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case TRANSACTION_ID: + case REPLY_CHARGING_ID: + case AUX_APPLIC_ID: + case APPLIC_ID: + case REPLY_APPLIC_ID: + case MESSAGE_ID: + case REPLACE_ID: + case CANCEL_ID: + case CONTENT_LOCATION: + case MESSAGE_CLASS: + case CONTENT_TYPE: + break; + default: + // This header value should not be Text-String. + throw new RuntimeException("Invalid header field!"); + } + mHeaderMap.put(field, value); + } + + /** + * Get EncodedStringValue value by header field. + * + * @param field the field + * @return the EncodedStringValue value of the pdu header + * with specified header field + */ + protected EncodedStringValue getEncodedStringValue(int field) { + return (EncodedStringValue) mHeaderMap.get(field); + } + + /** + * Get TO, CC or BCC header value. + * + * @param field the field + * @return the EncodeStringValue array of the pdu header + * with specified header field + */ + protected EncodedStringValue[] getEncodedStringValues(int field) { + ArrayList list = + (ArrayList) mHeaderMap.get(field); + if (null == list) { + return null; + } + EncodedStringValue[] values = new EncodedStringValue[list.size()]; + return list.toArray(values); + } + + /** + * Set EncodedStringValue value to pdu header by header field. + * + * @param value the value + * @param field the field + * @return the EncodedStringValue value of the pdu header + * with specified header field + * @throws NullPointerException if the value is null. + */ + protected void setEncodedStringValue(EncodedStringValue value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case SUBJECT: + case RECOMMENDED_RETRIEVAL_MODE_TEXT: + case RETRIEVE_TEXT: + case STATUS_TEXT: + case STORE_STATUS_TEXT: + case RESPONSE_TEXT: + case FROM: + case PREVIOUSLY_SENT_BY: + case MM_FLAGS: + break; + default: + // This header value should not be Encoded-String-Value. + throw new RuntimeException("Invalid header field!"); + } + + mHeaderMap.put(field, value); + } + + /** + * Set TO, CC or BCC header value. + * + * @param value the value + * @param field the field + * @return the EncodedStringValue value array of the pdu header + * with specified header field + * @throws NullPointerException if the value is null. + */ + protected void setEncodedStringValues(EncodedStringValue[] value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case BCC: + case CC: + case TO: + break; + default: + // This header value should not be Encoded-String-Value. + throw new RuntimeException("Invalid header field!"); + } + + ArrayList list = new ArrayList(); + for (int i = 0; i < value.length; i++) { + list.add(value[i]); + } + mHeaderMap.put(field, list); + } + + /** + * Append one EncodedStringValue to another. + * + * @param value the EncodedStringValue to append + * @param field the field + * @throws NullPointerException if the value is null. + */ + protected void appendEncodedStringValue(EncodedStringValue value, + int field) { + if (null == value) { + throw new NullPointerException(); + } + + switch (field) { + case BCC: + case CC: + case TO: + break; + default: + throw new RuntimeException("Invalid header field!"); + } + + ArrayList list = + (ArrayList) mHeaderMap.get(field); + if (null == list) { + list = new ArrayList(); + } + list.add(value); + mHeaderMap.put(field, list); + } + + /** + * Get LongInteger value by header field. + * + * @param field the field + * @return the LongInteger value of the pdu header + * with specified header field. if return -1, the + * field is not existed in pdu header. + */ + protected long getLongInteger(int field) { + Long longInteger = (Long) mHeaderMap.get(field); + if (null == longInteger) { + return -1; + } + + return longInteger.longValue(); + } + + /** + * Set LongInteger value to pdu header by header field. + * + * @param value the value + * @param field the field + */ + protected void setLongInteger(long value, int field) { + /** + * Check whether this field can be set for specific + * header and check validity of the field. + */ + switch (field) { + case DATE: + case REPLY_CHARGING_SIZE: + case MESSAGE_SIZE: + case MESSAGE_COUNT: + case START: + case LIMIT: + case DELIVERY_TIME: + case EXPIRY: + case REPLY_CHARGING_DEADLINE: + case PREVIOUSLY_SENT_DATE: + break; + default: + // This header value should not be LongInteger. + throw new RuntimeException("Invalid header field!"); + } + mHeaderMap.put(field, value); + } +} diff --git a/src/java/com/google/android/mms/pdu/PduParser.java b/src/java/com/google/android/mms/pdu/PduParser.java new file mode 100755 index 0000000..015d864 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduParser.java @@ -0,0 +1,1912 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import com.google.android.mms.ContentType; +import com.google.android.mms.InvalidHeaderValueException; + +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.HashMap; + +import android.content.res.Resources; + +public class PduParser { + /** + * The next are WAP values defined in WSP specification. + */ + private static final int QUOTE = 127; + private static final int LENGTH_QUOTE = 31; + private static final int TEXT_MIN = 32; + private static final int TEXT_MAX = 127; + private static final int SHORT_INTEGER_MAX = 127; + private static final int SHORT_LENGTH_MAX = 30; + private static final int LONG_INTEGER_LENGTH_MAX = 8; + private static final int QUOTED_STRING_FLAG = 34; + private static final int END_STRING_FLAG = 0x00; + //The next two are used by the interface "parseWapString" to + //distinguish Text-String and Quoted-String. + private static final int TYPE_TEXT_STRING = 0; + private static final int TYPE_QUOTED_STRING = 1; + private static final int TYPE_TOKEN_STRING = 2; + + /** + * Specify the part position. + */ + private static final int THE_FIRST_PART = 0; + private static final int THE_LAST_PART = 1; + + /** + * The pdu data. + */ + private ByteArrayInputStream mPduDataStream = null; + + /** + * Store pdu headers + */ + private PduHeaders mHeaders = null; + + /** + * Store pdu parts. + */ + private PduBody mBody = null; + + /** + * Store the "type" parameter in "Content-Type" header field. + */ + private static byte[] mTypeParam = null; + + /** + * Store the "start" parameter in "Content-Type" header field. + */ + private static byte[] mStartParam = null; + + /** + * The log tag. + */ + private static final String LOG_TAG = "PduParser"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + /** + * Constructor. + * + * @param pduDataStream pdu data to be parsed + */ + public PduParser(byte[] pduDataStream) { + mPduDataStream = new ByteArrayInputStream(pduDataStream); + } + + /** + * Parse the pdu. + * + * @return the pdu structure if parsing successfully. + * null if parsing error happened or mandatory fields are not set. + */ + public GenericPdu parse(){ + if (mPduDataStream == null) { + return null; + } + + /* parse headers */ + mHeaders = parseHeaders(mPduDataStream); + if (null == mHeaders) { + // Parse headers failed. + return null; + } + + /* get the message type */ + int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE); + + /* check mandatory header fields */ + if (false == checkMandatoryHeader(mHeaders)) { + log("check mandatory headers failed!"); + return null; + } + + if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) || + (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) { + /* need to parse the parts */ + mBody = parseParts(mPduDataStream); + if (null == mBody) { + // Parse parts failed. + return null; + } + } + + switch (messageType) { + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + SendReq sendReq = new SendReq(mHeaders, mBody); + return sendReq; + case PduHeaders.MESSAGE_TYPE_SEND_CONF: + SendConf sendConf = new SendConf(mHeaders); + return sendConf; + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + NotificationInd notificationInd = + new NotificationInd(mHeaders); + return notificationInd; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + NotifyRespInd notifyRespInd = + new NotifyRespInd(mHeaders); + return notifyRespInd; + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + RetrieveConf retrieveConf = + new RetrieveConf(mHeaders, mBody); + + byte[] contentType = retrieveConf.getContentType(); + if (null == contentType) { + return null; + } + String ctTypeStr = new String(contentType); + if (ctTypeStr.equals(ContentType.MULTIPART_MIXED) + || ctTypeStr.equals(ContentType.MULTIPART_RELATED) + || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) { + // The MMS content type must be "application/vnd.wap.multipart.mixed" + // or "application/vnd.wap.multipart.related" + // or "application/vnd.wap.multipart.alternative" + return retrieveConf; + } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) { + // "application/vnd.wap.multipart.alternative" + // should take only the first part. + PduPart firstPart = mBody.getPart(0); + mBody.removeAll(); + mBody.addPart(0, firstPart); + return retrieveConf; + } + return null; + case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: + DeliveryInd deliveryInd = + new DeliveryInd(mHeaders); + return deliveryInd; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + AcknowledgeInd acknowledgeInd = + new AcknowledgeInd(mHeaders); + return acknowledgeInd; + case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: + ReadOrigInd readOrigInd = + new ReadOrigInd(mHeaders); + return readOrigInd; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + ReadRecInd readRecInd = + new ReadRecInd(mHeaders); + return readRecInd; + default: + log("Parser doesn't support this message type in this version!"); + return null; + } + } + + /** + * Parse pdu headers. + * + * @param pduDataStream pdu data input stream + * @return headers in PduHeaders structure, null when parse fail + */ + protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){ + if (pduDataStream == null) { + return null; + } + + boolean keepParsing = true; + PduHeaders headers = new PduHeaders(); + + while (keepParsing && (pduDataStream.available() > 0)) { + pduDataStream.mark(1); + int headerField = extractByteValue(pduDataStream); + /* parse custom text header */ + if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) { + pduDataStream.reset(); + byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); + } + /* we should ignore it at the moment */ + continue; + } + switch (headerField) { + case PduHeaders.MESSAGE_TYPE: + { + int messageType = extractByteValue(pduDataStream); + switch (messageType) { + // We don't support these kind of messages now. + case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: + case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: + case PduHeaders.MESSAGE_TYPE_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: + case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: + return null; + } + try { + headers.setOctet(messageType, headerField); + } catch(InvalidHeaderValueException e) { + log("Set invalid Octet value: " + messageType + + " into the header filed: " + headerField); + return null; + } catch(RuntimeException e) { + log(headerField + "is not Octet header field!"); + return null; + } + break; + } + /* Octect value */ + case PduHeaders.REPORT_ALLOWED: + case PduHeaders.ADAPTATION_ALLOWED: + case PduHeaders.DELIVERY_REPORT: + case PduHeaders.DRM_CONTENT: + case PduHeaders.DISTRIBUTION_INDICATOR: + case PduHeaders.QUOTAS: + case PduHeaders.READ_REPORT: + case PduHeaders.STORE: + case PduHeaders.STORED: + case PduHeaders.TOTALS: + case PduHeaders.SENDER_VISIBILITY: + case PduHeaders.READ_STATUS: + case PduHeaders.CANCEL_STATUS: + case PduHeaders.PRIORITY: + case PduHeaders.STATUS: + case PduHeaders.REPLY_CHARGING: + case PduHeaders.MM_STATE: + case PduHeaders.RECOMMENDED_RETRIEVAL_MODE: + case PduHeaders.CONTENT_CLASS: + case PduHeaders.RETRIEVE_STATUS: + case PduHeaders.STORE_STATUS: + /** + * The following field has a different value when + * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. + * For now we ignore this fact, since we do not support these PDUs + */ + case PduHeaders.RESPONSE_STATUS: + { + int value = extractByteValue(pduDataStream); + + try { + headers.setOctet(value, headerField); + } catch(InvalidHeaderValueException e) { + log("Set invalid Octet value: " + value + + " into the header filed: " + headerField); + return null; + } catch(RuntimeException e) { + log(headerField + "is not Octet header field!"); + return null; + } + break; + } + + /* Long-Integer */ + case PduHeaders.DATE: + case PduHeaders.REPLY_CHARGING_SIZE: + case PduHeaders.MESSAGE_SIZE: + { + try { + long value = parseLongInteger(pduDataStream); + headers.setLongInteger(value, headerField); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + /* Integer-Value */ + case PduHeaders.MESSAGE_COUNT: + case PduHeaders.START: + case PduHeaders.LIMIT: + { + try { + long value = parseIntegerValue(pduDataStream); + headers.setLongInteger(value, headerField); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + /* Text-String */ + case PduHeaders.TRANSACTION_ID: + case PduHeaders.REPLY_CHARGING_ID: + case PduHeaders.AUX_APPLIC_ID: + case PduHeaders.APPLIC_ID: + case PduHeaders.REPLY_APPLIC_ID: + /** + * The next three header fields are email addresses + * as defined in RFC2822, + * not including the characters "<" and ">" + */ + case PduHeaders.MESSAGE_ID: + case PduHeaders.REPLACE_ID: + case PduHeaders.CANCEL_ID: + /** + * The following field has a different value when + * used in the M-Mbox-Delete.conf and M-Delete.conf PDU. + * For now we ignore this fact, since we do not support these PDUs + */ + case PduHeaders.CONTENT_LOCATION: + { + byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != value) { + try { + headers.setTextString(value, headerField); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } + break; + } + + /* Encoded-string-value */ + case PduHeaders.SUBJECT: + case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT: + case PduHeaders.RETRIEVE_TEXT: + case PduHeaders.STATUS_TEXT: + case PduHeaders.STORE_STATUS_TEXT: + /* the next one is not support + * M-Mbox-Delete.conf and M-Delete.conf now */ + case PduHeaders.RESPONSE_TEXT: + { + EncodedStringValue value = + parseEncodedStringValue(pduDataStream); + if (null != value) { + try { + headers.setEncodedStringValue(value, headerField); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch (RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + break; + } + + /* Addressing model */ + case PduHeaders.BCC: + case PduHeaders.CC: + case PduHeaders.TO: + { + EncodedStringValue value = + parseEncodedStringValue(pduDataStream); + if (null != value) { + byte[] address = value.getTextString(); + if (null != address) { + String str = new String(address); + int endIndex = str.indexOf("/"); + if (endIndex > 0) { + str = str.substring(0, endIndex); + } + try { + value.setTextString(str.getBytes()); + } catch(NullPointerException e) { + log("null pointer error!"); + return null; + } + } + + try { + headers.appendEncodedStringValue(value, headerField); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + break; + } + + /* Value-length + * (Absolute-token Date-value | Relative-token Delta-seconds-value) */ + case PduHeaders.DELIVERY_TIME: + case PduHeaders.EXPIRY: + case PduHeaders.REPLY_CHARGING_DEADLINE: + { + /* parse Value-length */ + parseValueLength(pduDataStream); + + /* Absolute-token or Relative-token */ + int token = extractByteValue(pduDataStream); + + /* Date-value or Delta-seconds-value */ + long timeValue; + try { + timeValue = parseLongInteger(pduDataStream); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + if (PduHeaders.VALUE_RELATIVE_TOKEN == token) { + /* need to convert the Delta-seconds-value + * into Date-value */ + timeValue = System.currentTimeMillis()/1000 + timeValue; + } + + try { + headers.setLongInteger(timeValue, headerField); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + case PduHeaders.FROM: { + /* From-value = + * Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + */ + EncodedStringValue from = null; + parseValueLength(pduDataStream); /* parse value-length */ + + /* Address-present-token or Insert-address-token */ + int fromToken = extractByteValue(pduDataStream); + + /* Address-present-token or Insert-address-token */ + if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) { + /* Encoded-string-value */ + from = parseEncodedStringValue(pduDataStream); + if (null != from) { + byte[] address = from.getTextString(); + if (null != address) { + String str = new String(address); + int endIndex = str.indexOf("/"); + if (endIndex > 0) { + str = str.substring(0, endIndex); + } + try { + from.setTextString(str.getBytes()); + } catch(NullPointerException e) { + log("null pointer error!"); + return null; + } + } + } + } else { + try { + from = new EncodedStringValue( + PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()); + } catch(NullPointerException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + + try { + headers.setEncodedStringValue(from, PduHeaders.FROM); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + break; + } + + case PduHeaders.MESSAGE_CLASS: { + /* Message-class-value = Class-identifier | Token-text */ + pduDataStream.mark(1); + int messageClass = extractByteValue(pduDataStream); + + if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) { + /* Class-identifier */ + try { + if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) { + headers.setTextString( + PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(), + PduHeaders.MESSAGE_CLASS); + } + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } else { + /* Token-text */ + pduDataStream.reset(); + byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != messageClassString) { + try { + headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } + } + break; + } + + case PduHeaders.MMS_VERSION: { + int version = parseShortInteger(pduDataStream); + + try { + headers.setOctet(version, PduHeaders.MMS_VERSION); + } catch(InvalidHeaderValueException e) { + log("Set invalid Octet value: " + version + + " into the header filed: " + headerField); + return null; + } catch(RuntimeException e) { + log(headerField + "is not Octet header field!"); + return null; + } + break; + } + + case PduHeaders.PREVIOUSLY_SENT_BY: { + /* Previously-sent-by-value = + * Value-length Forwarded-count-value Encoded-string-value */ + /* parse value-length */ + parseValueLength(pduDataStream); + + /* parse Forwarded-count-value */ + try { + parseIntegerValue(pduDataStream); + } catch(RuntimeException e) { + log(headerField + " is not Integer-Value"); + return null; + } + + /* parse Encoded-string-value */ + EncodedStringValue previouslySentBy = + parseEncodedStringValue(pduDataStream); + if (null != previouslySentBy) { + try { + headers.setEncodedStringValue(previouslySentBy, + PduHeaders.PREVIOUSLY_SENT_BY); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Encoded-String-Value header field!"); + return null; + } + } + break; + } + + case PduHeaders.PREVIOUSLY_SENT_DATE: { + /* Previously-sent-date-value = + * Value-length Forwarded-count-value Date-value */ + /* parse value-length */ + parseValueLength(pduDataStream); + + /* parse Forwarded-count-value */ + try { + parseIntegerValue(pduDataStream); + } catch(RuntimeException e) { + log(headerField + " is not Integer-Value"); + return null; + } + + /* Date-value */ + try { + long perviouslySentDate = parseLongInteger(pduDataStream); + headers.setLongInteger(perviouslySentDate, + PduHeaders.PREVIOUSLY_SENT_DATE); + } catch(RuntimeException e) { + log(headerField + "is not Long-Integer header field!"); + return null; + } + break; + } + + case PduHeaders.MM_FLAGS: { + /* MM-flags-value = + * Value-length + * ( Add-token | Remove-token | Filter-token ) + * Encoded-string-value + */ + + /* parse Value-length */ + parseValueLength(pduDataStream); + + /* Add-token | Remove-token | Filter-token */ + extractByteValue(pduDataStream); + + /* Encoded-string-value */ + parseEncodedStringValue(pduDataStream); + + /* not store this header filed in "headers", + * because now PduHeaders doesn't support it */ + break; + } + + /* Value-length + * (Message-total-token | Size-total-token) Integer-Value */ + case PduHeaders.MBOX_TOTALS: + case PduHeaders.MBOX_QUOTAS: + { + /* Value-length */ + parseValueLength(pduDataStream); + + /* Message-total-token | Size-total-token */ + extractByteValue(pduDataStream); + + /*Integer-Value*/ + try { + parseIntegerValue(pduDataStream); + } catch(RuntimeException e) { + log(headerField + " is not Integer-Value"); + return null; + } + + /* not store these headers filed in "headers", + because now PduHeaders doesn't support them */ + break; + } + + case PduHeaders.ELEMENT_DESCRIPTOR: { + parseContentType(pduDataStream, null); + + /* not store this header filed in "headers", + because now PduHeaders doesn't support it */ + break; + } + + case PduHeaders.CONTENT_TYPE: { + HashMap map = + new HashMap(); + byte[] contentType = + parseContentType(pduDataStream, map); + + if (null != contentType) { + try { + headers.setTextString(contentType, PduHeaders.CONTENT_TYPE); + } catch(NullPointerException e) { + log("null pointer error!"); + } catch(RuntimeException e) { + log(headerField + "is not Text-String header field!"); + return null; + } + } + + /* get start parameter */ + mStartParam = (byte[]) map.get(PduPart.P_START); + + /* get charset parameter */ + mTypeParam= (byte[]) map.get(PduPart.P_TYPE); + + keepParsing = false; + break; + } + + case PduHeaders.CONTENT: + case PduHeaders.ADDITIONAL_HEADERS: + case PduHeaders.ATTRIBUTES: + default: { + log("Unknown header"); + } + } + } + + return headers; + } + + /** + * Parse pdu parts. + * + * @param pduDataStream pdu data input stream + * @return parts in PduBody structure + */ + protected static PduBody parseParts(ByteArrayInputStream pduDataStream) { + if (pduDataStream == null) { + return null; + } + + int count = parseUnsignedInt(pduDataStream); // get the number of parts + PduBody body = new PduBody(); + + for (int i = 0 ; i < count ; i++) { + int headerLength = parseUnsignedInt(pduDataStream); + int dataLength = parseUnsignedInt(pduDataStream); + PduPart part = new PduPart(); + int startPos = pduDataStream.available(); + if (startPos <= 0) { + // Invalid part. + return null; + } + + /* parse part's content-type */ + HashMap map = new HashMap(); + byte[] contentType = parseContentType(pduDataStream, map); + if (null != contentType) { + part.setContentType(contentType); + } else { + part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*" + } + + /* get name parameter */ + byte[] name = (byte[]) map.get(PduPart.P_NAME); + if (null != name) { + part.setName(name); + } + + /* get charset parameter */ + Integer charset = (Integer) map.get(PduPart.P_CHARSET); + if (null != charset) { + part.setCharset(charset); + } + + /* parse part's headers */ + int endPos = pduDataStream.available(); + int partHeaderLen = headerLength - (startPos - endPos); + if (partHeaderLen > 0) { + if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) { + // Parse part header faild. + return null; + } + } else if (partHeaderLen < 0) { + // Invalid length of content-type. + return null; + } + + /* FIXME: check content-id, name, filename and content location, + * if not set anyone of them, generate a default content-location + */ + if ((null == part.getContentLocation()) + && (null == part.getName()) + && (null == part.getFilename()) + && (null == part.getContentId())) { + part.setContentLocation(Long.toOctalString( + System.currentTimeMillis()).getBytes()); + } + + /* get part's data */ + if (dataLength > 0) { + byte[] partData = new byte[dataLength]; + String partContentType = new String(part.getContentType()); + pduDataStream.read(partData, 0, dataLength); + if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) { + // parse "multipart/vnd.wap.multipart.alternative". + PduBody childBody = parseParts(new ByteArrayInputStream(partData)); + // take the first part of children. + part = childBody.getPart(0); + } else { + // Check Content-Transfer-Encoding. + byte[] partDataEncoding = part.getContentTransferEncoding(); + if (null != partDataEncoding) { + String encoding = new String(partDataEncoding); + if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { + // Decode "base64" into "binary". + partData = Base64.decodeBase64(partData); + } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { + // Decode "quoted-printable" into "binary". + partData = QuotedPrintable.decodeQuotedPrintable(partData); + } else { + // "binary" is the default encoding. + } + } + if (null == partData) { + log("Decode part data error!"); + return null; + } + part.setData(partData); + } + } + + /* add this part to body */ + if (THE_FIRST_PART == checkPartPosition(part)) { + /* this is the first part */ + body.addPart(0, part); + } else { + /* add the part to the end */ + body.addPart(part); + } + } + + return body; + } + + /** + * Log status. + * + * @param text log information + */ + private static void log(String text) { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, text); + } + } + + /** + * Parse unsigned integer. + * + * @param pduDataStream pdu data input stream + * @return the integer, -1 when failed + */ + protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * The maximum size of a uintvar is 32 bits. + * So it will be encoded in no more than 5 octets. + */ + assert(null != pduDataStream); + int result = 0; + int temp = pduDataStream.read(); + if (temp == -1) { + return temp; + } + + while((temp & 0x80) != 0) { + result = result << 7; + result |= temp & 0x7F; + temp = pduDataStream.read(); + if (temp == -1) { + return temp; + } + } + + result = result << 7; + result |= temp & 0x7F; + + return result; + } + + /** + * Parse value length. + * + * @param pduDataStream pdu data input stream + * @return the integer + */ + protected static int parseValueLength(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Value-length = Short-length | (Length-quote Length) + * Short-length = + * Length-quote = + * Length = Uintvar-integer + * Uintvar-integer = 1*5 OCTET + */ + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + int first = temp & 0xFF; + + if (first <= SHORT_LENGTH_MAX) { + return first; + } else if (first == LENGTH_QUOTE) { + return parseUnsignedInt(pduDataStream); + } + + throw new RuntimeException ("Value length > LENGTH_QUOTE!"); + } + + /** + * Parse encoded string value. + * + * @param pduDataStream pdu data input stream + * @return the EncodedStringValue + */ + protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){ + /** + * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf + * Encoded-string-value = Text-string | Value-length Char-set Text-string + */ + assert(null != pduDataStream); + pduDataStream.mark(1); + EncodedStringValue returnValue = null; + int charset = 0; + int temp = pduDataStream.read(); + assert(-1 != temp); + int first = temp & 0xFF; + if (first == 0) { + return null; // Blank subject, bail. + } + + pduDataStream.reset(); + if (first < TEXT_MIN) { + parseValueLength(pduDataStream); + + charset = parseShortInteger(pduDataStream); //get the "Charset" + } + + byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING); + + try { + if (0 != charset) { + returnValue = new EncodedStringValue(charset, textString); + } else { + returnValue = new EncodedStringValue(textString); + } + } catch(Exception e) { + return null; + } + + return returnValue; + } + + /** + * Parse Text-String or Quoted-String. + * + * @param pduDataStream pdu data input stream + * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING + * @return the string without End-of-string in byte array + */ + protected static byte[] parseWapString(ByteArrayInputStream pduDataStream, + int stringType) { + assert(null != pduDataStream); + /** + * From wap-230-wsp-20010705-a.pdf + * Text-string = [Quote] *TEXT End-of-string + * If the first character in the TEXT is in the range of 128-255, + * a Quote character must precede it. + * Otherwise the Quote character must be omitted. + * The Quote is not part of the contents. + * Quote = + * End-of-string = + * + * Quoted-string = *TEXT End-of-string + * + * Token-text = Token End-of-string + */ + + // Mark supposed beginning of Text-string + // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG + pduDataStream.mark(1); + + // Check first char + int temp = pduDataStream.read(); + assert(-1 != temp); + if ((TYPE_QUOTED_STRING == stringType) && + (QUOTED_STRING_FLAG == temp)) { + // Mark again if QUOTED_STRING_FLAG and ignore it + pduDataStream.mark(1); + } else if ((TYPE_TEXT_STRING == stringType) && + (QUOTE == temp)) { + // Mark again if QUOTE and ignore it + pduDataStream.mark(1); + } else { + // Otherwise go back to origin + pduDataStream.reset(); + } + + // We are now definitely at the beginning of string + /** + * Return *TOKEN or *TEXT (Text-String without QUOTE, + * Quoted-String without QUOTED_STRING_FLAG and without End-of-string) + */ + return getWapString(pduDataStream, stringType); + } + + /** + * Check TOKEN data defined in RFC2616. + * @param ch checking data + * @return true when ch is TOKEN, false when ch is not TOKEN + */ + protected static boolean isTokenCharacter(int ch) { + /** + * Token = 1* + * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64) + * | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34) + * | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61) + * | "{"(123) | "}"(125) | SP(32) | HT(9) + * CHAR = + * CTL = + * SP = + * HT = + */ + if((ch < 33) || (ch > 126)) { + return false; + } + + switch(ch) { + case '"': /* '"' */ + case '(': /* '(' */ + case ')': /* ')' */ + case ',': /* ',' */ + case '/': /* '/' */ + case ':': /* ':' */ + case ';': /* ';' */ + case '<': /* '<' */ + case '=': /* '=' */ + case '>': /* '>' */ + case '?': /* '?' */ + case '@': /* '@' */ + case '[': /* '[' */ + case '\\': /* '\' */ + case ']': /* ']' */ + case '{': /* '{' */ + case '}': /* '}' */ + return false; + } + + return true; + } + + /** + * Check TEXT data defined in RFC2616. + * @param ch checking data + * @return true when ch is TEXT, false when ch is not TEXT + */ + protected static boolean isText(int ch) { + /** + * TEXT = + * CTL = + * LWS = [CRLF] 1*( SP | HT ) + * CRLF = CR LF + * CR = + * LF = + */ + if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) { + return true; + } + + switch(ch) { + case '\t': /* '\t' */ + case '\n': /* '\n' */ + case '\r': /* '\r' */ + return true; + } + + return false; + } + + protected static byte[] getWapString(ByteArrayInputStream pduDataStream, + int stringType) { + assert(null != pduDataStream); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int temp = pduDataStream.read(); + assert(-1 != temp); + while((-1 != temp) && ('\0' != temp)) { + // check each of the character + if (stringType == TYPE_TOKEN_STRING) { + if (isTokenCharacter(temp)) { + out.write(temp); + } + } else { + if (isText(temp)) { + out.write(temp); + } + } + + temp = pduDataStream.read(); + assert(-1 != temp); + } + + if (out.size() > 0) { + return out.toByteArray(); + } + + return null; + } + + /** + * Extract a byte value from the input stream. + * + * @param pduDataStream pdu data input stream + * @return the byte + */ + protected static int extractByteValue(ByteArrayInputStream pduDataStream) { + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + return temp & 0xFF; + } + + /** + * Parse Short-Integer. + * + * @param pduDataStream pdu data input stream + * @return the byte + */ + protected static int parseShortInteger(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Short-integer = OCTET + * Integers in range 0-127 shall be encoded as a one + * octet value with the most significant bit set to one (1xxx xxxx) + * and with the value in the remaining least significant bits. + */ + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + return temp & 0x7F; + } + + /** + * Parse Long-Integer. + * + * @param pduDataStream pdu data input stream + * @return long integer + */ + protected static long parseLongInteger(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Long-integer = Short-length Multi-octet-integer + * The Short-length indicates the length of the Multi-octet-integer + * Multi-octet-integer = 1*30 OCTET + * The content octets shall be an unsigned integer value + * with the most significant octet encoded first (big-endian representation). + * The minimum number of octets must be used to encode the value. + * Short-length = + */ + assert(null != pduDataStream); + int temp = pduDataStream.read(); + assert(-1 != temp); + int count = temp & 0xFF; + + if (count > LONG_INTEGER_LENGTH_MAX) { + throw new RuntimeException("Octet count greater than 8 and I can't represent that!"); + } + + long result = 0; + + for (int i = 0 ; i < count ; i++) { + temp = pduDataStream.read(); + assert(-1 != temp); + result <<= 8; + result += (temp & 0xFF); + } + + return result; + } + + /** + * Parse Integer-Value. + * + * @param pduDataStream pdu data input stream + * @return long integer + */ + protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) { + /** + * From wap-230-wsp-20010705-a.pdf + * Integer-Value = Short-integer | Long-integer + */ + assert(null != pduDataStream); + pduDataStream.mark(1); + int temp = pduDataStream.read(); + assert(-1 != temp); + pduDataStream.reset(); + if (temp > SHORT_INTEGER_MAX) { + return parseShortInteger(pduDataStream); + } else { + return parseLongInteger(pduDataStream); + } + } + + /** + * To skip length of the wap value. + * + * @param pduDataStream pdu data input stream + * @param length area size + * @return the values in this area + */ + protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) { + assert(null != pduDataStream); + byte[] area = new byte[length]; + int readLen = pduDataStream.read(area, 0, length); + if (readLen < length) { //The actually read length is lower than the length + return -1; + } else { + return readLen; + } + } + + /** + * Parse content type parameters. For now we just support + * four parameters used in mms: "type", "start", "name", "charset". + * + * @param pduDataStream pdu data input stream + * @param map to store parameters of Content-Type field + * @param length length of all the parameters + */ + protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream, + HashMap map, Integer length) { + /** + * From wap-230-wsp-20010705-a.pdf + * Parameter = Typed-parameter | Untyped-parameter + * Typed-parameter = Well-known-parameter-token Typed-value + * the actual expected type of the value is implied by the well-known parameter + * Well-known-parameter-token = Integer-value + * the code values used for parameters are specified in the Assigned Numbers appendix + * Typed-value = Compact-value | Text-value + * In addition to the expected type, there may be no value. + * If the value cannot be encoded using the expected type, it shall be encoded as text. + * Compact-value = Integer-value | + * Date-value | Delta-seconds-value | Q-value | Version-value | + * Uri-value + * Untyped-parameter = Token-text Untyped-value + * the type of the value is unknown, but it shall be encoded as an integer, + * if that is possible. + * Untyped-value = Integer-value | Text-value + */ + assert(null != pduDataStream); + assert(length > 0); + + int startPos = pduDataStream.available(); + int tempPos = 0; + int lastLen = length; + while(0 < lastLen) { + int param = pduDataStream.read(); + assert(-1 != param); + lastLen--; + + switch (param) { + /** + * From rfc2387, chapter 3.1 + * The type parameter must be specified and its value is the MIME media + * type of the "root" body part. It permits a MIME user agent to + * determine the content-type without reference to the enclosed body + * part. If the value of the type parameter and the root body part's + * content-type differ then the User Agent's behavior is undefined. + * + * From wap-230-wsp-20010705-a.pdf + * type = Constrained-encoding + * Constrained-encoding = Extension-Media | Short-integer + * Extension-media = *TEXT End-of-string + */ + case PduPart.P_TYPE: + case PduPart.P_CT_MR_TYPE: + pduDataStream.mark(1); + int first = extractByteValue(pduDataStream); + pduDataStream.reset(); + if (first > TEXT_MAX) { + // Short-integer (well-known type) + int index = parseShortInteger(pduDataStream); + + if (index < PduContentTypes.contentTypes.length) { + byte[] type = (PduContentTypes.contentTypes[index]).getBytes(); + map.put(PduPart.P_TYPE, type); + } else { + //not support this type, ignore it. + } + } else { + // Text-String (extension-media) + byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if ((null != type) && (null != map)) { + map.put(PduPart.P_TYPE, type); + } + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + + /** + * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3. + * Start Parameter Referring to Presentation + * + * From rfc2387, chapter 3.2 + * The start parameter, if given, is the content-ID of the compound + * object's "root". If not present the "root" is the first body part in + * the Multipart/Related entity. The "root" is the element the + * applications processes first. + * + * From wap-230-wsp-20010705-a.pdf + * start = Text-String + */ + case PduPart.P_START: + case PduPart.P_DEP_START: + byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if ((null != start) && (null != map)) { + map.put(PduPart.P_START, start); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + + /** + * From oma-ts-mms-conf-v1_3.pdf + * In creation, the character set SHALL be either us-ascii + * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode]. + * In retrieval, both us-ascii and utf-8 SHALL be supported. + * + * From wap-230-wsp-20010705-a.pdf + * charset = Well-known-charset|Text-String + * Well-known-charset = Any-charset | Integer-value + * Both are encoded using values from Character Set + * Assignments table in Assigned Numbers + * Any-charset = + * Equivalent to the special RFC2616 charset value "*" + */ + case PduPart.P_CHARSET: + pduDataStream.mark(1); + int firstValue = extractByteValue(pduDataStream); + pduDataStream.reset(); + //Check first char + if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) || + (END_STRING_FLAG == firstValue)) { + //Text-String (extension-charset) + byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING); + try { + int charsetInt = CharacterSets.getMibEnumValue( + new String(charsetStr)); + map.put(PduPart.P_CHARSET, charsetInt); + } catch (UnsupportedEncodingException e) { + // Not a well-known charset, use "*". + Log.e(LOG_TAG, Arrays.toString(charsetStr), e); + map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET); + } + } else { + //Well-known-charset + int charset = (int) parseIntegerValue(pduDataStream); + if (map != null) { + map.put(PduPart.P_CHARSET, charset); + } + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + + /** + * From oma-ts-mms-conf-v1_3.pdf + * A name for multipart object SHALL be encoded using name-parameter + * for Content-Type header in WSP multipart headers. + * + * From wap-230-wsp-20010705-a.pdf + * name = Text-String + */ + case PduPart.P_DEP_NAME: + case PduPart.P_NAME: + byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if ((null != name) && (null != map)) { + map.put(PduPart.P_NAME, name); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + default: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "Not supported Content-Type parameter"); + } + if (-1 == skipWapValue(pduDataStream, lastLen)) { + Log.e(LOG_TAG, "Corrupt Content-Type"); + } else { + lastLen = 0; + } + break; + } + } + + if (0 != lastLen) { + Log.e(LOG_TAG, "Corrupt Content-Type"); + } + } + + /** + * Parse content type. + * + * @param pduDataStream pdu data input stream + * @param map to store parameters in Content-Type header field + * @return Content-Type value + */ + protected static byte[] parseContentType(ByteArrayInputStream pduDataStream, + HashMap map) { + /** + * From wap-230-wsp-20010705-a.pdf + * Content-type-value = Constrained-media | Content-general-form + * Content-general-form = Value-length Media-type + * Media-type = (Well-known-media | Extension-Media) *(Parameter) + */ + assert(null != pduDataStream); + + byte[] contentType = null; + pduDataStream.mark(1); + int temp = pduDataStream.read(); + assert(-1 != temp); + pduDataStream.reset(); + + int cur = (temp & 0xFF); + + if (cur < TEXT_MIN) { + int length = parseValueLength(pduDataStream); + int startPos = pduDataStream.available(); + pduDataStream.mark(1); + temp = pduDataStream.read(); + assert(-1 != temp); + pduDataStream.reset(); + int first = (temp & 0xFF); + + if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) { + contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); + } else if (first > TEXT_MAX) { + int index = parseShortInteger(pduDataStream); + + if (index < PduContentTypes.contentTypes.length) { //well-known type + contentType = (PduContentTypes.contentTypes[index]).getBytes(); + } else { + pduDataStream.reset(); + contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); + } + } else { + Log.e(LOG_TAG, "Corrupt content-type"); + return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" + } + + int endPos = pduDataStream.available(); + int parameterLen = length - (startPos - endPos); + if (parameterLen > 0) {//have parameters + parseContentTypeParams(pduDataStream, map, parameterLen); + } + + if (parameterLen < 0) { + Log.e(LOG_TAG, "Corrupt MMS message"); + return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*" + } + } else if (cur <= TEXT_MAX) { + contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING); + } else { + contentType = + (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes(); + } + + return contentType; + } + + /** + * Parse part's headers. + * + * @param pduDataStream pdu data input stream + * @param part to store the header informations of the part + * @param length length of the headers + * @return true if parse successfully, false otherwise + */ + protected static boolean parsePartHeaders(ByteArrayInputStream pduDataStream, + PduPart part, int length) { + assert(null != pduDataStream); + assert(null != part); + assert(length > 0); + + /** + * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2. + * A name for multipart object SHALL be encoded using name-parameter + * for Content-Type header in WSP multipart headers. + * In decoding, name-parameter of Content-Type SHALL be used if available. + * If name-parameter of Content-Type is not available, + * filename parameter of Content-Disposition header SHALL be used if available. + * If neither name-parameter of Content-Type header nor filename parameter + * of Content-Disposition header is available, + * Content-Location header SHALL be used if available. + * + * Within SMIL part the reference to the media object parts SHALL use + * either Content-ID or Content-Location mechanism [RFC2557] + * and the corresponding WSP part headers in media object parts + * contain the corresponding definitions. + */ + int startPos = pduDataStream.available(); + int tempPos = 0; + int lastLen = length; + while(0 < lastLen) { + int header = pduDataStream.read(); + assert(-1 != header); + lastLen--; + + if (header > TEXT_MAX) { + // Number assigned headers. + switch (header) { + case PduPart.P_CONTENT_LOCATION: + /** + * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 + * Content-location-value = Uri-value + */ + byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (null != contentLocation) { + part.setContentLocation(contentLocation); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + case PduPart.P_CONTENT_ID: + /** + * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 + * Content-ID-value = Quoted-string + */ + byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING); + if (null != contentId) { + part.setContentId(contentId); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + break; + case PduPart.P_DEP_CONTENT_DISPOSITION: + case PduPart.P_CONTENT_DISPOSITION: + /** + * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21 + * Content-disposition-value = Value-length Disposition *(Parameter) + * Disposition = Form-data | Attachment | Inline | Token-text + * Form-data = + * Attachment = + * Inline = + */ + + /* + * some carrier mmsc servers do not support content_disposition + * field correctly + */ + boolean contentDisposition = Resources.getSystem().getBoolean(com + .android.internal.R.bool.config_mms_content_disposition_support); + + if (contentDisposition) { + int len = parseValueLength(pduDataStream); + pduDataStream.mark(1); + int thisStartPos = pduDataStream.available(); + int thisEndPos = 0; + int value = pduDataStream.read(); + + if (value == PduPart.P_DISPOSITION_FROM_DATA ) { + part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA); + } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) { + part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT); + } else if (value == PduPart.P_DISPOSITION_INLINE) { + part.setContentDisposition(PduPart.DISPOSITION_INLINE); + } else { + pduDataStream.reset(); + /* Token-text */ + part.setContentDisposition(parseWapString(pduDataStream + , TYPE_TEXT_STRING)); + } + + /* get filename parameter and skip other parameters */ + thisEndPos = pduDataStream.available(); + if (thisStartPos - thisEndPos < len) { + value = pduDataStream.read(); + if (value == PduPart.P_FILENAME) { //filename is text-string + part.setFilename(parseWapString(pduDataStream + , TYPE_TEXT_STRING)); + } + + /* skip other parameters */ + thisEndPos = pduDataStream.available(); + if (thisStartPos - thisEndPos < len) { + int last = len - (thisStartPos - thisEndPos); + byte[] temp = new byte[last]; + pduDataStream.read(temp, 0, last); + } + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + } + break; + default: + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "Not supported Part headers: " + header); + } + if (-1 == skipWapValue(pduDataStream, lastLen)) { + Log.e(LOG_TAG, "Corrupt Part headers"); + return false; + } + lastLen = 0; + break; + } + } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) { + // Not assigned header. + byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING); + byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING); + + // Check the header whether it is "Content-Transfer-Encoding". + if (true == + PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) { + part.setContentTransferEncoding(tempValue); + } + + tempPos = pduDataStream.available(); + lastLen = length - (startPos - tempPos); + } else { + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "Not supported Part headers: " + header); + } + // Skip all headers of this part. + if (-1 == skipWapValue(pduDataStream, lastLen)) { + Log.e(LOG_TAG, "Corrupt Part headers"); + return false; + } + lastLen = 0; + } + } + + if (0 != lastLen) { + Log.e(LOG_TAG, "Corrupt Part headers"); + return false; + } + + return true; + } + + /** + * Check the position of a specified part. + * + * @param part the part to be checked + * @return part position, THE_FIRST_PART when it's the + * first one, THE_LAST_PART when it's the last one. + */ + private static int checkPartPosition(PduPart part) { + assert(null != part); + if ((null == mTypeParam) && + (null == mStartParam)) { + return THE_LAST_PART; + } + + /* check part's content-id */ + if (null != mStartParam) { + byte[] contentId = part.getContentId(); + if (null != contentId) { + if (true == Arrays.equals(mStartParam, contentId)) { + return THE_FIRST_PART; + } + } + } + + /* check part's content-type */ + if (null != mTypeParam) { + byte[] contentType = part.getContentType(); + if (null != contentType) { + if (true == Arrays.equals(mTypeParam, contentType)) { + return THE_FIRST_PART; + } + } + } + + return THE_LAST_PART; + } + + /** + * Check mandatory headers of a pdu. + * + * @param headers pdu headers + * @return true if the pdu has all of the mandatory headers, false otherwise. + */ + protected static boolean checkMandatoryHeader(PduHeaders headers) { + if (null == headers) { + return false; + } + + /* get message type */ + int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE); + + /* check Mms-Version field */ + int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION); + if (0 == mmsVersion) { + // Every message should have Mms-Version field. + return false; + } + + /* check mandatory header fields */ + switch (messageType) { + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + // Content-Type field. + byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); + if (null == srContentType) { + return false; + } + + // From field. + EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM); + if (null == srFrom) { + return false; + } + + // Transaction-Id field. + byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == srTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_SEND_CONF: + // Response-Status field. + int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS); + if (0 == scResponseStatus) { + return false; + } + + // Transaction-Id field. + byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == scTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + // Content-Location field. + byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION); + if (null == niContentLocation) { + return false; + } + + // Expiry field. + long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY); + if (-1 == niExpiry) { + return false; + } + + // Message-Class field. + byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS); + if (null == niMessageClass) { + return false; + } + + // Message-Size field. + long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE); + if (-1 == niMessageSize) { + return false; + } + + // Transaction-Id field. + byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == niTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + // Status field. + int nriStatus = headers.getOctet(PduHeaders.STATUS); + if (0 == nriStatus) { + return false; + } + + // Transaction-Id field. + byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == nriTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + // Content-Type field. + byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE); + if (null == rcContentType) { + return false; + } + + // Date field. + long rcDate = headers.getLongInteger(PduHeaders.DATE); + if (-1 == rcDate) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: + // Date field. + long diDate = headers.getLongInteger(PduHeaders.DATE); + if (-1 == diDate) { + return false; + } + + // Message-Id field. + byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); + if (null == diMessageId) { + return false; + } + + // Status field. + int diStatus = headers.getOctet(PduHeaders.STATUS); + if (0 == diStatus) { + return false; + } + + // To field. + EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO); + if (null == diTo) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + // Transaction-Id field. + byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID); + if (null == aiTransactionId) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: + // Date field. + long roDate = headers.getLongInteger(PduHeaders.DATE); + if (-1 == roDate) { + return false; + } + + // From field. + EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM); + if (null == roFrom) { + return false; + } + + // Message-Id field. + byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); + if (null == roMessageId) { + return false; + } + + // Read-Status field. + int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS); + if (0 == roReadStatus) { + return false; + } + + // To field. + EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO); + if (null == roTo) { + return false; + } + + break; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + // From field. + EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM); + if (null == rrFrom) { + return false; + } + + // Message-Id field. + byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID); + if (null == rrMessageId) { + return false; + } + + // Read-Status field. + int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS); + if (0 == rrReadStatus) { + return false; + } + + // To field. + EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO); + if (null == rrTo) { + return false; + } + + break; + default: + // Parser doesn't support this message type in this version. + return false; + } + + return true; + } +} diff --git a/src/java/com/google/android/mms/pdu/PduPart.java b/src/java/com/google/android/mms/pdu/PduPart.java new file mode 100644 index 0000000..b43e388 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduPart.java @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.net.Uri; + +import java.util.HashMap; +import java.util.Map; + +/** + * The pdu part. + */ +public class PduPart { + /** + * Well-Known Parameters. + */ + public static final int P_Q = 0x80; + public static final int P_CHARSET = 0x81; + public static final int P_LEVEL = 0x82; + public static final int P_TYPE = 0x83; + public static final int P_DEP_NAME = 0x85; + public static final int P_DEP_FILENAME = 0x86; + public static final int P_DIFFERENCES = 0x87; + public static final int P_PADDING = 0x88; + // This value of "TYPE" s used with Content-Type: multipart/related + public static final int P_CT_MR_TYPE = 0x89; + public static final int P_DEP_START = 0x8A; + public static final int P_DEP_START_INFO = 0x8B; + public static final int P_DEP_COMMENT = 0x8C; + public static final int P_DEP_DOMAIN = 0x8D; + public static final int P_MAX_AGE = 0x8E; + public static final int P_DEP_PATH = 0x8F; + public static final int P_SECURE = 0x90; + public static final int P_SEC = 0x91; + public static final int P_MAC = 0x92; + public static final int P_CREATION_DATE = 0x93; + public static final int P_MODIFICATION_DATE = 0x94; + public static final int P_READ_DATE = 0x95; + public static final int P_SIZE = 0x96; + public static final int P_NAME = 0x97; + public static final int P_FILENAME = 0x98; + public static final int P_START = 0x99; + public static final int P_START_INFO = 0x9A; + public static final int P_COMMENT = 0x9B; + public static final int P_DOMAIN = 0x9C; + public static final int P_PATH = 0x9D; + + /** + * Header field names. + */ + public static final int P_CONTENT_TYPE = 0x91; + public static final int P_CONTENT_LOCATION = 0x8E; + public static final int P_CONTENT_ID = 0xC0; + public static final int P_DEP_CONTENT_DISPOSITION = 0xAE; + public static final int P_CONTENT_DISPOSITION = 0xC5; + // The next header is unassigned header, use reserved header(0x48) value. + public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8; + + /** + * Content=Transfer-Encoding string. + */ + public static final String CONTENT_TRANSFER_ENCODING = + "Content-Transfer-Encoding"; + + /** + * Value of Content-Transfer-Encoding. + */ + public static final String P_BINARY = "binary"; + public static final String P_7BIT = "7bit"; + public static final String P_8BIT = "8bit"; + public static final String P_BASE64 = "base64"; + public static final String P_QUOTED_PRINTABLE = "quoted-printable"; + + /** + * Value of disposition can be set to PduPart when the value is octet in + * the PDU. + * "from-data" instead of Form-data. + * "attachment" instead of Attachment. + * "inline" instead of Inline. + */ + static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes(); + static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes(); + static final byte[] DISPOSITION_INLINE = "inline".getBytes(); + + /** + * Content-Disposition value. + */ + public static final int P_DISPOSITION_FROM_DATA = 0x80; + public static final int P_DISPOSITION_ATTACHMENT = 0x81; + public static final int P_DISPOSITION_INLINE = 0x82; + + /** + * Header of part. + */ + private Map mPartHeader = null; + + /** + * Data uri. + */ + private Uri mUri = null; + + /** + * Part data. + */ + private byte[] mPartData = null; + + private static final String TAG = "PduPart"; + + /** + * Empty Constructor. + */ + public PduPart() { + mPartHeader = new HashMap(); + } + + /** + * Set part data. The data are stored as byte array. + * + * @param data the data + */ + public void setData(byte[] data) { + if(data == null) { + return; + } + + mPartData = new byte[data.length]; + System.arraycopy(data, 0, mPartData, 0, data.length); + } + + /** + * @return A copy of the part data or null if the data wasn't set or + * the data is stored as Uri. + * @see #getDataUri + */ + public byte[] getData() { + if(mPartData == null) { + return null; + } + + byte[] byteArray = new byte[mPartData.length]; + System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length); + return byteArray; + } + + /** + * Set data uri. The data are stored as Uri. + * + * @param uri the uri + */ + public void setDataUri(Uri uri) { + mUri = uri; + } + + /** + * @return The Uri of the part data or null if the data wasn't set or + * the data is stored as byte array. + * @see #getData + */ + public Uri getDataUri() { + return mUri; + } + + /** + * Set Content-id value + * + * @param contentId the content-id value + * @throws NullPointerException if the value is null. + */ + public void setContentId(byte[] contentId) { + if((contentId == null) || (contentId.length == 0)) { + throw new IllegalArgumentException( + "Content-Id may not be null or empty."); + } + + if ((contentId.length > 1) + && ((char) contentId[0] == '<') + && ((char) contentId[contentId.length - 1] == '>')) { + mPartHeader.put(P_CONTENT_ID, contentId); + return; + } + + // Insert beginning '<' and trailing '>' for Content-Id. + byte[] buffer = new byte[contentId.length + 2]; + buffer[0] = (byte) (0xff & '<'); + buffer[buffer.length - 1] = (byte) (0xff & '>'); + System.arraycopy(contentId, 0, buffer, 1, contentId.length); + mPartHeader.put(P_CONTENT_ID, buffer); + } + + /** + * Get Content-id value. + * + * @return the value + */ + public byte[] getContentId() { + return (byte[]) mPartHeader.get(P_CONTENT_ID); + } + + /** + * Set Char-set value. + * + * @param charset the value + */ + public void setCharset(int charset) { + mPartHeader.put(P_CHARSET, charset); + } + + /** + * Get Char-set value + * + * @return the charset value. Return 0 if charset was not set. + */ + public int getCharset() { + Integer charset = (Integer) mPartHeader.get(P_CHARSET); + if(charset == null) { + return 0; + } else { + return charset.intValue(); + } + } + + /** + * Set Content-Location value. + * + * @param contentLocation the value + * @throws NullPointerException if the value is null. + */ + public void setContentLocation(byte[] contentLocation) { + if(contentLocation == null) { + throw new NullPointerException("null content-location"); + } + + mPartHeader.put(P_CONTENT_LOCATION, contentLocation); + } + + /** + * Get Content-Location value. + * + * @return the value + * return PduPart.disposition[0] instead of (Form-data). + * return PduPart.disposition[1] instead of (Attachment). + * return PduPart.disposition[2] instead of (Inline). + */ + public byte[] getContentLocation() { + return (byte[]) mPartHeader.get(P_CONTENT_LOCATION); + } + + /** + * Set Content-Disposition value. + * Use PduPart.disposition[0] instead of (Form-data). + * Use PduPart.disposition[1] instead of (Attachment). + * Use PduPart.disposition[2] instead of (Inline). + * + * @param contentDisposition the value + * @throws NullPointerException if the value is null. + */ + public void setContentDisposition(byte[] contentDisposition) { + if(contentDisposition == null) { + throw new NullPointerException("null content-disposition"); + } + + mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition); + } + + /** + * Get Content-Disposition value. + * + * @return the value + */ + public byte[] getContentDisposition() { + return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION); + } + + /** + * Set Content-Type value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setContentType(byte[] contentType) { + if(contentType == null) { + throw new NullPointerException("null content-type"); + } + + mPartHeader.put(P_CONTENT_TYPE, contentType); + } + + /** + * Get Content-Type value of part. + * + * @return the value + */ + public byte[] getContentType() { + return (byte[]) mPartHeader.get(P_CONTENT_TYPE); + } + + /** + * Set Content-Transfer-Encoding value + * + * @param contentId the content-id value + * @throws NullPointerException if the value is null. + */ + public void setContentTransferEncoding(byte[] contentTransferEncoding) { + if(contentTransferEncoding == null) { + throw new NullPointerException("null content-transfer-encoding"); + } + + mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding); + } + + /** + * Get Content-Transfer-Encoding value. + * + * @return the value + */ + public byte[] getContentTransferEncoding() { + return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING); + } + + /** + * Set Content-type parameter: name. + * + * @param name the name value + * @throws NullPointerException if the value is null. + */ + public void setName(byte[] name) { + if(null == name) { + throw new NullPointerException("null content-id"); + } + + mPartHeader.put(P_NAME, name); + } + + /** + * Get content-type parameter: name. + * + * @return the name + */ + public byte[] getName() { + return (byte[]) mPartHeader.get(P_NAME); + } + + /** + * Get Content-disposition parameter: filename + * + * @param fileName the filename value + * @throws NullPointerException if the value is null. + */ + public void setFilename(byte[] fileName) { + if(null == fileName) { + throw new NullPointerException("null content-id"); + } + + mPartHeader.put(P_FILENAME, fileName); + } + + /** + * Set Content-disposition parameter: filename + * + * @return the filename + */ + public byte[] getFilename() { + return (byte[]) mPartHeader.get(P_FILENAME); + } + + public String generateLocation() { + // Assumption: At least one of the content-location / name / filename + // or content-id should be set. This is guaranteed by the PduParser + // for incoming messages and by MM composer for outgoing messages. + byte[] location = (byte[]) mPartHeader.get(P_NAME); + if(null == location) { + location = (byte[]) mPartHeader.get(P_FILENAME); + + if (null == location) { + location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION); + } + } + + if (null == location) { + byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID); + return "cid:" + new String(contentId); + } else { + return new String(location); + } + } +} + diff --git a/src/java/com/google/android/mms/pdu/PduPersister.java b/src/java/com/google/android/mms/pdu/PduPersister.java new file mode 100644 index 0000000..ee285aa --- /dev/null +++ b/src/java/com/google/android/mms/pdu/PduPersister.java @@ -0,0 +1,1477 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import com.google.android.mms.ContentType; +import com.google.android.mms.InvalidHeaderValueException; +import com.google.android.mms.MmsException; +import com.google.android.mms.util.DownloadDrmHelper; +import com.google.android.mms.util.DrmConvertSession; +import com.google.android.mms.util.PduCache; +import com.google.android.mms.util.PduCacheEntry; +import com.google.android.mms.util.SqliteWrapper; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteException; +import android.drm.DrmManagerClient; +import android.net.Uri; +import android.os.FileUtils; +import android.provider.MediaStore; +import android.provider.Telephony; +import android.provider.Telephony.Mms; +import android.provider.Telephony.MmsSms; +import android.provider.Telephony.Threads; +import android.provider.Telephony.Mms.Addr; +import android.provider.Telephony.Mms.Part; +import android.provider.Telephony.MmsSms.PendingMessages; +import android.text.TextUtils; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import com.google.android.mms.pdu.EncodedStringValue; + +/** + * This class is the high-level manager of PDU storage. + */ +public class PduPersister { + private static final String TAG = "PduPersister"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + private static final long DUMMY_THREAD_ID = Long.MAX_VALUE; + + /** + * The uri of temporary drm objects. + */ + public static final String TEMPORARY_DRM_OBJECT_URI = + "content://mms/" + Long.MAX_VALUE + "/part"; + /** + * Indicate that we transiently failed to process a MM. + */ + public static final int PROC_STATUS_TRANSIENT_FAILURE = 1; + /** + * Indicate that we permanently failed to process a MM. + */ + public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2; + /** + * Indicate that we have successfully processed a MM. + */ + public static final int PROC_STATUS_COMPLETED = 3; + + private static PduPersister sPersister; + private static final PduCache PDU_CACHE_INSTANCE; + + private static final int[] ADDRESS_FIELDS = new int[] { + PduHeaders.BCC, + PduHeaders.CC, + PduHeaders.FROM, + PduHeaders.TO + }; + + private static final String[] PDU_PROJECTION = new String[] { + Mms._ID, + Mms.MESSAGE_BOX, + Mms.THREAD_ID, + Mms.RETRIEVE_TEXT, + Mms.SUBJECT, + Mms.CONTENT_LOCATION, + Mms.CONTENT_TYPE, + Mms.MESSAGE_CLASS, + Mms.MESSAGE_ID, + Mms.RESPONSE_TEXT, + Mms.TRANSACTION_ID, + Mms.CONTENT_CLASS, + Mms.DELIVERY_REPORT, + Mms.MESSAGE_TYPE, + Mms.MMS_VERSION, + Mms.PRIORITY, + Mms.READ_REPORT, + Mms.READ_STATUS, + Mms.REPORT_ALLOWED, + Mms.RETRIEVE_STATUS, + Mms.STATUS, + Mms.DATE, + Mms.DELIVERY_TIME, + Mms.EXPIRY, + Mms.MESSAGE_SIZE, + Mms.SUBJECT_CHARSET, + Mms.RETRIEVE_TEXT_CHARSET, + }; + + private static final int PDU_COLUMN_ID = 0; + private static final int PDU_COLUMN_MESSAGE_BOX = 1; + private static final int PDU_COLUMN_THREAD_ID = 2; + private static final int PDU_COLUMN_RETRIEVE_TEXT = 3; + private static final int PDU_COLUMN_SUBJECT = 4; + private static final int PDU_COLUMN_CONTENT_LOCATION = 5; + private static final int PDU_COLUMN_CONTENT_TYPE = 6; + private static final int PDU_COLUMN_MESSAGE_CLASS = 7; + private static final int PDU_COLUMN_MESSAGE_ID = 8; + private static final int PDU_COLUMN_RESPONSE_TEXT = 9; + private static final int PDU_COLUMN_TRANSACTION_ID = 10; + private static final int PDU_COLUMN_CONTENT_CLASS = 11; + private static final int PDU_COLUMN_DELIVERY_REPORT = 12; + private static final int PDU_COLUMN_MESSAGE_TYPE = 13; + private static final int PDU_COLUMN_MMS_VERSION = 14; + private static final int PDU_COLUMN_PRIORITY = 15; + private static final int PDU_COLUMN_READ_REPORT = 16; + private static final int PDU_COLUMN_READ_STATUS = 17; + private static final int PDU_COLUMN_REPORT_ALLOWED = 18; + private static final int PDU_COLUMN_RETRIEVE_STATUS = 19; + private static final int PDU_COLUMN_STATUS = 20; + private static final int PDU_COLUMN_DATE = 21; + private static final int PDU_COLUMN_DELIVERY_TIME = 22; + private static final int PDU_COLUMN_EXPIRY = 23; + private static final int PDU_COLUMN_MESSAGE_SIZE = 24; + private static final int PDU_COLUMN_SUBJECT_CHARSET = 25; + private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26; + + private static final String[] PART_PROJECTION = new String[] { + Part._ID, + Part.CHARSET, + Part.CONTENT_DISPOSITION, + Part.CONTENT_ID, + Part.CONTENT_LOCATION, + Part.CONTENT_TYPE, + Part.FILENAME, + Part.NAME, + Part.TEXT + }; + + private static final int PART_COLUMN_ID = 0; + private static final int PART_COLUMN_CHARSET = 1; + private static final int PART_COLUMN_CONTENT_DISPOSITION = 2; + private static final int PART_COLUMN_CONTENT_ID = 3; + private static final int PART_COLUMN_CONTENT_LOCATION = 4; + private static final int PART_COLUMN_CONTENT_TYPE = 5; + private static final int PART_COLUMN_FILENAME = 6; + private static final int PART_COLUMN_NAME = 7; + private static final int PART_COLUMN_TEXT = 8; + + private static final HashMap MESSAGE_BOX_MAP; + // These map are used for convenience in persist() and load(). + private static final HashMap CHARSET_COLUMN_INDEX_MAP; + private static final HashMap ENCODED_STRING_COLUMN_INDEX_MAP; + private static final HashMap TEXT_STRING_COLUMN_INDEX_MAP; + private static final HashMap OCTET_COLUMN_INDEX_MAP; + private static final HashMap LONG_COLUMN_INDEX_MAP; + private static final HashMap CHARSET_COLUMN_NAME_MAP; + private static final HashMap ENCODED_STRING_COLUMN_NAME_MAP; + private static final HashMap TEXT_STRING_COLUMN_NAME_MAP; + private static final HashMap OCTET_COLUMN_NAME_MAP; + private static final HashMap LONG_COLUMN_NAME_MAP; + + static { + MESSAGE_BOX_MAP = new HashMap(); + MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX); + MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT); + MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS); + MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX); + + CHARSET_COLUMN_INDEX_MAP = new HashMap(); + CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET); + CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET); + + CHARSET_COLUMN_NAME_MAP = new HashMap(); + CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET); + CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET); + + // Encoded string field code -> column index/name map. + ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap(); + ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT); + ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT); + + ENCODED_STRING_COLUMN_NAME_MAP = new HashMap(); + ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT); + ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT); + + // Text string field code -> column index/name map. + TEXT_STRING_COLUMN_INDEX_MAP = new HashMap(); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT); + TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID); + + TEXT_STRING_COLUMN_NAME_MAP = new HashMap(); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT); + TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID); + + // Octet field code -> column index/name map. + OCTET_COLUMN_INDEX_MAP = new HashMap(); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS); + OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS); + + OCTET_COLUMN_NAME_MAP = new HashMap(); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS); + OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS); + + // Long field code -> column index/name map. + LONG_COLUMN_INDEX_MAP = new HashMap(); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY); + LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE); + + LONG_COLUMN_NAME_MAP = new HashMap(); + LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE); + LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME); + LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY); + LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE); + + PDU_CACHE_INSTANCE = PduCache.getInstance(); + } + + private final Context mContext; + private final ContentResolver mContentResolver; + private final DrmManagerClient mDrmManagerClient; + + private PduPersister(Context context) { + mContext = context; + mContentResolver = context.getContentResolver(); + mDrmManagerClient = new DrmManagerClient(context); + } + + /** Get(or create if not exist) an instance of PduPersister */ + public static PduPersister getPduPersister(Context context) { + if ((sPersister == null) || !context.equals(sPersister.mContext)) { + sPersister = new PduPersister(context); + } + + return sPersister; + } + + private void setEncodedStringValueToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) { + String s = c.getString(columnIndex); + if ((s != null) && (s.length() > 0)) { + int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn); + int charset = c.getInt(charsetColumnIndex); + EncodedStringValue value = new EncodedStringValue( + charset, getBytes(s)); + headers.setEncodedStringValue(value, mapColumn); + } + } + + private void setTextStringToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) { + String s = c.getString(columnIndex); + if (s != null) { + headers.setTextString(getBytes(s), mapColumn); + } + } + + private void setOctetToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) throws InvalidHeaderValueException { + if (!c.isNull(columnIndex)) { + int b = c.getInt(columnIndex); + headers.setOctet(b, mapColumn); + } + } + + private void setLongToHeaders( + Cursor c, int columnIndex, + PduHeaders headers, int mapColumn) { + if (!c.isNull(columnIndex)) { + long l = c.getLong(columnIndex); + headers.setLongInteger(l, mapColumn); + } + } + + private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) { + if (!c.isNull(columnIndex)) { + return c.getInt(columnIndex); + } + return null; + } + + private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) { + if (!c.isNull(columnIndex)) { + return getBytes(c.getString(columnIndex)); + } + return null; + } + + private PduPart[] loadParts(long msgId) throws MmsException { + Cursor c = SqliteWrapper.query(mContext, mContentResolver, + Uri.parse("content://mms/" + msgId + "/part"), + PART_PROJECTION, null, null, null); + + PduPart[] parts = null; + + try { + if ((c == null) || (c.getCount() == 0)) { + if (LOCAL_LOGV) { + Log.v(TAG, "loadParts(" + msgId + "): no part to load."); + } + return null; + } + + int partCount = c.getCount(); + int partIdx = 0; + parts = new PduPart[partCount]; + while (c.moveToNext()) { + PduPart part = new PduPart(); + Integer charset = getIntegerFromPartColumn( + c, PART_COLUMN_CHARSET); + if (charset != null) { + part.setCharset(charset); + } + + byte[] contentDisposition = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_DISPOSITION); + if (contentDisposition != null) { + part.setContentDisposition(contentDisposition); + } + + byte[] contentId = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_ID); + if (contentId != null) { + part.setContentId(contentId); + } + + byte[] contentLocation = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_LOCATION); + if (contentLocation != null) { + part.setContentLocation(contentLocation); + } + + byte[] contentType = getByteArrayFromPartColumn( + c, PART_COLUMN_CONTENT_TYPE); + if (contentType != null) { + part.setContentType(contentType); + } else { + throw new MmsException("Content-Type must be set."); + } + + byte[] fileName = getByteArrayFromPartColumn( + c, PART_COLUMN_FILENAME); + if (fileName != null) { + part.setFilename(fileName); + } + + byte[] name = getByteArrayFromPartColumn( + c, PART_COLUMN_NAME); + if (name != null) { + part.setName(name); + } + + // Construct a Uri for this part. + long partId = c.getLong(PART_COLUMN_ID); + Uri partURI = Uri.parse("content://mms/part/" + partId); + part.setDataUri(partURI); + + // For images/audio/video, we won't keep their data in Part + // because their renderer accept Uri as source. + String type = toIsoString(contentType); + if (!ContentType.isImageType(type) + && !ContentType.isAudioType(type) + && !ContentType.isVideoType(type)) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = null; + + // Store simple string values directly in the database instead of an + // external file. This makes the text searchable and retrieval slightly + // faster. + if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type) + || ContentType.TEXT_HTML.equals(type)) { + String text = c.getString(PART_COLUMN_TEXT); + byte [] blob = new EncodedStringValue(text != null ? text : "") + .getTextString(); + baos.write(blob, 0, blob.length); + } else { + + try { + is = mContentResolver.openInputStream(partURI); + + byte[] buffer = new byte[256]; + int len = is.read(buffer); + while (len >= 0) { + baos.write(buffer, 0, len); + len = is.read(buffer); + } + } catch (IOException e) { + Log.e(TAG, "Failed to load part data", e); + c.close(); + throw new MmsException(e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(TAG, "Failed to close stream", e); + } // Ignore + } + } + } + part.setData(baos.toByteArray()); + } + parts[partIdx++] = part; + } + } finally { + if (c != null) { + c.close(); + } + } + + return parts; + } + + private void loadAddress(long msgId, PduHeaders headers) { + Cursor c = SqliteWrapper.query(mContext, mContentResolver, + Uri.parse("content://mms/" + msgId + "/addr"), + new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE }, + null, null, null); + + if (c != null) { + try { + while (c.moveToNext()) { + String addr = c.getString(0); + if (!TextUtils.isEmpty(addr)) { + int addrType = c.getInt(2); + switch (addrType) { + case PduHeaders.FROM: + headers.setEncodedStringValue( + new EncodedStringValue(c.getInt(1), getBytes(addr)), + addrType); + break; + case PduHeaders.TO: + case PduHeaders.CC: + case PduHeaders.BCC: + headers.appendEncodedStringValue( + new EncodedStringValue(c.getInt(1), getBytes(addr)), + addrType); + break; + default: + Log.e(TAG, "Unknown address type: " + addrType); + break; + } + } + } + } finally { + c.close(); + } + } + } + + /** + * Load a PDU from storage by given Uri. + * + * @param uri The Uri of the PDU to be loaded. + * @return A generic PDU object, it may be cast to dedicated PDU. + * @throws MmsException Failed to load some fields of a PDU. + */ + public GenericPdu load(Uri uri) throws MmsException { + GenericPdu pdu = null; + PduCacheEntry cacheEntry = null; + int msgBox = 0; + long threadId = -1; + try { + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "load: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + return cacheEntry.getPdu(); + } + } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); + } + + Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, + PDU_PROJECTION, null, null, null); + PduHeaders headers = new PduHeaders(); + Set> set; + long msgId = ContentUris.parseId(uri); + + try { + if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { + throw new MmsException("Bad uri: " + uri); + } + + msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); + threadId = c.getLong(PDU_COLUMN_THREAD_ID); + + set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setEncodedStringValueToHeaders( + c, e.getValue(), headers, e.getKey()); + } + + set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setTextStringToHeaders( + c, e.getValue(), headers, e.getKey()); + } + + set = OCTET_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setOctetToHeaders( + c, e.getValue(), headers, e.getKey()); + } + + set = LONG_COLUMN_INDEX_MAP.entrySet(); + for (Entry e : set) { + setLongToHeaders( + c, e.getValue(), headers, e.getKey()); + } + } finally { + if (c != null) { + c.close(); + } + } + + // Check whether 'msgId' has been assigned a valid value. + if (msgId == -1L) { + throw new MmsException("Error! ID of the message: -1."); + } + + // Load address information of the MM. + loadAddress(msgId, headers); + + int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); + PduBody body = new PduBody(); + + // For PDU which type is M_retrieve.conf or Send.req, we should + // load multiparts and put them into the body of the PDU. + if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) + || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { + PduPart[] parts = loadParts(msgId); + if (parts != null) { + int partsNum = parts.length; + for (int i = 0; i < partsNum; i++) { + body.addPart(parts[i]); + } + } + } + + switch (msgType) { + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + pdu = new NotificationInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: + pdu = new DeliveryInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: + pdu = new ReadOrigInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + pdu = new RetrieveConf(headers, body); + break; + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + pdu = new SendReq(headers, body); + break; + case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: + pdu = new AcknowledgeInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: + pdu = new NotifyRespInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_READ_REC_IND: + pdu = new ReadRecInd(headers); + break; + case PduHeaders.MESSAGE_TYPE_SEND_CONF: + case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: + case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: + case PduHeaders.MESSAGE_TYPE_DELETE_REQ: + case PduHeaders.MESSAGE_TYPE_DELETE_CONF: + case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: + case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: + throw new MmsException( + "Unsupported PDU type: " + Integer.toHexString(msgType)); + + default: + throw new MmsException( + "Unrecognized PDU type: " + Integer.toHexString(msgType)); + } + } finally { + synchronized(PDU_CACHE_INSTANCE) { + if (pdu != null) { + assert(PDU_CACHE_INSTANCE.get(uri) == null); + // Update the cache entry with the real info + cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); + PDU_CACHE_INSTANCE.put(uri, cacheEntry); + } + PDU_CACHE_INSTANCE.setUpdating(uri, false); + PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead + } + } + return pdu; + } + + private void persistAddress( + long msgId, int type, EncodedStringValue[] array) { + ContentValues values = new ContentValues(3); + + for (EncodedStringValue addr : array) { + values.clear(); // Clear all values first. + values.put(Addr.ADDRESS, toIsoString(addr.getTextString())); + values.put(Addr.CHARSET, addr.getCharacterSet()); + values.put(Addr.TYPE, type); + + Uri uri = Uri.parse("content://mms/" + msgId + "/addr"); + SqliteWrapper.insert(mContext, mContentResolver, uri, values); + } + } + + public Uri persistPart(PduPart part, long msgId) + throws MmsException { + Uri uri = Uri.parse("content://mms/" + msgId + "/part"); + ContentValues values = new ContentValues(8); + + int charset = part.getCharset(); + if (charset != 0 ) { + values.put(Part.CHARSET, charset); + } + + String contentType = null; + if (part.getContentType() != null) { + contentType = toIsoString(part.getContentType()); + + // There is no "image/jpg" in Android (and it's an invalid mimetype). + // Change it to "image/jpeg" + if (ContentType.IMAGE_JPG.equals(contentType)) { + contentType = ContentType.IMAGE_JPEG; + } + + values.put(Part.CONTENT_TYPE, contentType); + // To ensure the SMIL part is always the first part. + if (ContentType.APP_SMIL.equals(contentType)) { + values.put(Part.SEQ, -1); + } + } else { + throw new MmsException("MIME type of the part must be set."); + } + + if (part.getFilename() != null) { + String fileName = new String(part.getFilename()); + values.put(Part.FILENAME, fileName); + } + + if (part.getName() != null) { + String name = new String(part.getName()); + values.put(Part.NAME, name); + } + + Object value = null; + if (part.getContentDisposition() != null) { + value = toIsoString(part.getContentDisposition()); + values.put(Part.CONTENT_DISPOSITION, (String) value); + } + + if (part.getContentId() != null) { + value = toIsoString(part.getContentId()); + values.put(Part.CONTENT_ID, (String) value); + } + + if (part.getContentLocation() != null) { + value = toIsoString(part.getContentLocation()); + values.put(Part.CONTENT_LOCATION, (String) value); + } + + Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); + if (res == null) { + throw new MmsException("Failed to persist part, return null."); + } + + persistData(part, res, contentType); + // After successfully store the data, we should update + // the dataUri of the part. + part.setDataUri(res); + + return res; + } + + /** + * Save data of the part into storage. The source data may be given + * by a byte[] or a Uri. If it's a byte[], directly save it + * into storage, otherwise load source data from the dataUri and then + * save it. If the data is an image, we may scale down it according + * to user preference. + * + * @param part The PDU part which contains data to be saved. + * @param uri The URI of the part. + * @param contentType The MIME type of the part. + * @throws MmsException Cannot find source data or error occurred + * while saving the data. + */ + private void persistData(PduPart part, Uri uri, + String contentType) + throws MmsException { + OutputStream os = null; + InputStream is = null; + DrmConvertSession drmConvertSession = null; + Uri dataUri = null; + String path = null; + + try { + byte[] data = part.getData(); + if (ContentType.TEXT_PLAIN.equals(contentType) + || ContentType.APP_SMIL.equals(contentType) + || ContentType.TEXT_HTML.equals(contentType)) { + ContentValues cv = new ContentValues(); + cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString()); + if (mContentResolver.update(uri, cv, null, null) != 1) { + throw new MmsException("unable to update " + uri.toString()); + } + } else { + boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType); + if (isDrm) { + if (uri != null) { + try { + path = convertUriToPath(mContext, uri); + if (LOCAL_LOGV) { + Log.v(TAG, "drm uri: " + uri + " path: " + path); + } + File f = new File(path); + long len = f.length(); + if (LOCAL_LOGV) { + Log.v(TAG, "drm path: " + path + " len: " + len); + } + if (len > 0) { + // we're not going to re-persist and re-encrypt an already + // converted drm file + return; + } + } catch (Exception e) { + Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e); + } + } + // We haven't converted the file yet, start the conversion + drmConvertSession = DrmConvertSession.open(mContext, contentType); + if (drmConvertSession == null) { + throw new MmsException("Mimetype " + contentType + + " can not be converted."); + } + } + os = mContentResolver.openOutputStream(uri); + if (data == null) { + dataUri = part.getDataUri(); + if ((dataUri == null) || (dataUri == uri)) { + Log.w(TAG, "Can't find data for this part."); + return; + } + is = mContentResolver.openInputStream(dataUri); + + if (LOCAL_LOGV) { + Log.v(TAG, "Saving data to: " + uri); + } + + byte[] buffer = new byte[8192]; + for (int len = 0; (len = is.read(buffer)) != -1; ) { + if (!isDrm) { + os.write(buffer, 0, len); + } else { + byte[] convertedData = drmConvertSession.convert(buffer, len); + if (convertedData != null) { + os.write(convertedData, 0, convertedData.length); + } else { + throw new MmsException("Error converting drm data."); + } + } + } + } else { + if (LOCAL_LOGV) { + Log.v(TAG, "Saving data to: " + uri); + } + if (!isDrm) { + os.write(data); + } else { + dataUri = uri; + byte[] convertedData = drmConvertSession.convert(data, data.length); + if (convertedData != null) { + os.write(convertedData, 0, convertedData.length); + } else { + throw new MmsException("Error converting drm data."); + } + } + } + } + } catch (FileNotFoundException e) { + Log.e(TAG, "Failed to open Input/Output stream.", e); + throw new MmsException(e); + } catch (IOException e) { + Log.e(TAG, "Failed to read/write data.", e); + throw new MmsException(e); + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e) { + Log.e(TAG, "IOException while closing: " + os, e); + } // Ignore + } + if (is != null) { + try { + is.close(); + } catch (IOException e) { + Log.e(TAG, "IOException while closing: " + is, e); + } // Ignore + } + if (drmConvertSession != null) { + drmConvertSession.close(path); + + // Reset the permissions on the encrypted part file so everyone has only read + // permission. + File f = new File(path); + ContentValues values = new ContentValues(0); + SqliteWrapper.update(mContext, mContentResolver, + Uri.parse("content://mms/resetFilePerm/" + f.getName()), + values, null, null); + } + } + } + + /** + * This method expects uri in the following format + * content://media// (or) + * file://sdcard/test.mp4 + * http://test.com/test.mp4 + * + * Here shall be "video" or "audio" or "images" + * the index of the content in given table + */ + static public String convertUriToPath(Context context, Uri uri) { + String path = null; + if (null != uri) { + String scheme = uri.getScheme(); + if (null == scheme || scheme.equals("") || + scheme.equals(ContentResolver.SCHEME_FILE)) { + path = uri.getPath(); + + } else if (scheme.equals("http")) { + path = uri.toString(); + + } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) { + String[] projection = new String[] {MediaStore.MediaColumns.DATA}; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, projection, null, + null, null); + if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) { + throw new IllegalArgumentException("Given Uri could not be found" + + " in media store"); + } + int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + path = cursor.getString(pathIndex); + } catch (SQLiteException e) { + throw new IllegalArgumentException("Given Uri is not formatted in a way " + + "so that it can be found in media store."); + } finally { + if (null != cursor) { + cursor.close(); + } + } + } else { + throw new IllegalArgumentException("Given Uri scheme is not supported"); + } + } + return path; + } + + private void updateAddress( + long msgId, int type, EncodedStringValue[] array) { + // Delete old address information and then insert new ones. + SqliteWrapper.delete(mContext, mContentResolver, + Uri.parse("content://mms/" + msgId + "/addr"), + Addr.TYPE + "=" + type, null); + + persistAddress(msgId, type, array); + } + + /** + * Update headers of a SendReq. + * + * @param uri The PDU which need to be updated. + * @param pdu New headers. + * @throws MmsException Bad URI or updating failed. + */ + public void updateHeaders(Uri uri, SendReq sendReq) { + synchronized(PDU_CACHE_INSTANCE) { + // If the cache item is getting updated, wait until it's done updating before + // purging it. + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "updateHeaders: ", e); + } + } + } + PDU_CACHE_INSTANCE.purge(uri); + + ContentValues values = new ContentValues(10); + byte[] contentType = sendReq.getContentType(); + if (contentType != null) { + values.put(Mms.CONTENT_TYPE, toIsoString(contentType)); + } + + long date = sendReq.getDate(); + if (date != -1) { + values.put(Mms.DATE, date); + } + + int deliveryReport = sendReq.getDeliveryReport(); + if (deliveryReport != 0) { + values.put(Mms.DELIVERY_REPORT, deliveryReport); + } + + long expiry = sendReq.getExpiry(); + if (expiry != -1) { + values.put(Mms.EXPIRY, expiry); + } + + byte[] msgClass = sendReq.getMessageClass(); + if (msgClass != null) { + values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass)); + } + + int priority = sendReq.getPriority(); + if (priority != 0) { + values.put(Mms.PRIORITY, priority); + } + + int readReport = sendReq.getReadReport(); + if (readReport != 0) { + values.put(Mms.READ_REPORT, readReport); + } + + byte[] transId = sendReq.getTransactionId(); + if (transId != null) { + values.put(Mms.TRANSACTION_ID, toIsoString(transId)); + } + + EncodedStringValue subject = sendReq.getSubject(); + if (subject != null) { + values.put(Mms.SUBJECT, toIsoString(subject.getTextString())); + values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet()); + } else { + values.put(Mms.SUBJECT, ""); + } + + long messageSize = sendReq.getMessageSize(); + if (messageSize > 0) { + values.put(Mms.MESSAGE_SIZE, messageSize); + } + + PduHeaders headers = sendReq.getPduHeaders(); + HashSet recipients = new HashSet(); + for (int addrType : ADDRESS_FIELDS) { + EncodedStringValue[] array = null; + if (addrType == PduHeaders.FROM) { + EncodedStringValue v = headers.getEncodedStringValue(addrType); + if (v != null) { + array = new EncodedStringValue[1]; + array[0] = v; + } + } else { + array = headers.getEncodedStringValues(addrType); + } + + if (array != null) { + long msgId = ContentUris.parseId(uri); + updateAddress(msgId, addrType, array); + if (addrType == PduHeaders.TO) { + for (EncodedStringValue v : array) { + if (v != null) { + recipients.add(v.getString()); + } + } + } + } + } + if (!recipients.isEmpty()) { + long threadId = Threads.getOrCreateThreadId(mContext, recipients); + values.put(Mms.THREAD_ID, threadId); + } + + SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); + } + + private void updatePart(Uri uri, PduPart part) throws MmsException { + ContentValues values = new ContentValues(7); + + int charset = part.getCharset(); + if (charset != 0 ) { + values.put(Part.CHARSET, charset); + } + + String contentType = null; + if (part.getContentType() != null) { + contentType = toIsoString(part.getContentType()); + values.put(Part.CONTENT_TYPE, contentType); + } else { + throw new MmsException("MIME type of the part must be set."); + } + + if (part.getFilename() != null) { + String fileName = new String(part.getFilename()); + values.put(Part.FILENAME, fileName); + } + + if (part.getName() != null) { + String name = new String(part.getName()); + values.put(Part.NAME, name); + } + + Object value = null; + if (part.getContentDisposition() != null) { + value = toIsoString(part.getContentDisposition()); + values.put(Part.CONTENT_DISPOSITION, (String) value); + } + + if (part.getContentId() != null) { + value = toIsoString(part.getContentId()); + values.put(Part.CONTENT_ID, (String) value); + } + + if (part.getContentLocation() != null) { + value = toIsoString(part.getContentLocation()); + values.put(Part.CONTENT_LOCATION, (String) value); + } + + SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); + + // Only update the data when: + // 1. New binary data supplied or + // 2. The Uri of the part is different from the current one. + if ((part.getData() != null) + || (uri != part.getDataUri())) { + persistData(part, uri, contentType); + } + } + + /** + * Update all parts of a PDU. + * + * @param uri The PDU which need to be updated. + * @param body New message body of the PDU. + * @throws MmsException Bad URI or updating failed. + */ + public void updateParts(Uri uri, PduBody body) + throws MmsException { + try { + PduCacheEntry cacheEntry; + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "updateParts: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body); + } + } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); + } + + ArrayList toBeCreated = new ArrayList(); + HashMap toBeUpdated = new HashMap(); + + int partsNum = body.getPartsNum(); + StringBuilder filter = new StringBuilder().append('('); + for (int i = 0; i < partsNum; i++) { + PduPart part = body.getPart(i); + Uri partUri = part.getDataUri(); + if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) { + toBeCreated.add(part); + } else { + toBeUpdated.put(partUri, part); + + // Don't use 'i > 0' to determine whether we should append + // 'AND' since 'i = 0' may be skipped in another branch. + if (filter.length() > 1) { + filter.append(" AND "); + } + + filter.append(Part._ID); + filter.append("!="); + DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment()); + } + } + filter.append(')'); + + long msgId = ContentUris.parseId(uri); + + // Remove the parts which doesn't exist anymore. + SqliteWrapper.delete(mContext, mContentResolver, + Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"), + filter.length() > 2 ? filter.toString() : null, null); + + // Create new parts which didn't exist before. + for (PduPart part : toBeCreated) { + persistPart(part, msgId); + } + + // Update the modified parts. + for (Map.Entry e : toBeUpdated.entrySet()) { + updatePart(e.getKey(), e.getValue()); + } + } finally { + synchronized(PDU_CACHE_INSTANCE) { + PDU_CACHE_INSTANCE.setUpdating(uri, false); + PDU_CACHE_INSTANCE.notifyAll(); + } + } + } + + /** + * Persist a PDU object to specific location in the storage. + * + * @param pdu The PDU object to be stored. + * @param uri Where to store the given PDU object. + * @return A Uri which can be used to access the stored PDU. + */ + public Uri persist(GenericPdu pdu, Uri uri) throws MmsException { + if (uri == null) { + throw new MmsException("Uri may not be null."); + } + long msgId = -1; + try { + msgId = ContentUris.parseId(uri); + } catch (NumberFormatException e) { + // the uri ends with "inbox" or something else like that + } + boolean existingUri = msgId != -1; + + if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) { + throw new MmsException( + "Bad destination, must be one of " + + "content://mms/inbox, content://mms/sent, " + + "content://mms/drafts, content://mms/outbox, " + + "content://mms/temp."); + } + synchronized(PDU_CACHE_INSTANCE) { + // If the cache item is getting updated, wait until it's done updating before + // purging it. + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "persist: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "persist1: ", e); + } + } + } + PDU_CACHE_INSTANCE.purge(uri); + + PduHeaders header = pdu.getPduHeaders(); + PduBody body = null; + ContentValues values = new ContentValues(); + Set> set; + + set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set) { + int field = e.getKey(); + EncodedStringValue encodedString = header.getEncodedStringValue(field); + if (encodedString != null) { + String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field); + values.put(e.getValue(), toIsoString(encodedString.getTextString())); + values.put(charsetColumn, encodedString.getCharacterSet()); + } + } + + set = TEXT_STRING_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set){ + byte[] text = header.getTextString(e.getKey()); + if (text != null) { + values.put(e.getValue(), toIsoString(text)); + } + } + + set = OCTET_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set){ + int b = header.getOctet(e.getKey()); + if (b != 0) { + values.put(e.getValue(), b); + } + } + + set = LONG_COLUMN_NAME_MAP.entrySet(); + for (Entry e : set){ + long l = header.getLongInteger(e.getKey()); + if (l != -1L) { + values.put(e.getValue(), l); + } + } + + HashMap addressMap = + new HashMap(ADDRESS_FIELDS.length); + // Save address information. + for (int addrType : ADDRESS_FIELDS) { + EncodedStringValue[] array = null; + if (addrType == PduHeaders.FROM) { + EncodedStringValue v = header.getEncodedStringValue(addrType); + if (v != null) { + array = new EncodedStringValue[1]; + array[0] = v; + } + } else { + array = header.getEncodedStringValues(addrType); + } + addressMap.put(addrType, array); + } + + HashSet recipients = new HashSet(); + int msgType = pdu.getMessageType(); + // Here we only allocate thread ID for M-Notification.ind, + // M-Retrieve.conf and M-Send.req. + // Some of other PDU types may be allocated a thread ID outside + // this scope. + if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) + || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) + || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { + EncodedStringValue[] array = null; + switch (msgType) { + case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: + case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: + array = addressMap.get(PduHeaders.FROM); + break; + case PduHeaders.MESSAGE_TYPE_SEND_REQ: + array = addressMap.get(PduHeaders.TO); + break; + } + + if (array != null) { + for (EncodedStringValue v : array) { + if (v != null) { + recipients.add(v.getString()); + } + } + } + if (!recipients.isEmpty()) { + long threadId = Threads.getOrCreateThreadId(mContext, recipients); + values.put(Mms.THREAD_ID, threadId); + } + } + + // Save parts first to avoid inconsistent message is loaded + // while saving the parts. + long dummyId = System.currentTimeMillis(); // Dummy ID of the msg. + // Get body if the PDU is a RetrieveConf or SendReq. + if (pdu instanceof MultimediaMessagePdu) { + body = ((MultimediaMessagePdu) pdu).getBody(); + // Start saving parts if necessary. + if (body != null) { + int partsNum = body.getPartsNum(); + for (int i = 0; i < partsNum; i++) { + PduPart part = body.getPart(i); + persistPart(part, dummyId); + } + } + } + + Uri res = null; + if (existingUri) { + res = uri; + SqliteWrapper.update(mContext, mContentResolver, res, values, null, null); + } else { + res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); + if (res == null) { + throw new MmsException("persist() failed: return null."); + } + // Get the real ID of the PDU and update all parts which were + // saved with the dummy ID. + msgId = ContentUris.parseId(res); + } + + values = new ContentValues(1); + values.put(Part.MSG_ID, msgId); + SqliteWrapper.update(mContext, mContentResolver, + Uri.parse("content://mms/" + dummyId + "/part"), + values, null, null); + // We should return the longest URI of the persisted PDU, for + // example, if input URI is "content://mms/inbox" and the _ID of + // persisted PDU is '8', we should return "content://mms/inbox/8" + // instead of "content://mms/8". + // FIXME: Should the MmsProvider be responsible for this??? + if (!existingUri) { + res = Uri.parse(uri + "/" + msgId); + } + + // Save address information. + for (int addrType : ADDRESS_FIELDS) { + EncodedStringValue[] array = addressMap.get(addrType); + if (array != null) { + persistAddress(msgId, addrType, array); + } + } + + return res; + } + + /** + * Move a PDU object from one location to another. + * + * @param from Specify the PDU object to be moved. + * @param to The destination location, should be one of the following: + * "content://mms/inbox", "content://mms/sent", + * "content://mms/drafts", "content://mms/outbox", + * "content://mms/trash". + * @return New Uri of the moved PDU. + * @throws MmsException Error occurred while moving the message. + */ + public Uri move(Uri from, Uri to) throws MmsException { + // Check whether the 'msgId' has been assigned a valid value. + long msgId = ContentUris.parseId(from); + if (msgId == -1L) { + throw new MmsException("Error! ID of the message: -1."); + } + + // Get corresponding int value of destination box. + Integer msgBox = MESSAGE_BOX_MAP.get(to); + if (msgBox == null) { + throw new MmsException( + "Bad destination, must be one of " + + "content://mms/inbox, content://mms/sent, " + + "content://mms/drafts, content://mms/outbox, " + + "content://mms/temp."); + } + + ContentValues values = new ContentValues(1); + values.put(Mms.MESSAGE_BOX, msgBox); + SqliteWrapper.update(mContext, mContentResolver, from, values, null, null); + return ContentUris.withAppendedId(to, msgId); + } + + /** + * Wrap a byte[] into a String. + */ + public static String toIsoString(byte[] bytes) { + try { + return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + // Impossible to reach here! + Log.e(TAG, "ISO_8859_1 must be supported!", e); + return ""; + } + } + + /** + * Unpack a given String into a byte[]. + */ + public static byte[] getBytes(String data) { + try { + return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1); + } catch (UnsupportedEncodingException e) { + // Impossible to reach here! + Log.e(TAG, "ISO_8859_1 must be supported!", e); + return new byte[0]; + } + } + + /** + * Remove all objects in the temporary path. + */ + public void release() { + Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI); + SqliteWrapper.delete(mContext, mContentResolver, uri, null, null); + } + + /** + * Find all messages to be sent or downloaded before certain time. + */ + public Cursor getPendingMessages(long dueTime) { + Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); + uriBuilder.appendQueryParameter("protocol", "mms"); + + String selection = PendingMessages.ERROR_TYPE + " < ?" + + " AND " + PendingMessages.DUE_TIME + " <= ?"; + + String[] selectionArgs = new String[] { + String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT), + String.valueOf(dueTime) + }; + + return SqliteWrapper.query(mContext, mContentResolver, + uriBuilder.build(), null, selection, selectionArgs, + PendingMessages.DUE_TIME); + } +} diff --git a/src/java/com/google/android/mms/pdu/QuotedPrintable.java b/src/java/com/google/android/mms/pdu/QuotedPrintable.java new file mode 100644 index 0000000..a34ed12 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/QuotedPrintable.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import java.io.ByteArrayOutputStream; + +public class QuotedPrintable { + private static byte ESCAPE_CHAR = '='; + + /** + * Decodes an array quoted-printable characters into an array of original bytes. + * Escaped characters are converted back to their original representation. + * + *

+ * This function implements a subset of + * quoted-printable encoding specification (rule #1 and rule #2) + * as defined in RFC 1521. + *

+ * + * @param bytes array of quoted-printable characters + * @return array of original bytes, + * null if quoted-printable decoding is unsuccessful. + */ + public static final byte[] decodeQuotedPrintable(byte[] bytes) { + if (bytes == null) { + return null; + } + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < bytes.length; i++) { + int b = bytes[i]; + if (b == ESCAPE_CHAR) { + try { + if('\r' == (char)bytes[i + 1] && + '\n' == (char)bytes[i + 2]) { + i += 2; + continue; + } + int u = Character.digit((char) bytes[++i], 16); + int l = Character.digit((char) bytes[++i], 16); + if (u == -1 || l == -1) { + return null; + } + buffer.write((char) ((u << 4) + l)); + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } else { + buffer.write(b); + } + } + return buffer.toByteArray(); + } +} diff --git a/src/java/com/google/android/mms/pdu/ReadOrigInd.java b/src/java/com/google/android/mms/pdu/ReadOrigInd.java new file mode 100644 index 0000000..1bfc0bb --- /dev/null +++ b/src/java/com/google/android/mms/pdu/ReadOrigInd.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +public class ReadOrigInd extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + public ReadOrigInd() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + ReadOrigInd(PduHeaders headers) { + super(headers); + } + + /** + * Get Date value. + * + * @return the value + */ + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value. + * + * @param value the value + */ + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get X-MMS-Read-status value. + * + * @return the value + */ + public int getReadStatus() { + return mPduHeaders.getOctet(PduHeaders.READ_STATUS); + } + + /** + * Set X-MMS-Read-status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReadStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); + } + + /** + * Get To value. + * + * @return the value + */ + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * Set To value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/ReadRecInd.java b/src/java/com/google/android/mms/pdu/ReadRecInd.java new file mode 100644 index 0000000..880e3ac --- /dev/null +++ b/src/java/com/google/android/mms/pdu/ReadRecInd.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +public class ReadRecInd extends GenericPdu { + /** + * Constructor, used when composing a M-ReadRec.ind pdu. + * + * @param from the from value + * @param messageId the message ID value + * @param mmsVersion current viersion of mms + * @param readStatus the read status value + * @param to the to value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if messageId or to is null. + */ + public ReadRecInd(EncodedStringValue from, + byte[] messageId, + int mmsVersion, + int readStatus, + EncodedStringValue[] to) throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND); + setFrom(from); + setMessageId(messageId); + setMmsVersion(mmsVersion); + setTo(to); + setReadStatus(readStatus); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + ReadRecInd(PduHeaders headers) { + super(headers); + } + + /** + * Get Date value. + * + * @return the value + */ + public long getDate() { + return mPduHeaders.getLongInteger(PduHeaders.DATE); + } + + /** + * Set Date value. + * + * @param value the value + */ + public void setDate(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.DATE); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get To value. + * + * @return the value + */ + public EncodedStringValue[] getTo() { + return mPduHeaders.getEncodedStringValues(PduHeaders.TO); + } + + /** + * Set To value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /** + * Get X-MMS-Read-status value. + * + * @return the value + */ + public int getReadStatus() { + return mPduHeaders.getOctet(PduHeaders.READ_STATUS); + } + + /** + * Set X-MMS-Read-status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReadStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_STATUS); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/RetrieveConf.java b/src/java/com/google/android/mms/pdu/RetrieveConf.java new file mode 100644 index 0000000..98e67c0 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/RetrieveConf.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +/** + * M-Retrive.conf Pdu. + */ +public class RetrieveConf extends MultimediaMessagePdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + public RetrieveConf() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + RetrieveConf(PduHeaders headers) { + super(headers); + } + + /** + * Constructor with given headers and body + * + * @param headers Headers for this PDU. + * @param body Body of this PDu. + */ + RetrieveConf(PduHeaders headers, PduBody body) { + super(headers, body); + } + + /** + * Get CC value. + * + * @return the value + */ + public EncodedStringValue[] getCc() { + return mPduHeaders.getEncodedStringValues(PduHeaders.CC); + } + + /** + * Add a "CC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void addCc(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); + } + + /** + * Get Content-type value. + * + * @return the value + */ + public byte[] getContentType() { + return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); + } + + /** + * Set Content-type value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setContentType(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); + } + + /** + * Get X-Mms-Delivery-Report value. + * + * @return the value + */ + public int getDeliveryReport() { + return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); + } + + /** + * Set X-Mms-Delivery-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setDeliveryReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); + } + + /** + * Get From value. + * From-value = Value-length + * (Address-present-token Encoded-string-value | Insert-address-token) + * + * @return the value + */ + public EncodedStringValue getFrom() { + return mPduHeaders.getEncodedStringValue(PduHeaders.FROM); + } + + /** + * Set From value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setFrom(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM); + } + + /** + * Get X-Mms-Message-Class value. + * Message-class-value = Class-identifier | Token-text + * Class-identifier = Personal | Advertisement | Informational | Auto + * + * @return the value + */ + public byte[] getMessageClass() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); + } + + /** + * Set X-Mms-Message-Class value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageClass(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get X-Mms-Read-Report value. + * + * @return the value + */ + public int getReadReport() { + return mPduHeaders.getOctet(PduHeaders.READ_REPORT); + } + + /** + * Set X-Mms-Read-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReadReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); + } + + /** + * Get X-Mms-Retrieve-Status value. + * + * @return the value + */ + public int getRetrieveStatus() { + return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS); + } + + /** + * Set X-Mms-Retrieve-Status value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setRetrieveStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS); + } + + /** + * Get X-Mms-Retrieve-Text value. + * + * @return the value + */ + public EncodedStringValue getRetrieveText() { + return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT); + } + + /** + * Set X-Mms-Retrieve-Text value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setRetrieveText(EncodedStringValue value) { + mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT); + } + + /** + * Get X-Mms-Transaction-Id. + * + * @return the value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte getContentClass() {return 0x00;} + * public void setApplicId(byte value) {} + * + * public byte getDrmContent() {return 0x00;} + * public void setDrmContent(byte value) {} + * + * public byte getDistributionIndicator() {return 0x00;} + * public void setDistributionIndicator(byte value) {} + * + * public PreviouslySentByValue getPreviouslySentBy() {return null;} + * public void setPreviouslySentBy(PreviouslySentByValue value) {} + * + * public PreviouslySentDateValue getPreviouslySentDate() {} + * public void setPreviouslySentDate(PreviouslySentDateValue value) {} + * + * public MmFlagsValue getMmFlags() {return null;} + * public void setMmFlags(MmFlagsValue value) {} + * + * public MmStateValue getMmState() {return null;} + * public void getMmState(MmStateValue value) {} + * + * public byte[] getReplaceId() {return 0x00;} + * public void setReplaceId(byte[] value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getReplyCharging() {return 0x00;} + * public void setReplyCharging(byte value) {} + * + * public byte getReplyChargingDeadline() {return 0x00;} + * public void setReplyChargingDeadline(byte value) {} + * + * public byte[] getReplyChargingId() {return 0x00;} + * public void setReplyChargingId(byte[] value) {} + * + * public long getReplyChargingSize() {return 0;} + * public void setReplyChargingSize(long value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/SendConf.java b/src/java/com/google/android/mms/pdu/SendConf.java new file mode 100644 index 0000000..0568fe7 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/SendConf.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2007 Esmertec AG. + * Copyright (C) 2007 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.google.android.mms.pdu; + +import com.google.android.mms.InvalidHeaderValueException; + +public class SendConf extends GenericPdu { + /** + * Empty constructor. + * Since the Pdu corresponding to this class is constructed + * by the Proxy-Relay server, this class is only instantiated + * by the Pdu Parser. + * + * @throws InvalidHeaderValueException if error occurs. + */ + public SendConf() throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + SendConf(PduHeaders headers) { + super(headers); + } + + /** + * Get Message-ID value. + * + * @return the value + */ + public byte[] getMessageId() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID); + } + + /** + * Set Message-ID value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID); + } + + /** + * Get X-Mms-Response-Status. + * + * @return the value + */ + public int getResponseStatus() { + return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS); + } + + /** + * Set X-Mms-Response-Status. + * + * @param value the values + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setResponseStatus(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /* + * Optional, not supported header fields: + * + * public byte[] getContentLocation() {return null;} + * public void setContentLocation(byte[] value) {} + * + * public EncodedStringValue getResponseText() {return null;} + * public void setResponseText(EncodedStringValue value) {} + * + * public byte getStoreStatus() {return 0x00;} + * public void setStoreStatus(byte value) {} + * + * public byte[] getStoreStatusText() {return null;} + * public void setStoreStatusText(byte[] value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/SendReq.java b/src/java/com/google/android/mms/pdu/SendReq.java new file mode 100644 index 0000000..597cd00 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/SendReq.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2007-2008 Esmertec AG. + * Copyright (C) 2007-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.google.android.mms.pdu; + +import android.util.Log; + +import com.google.android.mms.InvalidHeaderValueException; + +public class SendReq extends MultimediaMessagePdu { + private static final String TAG = "SendReq"; + + public SendReq() { + super(); + + try { + setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); + setMmsVersion(PduHeaders.CURRENT_MMS_VERSION); + // FIXME: Content-type must be decided according to whether + // SMIL part present. + setContentType("application/vnd.wap.multipart.related".getBytes()); + setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes())); + setTransactionId(generateTransactionId()); + } catch (InvalidHeaderValueException e) { + // Impossible to reach here since all headers we set above are valid. + Log.e(TAG, "Unexpected InvalidHeaderValueException.", e); + throw new RuntimeException(e); + } + } + + private byte[] generateTransactionId() { + String transactionId = "T" + Long.toHexString(System.currentTimeMillis()); + return transactionId.getBytes(); + } + + /** + * Constructor, used when composing a M-Send.req pdu. + * + * @param contentType the content type value + * @param from the from value + * @param mmsVersion current viersion of mms + * @param transactionId the transaction-id value + * @throws InvalidHeaderValueException if parameters are invalid. + * NullPointerException if contentType, form or transactionId is null. + */ + public SendReq(byte[] contentType, + EncodedStringValue from, + int mmsVersion, + byte[] transactionId) throws InvalidHeaderValueException { + super(); + setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ); + setContentType(contentType); + setFrom(from); + setMmsVersion(mmsVersion); + setTransactionId(transactionId); + } + + /** + * Constructor with given headers. + * + * @param headers Headers for this PDU. + */ + SendReq(PduHeaders headers) { + super(headers); + } + + /** + * Constructor with given headers and body + * + * @param headers Headers for this PDU. + * @param body Body of this PDu. + */ + SendReq(PduHeaders headers, PduBody body) { + super(headers, body); + } + + /** + * Get Bcc value. + * + * @return the value + */ + public EncodedStringValue[] getBcc() { + return mPduHeaders.getEncodedStringValues(PduHeaders.BCC); + } + + /** + * Add a "BCC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void addBcc(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC); + } + + /** + * Set "BCC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setBcc(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC); + } + + /** + * Get CC value. + * + * @return the value + */ + public EncodedStringValue[] getCc() { + return mPduHeaders.getEncodedStringValues(PduHeaders.CC); + } + + /** + * Add a "CC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void addCc(EncodedStringValue value) { + mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC); + } + + /** + * Set "CC" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setCc(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.CC); + } + + /** + * Get Content-type value. + * + * @return the value + */ + public byte[] getContentType() { + return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE); + } + + /** + * Set Content-type value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setContentType(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE); + } + + /** + * Get X-Mms-Delivery-Report value. + * + * @return the value + */ + public int getDeliveryReport() { + return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT); + } + + /** + * Set X-Mms-Delivery-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setDeliveryReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT); + } + + /** + * Get X-Mms-Expiry value. + * + * Expiry-value = Value-length + * (Absolute-token Date-value | Relative-token Delta-seconds-value) + * + * @return the value + */ + public long getExpiry() { + return mPduHeaders.getLongInteger(PduHeaders.EXPIRY); + } + + /** + * Set X-Mms-Expiry value. + * + * @param value the value + */ + public void setExpiry(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY); + } + + /** + * Get X-Mms-MessageSize value. + * + * Expiry-value = size of message + * + * @return the value + */ + public long getMessageSize() { + return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE); + } + + /** + * Set X-Mms-MessageSize value. + * + * @param value the value + */ + public void setMessageSize(long value) { + mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE); + } + + /** + * Get X-Mms-Message-Class value. + * Message-class-value = Class-identifier | Token-text + * Class-identifier = Personal | Advertisement | Informational | Auto + * + * @return the value + */ + public byte[] getMessageClass() { + return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS); + } + + /** + * Set X-Mms-Message-Class value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setMessageClass(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS); + } + + /** + * Get X-Mms-Read-Report value. + * + * @return the value + */ + public int getReadReport() { + return mPduHeaders.getOctet(PduHeaders.READ_REPORT); + } + + /** + * Set X-Mms-Read-Report value. + * + * @param value the value + * @throws InvalidHeaderValueException if the value is invalid. + */ + public void setReadReport(int value) throws InvalidHeaderValueException { + mPduHeaders.setOctet(value, PduHeaders.READ_REPORT); + } + + /** + * Set "To" value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTo(EncodedStringValue[] value) { + mPduHeaders.setEncodedStringValues(value, PduHeaders.TO); + } + + /** + * Get X-Mms-Transaction-Id field value. + * + * @return the X-Mms-Report-Allowed value + */ + public byte[] getTransactionId() { + return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID); + } + + /** + * Set X-Mms-Transaction-Id field value. + * + * @param value the value + * @throws NullPointerException if the value is null. + */ + public void setTransactionId(byte[] value) { + mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID); + } + + /* + * Optional, not supported header fields: + * + * public byte getAdaptationAllowed() {return 0}; + * public void setAdaptationAllowed(btye value) {}; + * + * public byte[] getApplicId() {return null;} + * public void setApplicId(byte[] value) {} + * + * public byte[] getAuxApplicId() {return null;} + * public void getAuxApplicId(byte[] value) {} + * + * public byte getContentClass() {return 0x00;} + * public void setApplicId(byte value) {} + * + * public long getDeliveryTime() {return 0}; + * public void setDeliveryTime(long value) {}; + * + * public byte getDrmContent() {return 0x00;} + * public void setDrmContent(byte value) {} + * + * public MmFlagsValue getMmFlags() {return null;} + * public void setMmFlags(MmFlagsValue value) {} + * + * public MmStateValue getMmState() {return null;} + * public void getMmState(MmStateValue value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getReplyCharging() {return 0x00;} + * public void setReplyCharging(byte value) {} + * + * public byte getReplyChargingDeadline() {return 0x00;} + * public void setReplyChargingDeadline(byte value) {} + * + * public byte[] getReplyChargingId() {return 0x00;} + * public void setReplyChargingId(byte[] value) {} + * + * public long getReplyChargingSize() {return 0;} + * public void setReplyChargingSize(long value) {} + * + * public byte[] getReplyApplicId() {return 0x00;} + * public void setReplyApplicId(byte[] value) {} + * + * public byte getStore() {return 0x00;} + * public void setStore(byte value) {} + */ +} diff --git a/src/java/com/google/android/mms/pdu/package.html b/src/java/com/google/android/mms/pdu/package.html new file mode 100755 index 0000000..c9f96a6 --- /dev/null +++ b/src/java/com/google/android/mms/pdu/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + diff --git a/src/java/com/google/android/mms/util/AbstractCache.java b/src/java/com/google/android/mms/util/AbstractCache.java new file mode 100644 index 0000000..39b2abf --- /dev/null +++ b/src/java/com/google/android/mms/util/AbstractCache.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * 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.google.android.mms.util; + +import android.util.Log; + +import java.util.HashMap; + +public abstract class AbstractCache { + private static final String TAG = "AbstractCache"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + private static final int MAX_CACHED_ITEMS = 500; + + private final HashMap> mCacheMap; + + protected AbstractCache() { + mCacheMap = new HashMap>(); + } + + public boolean put(K key, V value) { + if (LOCAL_LOGV) { + Log.v(TAG, "Trying to put " + key + " into cache."); + } + + if (mCacheMap.size() >= MAX_CACHED_ITEMS) { + // TODO Should remove the oldest or least hit cached entry + // and then cache the new one. + if (LOCAL_LOGV) { + Log.v(TAG, "Failed! size limitation reached."); + } + return false; + } + + if (key != null) { + CacheEntry cacheEntry = new CacheEntry(); + cacheEntry.value = value; + mCacheMap.put(key, cacheEntry); + + if (LOCAL_LOGV) { + Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total."); + } + return true; + } + return false; + } + + public V get(K key) { + if (LOCAL_LOGV) { + Log.v(TAG, "Trying to get " + key + " from cache."); + } + + if (key != null) { + CacheEntry cacheEntry = mCacheMap.get(key); + if (cacheEntry != null) { + cacheEntry.hit++; + if (LOCAL_LOGV) { + Log.v(TAG, key + " hit " + cacheEntry.hit + " times."); + } + return cacheEntry.value; + } + } + return null; + } + + public V purge(K key) { + if (LOCAL_LOGV) { + Log.v(TAG, "Trying to purge " + key); + } + + CacheEntry v = mCacheMap.remove(key); + + if (LOCAL_LOGV) { + Log.v(TAG, mCacheMap.size() + " items cached."); + } + + return v != null ? v.value : null; + } + + public void purgeAll() { + if (LOCAL_LOGV) { + Log.v(TAG, "Purging cache, " + mCacheMap.size() + + " items dropped."); + } + mCacheMap.clear(); + } + + public int size() { + return mCacheMap.size(); + } + + private static class CacheEntry { + int hit; + V value; + } +} diff --git a/src/java/com/google/android/mms/util/DownloadDrmHelper.java b/src/java/com/google/android/mms/util/DownloadDrmHelper.java new file mode 100644 index 0000000..6852eca --- /dev/null +++ b/src/java/com/google/android/mms/util/DownloadDrmHelper.java @@ -0,0 +1,111 @@ +/* + * 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.google.android.mms.util; + +import android.content.Context; +import android.drm.DrmManagerClient; +import android.util.Log; + +public class DownloadDrmHelper { + private static final String TAG = "DownloadDrmHelper"; + + /** The MIME type of special DRM files */ + public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message"; + + /** The extensions of special DRM files */ + public static final String EXTENSION_DRM_MESSAGE = ".dm"; + + public static final String EXTENSION_INTERNAL_FWDL = ".fl"; + + /** + * Checks if the Media Type is a DRM Media Type + * + * @param drmManagerClient A DrmManagerClient + * @param mimetype Media Type to check + * @return True if the Media Type is DRM else false + */ + public static boolean isDrmMimeType(Context context, String mimetype) { + boolean result = false; + if (context != null) { + try { + DrmManagerClient drmClient = new DrmManagerClient(context); + if (drmClient != null && mimetype != null && mimetype.length() > 0) { + result = drmClient.canHandle("", mimetype); + } + } catch (IllegalArgumentException e) { + Log.w(TAG, + "DrmManagerClient instance could not be created, context is Illegal."); + } catch (IllegalStateException e) { + Log.w(TAG, "DrmManagerClient didn't initialize properly."); + } + } + return result; + } + + /** + * Checks if the Media Type needs to be DRM converted + * + * @param mimetype Media type of the content + * @return True if convert is needed else false + */ + public static boolean isDrmConvertNeeded(String mimetype) { + return MIMETYPE_DRM_MESSAGE.equals(mimetype); + } + + /** + * Modifies the file extension for a DRM Forward Lock file NOTE: This + * function shouldn't be called if the file shouldn't be DRM converted + */ + public static String modifyDrmFwLockFileExtension(String filename) { + if (filename != null) { + int extensionIndex; + extensionIndex = filename.lastIndexOf("."); + if (extensionIndex != -1) { + filename = filename.substring(0, extensionIndex); + } + filename = filename.concat(EXTENSION_INTERNAL_FWDL); + } + return filename; + } + + /** + * Gets the original mime type of DRM protected content. + * + * @param context The context + * @param path Path to the file + * @param containingMime The current mime type of of the file i.e. the + * containing mime type + * @return The original mime type of the file if DRM protected else the + * currentMime + */ + public static String getOriginalMimeType(Context context, String path, String containingMime) { + String result = containingMime; + DrmManagerClient drmClient = new DrmManagerClient(context); + try { + if (drmClient.canHandle(path, null)) { + result = drmClient.getOriginalMimeType(path); + } + } catch (IllegalArgumentException ex) { + Log.w(TAG, + "Can't get original mime type since path is null or empty string."); + } catch (IllegalStateException ex) { + Log.w(TAG, "DrmManagerClient didn't initialize properly."); + } + return result; + } +} diff --git a/src/java/com/google/android/mms/util/DrmConvertSession.java b/src/java/com/google/android/mms/util/DrmConvertSession.java new file mode 100644 index 0000000..2d8f274 --- /dev/null +++ b/src/java/com/google/android/mms/util/DrmConvertSession.java @@ -0,0 +1,172 @@ +/* + * 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.google.android.mms.util; + +import android.content.Context; +import android.drm.DrmConvertedStatus; +import android.drm.DrmManagerClient; +import android.util.Log; +import android.provider.Downloads; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + + +public class DrmConvertSession { + private DrmManagerClient mDrmClient; + private int mConvertSessionId; + private static final String TAG = "DrmConvertSession"; + + private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) { + mDrmClient = drmClient; + mConvertSessionId = convertSessionId; + } + + /** + * Start of converting a file. + * + * @param context The context of the application running the convert session. + * @param mimeType Mimetype of content that shall be converted. + * @return A convert session or null in case an error occurs. + */ + public static DrmConvertSession open(Context context, String mimeType) { + DrmManagerClient drmClient = null; + int convertSessionId = -1; + if (context != null && mimeType != null && !mimeType.equals("")) { + try { + drmClient = new DrmManagerClient(context); + try { + convertSessionId = drmClient.openConvertSession(mimeType); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Conversion of Mimetype: " + mimeType + + " is not supported.", e); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not access Open DrmFramework.", e); + } + } catch (IllegalArgumentException e) { + Log.w(TAG, + "DrmManagerClient instance could not be created, context is Illegal."); + } catch (IllegalStateException e) { + Log.w(TAG, "DrmManagerClient didn't initialize properly."); + } + } + + if (drmClient == null || convertSessionId < 0) { + return null; + } else { + return new DrmConvertSession(drmClient, convertSessionId); + } + } + /** + * Convert a buffer of data to protected format. + * + * @param buffer Buffer filled with data to convert. + * @param size The number of bytes that shall be converted. + * @return A Buffer filled with converted data, if execution is ok, in all + * other case null. + */ + public byte [] convert(byte[] inBuffer, int size) { + byte[] result = null; + if (inBuffer != null) { + DrmConvertedStatus convertedStatus = null; + try { + if (size != inBuffer.length) { + byte[] buf = new byte[size]; + System.arraycopy(inBuffer, 0, buf, 0, size); + convertedStatus = mDrmClient.convertData(mConvertSessionId, buf); + } else { + convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer); + } + + if (convertedStatus != null && + convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK && + convertedStatus.convertedData != null) { + result = convertedStatus.convertedData; + } + } catch (IllegalArgumentException e) { + Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: " + + mConvertSessionId, e); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not convert data. Convertsession: " + + mConvertSessionId, e); + } + } else { + throw new IllegalArgumentException("Parameter inBuffer is null"); + } + return result; + } + + /** + * Ends a conversion session of a file. + * + * @param fileName The filename of the converted file. + * @return Downloads.Impl.STATUS_SUCCESS if execution is ok. + * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not + * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem + * occurs when accessing drm framework. + * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred. + */ + public int close(String filename) { + DrmConvertedStatus convertedStatus = null; + int result = Downloads.Impl.STATUS_UNKNOWN_ERROR; + if (mDrmClient != null && mConvertSessionId >= 0) { + try { + convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId); + if (convertedStatus == null || + convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK || + convertedStatus.convertedData == null) { + result = Downloads.Impl.STATUS_NOT_ACCEPTABLE; + } else { + RandomAccessFile rndAccessFile = null; + try { + rndAccessFile = new RandomAccessFile(filename, "rw"); + rndAccessFile.seek(convertedStatus.offset); + rndAccessFile.write(convertedStatus.convertedData); + result = Downloads.Impl.STATUS_SUCCESS; + } catch (FileNotFoundException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "File: " + filename + " could not be found.", e); + } catch (IOException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "Could not access File: " + filename + " .", e); + } catch (IllegalArgumentException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "Could not open file in mode: rw", e); + } catch (SecurityException e) { + Log.w(TAG, "Access to File: " + filename + + " was denied denied by SecurityManager.", e); + } finally { + if (rndAccessFile != null) { + try { + rndAccessFile.close(); + } catch (IOException e) { + result = Downloads.Impl.STATUS_FILE_ERROR; + Log.w(TAG, "Failed to close File:" + filename + + ".", e); + } + } + } + } + } catch (IllegalStateException e) { + Log.w(TAG, "Could not close convertsession. Convertsession: " + + mConvertSessionId, e); + } + } + return result; + } +} diff --git a/src/java/com/google/android/mms/util/PduCache.java b/src/java/com/google/android/mms/util/PduCache.java new file mode 100644 index 0000000..de83124 --- /dev/null +++ b/src/java/com/google/android/mms/util/PduCache.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * 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.google.android.mms.util; + +import android.content.ContentUris; +import android.content.UriMatcher; +import android.net.Uri; +import android.provider.Telephony.Mms; +import android.util.Log; + +import java.util.HashMap; +import java.util.HashSet; + +public final class PduCache extends AbstractCache { + private static final String TAG = "PduCache"; + private static final boolean DEBUG = false; + private static final boolean LOCAL_LOGV = false; + + private static final int MMS_ALL = 0; + private static final int MMS_ALL_ID = 1; + private static final int MMS_INBOX = 2; + private static final int MMS_INBOX_ID = 3; + private static final int MMS_SENT = 4; + private static final int MMS_SENT_ID = 5; + private static final int MMS_DRAFTS = 6; + private static final int MMS_DRAFTS_ID = 7; + private static final int MMS_OUTBOX = 8; + private static final int MMS_OUTBOX_ID = 9; + private static final int MMS_CONVERSATION = 10; + private static final int MMS_CONVERSATION_ID = 11; + + private static final UriMatcher URI_MATCHER; + private static final HashMap MATCH_TO_MSGBOX_ID_MAP; + + private static PduCache sInstance; + + static { + URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + URI_MATCHER.addURI("mms", null, MMS_ALL); + URI_MATCHER.addURI("mms", "#", MMS_ALL_ID); + URI_MATCHER.addURI("mms", "inbox", MMS_INBOX); + URI_MATCHER.addURI("mms", "inbox/#", MMS_INBOX_ID); + URI_MATCHER.addURI("mms", "sent", MMS_SENT); + URI_MATCHER.addURI("mms", "sent/#", MMS_SENT_ID); + URI_MATCHER.addURI("mms", "drafts", MMS_DRAFTS); + URI_MATCHER.addURI("mms", "drafts/#", MMS_DRAFTS_ID); + URI_MATCHER.addURI("mms", "outbox", MMS_OUTBOX); + URI_MATCHER.addURI("mms", "outbox/#", MMS_OUTBOX_ID); + URI_MATCHER.addURI("mms-sms", "conversations", MMS_CONVERSATION); + URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID); + + MATCH_TO_MSGBOX_ID_MAP = new HashMap(); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX, Mms.MESSAGE_BOX_INBOX); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT, Mms.MESSAGE_BOX_SENT); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS); + MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX); + } + + private final HashMap> mMessageBoxes; + private final HashMap> mThreads; + private final HashSet mUpdating; + + private PduCache() { + mMessageBoxes = new HashMap>(); + mThreads = new HashMap>(); + mUpdating = new HashSet(); + } + + synchronized public static final PduCache getInstance() { + if (sInstance == null) { + if (LOCAL_LOGV) { + Log.v(TAG, "Constructing new PduCache instance."); + } + sInstance = new PduCache(); + } + return sInstance; + } + + @Override + synchronized public boolean put(Uri uri, PduCacheEntry entry) { + int msgBoxId = entry.getMessageBox(); + HashSet msgBox = mMessageBoxes.get(msgBoxId); + if (msgBox == null) { + msgBox = new HashSet(); + mMessageBoxes.put(msgBoxId, msgBox); + } + + long threadId = entry.getThreadId(); + HashSet thread = mThreads.get(threadId); + if (thread == null) { + thread = new HashSet(); + mThreads.put(threadId, thread); + } + + Uri finalKey = normalizeKey(uri); + boolean result = super.put(finalKey, entry); + if (result) { + msgBox.add(finalKey); + thread.add(finalKey); + } + setUpdating(uri, false); + return result; + } + + synchronized public void setUpdating(Uri uri, boolean updating) { + if (updating) { + mUpdating.add(uri); + } else { + mUpdating.remove(uri); + } + } + + synchronized public boolean isUpdating(Uri uri) { + return mUpdating.contains(uri); + } + + @Override + synchronized public PduCacheEntry purge(Uri uri) { + int match = URI_MATCHER.match(uri); + switch (match) { + case MMS_ALL_ID: + return purgeSingleEntry(uri); + case MMS_INBOX_ID: + case MMS_SENT_ID: + case MMS_DRAFTS_ID: + case MMS_OUTBOX_ID: + String msgId = uri.getLastPathSegment(); + return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId)); + // Implicit batch of purge, return null. + case MMS_ALL: + case MMS_CONVERSATION: + purgeAll(); + return null; + case MMS_INBOX: + case MMS_SENT: + case MMS_DRAFTS: + case MMS_OUTBOX: + purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match)); + return null; + case MMS_CONVERSATION_ID: + purgeByThreadId(ContentUris.parseId(uri)); + return null; + default: + return null; + } + } + + private PduCacheEntry purgeSingleEntry(Uri key) { + mUpdating.remove(key); + PduCacheEntry entry = super.purge(key); + if (entry != null) { + removeFromThreads(key, entry); + removeFromMessageBoxes(key, entry); + return entry; + } + return null; + } + + @Override + synchronized public void purgeAll() { + super.purgeAll(); + + mMessageBoxes.clear(); + mThreads.clear(); + mUpdating.clear(); + } + + /** + * @param uri The Uri to be normalized. + * @return Uri The normalized key of cached entry. + */ + private Uri normalizeKey(Uri uri) { + int match = URI_MATCHER.match(uri); + Uri normalizedKey = null; + + switch (match) { + case MMS_ALL_ID: + normalizedKey = uri; + break; + case MMS_INBOX_ID: + case MMS_SENT_ID: + case MMS_DRAFTS_ID: + case MMS_OUTBOX_ID: + String msgId = uri.getLastPathSegment(); + normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId); + break; + default: + return null; + } + + if (LOCAL_LOGV) { + Log.v(TAG, uri + " -> " + normalizedKey); + } + return normalizedKey; + } + + private void purgeByMessageBox(Integer msgBoxId) { + if (LOCAL_LOGV) { + Log.v(TAG, "Purge cache in message box: " + msgBoxId); + } + + if (msgBoxId != null) { + HashSet msgBox = mMessageBoxes.remove(msgBoxId); + if (msgBox != null) { + for (Uri key : msgBox) { + mUpdating.remove(key); + PduCacheEntry entry = super.purge(key); + if (entry != null) { + removeFromThreads(key, entry); + } + } + } + } + } + + private void removeFromThreads(Uri key, PduCacheEntry entry) { + HashSet thread = mThreads.get(entry.getThreadId()); + if (thread != null) { + thread.remove(key); + } + } + + private void purgeByThreadId(long threadId) { + if (LOCAL_LOGV) { + Log.v(TAG, "Purge cache in thread: " + threadId); + } + + HashSet thread = mThreads.remove(threadId); + if (thread != null) { + for (Uri key : thread) { + mUpdating.remove(key); + PduCacheEntry entry = super.purge(key); + if (entry != null) { + removeFromMessageBoxes(key, entry); + } + } + } + } + + private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) { + HashSet msgBox = mThreads.get(Long.valueOf(entry.getMessageBox())); + if (msgBox != null) { + msgBox.remove(key); + } + } +} diff --git a/src/java/com/google/android/mms/util/PduCacheEntry.java b/src/java/com/google/android/mms/util/PduCacheEntry.java new file mode 100644 index 0000000..8b41386 --- /dev/null +++ b/src/java/com/google/android/mms/util/PduCacheEntry.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * 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.google.android.mms.util; + +import com.google.android.mms.pdu.GenericPdu; + +public final class PduCacheEntry { + private final GenericPdu mPdu; + private final int mMessageBox; + private final long mThreadId; + + public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) { + mPdu = pdu; + mMessageBox = msgBox; + mThreadId = threadId; + } + + public GenericPdu getPdu() { + return mPdu; + } + + public int getMessageBox() { + return mMessageBox; + } + + public long getThreadId() { + return mThreadId; + } +} diff --git a/src/java/com/google/android/mms/util/SqliteWrapper.java b/src/java/com/google/android/mms/util/SqliteWrapper.java new file mode 100644 index 0000000..bcdac22 --- /dev/null +++ b/src/java/com/google/android/mms/util/SqliteWrapper.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008 Esmertec AG. + * 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.google.android.mms.util; + +import android.app.ActivityManager; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.util.Log; +import android.widget.Toast; + +public final class SqliteWrapper { + private static final String TAG = "SqliteWrapper"; + private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE + = "unable to open database file"; + + private SqliteWrapper() { + // Forbidden being instantiated. + } + + // FIXME: It looks like outInfo.lowMemory does not work well as we expected. + // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false. + private static boolean isLowMemory(Context context) { + if (null == context) { + return false; + } + + ActivityManager am = (ActivityManager) + context.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo(); + am.getMemoryInfo(outInfo); + + return outInfo.lowMemory; + } + + // FIXME: need to optimize this method. + private static boolean isLowMemory(SQLiteException e) { + return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE); + } + + public static void checkSQLiteException(Context context, SQLiteException e) { + if (isLowMemory(e)) { + Toast.makeText(context, com.android.internal.R.string.low_memory, + Toast.LENGTH_SHORT).show(); + } else { + throw e; + } + } + + public static Cursor query(Context context, ContentResolver resolver, Uri uri, + String[] projection, String selection, String[] selectionArgs, String sortOrder) { + try { + return resolver.query(uri, projection, selection, selectionArgs, sortOrder); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when query: ", e); + checkSQLiteException(context, e); + return null; + } + } + + public static boolean requery(Context context, Cursor cursor) { + try { + return cursor.requery(); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when requery: ", e); + checkSQLiteException(context, e); + return false; + } + } + public static int update(Context context, ContentResolver resolver, Uri uri, + ContentValues values, String where, String[] selectionArgs) { + try { + return resolver.update(uri, values, where, selectionArgs); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when update: ", e); + checkSQLiteException(context, e); + return -1; + } + } + + public static int delete(Context context, ContentResolver resolver, Uri uri, + String where, String[] selectionArgs) { + try { + return resolver.delete(uri, where, selectionArgs); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when delete: ", e); + checkSQLiteException(context, e); + return -1; + } + } + + public static Uri insert(Context context, ContentResolver resolver, + Uri uri, ContentValues values) { + try { + return resolver.insert(uri, values); + } catch (SQLiteException e) { + Log.e(TAG, "Catch a SQLiteException when insert: ", e); + checkSQLiteException(context, e); + return null; + } + } +} diff --git a/src/java/com/google/android/mms/util/package.html b/src/java/com/google/android/mms/util/package.html new file mode 100755 index 0000000..c9f96a6 --- /dev/null +++ b/src/java/com/google/android/mms/util/package.html @@ -0,0 +1,5 @@ + + +{@hide} + + -- cgit v1.2.3