/*
* Copyright (c) 2009 Christoph Studer <chstuder@gmail.com>
* Copyright (c) 2010 Jan Berkel <jan.berkel@gmail.com>
*
* 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.zegoggles.smssync.mail;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.Telephony;
import android.util.Log;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeUtility;
import com.zegoggles.smssync.MmsConsts;
import com.zegoggles.smssync.contacts.ContactAccessor;
import com.zegoggles.smssync.contacts.ContactGroup;
import com.zegoggles.smssync.contacts.ContactGroupIds;
import com.zegoggles.smssync.preferences.AddressStyle;
import com.zegoggles.smssync.preferences.MarkAsReadTypes;
import com.zegoggles.smssync.preferences.Preferences;
import com.zegoggles.smssync.utils.ThreadHelper;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import static com.zegoggles.smssync.App.LOCAL_LOGV;
import static com.zegoggles.smssync.App.TAG;
public class MessageConverter {
//ContactsContract.CommonDataKinds.Email.CONTENT_URI
public static final Uri ECLAIR_CONTENT_URI =
Uri.parse("content://com.android.contacts/data/emails");
private final Context mContext;
private final ThreadHelper threadHelper = new ThreadHelper();
private final MarkAsReadTypes mMarkAsReadType;
private final PersonLookup mPersonLookup;
private final MessageGenerator mMessageGenerator;
private final boolean mMarkAsReadOnRestore;
public MessageConverter(Context context, Preferences preferences,
String userEmail,
PersonLookup personLookup,
ContactAccessor contactAccessor) {
mContext = context;
mMarkAsReadType = preferences.getMarkAsReadType();
mPersonLookup = personLookup;
mMarkAsReadOnRestore = preferences.getMarkAsReadOnRestore();
String referenceUid = preferences.getReferenceUid();
if (referenceUid == null) {
referenceUid = generateReferenceValue();
preferences.setReferenceUid(referenceUid);
}
final ContactGroup backupContactGroup = preferences.getBackupContactGroup();
ContactGroupIds allowedIds = contactAccessor.getGroupContactIds(context.getContentResolver(), backupContactGroup);
if (LOCAL_LOGV) Log.v(TAG, "whitelisted ids for backup: " + allowedIds);
mMessageGenerator = new MessageGenerator(mContext,
new Address(userEmail),
AddressStyle.getEmailAddressStyle(preferences),
new HeaderGenerator(referenceUid, preferences.getVersion(true)),
mPersonLookup,
preferences.getMailSubjectPrefix(),
allowedIds,
new MmsSupport(mContext.getContentResolver(), mPersonLookup));
}
private boolean markAsSeen(DataType dataType, Map<String, String> msgMap) {
switch (mMarkAsReadType) {
case MESSAGE_STATUS:
switch (dataType) {
case SMS:
return "1".equals(msgMap.get(Telephony.TextBasedSmsColumns.READ));
case MMS:
return "1".equals(msgMap.get(Telephony.BaseMmsColumns.READ));
default:
return true;
}
case UNREAD:
return false;
case READ:
default:
return true;
}
}
public @NotNull ConversionResult convertMessages(final Cursor cursor, DataType dataType)
throws MessagingException {
final Map<String, String> msgMap = getMessageMap(cursor);
final Message m = mMessageGenerator.messageForDataType(msgMap, dataType);
final ConversionResult result = new ConversionResult(dataType);
if (m != null) {
m.setFlag(Flag.SEEN, markAsSeen(dataType, msgMap));
result.add(m, msgMap);
}
return result;
}
public @NotNull ContentValues messageToContentValues(final Message message)
throws IOException, MessagingException {
if (message == null) throw new MessagingException("message is null");
final ContentValues values = new ContentValues();
switch (getDataType(message)) {
case SMS:
if (message.getBody() == null) throw new MessagingException("body is null");
InputStream is = MimeUtility.decodeBody(message.getBody());
if (is == null) {
throw new MessagingException("body.getInputStream() is null for " + message.getBody());
}
final String body = IOUtils.toString(is);
final String address = Headers.get(message, Headers.ADDRESS);
values.put(Telephony.TextBasedSmsColumns.BODY, body);
values.put(Telephony.TextBasedSmsColumns.ADDRESS, address);
values.put(Telephony.TextBasedSmsColumns.TYPE, Headers.get(message, Headers.TYPE));
values.put(Telephony.TextBasedSmsColumns.PROTOCOL, Headers.get(message, Headers.PROTOCOL));
values.put(Telephony.TextBasedSmsColumns.SERVICE_CENTER, Headers.get(message, Headers.SERVICE_CENTER));
values.put(Telephony.TextBasedSmsColumns.DATE, Headers.get(message, Headers.DATE));
values.put(Telephony.TextBasedSmsColumns.STATUS, Headers.get(message, Headers.STATUS));
values.put(Telephony.TextBasedSmsColumns.THREAD_ID, threadHelper.getThreadId(mContext, address));
values.put(Telephony.TextBasedSmsColumns.READ,
mMarkAsReadOnRestore ? "1" : Headers.get(message, Headers.READ));
break;
case CALLLOG:
values.put(CallLog.Calls.NUMBER, Headers.get(message, Headers.ADDRESS));
values.put(CallLog.Calls.TYPE, Integer.valueOf(Headers.get(message, Headers.TYPE)));
values.put(CallLog.Calls.DATE, Headers.get(message, Headers.DATE));
values.put(CallLog.Calls.DURATION, Long.valueOf(Headers.get(message, Headers.DURATION)));
values.put(CallLog.Calls.NEW, 0);
PersonRecord record = mPersonLookup.lookupPerson(Headers.get(message, Headers.ADDRESS));
if (!record.isUnknown()) {
values.put(CallLog.Calls.CACHED_NAME, record.getName());
values.put(CallLog.Calls.CACHED_NUMBER_TYPE, -2);
}
break;
default:
throw new MessagingException("don't know how to restore " + getDataType(message));
}
return values;
}
public DataType getDataType(Message message) {
final String dataTypeHeader = Headers.get(message, Headers.DATATYPE);
final String typeHeader = Headers.get(message, Headers.TYPE);
//we have two possible header sets here
//legacy: there is Headers.DATATYPE .Headers.TYPE
// contains either the string "mms" or an integer which is the internal type of the sms
//current: there IS a Headers.DATATYPE containing a string representation of Headers.DataType
// Headers.TYPE then contains the type of the sms, mms or calllog entry
//The current header set was introduced in version 1.2.00
if (dataTypeHeader == null) {
return MmsConsts.LEGACY_HEADER.equalsIgnoreCase(typeHeader) ? DataType.MMS : DataType.SMS;
} else {
try {
return DataType.valueOf(dataTypeHeader.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
return DataType.SMS; // whateva
}
}
}
private Map<String, String> getMessageMap(Cursor cursor) {
final String[] columns = cursor.getColumnNames();
final Map<String, String> msgMap = new HashMap<String, String>(columns.length);
for (String column : columns) {
String value;
try {
final int index = cursor.getColumnIndex(column);
if (index != -1) {
value = cursor.getString(index);
} else {
continue;
}
} catch (SQLiteException ignored) {
// this can happen in case of BLOBS in the DB
// column type checking is API level >= 11
value = "[BLOB]";
}
msgMap.put(column, value);
}
return msgMap;
}
private static String generateReferenceValue() {
final StringBuilder sb = new StringBuilder();
final Random random = new Random();
for (int i = 0; i < 24; i++) {
sb.append(Integer.toString(random.nextInt(35), 36));
}
return sb.toString();
}
}