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 MessageDetailsDialog()50 private MessageDetailsDialog() { 51 } 52 show(final Context context, final ConversationMessageData data, final ConversationParticipantsData participants, final ParticipantData self)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 getMessageDetails(final Context context, final ConversationMessageData data, final ConversationParticipantsData participants, final ParticipantData self)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 showDialog(final Context context, String messageDetails)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 */ getSmsMessageDetails(final ConversationMessageData data, final ConversationParticipantsData participants, final ParticipantData self)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 */ getMmsMessageDetails(Context context, final ConversationMessageData data, final ConversationParticipantsData participants, final ParticipantData self)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 appendSentOrReceivedTimestamp(Resources res, StringBuilder details, ConversationMessageData data)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 appendDebugInfo(StringBuilder details, ConversationMessageData data)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 getRecipientParticipantString( final ConversationParticipantsData participants, final String senderId, final boolean addSelf, final String selfId)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 */ getPriorityDescription(final Resources res, final int priorityValue)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 appendSimInfo(final Resources res, final ParticipantData self, final StringBuilder outString)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