package com.zegoggles.smssync.mail;
import android.content.Context;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.Telephony;
import android.text.TextUtils;
import android.util.Log;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
import com.zegoggles.smssync.Consts;
import com.zegoggles.smssync.contacts.ContactGroupIds;
import com.zegoggles.smssync.preferences.AddressStyle;
import com.zegoggles.smssync.preferences.CallLogTypes;
import com.zegoggles.smssync.preferences.Preferences;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import static com.fsck.k9.mail.internet.MimeMessageHelper.setBody;
import static com.zegoggles.smssync.App.LOCAL_LOGV;
import static com.zegoggles.smssync.App.TAG;
import static com.zegoggles.smssync.Consts.MMS_PART;
class MessageGenerator {
private static final String ERROR_PARSING_DATE = "error parsing date";
private final Context mContext;
private final HeaderGenerator mHeaderGenerator;
private final Address mUserAddress;
private final PersonLookup mPersonLookup;
private final boolean mPrefix;
private final @Nullable ContactGroupIds mContactsToBackup;
private final CallFormatter mCallFormatter;
private final AddressStyle mAddressStyle;
private final MmsSupport mMmsSupport;
private final CallLogTypes mCallLogTypes;
public MessageGenerator(Context context,
Address userAddress,
AddressStyle addressStyle,
HeaderGenerator headerGenerator,
PersonLookup personLookup,
boolean mailSubjectPrefix,
@Nullable ContactGroupIds contactsToBackup,
MmsSupport mmsSupport) {
mHeaderGenerator = headerGenerator;
mUserAddress = userAddress;
mAddressStyle = addressStyle;
mContext = context;
mPersonLookup = personLookup;
mPrefix = mailSubjectPrefix;
mContactsToBackup = contactsToBackup;
mCallFormatter = new CallFormatter(mContext.getResources());
mMmsSupport = mmsSupport;
mCallLogTypes = CallLogTypes.getCallLogType(new Preferences(context));
}
public @Nullable Message messageForDataType(Map<String, String> msgMap, DataType dataType) throws MessagingException {
switch (dataType) {
case SMS: return messageFromMapSms(msgMap);
case MMS: return messageFromMapMms(msgMap);
case CALLLOG: return messageFromMapCallLog(msgMap);
default: return null;
}
}
private @Nullable Message messageFromMapSms(Map<String, String> msgMap) throws MessagingException {
final String address = msgMap.get(Telephony.TextBasedSmsColumns.ADDRESS);
if (TextUtils.isEmpty(address)) return null;
PersonRecord record = mPersonLookup.lookupPerson(address);
if (!includePersonInBackup(record, DataType.SMS)) return null;
final Message msg = new MimeMessage();
msg.setSubject(getSubject(DataType.SMS, record));
setBody(msg, new TextBody(msgMap.get(Telephony.TextBasedSmsColumns.BODY)));
final int messageType = toInt(msgMap.get(Telephony.TextBasedSmsColumns.TYPE));
if (Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX == messageType) {
// Received message
msg.setFrom(record.getAddress(mAddressStyle));
msg.setRecipient(Message.RecipientType.TO, mUserAddress);
} else {
// Sent message
msg.setRecipient(Message.RecipientType.TO, record.getAddress(mAddressStyle));
msg.setFrom(mUserAddress);
}
Date sentDate;
try {
sentDate = new Date(Long.valueOf(msgMap.get(Telephony.TextBasedSmsColumns.DATE)));
} catch (NumberFormatException n) {
Log.e(TAG, ERROR_PARSING_DATE, n);
sentDate = new Date();
}
mHeaderGenerator.setHeaders(msg, msgMap, DataType.SMS, address, record, sentDate, messageType);
return msg;
}
private @Nullable Message messageFromMapMms(Map<String, String> msgMap) throws MessagingException {
if (LOCAL_LOGV) Log.v(TAG, "messageFromMapMms(" + msgMap + ")");
final Uri mmsUri = Uri.withAppendedPath(Consts.MMS_PROVIDER, msgMap.get(Telephony.BaseMmsColumns._ID));
MmsSupport.MmsDetails details = mMmsSupport.getDetails(mmsUri, mAddressStyle);
if (details.isEmpty()) {
Log.w(TAG, "no recipients found");
return null;
} else if (!includeInBackup(DataType.MMS, details.records)) {
Log.w(TAG, "no recipients included");
return null;
}
final Message msg = new MimeMessage();
msg.setSubject(getSubject(DataType.MMS, details.getRecipient()));
if (details.inbound) {
// msg_box == MmsConsts.MESSAGE_BOX_INBOX does not work
msg.setFrom(details.getRecipientAddress());
msg.setRecipient(Message.RecipientType.TO, mUserAddress);
} else {
msg.setRecipients(Message.RecipientType.TO, details.getAddresses());
msg.setFrom(mUserAddress);
}
Date sentDate;
try {
sentDate = new Date(1000 * Long.valueOf(msgMap.get(Telephony.BaseMmsColumns.DATE)));
} catch (NumberFormatException n) {
Log.e(TAG, ERROR_PARSING_DATE, n);
sentDate = new Date();
}
final int msg_box = toInt(msgMap.get("msg_box"));
mHeaderGenerator.setHeaders(msg, msgMap, DataType.MMS, details.address, details.getRecipient(), sentDate, msg_box);
MimeMultipart body = MimeMultipart.newInstance();
for (BodyPart p : mMmsSupport.getMMSBodyParts(Uri.withAppendedPath(mmsUri, MMS_PART))) {
body.addBodyPart(p);
}
setBody(msg, body);
return msg;
}
private @Nullable Message messageFromMapCallLog(Map<String, String> msgMap) throws MessagingException {
final String address = msgMap.get(CallLog.Calls.NUMBER);
final int callType = toInt(msgMap.get(CallLog.Calls.TYPE));
if (!mCallLogTypes.isTypeEnabled(callType)) {
if (LOCAL_LOGV) Log.v(TAG, "ignoring call log entry: " + msgMap);
return null;
}
PersonRecord record = mPersonLookup.lookupPerson(address);
if (!includePersonInBackup(record, DataType.CALLLOG)) return null;
final Message msg = new MimeMessage();
msg.setSubject(getSubject(DataType.CALLLOG, record));
switch (callType) {
case CallLog.Calls.OUTGOING_TYPE:
msg.setFrom(mUserAddress);
msg.setRecipient(Message.RecipientType.TO, record.getAddress(mAddressStyle));
break;
case CallLog.Calls.MISSED_TYPE:
case CallLog.Calls.INCOMING_TYPE:
msg.setFrom(record.getAddress(mAddressStyle));
msg.setRecipient(Message.RecipientType.TO, mUserAddress);
break;
default:
// some weird phones seem to have SMS in their call logs, which is
// not part of the official API.
Log.w(TAG, "ignoring unknown call type: " + callType);
return null;
}
final int duration = msgMap.get(CallLog.Calls.DURATION) == null ? 0 :
toInt(msgMap.get(CallLog.Calls.DURATION));
setBody(msg, new TextBody(mCallFormatter.format(callType, record.getNumber(), duration)));
Date sentDate;
try {
sentDate = new Date(Long.valueOf(msgMap.get(CallLog.Calls.DATE)));
} catch (NumberFormatException n) {
Log.e(TAG, ERROR_PARSING_DATE, n);
sentDate = new Date();
}
mHeaderGenerator.setHeaders(msg, msgMap, DataType.CALLLOG, address, record, sentDate, callType);
return msg;
}
private String getSubject(@NotNull DataType type, @NotNull PersonRecord record) {
return mPrefix ?
String.format(Locale.ENGLISH, "[%s] %s", type.getFolder(mContext), record.getName()) :
mContext.getString(type.withField, record.getName());
}
private boolean includeInBackup(DataType type, Iterable<PersonRecord> records) {
for (PersonRecord r : records) {
if (includePersonInBackup(r, type)) {
return true;
}
}
return false;
}
private boolean includePersonInBackup(PersonRecord record, DataType type) {
final boolean backup = mContactsToBackup == null || mContactsToBackup.contains(record);
//noinspection PointlessBooleanExpression,ConstantConditions
if (LOCAL_LOGV && !backup) Log.v(TAG, "not backing up " + type + " / " + record);
return backup;
}
private static int toInt(String s) {
try {
return Integer.valueOf(s);
} catch (NumberFormatException e) {
return -1;
}
}
}