Home | History | Annotate | Download | only in conversation
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.messaging.ui.conversation;
     18 
     19 import android.app.AlertDialog;
     20 import android.content.Context;
     21 import android.content.res.Resources;
     22 import android.net.Uri;
     23 import android.text.TextUtils;
     24 import android.text.format.Formatter;
     25 
     26 import com.android.messaging.Factory;
     27 import com.android.messaging.R;
     28 import com.android.messaging.datamodel.BugleDatabaseOperations;
     29 import com.android.messaging.datamodel.DataModel;
     30 import com.android.messaging.datamodel.data.ConversationMessageData;
     31 import com.android.messaging.datamodel.data.ConversationParticipantsData;
     32 import com.android.messaging.datamodel.data.ParticipantData;
     33 import com.android.messaging.mmslib.pdu.PduHeaders;
     34 import com.android.messaging.sms.DatabaseMessages.MmsMessage;
     35 import com.android.messaging.sms.MmsUtils;
     36 import com.android.messaging.util.Assert;
     37 import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
     38 import com.android.messaging.util.Dates;
     39 import com.android.messaging.util.DebugUtils;
     40 import com.android.messaging.util.OsUtil;
     41 import com.android.messaging.util.PhoneUtils;
     42 import com.android.messaging.util.SafeAsyncTask;
     43 
     44 import java.util.List;
     45 
     46 public class MessageDetailsDialog {
     47     private static final String RECIPIENT_SEPARATOR = ", ";
     48 
     49     // All methods are static, no creating this class
     50     private MessageDetailsDialog() {
     51     }
     52 
     53     public static void show(final Context context, final ConversationMessageData data,
     54             final ConversationParticipantsData participants, final ParticipantData self) {
     55         if (DebugUtils.isDebugEnabled()) {
     56             new SafeAsyncTask<Void, Void, String>() {
     57                 @Override
     58                 protected String doInBackgroundTimed(Void... params) {
     59                     return getMessageDetails(context, data, participants, self);
     60                 }
     61 
     62                 @Override
     63                 protected void onPostExecute(String messageDetails) {
     64                     showDialog(context, messageDetails);
     65                 }
     66             }.executeOnThreadPool(null, null, null);
     67         } else {
     68             String messageDetails = getMessageDetails(context, data, participants, self);
     69             showDialog(context, messageDetails);
     70         }
     71     }
     72 
     73     private static String getMessageDetails(final Context context,
     74             final ConversationMessageData data,
     75             final ConversationParticipantsData participants, final ParticipantData self) {
     76         String messageDetails = null;
     77         if (data.getIsSms()) {
     78             messageDetails = getSmsMessageDetails(data, participants, self);
     79         } else {
     80             // TODO: Handle SMS_TYPE_MMS_PUSH_NOTIFICATION type differently?
     81             messageDetails = getMmsMessageDetails(context, data, participants, self);
     82         }
     83 
     84         return messageDetails;
     85     }
     86 
     87     private static void showDialog(final Context context, String messageDetails) {
     88         if (!TextUtils.isEmpty(messageDetails)) {
     89             new AlertDialog.Builder(context)
     90                     .setTitle(R.string.message_details_title)
     91                     .setMessage(messageDetails)
     92                     .setCancelable(true)
     93                     .show();
     94         }
     95     }
     96 
     97     /**
     98      * Return a string, separated by newlines, that contains a number of labels and values
     99      * for this sms message. The string will be displayed in a modal dialog.
    100      * @return string list of various message properties
    101      */
    102     private static String getSmsMessageDetails(final ConversationMessageData data,
    103             final ConversationParticipantsData participants, final ParticipantData self) {
    104         final Resources res = Factory.get().getApplicationContext().getResources();
    105         final StringBuilder details = new StringBuilder();
    106 
    107         // Type: Text message
    108         details.append(res.getString(R.string.message_type_label));
    109         details.append(res.getString(R.string.text_message));
    110 
    111         // From: +1425xxxxxxx
    112         // or To: +1425xxxxxxx
    113         final String rawSender = data.getSenderNormalizedDestination();
    114         if (!TextUtils.isEmpty(rawSender)) {
    115             details.append('\n');
    116             details.append(res.getString(R.string.from_label));
    117             details.append(rawSender);
    118         }
    119         final String rawRecipients = getRecipientParticipantString(participants,
    120                 data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
    121         if (!TextUtils.isEmpty(rawRecipients)) {
    122             details.append('\n');
    123             details.append(res.getString(R.string.to_address_label));
    124             details.append(rawRecipients);
    125         }
    126 
    127         // Sent: Mon 11:42AM
    128         if (data.getIsIncoming()) {
    129             if (data.getSentTimeStamp() != MmsUtils.INVALID_TIMESTAMP) {
    130                 details.append('\n');
    131                 details.append(res.getString(R.string.sent_label));
    132                 details.append(
    133                         Dates.getMessageDetailsTimeString(data.getSentTimeStamp()).toString());
    134             }
    135         }
    136 
    137         // Sent: Mon 11:43AM
    138         // or Received: Mon 11:43AM
    139         appendSentOrReceivedTimestamp(res, details, data);
    140 
    141         appendSimInfo(res, self, details);
    142 
    143         if (DebugUtils.isDebugEnabled()) {
    144             appendDebugInfo(details, data);
    145         }
    146 
    147         return details.toString();
    148     }
    149 
    150     /**
    151      * Return a string, separated by newlines, that contains a number of labels and values
    152      * for this mms message. The string will be displayed in a modal dialog.
    153      * @return string list of various message properties
    154      */
    155     private static String getMmsMessageDetails(Context context, final ConversationMessageData data,
    156             final ConversationParticipantsData participants, final ParticipantData self) {
    157         final Resources res = Factory.get().getApplicationContext().getResources();
    158         // TODO: when we support non-auto-download of mms messages, we'll have to handle
    159         // the case when the message is a PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND and display
    160         // something different. See the Messaging app's MessageUtils.getNotificationIndDetails()
    161 
    162         final StringBuilder details = new StringBuilder();
    163 
    164         // Type: Multimedia message.
    165         details.append(res.getString(R.string.message_type_label));
    166         details.append(res.getString(R.string.multimedia_message));
    167 
    168         // From: +1425xxxxxxx
    169         final String rawSender = data.getSenderNormalizedDestination();
    170         details.append('\n');
    171         details.append(res.getString(R.string.from_label));
    172         details.append(!TextUtils.isEmpty(rawSender) ? rawSender :
    173                 res.getString(R.string.hidden_sender_address));
    174 
    175         // To: +1425xxxxxxx
    176         final String rawRecipients = getRecipientParticipantString(participants,
    177                 data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
    178         if (!TextUtils.isEmpty(rawRecipients)) {
    179             details.append('\n');
    180             details.append(res.getString(R.string.to_address_label));
    181             details.append(rawRecipients);
    182         }
    183 
    184         // Sent: Tue 3:05PM
    185         // or Received: Tue 3:05PM
    186         appendSentOrReceivedTimestamp(res, details, data);
    187 
    188         // Subject: You're awesome
    189         details.append('\n');
    190         details.append(res.getString(R.string.subject_label));
    191         if (!TextUtils.isEmpty(MmsUtils.cleanseMmsSubject(res, data.getMmsSubject()))) {
    192             details.append(data.getMmsSubject());
    193         }
    194 
    195         // Priority: High/Normal/Low
    196         details.append('\n');
    197         details.append(res.getString(R.string.priority_label));
    198         details.append(getPriorityDescription(res, data.getSmsPriority()));
    199 
    200         // Message size: 30 KB
    201         if (data.getSmsMessageSize() > 0) {
    202             details.append('\n');
    203             details.append(res.getString(R.string.message_size_label));
    204             details.append(Formatter.formatFileSize(context, data.getSmsMessageSize()));
    205         }
    206 
    207         appendSimInfo(res, self, details);
    208 
    209         if (DebugUtils.isDebugEnabled()) {
    210             appendDebugInfo(details, data);
    211         }
    212 
    213         return details.toString();
    214     }
    215 
    216     private static void appendSentOrReceivedTimestamp(Resources res, StringBuilder details,
    217             ConversationMessageData data) {
    218         int labelId = -1;
    219         if (data.getIsIncoming()) {
    220             labelId = R.string.received_label;
    221         } else if (data.getIsSendComplete()) {
    222             labelId = R.string.sent_label;
    223         }
    224         if (labelId >= 0) {
    225             details.append('\n');
    226             details.append(res.getString(labelId));
    227             details.append(
    228                     Dates.getMessageDetailsTimeString(data.getReceivedTimeStamp()).toString());
    229         }
    230     }
    231 
    232     @DoesNotRunOnMainThread
    233     private static void appendDebugInfo(StringBuilder details, ConversationMessageData data) {
    234         // We grab the thread id from the database, so this needs to run in the background
    235         Assert.isNotMainThread();
    236         details.append("\n\n");
    237         details.append("DEBUG");
    238 
    239         details.append('\n');
    240         details.append("Message id: ");
    241         details.append(data.getMessageId());
    242 
    243         final String telephonyUri = data.getSmsMessageUri();
    244         details.append('\n');
    245         details.append("Telephony uri: ");
    246         details.append(telephonyUri);
    247 
    248         final String conversationId = data.getConversationId();
    249 
    250         if (conversationId == null) {
    251             return;
    252         }
    253 
    254         details.append('\n');
    255         details.append("Conversation id: ");
    256         details.append(conversationId);
    257 
    258         final long threadId = BugleDatabaseOperations.getThreadId(DataModel.get().getDatabase(),
    259                 conversationId);
    260 
    261         details.append('\n');
    262         details.append("Conversation telephony thread id: ");
    263         details.append(threadId);
    264 
    265         MmsMessage mms = null;
    266 
    267         if (data.getIsMms()) {
    268             if (telephonyUri == null) {
    269                 return;
    270             }
    271             mms = MmsUtils.loadMms(Uri.parse(telephonyUri));
    272             if (mms == null) {
    273                 return;
    274             }
    275 
    276             // We log the thread id again to check that they are internally consistent
    277             final long mmsThreadId = mms.mThreadId;
    278             details.append('\n');
    279             details.append("Telephony thread id: ");
    280             details.append(mmsThreadId);
    281 
    282             // Log the MMS content location
    283             final String mmsContentLocation = mms.mContentLocation;
    284             details.append('\n');
    285             details.append("Content location URL: ");
    286             details.append(mmsContentLocation);
    287         }
    288 
    289         final String recipientsString = MmsUtils.getRawRecipientIdsForThread(threadId);
    290         if (recipientsString != null) {
    291             details.append('\n');
    292             details.append("Thread recipient ids: ");
    293             details.append(recipientsString);
    294         }
    295 
    296         final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
    297         if (recipients != null) {
    298             details.append('\n');
    299             details.append("Thread recipients: ");
    300             details.append(recipients.toString());
    301 
    302             if (mms != null) {
    303                 final String from = MmsUtils.getMmsSender(recipients, mms.getUri());
    304                 details.append('\n');
    305                 details.append("Sender: ");
    306                 details.append(from);
    307             }
    308         }
    309     }
    310 
    311     private static String getRecipientParticipantString(
    312             final ConversationParticipantsData participants, final String senderId,
    313             final boolean addSelf, final String selfId) {
    314         final StringBuilder recipients = new StringBuilder();
    315         for (final ParticipantData participant : participants) {
    316             if (TextUtils.equals(participant.getId(), senderId)) {
    317                 // Don't add sender
    318                 continue;
    319             }
    320             if (participant.isSelf() &&
    321                     (!participant.getId().equals(selfId) || !addSelf)) {
    322                 // For self participants, don't add the one that's not relevant to this message
    323                 // or if we are asked not to add self
    324                 continue;
    325             }
    326             final String phoneNumber = participant.getNormalizedDestination();
    327             // Don't add empty number. This should not happen. But if that happens
    328             // we should not add it.
    329             if (!TextUtils.isEmpty(phoneNumber)) {
    330                 if (recipients.length() > 0) {
    331                     recipients.append(RECIPIENT_SEPARATOR);
    332                 }
    333                 recipients.append(phoneNumber);
    334             }
    335         }
    336         return recipients.toString();
    337     }
    338 
    339     /**
    340      * Convert the numeric mms priority into a human-readable string
    341      * @param res
    342      * @param priorityValue coded PduHeader priority
    343      * @return string representation of the priority
    344      */
    345     private static String getPriorityDescription(final Resources res, final int priorityValue) {
    346         switch(priorityValue) {
    347             case PduHeaders.PRIORITY_HIGH:
    348                 return res.getString(R.string.priority_high);
    349             case PduHeaders.PRIORITY_LOW:
    350                 return res.getString(R.string.priority_low);
    351             case PduHeaders.PRIORITY_NORMAL:
    352             default:
    353                 return res.getString(R.string.priority_normal);
    354         }
    355     }
    356 
    357     private static void appendSimInfo(final Resources res,
    358             final ParticipantData self, final StringBuilder outString) {
    359         if (!OsUtil.isAtLeastL_MR1()
    360                 || self == null
    361                 || PhoneUtils.getDefault().getActiveSubscriptionCount() < 2) {
    362             return;
    363         }
    364         // The appended SIM info would look like:
    365         // SIM: SUB 01
    366         // or SIM: SIM 1
    367         // or SIM: Unknown
    368         Assert.isTrue(self.isSelf());
    369         outString.append('\n');
    370         outString.append(res.getString(R.string.sim_label));
    371         if (self.isActiveSubscription() && !self.isDefaultSelf()) {
    372             final String subscriptionName = self.getSubscriptionName();
    373             if (TextUtils.isEmpty(subscriptionName)) {
    374                 outString.append(res.getString(R.string.sim_slot_identifier,
    375                         self.getDisplaySlotId()));
    376             } else {
    377                 outString.append(subscriptionName);
    378             }
    379         }
    380     }
    381 }
    382