| /* |
| * 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); |
| } |
| } |
| } |
| } |