blob: 0f662e53768e47307c2b792192d2b1dbf644fca8 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.sms;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.android.messaging.Factory;
import com.android.messaging.datamodel.data.MessageData;
import com.android.messaging.datamodel.media.VideoThumbnailRequest;
import com.android.messaging.mmslib.pdu.CharacterSets;
import com.android.messaging.util.Assert;
import com.android.messaging.util.ContentType;
import com.android.messaging.util.LogUtil;
import com.android.messaging.util.MediaMetadataRetrieverWrapper;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import com.google.common.collect.Lists;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* Class contains various SMS/MMS database entities from telephony provider
*/
public class DatabaseMessages {
private static final String TAG = LogUtil.BUGLE_TAG;
public abstract static class DatabaseMessage {
public abstract int getProtocol();
public abstract String getUri();
public abstract long getTimestampInMillis();
@Override
public boolean equals(final Object other) {
if (other == null || !(other instanceof DatabaseMessage)) {
return false;
}
final DatabaseMessage otherDbMsg = (DatabaseMessage) other;
// No need to check timestamp since we only need this when we compare
// messages at the same timestamp
return TextUtils.equals(getUri(), otherDbMsg.getUri());
}
@Override
public int hashCode() {
// No need to check timestamp since we only need this when we compare
// messages at the same timestamp
return getUri().hashCode();
}
}
/**
* SMS message
*/
public static class SmsMessage extends DatabaseMessage implements Parcelable {
private static int sIota = 0;
public static final int INDEX_ID = sIota++;
public static final int INDEX_TYPE = sIota++;
public static final int INDEX_ADDRESS = sIota++;
public static final int INDEX_BODY = sIota++;
public static final int INDEX_DATE = sIota++;
public static final int INDEX_THREAD_ID = sIota++;
public static final int INDEX_STATUS = sIota++;
public static final int INDEX_READ = sIota++;
public static final int INDEX_SEEN = sIota++;
public static final int INDEX_DATE_SENT = sIota++;
public static final int INDEX_SUB_ID = sIota++;
private static String[] sProjection;
public static String[] getProjection() {
if (sProjection == null) {
String[] projection = new String[] {
Sms._ID,
Sms.TYPE,
Sms.ADDRESS,
Sms.BODY,
Sms.DATE,
Sms.THREAD_ID,
Sms.STATUS,
Sms.READ,
Sms.SEEN,
Sms.DATE_SENT,
Sms.SUBSCRIPTION_ID,
};
if (!MmsUtils.hasSmsDateSentColumn()) {
projection[INDEX_DATE_SENT] = Sms.DATE;
}
if (!OsUtil.isAtLeastL_MR1()) {
Assert.equals(INDEX_SUB_ID, projection.length - 1);
String[] withoutSubId = new String[projection.length - 1];
System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
projection = withoutSubId;
}
sProjection = projection;
}
return sProjection;
}
public String mUri;
public String mAddress;
public String mBody;
private long mRowId;
public long mTimestampInMillis;
public long mTimestampSentInMillis;
public int mType;
public long mThreadId;
public int mStatus;
public boolean mRead;
public boolean mSeen;
public int mSubId;
private SmsMessage() {
}
/**
* Load from a cursor of a query that returns the SMS to import
*
* @param cursor
*/
private void load(final Cursor cursor) {
mRowId = cursor.getLong(INDEX_ID);
mAddress = cursor.getString(INDEX_ADDRESS);
mBody = cursor.getString(INDEX_BODY);
mTimestampInMillis = cursor.getLong(INDEX_DATE);
// Before ICS, there is no "date_sent" so use copy of "date" value
mTimestampSentInMillis = cursor.getLong(INDEX_DATE_SENT);
mType = cursor.getInt(INDEX_TYPE);
mThreadId = cursor.getLong(INDEX_THREAD_ID);
mStatus = cursor.getInt(INDEX_STATUS);
mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
mUri = ContentUris.withAppendedId(Sms.CONTENT_URI, mRowId).toString();
mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
}
/**
* Get a new SmsMessage by loading from the cursor of a query
* that returns the SMS to import
*
* @param cursor
* @return
*/
public static SmsMessage get(final Cursor cursor) {
final SmsMessage msg = new SmsMessage();
msg.load(cursor);
return msg;
}
@Override
public String getUri() {
return mUri;
}
public int getSubId() {
return mSubId;
}
@Override
public int getProtocol() {
return MessageData.PROTOCOL_SMS;
}
@Override
public long getTimestampInMillis() {
return mTimestampInMillis;
}
@Override
public int describeContents() {
return 0;
}
private SmsMessage(final Parcel in) {
mUri = in.readString();
mRowId = in.readLong();
mTimestampInMillis = in.readLong();
mTimestampSentInMillis = in.readLong();
mType = in.readInt();
mThreadId = in.readLong();
mStatus = in.readInt();
mRead = in.readInt() != 0;
mSeen = in.readInt() != 0;
mSubId = in.readInt();
// SMS specific
mAddress = in.readString();
mBody = in.readString();
}
public static final Parcelable.Creator<SmsMessage> CREATOR
= new Parcelable.Creator<SmsMessage>() {
@Override
public SmsMessage createFromParcel(final Parcel in) {
return new SmsMessage(in);
}
@Override
public SmsMessage[] newArray(final int size) {
return new SmsMessage[size];
}
};
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeString(mUri);
out.writeLong(mRowId);
out.writeLong(mTimestampInMillis);
out.writeLong(mTimestampSentInMillis);
out.writeInt(mType);
out.writeLong(mThreadId);
out.writeInt(mStatus);
out.writeInt(mRead ? 1 : 0);
out.writeInt(mSeen ? 1 : 0);
out.writeInt(mSubId);
// SMS specific
out.writeString(mAddress);
out.writeString(mBody);
}
}
/**
* MMS message
*/
public static class MmsMessage extends DatabaseMessage implements Parcelable {
private static int sIota = 0;
public static final int INDEX_ID = sIota++;
public static final int INDEX_MESSAGE_BOX = sIota++;
public static final int INDEX_SUBJECT = sIota++;
public static final int INDEX_SUBJECT_CHARSET = sIota++;
public static final int INDEX_MESSAGE_SIZE = sIota++;
public static final int INDEX_DATE = sIota++;
public static final int INDEX_DATE_SENT = sIota++;
public static final int INDEX_THREAD_ID = sIota++;
public static final int INDEX_PRIORITY = sIota++;
public static final int INDEX_STATUS = sIota++;
public static final int INDEX_READ = sIota++;
public static final int INDEX_SEEN = sIota++;
public static final int INDEX_CONTENT_LOCATION = sIota++;
public static final int INDEX_TRANSACTION_ID = sIota++;
public static final int INDEX_MESSAGE_TYPE = sIota++;
public static final int INDEX_EXPIRY = sIota++;
public static final int INDEX_RESPONSE_STATUS = sIota++;
public static final int INDEX_RETRIEVE_STATUS = sIota++;
public static final int INDEX_SUB_ID = sIota++;
private static String[] sProjection;
public static String[] getProjection() {
if (sProjection == null) {
String[] projection = new String[] {
Mms._ID,
Mms.MESSAGE_BOX,
Mms.SUBJECT,
Mms.SUBJECT_CHARSET,
Mms.MESSAGE_SIZE,
Mms.DATE,
Mms.DATE_SENT,
Mms.THREAD_ID,
Mms.PRIORITY,
Mms.STATUS,
Mms.READ,
Mms.SEEN,
Mms.CONTENT_LOCATION,
Mms.TRANSACTION_ID,
Mms.MESSAGE_TYPE,
Mms.EXPIRY,
Mms.RESPONSE_STATUS,
Mms.RETRIEVE_STATUS,
Mms.SUBSCRIPTION_ID,
};
if (!OsUtil.isAtLeastL_MR1()) {
Assert.equals(INDEX_SUB_ID, projection.length - 1);
String[] withoutSubId = new String[projection.length - 1];
System.arraycopy(projection, 0, withoutSubId, 0, withoutSubId.length);
projection = withoutSubId;
}
sProjection = projection;
}
return sProjection;
}
public String mUri;
private long mRowId;
public int mType;
public String mSubject;
public int mSubjectCharset;
private long mSize;
public long mTimestampInMillis;
public long mSentTimestampInMillis;
public long mThreadId;
public int mPriority;
public int mStatus;
public boolean mRead;
public boolean mSeen;
public String mContentLocation;
public String mTransactionId;
public int mMmsMessageType;
public long mExpiryInMillis;
public int mSubId;
public String mSender;
public int mResponseStatus;
public int mRetrieveStatus;
public List<MmsPart> mParts = Lists.newArrayList();
private boolean mPartsProcessed = false;
private MmsMessage() {
}
/**
* Load from a cursor of a query that returns the MMS to import
*
* @param cursor
*/
public void load(final Cursor cursor) {
mRowId = cursor.getLong(INDEX_ID);
mType = cursor.getInt(INDEX_MESSAGE_BOX);
mSubject = cursor.getString(INDEX_SUBJECT);
mSubjectCharset = cursor.getInt(INDEX_SUBJECT_CHARSET);
if (!TextUtils.isEmpty(mSubject)) {
// PduPersister stores the subject using ISO_8859_1
// Let's load it using that encoding and convert it back to its original
// See PduPersister.persist and PduPersister.toIsoString
// (Refer to bug b/11162476)
mSubject = getDecodedString(
getStringBytes(mSubject, CharacterSets.ISO_8859_1), mSubjectCharset);
}
mSize = cursor.getLong(INDEX_MESSAGE_SIZE);
// MMS db times are in seconds
mTimestampInMillis = cursor.getLong(INDEX_DATE) * 1000;
mSentTimestampInMillis = cursor.getLong(INDEX_DATE_SENT) * 1000;
mThreadId = cursor.getLong(INDEX_THREAD_ID);
mPriority = cursor.getInt(INDEX_PRIORITY);
mStatus = cursor.getInt(INDEX_STATUS);
mRead = cursor.getInt(INDEX_READ) == 0 ? false : true;
mSeen = cursor.getInt(INDEX_SEEN) == 0 ? false : true;
mContentLocation = cursor.getString(INDEX_CONTENT_LOCATION);
mTransactionId = cursor.getString(INDEX_TRANSACTION_ID);
mMmsMessageType = cursor.getInt(INDEX_MESSAGE_TYPE);
mExpiryInMillis = cursor.getLong(INDEX_EXPIRY) * 1000;
mResponseStatus = cursor.getInt(INDEX_RESPONSE_STATUS);
mRetrieveStatus = cursor.getInt(INDEX_RETRIEVE_STATUS);
// Clear all parts in case we reuse this object
mParts.clear();
mPartsProcessed = false;
mUri = ContentUris.withAppendedId(Mms.CONTENT_URI, mRowId).toString();
mSubId = PhoneUtils.getDefault().getSubIdFromTelephony(cursor, INDEX_SUB_ID);
}
/**
* Get a new MmsMessage by loading from the cursor of a query
* that returns the MMS to import
*
* @param cursor
* @return
*/
public static MmsMessage get(final Cursor cursor) {
final MmsMessage msg = new MmsMessage();
msg.load(cursor);
return msg;
}
/**
* Add a loaded MMS part
*
* @param part
*/
public void addPart(final MmsPart part) {
mParts.add(part);
}
public List<MmsPart> getParts() {
return mParts;
}
public long getSize() {
if (!mPartsProcessed) {
processParts();
}
return mSize;
}
/**
* Process loaded MMS parts to obtain the combined text, the combined attachment url,
* the combined content type and the combined size.
*/
private void processParts() {
if (mPartsProcessed) {
return;
}
mPartsProcessed = true;
// Remember the width and height of the first media part
// These are needed when building attachment list
long sizeOfParts = 0L;
for (final MmsPart part : mParts) {
sizeOfParts += part.mSize;
}
if (mSize <= 0) {
mSize = mSubject != null ? mSubject.getBytes().length : 0L;
mSize += sizeOfParts;
}
}
@Override
public String getUri() {
return mUri;
}
public long getId() {
return mRowId;
}
public int getSubId() {
return mSubId;
}
@Override
public int getProtocol() {
return MessageData.PROTOCOL_MMS;
}
@Override
public long getTimestampInMillis() {
return mTimestampInMillis;
}
@Override
public int describeContents() {
return 0;
}
public void setSender(final String sender) {
mSender = sender;
}
private MmsMessage(final Parcel in) {
mUri = in.readString();
mRowId = in.readLong();
mTimestampInMillis = in.readLong();
mSentTimestampInMillis = in.readLong();
mType = in.readInt();
mThreadId = in.readLong();
mStatus = in.readInt();
mRead = in.readInt() != 0;
mSeen = in.readInt() != 0;
mSubId = in.readInt();
// MMS specific
mSubject = in.readString();
mContentLocation = in.readString();
mTransactionId = in.readString();
mSender = in.readString();
mSize = in.readLong();
mExpiryInMillis = in.readLong();
mSubjectCharset = in.readInt();
mPriority = in.readInt();
mMmsMessageType = in.readInt();
mResponseStatus = in.readInt();
mRetrieveStatus = in.readInt();
final int nParts = in.readInt();
mParts = new ArrayList<MmsPart>();
mPartsProcessed = false;
for (int i = 0; i < nParts; i++) {
mParts.add((MmsPart) in.readParcelable(getClass().getClassLoader()));
}
}
public static final Parcelable.Creator<MmsMessage> CREATOR
= new Parcelable.Creator<MmsMessage>() {
@Override
public MmsMessage createFromParcel(final Parcel in) {
return new MmsMessage(in);
}
@Override
public MmsMessage[] newArray(final int size) {
return new MmsMessage[size];
}
};
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeString(mUri);
out.writeLong(mRowId);
out.writeLong(mTimestampInMillis);
out.writeLong(mSentTimestampInMillis);
out.writeInt(mType);
out.writeLong(mThreadId);
out.writeInt(mStatus);
out.writeInt(mRead ? 1 : 0);
out.writeInt(mSeen ? 1 : 0);
out.writeInt(mSubId);
out.writeString(mSubject);
out.writeString(mContentLocation);
out.writeString(mTransactionId);
out.writeString(mSender);
out.writeLong(mSize);
out.writeLong(mExpiryInMillis);
out.writeInt(mSubjectCharset);
out.writeInt(mPriority);
out.writeInt(mMmsMessageType);
out.writeInt(mResponseStatus);
out.writeInt(mRetrieveStatus);
out.writeInt(mParts.size());
for (final MmsPart part : mParts) {
out.writeParcelable(part, 0);
}
}
}
/**
* Part of an MMS message
*/
public static class MmsPart implements Parcelable {
public static final String[] PROJECTION = new String[] {
Mms.Part._ID,
Mms.Part.MSG_ID,
Mms.Part.CHARSET,
Mms.Part.CONTENT_TYPE,
Mms.Part.TEXT,
};
private static int sIota = 0;
public static final int INDEX_ID = sIota++;
public static final int INDEX_MSG_ID = sIota++;
public static final int INDEX_CHARSET = sIota++;
public static final int INDEX_CONTENT_TYPE = sIota++;
public static final int INDEX_TEXT = sIota++;
public String mUri;
public long mRowId;
public long mMessageId;
public String mContentType;
public String mText;
public int mCharset;
private int mWidth;
private int mHeight;
public long mSize;
private MmsPart() {
}
/**
* Load from a cursor of a query that returns the MMS part to import
*
* @param cursor
*/
public void load(final Cursor cursor, final boolean loadMedia) {
mRowId = cursor.getLong(INDEX_ID);
mMessageId = cursor.getLong(INDEX_MSG_ID);
mContentType = cursor.getString(INDEX_CONTENT_TYPE);
mText = cursor.getString(INDEX_TEXT);
mCharset = cursor.getInt(INDEX_CHARSET);
mWidth = 0;
mHeight = 0;
mSize = 0;
if (isMedia()) {
// For importing we don't load media since performance is critical
// For loading when we receive mms, we do load media to get enough
// information of the media file
if (loadMedia) {
if (ContentType.isImageType(mContentType)) {
loadImage();
} else if (ContentType.isVideoType(mContentType)) {
loadVideo();
} // No need to load audio for parsing
mSize = MmsUtils.getMediaFileSize(getDataUri());
}
} else {
// Load text if not media type
loadText();
}
mUri = Uri.withAppendedPath(Mms.CONTENT_URI, cursor.getString(INDEX_ID)).toString();
}
/**
* Get content type from file extension
*/
private static String extractContentType(final Context context, final Uri uri) {
final String path = uri.getPath();
final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
String extension = MimeTypeMap.getFileExtensionFromUrl(path);
if (TextUtils.isEmpty(extension)) {
// getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle
// urlEncoded strings. Let's try one last time at finding the extension.
final int dotPos = path.lastIndexOf('.');
if (0 <= dotPos) {
extension = path.substring(dotPos + 1);
}
}
return mimeTypeMap.getMimeTypeFromExtension(extension);
}
/**
* Get text of a text part
*/
private void loadText() {
byte[] data = null;
if (isEmbeddedTextType()) {
// Embedded text, get from the "text" column
if (!TextUtils.isEmpty(mText)) {
data = getStringBytes(mText, mCharset);
}
} else {
// Not embedded, load from disk
final ContentResolver resolver =
Factory.get().getApplicationContext().getContentResolver();
final Uri uri = getDataUri();
InputStream is = null;
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = resolver.openInputStream(uri);
final byte[] buffer = new byte[256];
int len = is.read(buffer);
while (len >= 0) {
baos.write(buffer, 0, len);
len = is.read(buffer);
}
} catch (final IOException e) {
LogUtil.e(TAG,
"DatabaseMessages.MmsPart: loading text from file failed: " + e, e);
} finally {
if (is != null) {
try {
is.close();
} catch (final IOException e) {
LogUtil.e(TAG, "DatabaseMessages.MmsPart: close file failed: " + e, e);
}
}
}
data = baos.toByteArray();
}
if (data != null && data.length > 0) {
mSize = data.length;
mText = getDecodedString(data, mCharset);
}
}
/**
* Load image file of an image part and parse the dimensions and type
*/
private void loadImage() {
final Context context = Factory.get().getApplicationContext();
final ContentResolver resolver = context.getContentResolver();
final Uri uri = getDataUri();
// We have to get the width and height of the image -- they're needed when adding
// an attachment in bugle.
InputStream is = null;
try {
is = resolver.openInputStream(uri);
final BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, opt);
mContentType = opt.outMimeType;
mWidth = opt.outWidth;
mHeight = opt.outHeight;
if (TextUtils.isEmpty(mContentType)) {
// BitmapFactory couldn't figure out the image type. That's got to be a bad
// sign, but see if we can figure it out from the file extension.
mContentType = extractContentType(context, uri);
}
} catch (final FileNotFoundException e) {
LogUtil.e(TAG, "DatabaseMessages.MmsPart.loadImage: file not found", e);
} finally {
if (is != null) {
try {
is.close();
} catch (final IOException e) {
Log.e(TAG, "IOException caught while closing stream", e);
}
}
}
}
/**
* Load video file of a video part and parse the dimensions and type
*/
private void loadVideo() {
// This is a coarse check, and should not be applied to outgoing messages. However,
// currently, this does not cause any problems.
if (!VideoThumbnailRequest.shouldShowIncomingVideoThumbnails()) {
return;
}
final Uri uri = getDataUri();
final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
try {
retriever.setDataSource(uri);
// FLAG: This inadvertently fixes a problem with phone receiving audio
// messages on some carrier. We should handle this in a less accidental way so that
// we don't break it again. (The carrier changes the content type in the wrapper
// in-transit from audio/mp4 to video/3gpp without changing the data)
// Also note: There is a bug in some OEM device where mmr returns
// video/ffmpeg for image files. That shouldn't happen here but be aware.
mContentType =
retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
final Bitmap bitmap = retriever.getFrameAtTime(-1);
if (bitmap != null) {
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
} else {
// Get here if it's not actually video (see above)
LogUtil.i(LogUtil.BUGLE_TAG, "loadVideo: Got null bitmap from " + uri);
}
} catch (IOException e) {
LogUtil.i(LogUtil.BUGLE_TAG, "Error extracting metadata from " + uri, e);
} finally {
retriever.release();
}
}
/**
* Get media file size
*/
private long getMediaFileSize() {
final Context context = Factory.get().getApplicationContext();
final Uri uri = getDataUri();
AssetFileDescriptor fd = null;
try {
fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
if (fd != null) {
return fd.getParcelFileDescriptor().getStatSize();
}
} catch (final FileNotFoundException e) {
LogUtil.e(TAG, "DatabaseMessages.MmsPart: cound not find media file: " + e, e);
} finally {
if (fd != null) {
try {
fd.close();
} catch (final IOException e) {
LogUtil.e(TAG, "DatabaseMessages.MmsPart: failed to close " + e, e);
}
}
}
return 0L;
}
/**
* @return If the type is a text type that stores text embedded (i.e. in db table)
*/
private boolean isEmbeddedTextType() {
return ContentType.TEXT_PLAIN.equals(mContentType)
|| ContentType.APP_SMIL.equals(mContentType)
|| ContentType.TEXT_HTML.equals(mContentType);
}
/**
* Get an instance of the MMS part from the part table cursor
*
* @param cursor
* @param loadMedia Whether to load the media file of the part
* @return
*/
public static MmsPart get(final Cursor cursor, final boolean loadMedia) {
final MmsPart part = new MmsPart();
part.load(cursor, loadMedia);
return part;
}
public boolean isText() {
return ContentType.TEXT_PLAIN.equals(mContentType)
|| ContentType.TEXT_HTML.equals(mContentType)
|| ContentType.APP_WAP_XHTML.equals(mContentType);
}
public boolean isMedia() {
return ContentType.isImageType(mContentType)
|| ContentType.isVideoType(mContentType)
|| ContentType.isAudioType(mContentType)
|| ContentType.isVCardType(mContentType);
}
public boolean isImage() {
return ContentType.isImageType(mContentType);
}
public Uri getDataUri() {
return Uri.parse("content://mms/part/" + mRowId);
}
@Override
public int describeContents() {
return 0;
}
private MmsPart(final Parcel in) {
mUri = in.readString();
mRowId = in.readLong();
mMessageId = in.readLong();
mContentType = in.readString();
mText = in.readString();
mCharset = in.readInt();
mWidth = in.readInt();
mHeight = in.readInt();
mSize = in.readLong();
}
public static final Parcelable.Creator<MmsPart> CREATOR
= new Parcelable.Creator<MmsPart>() {
@Override
public MmsPart createFromParcel(final Parcel in) {
return new MmsPart(in);
}
@Override
public MmsPart[] newArray(final int size) {
return new MmsPart[size];
}
};
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeString(mUri);
out.writeLong(mRowId);
out.writeLong(mMessageId);
out.writeString(mContentType);
out.writeString(mText);
out.writeInt(mCharset);
out.writeInt(mWidth);
out.writeInt(mHeight);
out.writeLong(mSize);
}
}
/**
* This class provides the same DatabaseMessage interface over a local SMS db message
*/
public static class LocalDatabaseMessage extends DatabaseMessage implements Parcelable {
private final int mProtocol;
private final String mUri;
private final long mTimestamp;
private final long mLocalId;
private final String mConversationId;
public LocalDatabaseMessage(final long localId, final int protocol, final String uri,
final long timestamp, final String conversationId) {
mLocalId = localId;
mProtocol = protocol;
mUri = uri;
mTimestamp = timestamp;
mConversationId = conversationId;
}
@Override
public int getProtocol() {
return mProtocol;
}
@Override
public long getTimestampInMillis() {
return mTimestamp;
}
@Override
public String getUri() {
return mUri;
}
public long getLocalId() {
return mLocalId;
}
public String getConversationId() {
return mConversationId;
}
@Override
public int describeContents() {
return 0;
}
private LocalDatabaseMessage(final Parcel in) {
mUri = in.readString();
mConversationId = in.readString();
mLocalId = in.readLong();
mTimestamp = in.readLong();
mProtocol = in.readInt();
}
public static final Parcelable.Creator<LocalDatabaseMessage> CREATOR
= new Parcelable.Creator<LocalDatabaseMessage>() {
@Override
public LocalDatabaseMessage createFromParcel(final Parcel in) {
return new LocalDatabaseMessage(in);
}
@Override
public LocalDatabaseMessage[] newArray(final int size) {
return new LocalDatabaseMessage[size];
}
};
@Override
public void writeToParcel(final Parcel out, final int flags) {
out.writeString(mUri);
out.writeString(mConversationId);
out.writeLong(mLocalId);
out.writeLong(mTimestamp);
out.writeInt(mProtocol);
}
}
/**
* Address for MMS message
*/
public static class MmsAddr {
public static final String[] PROJECTION = new String[] {
Mms.Addr.ADDRESS,
Mms.Addr.CHARSET,
};
private static int sIota = 0;
public static final int INDEX_ADDRESS = sIota++;
public static final int INDEX_CHARSET = sIota++;
public static String get(final Cursor cursor) {
final int charset = cursor.getInt(INDEX_CHARSET);
// PduPersister stores the addresses using ISO_8859_1
// Let's load it using that encoding and convert it back to its original
// See PduPersister.persistAddress
return getDecodedString(
getStringBytes(cursor.getString(INDEX_ADDRESS), CharacterSets.ISO_8859_1),
charset);
}
}
/**
* Decoded string by character set
*/
public static String getDecodedString(final byte[] data, final int charset) {
if (CharacterSets.ANY_CHARSET == charset) {
return new String(data); // system default encoding.
} else {
try {
final String name = CharacterSets.getMimeName(charset);
return new String(data, name);
} catch (final UnsupportedEncodingException e) {
try {
return new String(data, CharacterSets.MIMENAME_ISO_8859_1);
} catch (final UnsupportedEncodingException exception) {
return new String(data); // system default encoding.
}
}
}
}
/**
* Unpack a given String into a byte[].
*/
public static byte[] getStringBytes(final String data, final int charset) {
if (CharacterSets.ANY_CHARSET == charset) {
return data.getBytes();
} else {
try {
final String name = CharacterSets.getMimeName(charset);
return data.getBytes(name);
} catch (final UnsupportedEncodingException e) {
return data.getBytes();
}
}
}
}