blob: 89b91485e83e0923b889b85df426c6186fae8c0f [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.ui.conversation;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.text.TextUtils;
import android.text.format.Formatter;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.BugleDatabaseOperations;
import com.android.messaging.datamodel.DataModel;
import com.android.messaging.datamodel.data.ConversationMessageData;
import com.android.messaging.datamodel.data.ConversationParticipantsData;
import com.android.messaging.datamodel.data.ParticipantData;
import com.android.messaging.mmslib.pdu.PduHeaders;
import com.android.messaging.sms.DatabaseMessages.MmsMessage;
import com.android.messaging.sms.MmsUtils;
import com.android.messaging.util.Assert;
import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
import com.android.messaging.util.Dates;
import com.android.messaging.util.DebugUtils;
import com.android.messaging.util.OsUtil;
import com.android.messaging.util.PhoneUtils;
import com.android.messaging.util.SafeAsyncTask;
import java.util.List;
public class MessageDetailsDialog {
private static final String RECIPIENT_SEPARATOR = ", ";
// All methods are static, no creating this class
private MessageDetailsDialog() {
}
public static void show(final Context context, final ConversationMessageData data,
final ConversationParticipantsData participants, final ParticipantData self) {
if (DebugUtils.isDebugEnabled()) {
new SafeAsyncTask<Void, Void, String>() {
@Override
protected String doInBackgroundTimed(Void... params) {
return getMessageDetails(context, data, participants, self);
}
@Override
protected void onPostExecute(String messageDetails) {
showDialog(context, messageDetails);
}
}.executeOnThreadPool(null, null, null);
} else {
String messageDetails = getMessageDetails(context, data, participants, self);
showDialog(context, messageDetails);
}
}
private static String getMessageDetails(final Context context,
final ConversationMessageData data,
final ConversationParticipantsData participants, final ParticipantData self) {
String messageDetails = null;
if (data.getIsSms()) {
messageDetails = getSmsMessageDetails(data, participants, self);
} else {
// TODO: Handle SMS_TYPE_MMS_PUSH_NOTIFICATION type differently?
messageDetails = getMmsMessageDetails(context, data, participants, self);
}
return messageDetails;
}
private static void showDialog(final Context context, String messageDetails) {
if (!TextUtils.isEmpty(messageDetails)) {
new AlertDialog.Builder(context)
.setTitle(R.string.message_details_title)
.setMessage(messageDetails)
.setCancelable(true)
.show();
}
}
/**
* Return a string, separated by newlines, that contains a number of labels and values
* for this sms message. The string will be displayed in a modal dialog.
* @return string list of various message properties
*/
private static String getSmsMessageDetails(final ConversationMessageData data,
final ConversationParticipantsData participants, final ParticipantData self) {
final Resources res = Factory.get().getApplicationContext().getResources();
final StringBuilder details = new StringBuilder();
// Type: Text message
details.append(res.getString(R.string.message_type_label));
details.append(res.getString(R.string.text_message));
// From: +1425xxxxxxx
// or To: +1425xxxxxxx
final String rawSender = data.getSenderNormalizedDestination();
if (!TextUtils.isEmpty(rawSender)) {
details.append('\n');
details.append(res.getString(R.string.from_label));
details.append(rawSender);
}
final String rawRecipients = getRecipientParticipantString(participants,
data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
if (!TextUtils.isEmpty(rawRecipients)) {
details.append('\n');
details.append(res.getString(R.string.to_address_label));
details.append(rawRecipients);
}
// Sent: Mon 11:42AM
if (data.getIsIncoming()) {
if (data.getSentTimeStamp() != MmsUtils.INVALID_TIMESTAMP) {
details.append('\n');
details.append(res.getString(R.string.sent_label));
details.append(
Dates.getMessageDetailsTimeString(data.getSentTimeStamp()).toString());
}
}
// Sent: Mon 11:43AM
// or Received: Mon 11:43AM
appendSentOrReceivedTimestamp(res, details, data);
appendSimInfo(res, self, details);
if (DebugUtils.isDebugEnabled()) {
appendDebugInfo(details, data);
}
return details.toString();
}
/**
* Return a string, separated by newlines, that contains a number of labels and values
* for this mms message. The string will be displayed in a modal dialog.
* @return string list of various message properties
*/
private static String getMmsMessageDetails(Context context, final ConversationMessageData data,
final ConversationParticipantsData participants, final ParticipantData self) {
final Resources res = Factory.get().getApplicationContext().getResources();
// TODO: when we support non-auto-download of mms messages, we'll have to handle
// the case when the message is a PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND and display
// something different. See the Messaging app's MessageUtils.getNotificationIndDetails()
final StringBuilder details = new StringBuilder();
// Type: Multimedia message.
details.append(res.getString(R.string.message_type_label));
details.append(res.getString(R.string.multimedia_message));
// From: +1425xxxxxxx
final String rawSender = data.getSenderNormalizedDestination();
details.append('\n');
details.append(res.getString(R.string.from_label));
details.append(!TextUtils.isEmpty(rawSender) ? rawSender :
res.getString(R.string.hidden_sender_address));
// To: +1425xxxxxxx
final String rawRecipients = getRecipientParticipantString(participants,
data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
if (!TextUtils.isEmpty(rawRecipients)) {
details.append('\n');
details.append(res.getString(R.string.to_address_label));
details.append(rawRecipients);
}
// Sent: Tue 3:05PM
// or Received: Tue 3:05PM
appendSentOrReceivedTimestamp(res, details, data);
// Subject: You're awesome
details.append('\n');
details.append(res.getString(R.string.subject_label));
if (!TextUtils.isEmpty(MmsUtils.cleanseMmsSubject(res, data.getMmsSubject()))) {
details.append(data.getMmsSubject());
}
// Priority: High/Normal/Low
details.append('\n');
details.append(res.getString(R.string.priority_label));
details.append(getPriorityDescription(res, data.getSmsPriority()));
// Message size: 30 KB
if (data.getSmsMessageSize() > 0) {
details.append('\n');
details.append(res.getString(R.string.message_size_label));
details.append(Formatter.formatFileSize(context, data.getSmsMessageSize()));
}
appendSimInfo(res, self, details);
if (DebugUtils.isDebugEnabled()) {
appendDebugInfo(details, data);
}
return details.toString();
}
private static void appendSentOrReceivedTimestamp(Resources res, StringBuilder details,
ConversationMessageData data) {
int labelId = -1;
if (data.getIsIncoming()) {
labelId = R.string.received_label;
} else if (data.getIsSendComplete()) {
labelId = R.string.sent_label;
}
if (labelId >= 0) {
details.append('\n');
details.append(res.getString(labelId));
details.append(
Dates.getMessageDetailsTimeString(data.getReceivedTimeStamp()).toString());
}
}
@DoesNotRunOnMainThread
private static void appendDebugInfo(StringBuilder details, ConversationMessageData data) {
// We grab the thread id from the database, so this needs to run in the background
Assert.isNotMainThread();
details.append("\n\n");
details.append("DEBUG");
details.append('\n');
details.append("Message id: ");
details.append(data.getMessageId());
final String telephonyUri = data.getSmsMessageUri();
details.append('\n');
details.append("Telephony uri: ");
details.append(telephonyUri);
final String conversationId = data.getConversationId();
if (conversationId == null) {
return;
}
details.append('\n');
details.append("Conversation id: ");
details.append(conversationId);
final long threadId = BugleDatabaseOperations.getThreadId(DataModel.get().getDatabase(),
conversationId);
details.append('\n');
details.append("Conversation telephony thread id: ");
details.append(threadId);
MmsMessage mms = null;
if (data.getIsMms()) {
if (telephonyUri == null) {
return;
}
mms = MmsUtils.loadMms(Uri.parse(telephonyUri));
if (mms == null) {
return;
}
// We log the thread id again to check that they are internally consistent
final long mmsThreadId = mms.mThreadId;
details.append('\n');
details.append("Telephony thread id: ");
details.append(mmsThreadId);
// Log the MMS content location
final String mmsContentLocation = mms.mContentLocation;
details.append('\n');
details.append("Content location URL: ");
details.append(mmsContentLocation);
}
final String recipientsString = MmsUtils.getRawRecipientIdsForThread(threadId);
if (recipientsString != null) {
details.append('\n');
details.append("Thread recipient ids: ");
details.append(recipientsString);
}
final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
if (recipients != null) {
details.append('\n');
details.append("Thread recipients: ");
details.append(recipients.toString());
if (mms != null) {
final String from = MmsUtils.getMmsSender(recipients, mms.getUri());
details.append('\n');
details.append("Sender: ");
details.append(from);
}
}
}
private static String getRecipientParticipantString(
final ConversationParticipantsData participants, final String senderId,
final boolean addSelf, final String selfId) {
final StringBuilder recipients = new StringBuilder();
for (final ParticipantData participant : participants) {
if (TextUtils.equals(participant.getId(), senderId)) {
// Don't add sender
continue;
}
if (participant.isSelf() &&
(!participant.getId().equals(selfId) || !addSelf)) {
// For self participants, don't add the one that's not relevant to this message
// or if we are asked not to add self
continue;
}
final String phoneNumber = participant.getNormalizedDestination();
// Don't add empty number. This should not happen. But if that happens
// we should not add it.
if (!TextUtils.isEmpty(phoneNumber)) {
if (recipients.length() > 0) {
recipients.append(RECIPIENT_SEPARATOR);
}
recipients.append(phoneNumber);
}
}
return recipients.toString();
}
/**
* Convert the numeric mms priority into a human-readable string
* @param res
* @param priorityValue coded PduHeader priority
* @return string representation of the priority
*/
private static String getPriorityDescription(final Resources res, final int priorityValue) {
switch(priorityValue) {
case PduHeaders.PRIORITY_HIGH:
return res.getString(R.string.priority_high);
case PduHeaders.PRIORITY_LOW:
return res.getString(R.string.priority_low);
case PduHeaders.PRIORITY_NORMAL:
default:
return res.getString(R.string.priority_normal);
}
}
private static void appendSimInfo(final Resources res,
final ParticipantData self, final StringBuilder outString) {
if (!OsUtil.isAtLeastL_MR1()
|| self == null
|| PhoneUtils.getDefault().getActiveSubscriptionCount() < 2) {
return;
}
// The appended SIM info would look like:
// SIM: SUB 01
// or SIM: SIM 1
// or SIM: Unknown
Assert.isTrue(self.isSelf());
outString.append('\n');
outString.append(res.getString(R.string.sim_label));
if (self.isActiveSubscription() && !self.isDefaultSelf()) {
final String subscriptionName = self.getSubscriptionName();
if (TextUtils.isEmpty(subscriptionName)) {
outString.append(res.getString(R.string.sim_slot_identifier,
self.getDisplaySlotId()));
} else {
outString.append(subscriptionName);
}
}
}
}