1 /*
2  * Copyright (C) 2008 Esmertec AG.
3  * Copyright (C) 2008 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.mms.ui;
19 
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 
27 import android.app.ListActivity;
28 import android.content.Intent;
29 import android.database.Cursor;
30 import android.database.sqlite.SqliteWrapper;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.provider.Telephony.Mms;
34 import android.provider.Telephony.Sms;
35 import android.telephony.PhoneNumberUtils;
36 import android.text.TextUtils;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.View;
40 import android.view.Window;
41 import android.widget.ListView;
42 
43 import com.android.mms.LogTag;
44 import com.android.mms.R;
45 import com.google.android.mms.pdu.PduHeaders;
46 
47 /**
48  * This is the UI for displaying a delivery report:
49  *
50  * This activity can handle the following parameters from the intent
51  * by which it is launched:
52  *
53  * thread_id long The id of the conversation from which to get the recipients
54  *      for the report.
55  * message_id long The id of the message about which a report should be displayed.
56  * message_type String The type of message (Sms or Mms).  This is used in
57  *      conjunction with the message id to retrive the particular message that
58  *      the report will be about.
59  */
60 public class DeliveryReportActivity extends ListActivity {
61     private static final String LOG_TAG = LogTag.TAG;
62 
63     static final String[] MMS_REPORT_REQUEST_PROJECTION = new String[] {
64         Mms.Addr.ADDRESS,       //0
65         Mms.DELIVERY_REPORT,    //1
66         Mms.READ_REPORT         //2
67     };
68 
69     static final String[] MMS_REPORT_STATUS_PROJECTION = new String[] {
70         Mms.Addr.ADDRESS,       //0
71         "delivery_status",      //1
72         "read_status"           //2
73     };
74 
75     static final String[] SMS_REPORT_STATUS_PROJECTION = new String[] {
76         Sms.ADDRESS,            //0
77         Sms.STATUS,             //1
78         Sms.DATE_SENT,          //2
79         Sms.TYPE                //3
80     };
81 
82     // These indices must sync up with the projections above.
83     static final int COLUMN_RECIPIENT           = 0;
84     static final int COLUMN_DELIVERY_REPORT     = 1;
85     static final int COLUMN_READ_REPORT         = 2;
86     static final int COLUMN_DELIVERY_STATUS     = 1;
87     static final int COLUMN_READ_STATUS         = 2;
88     static final int COLUMN_DATE_SENT           = 2;
89     static final int COLUMN_MESSAGE_TYPE        = 3;
90 
91     private long mMessageId;
92     private String mMessageType;
93 
94     @Override
onCreate(Bundle icicle)95     protected void onCreate(Bundle icicle) {
96         super.onCreate(icicle);
97         requestWindowFeature(Window.FEATURE_NO_TITLE);
98         setContentView(R.layout.delivery_report_activity);
99 
100         Intent intent = getIntent();
101         mMessageId = getMessageId(icicle, intent);
102         mMessageType = getMessageType(icicle, intent);
103 
104         initListView();
105         initListAdapter();
106     }
107 
initListView()108     private void initListView() {
109         // Add the header for the list view.
110         LayoutInflater inflater = getLayoutInflater();
111         View header = inflater.inflate(R.layout.delivery_report_header, null);
112         getListView().addHeaderView(header, null, true);
113     }
114 
initListAdapter()115     private void initListAdapter() {
116         List<DeliveryReportItem> items = getReportItems();
117         if (items == null) {
118             items = new ArrayList<DeliveryReportItem>(1);
119             items.add(new DeliveryReportItem("", getString(R.string.status_none), null));
120             Log.w(LOG_TAG, "cursor == null");
121         }
122         setListAdapter(new DeliveryReportAdapter(this, items));
123     }
124 
125     @Override
onResume()126     public void onResume() {
127         super.onResume();
128         refreshDeliveryReport();
129     }
130 
refreshDeliveryReport()131     private void refreshDeliveryReport() {
132         ListView list = getListView();
133         list.invalidateViews();
134         list.requestFocus();
135     }
136 
getMessageId(Bundle icicle, Intent intent)137     private long getMessageId(Bundle icicle, Intent intent) {
138         long msgId = 0L;
139 
140         if (icicle != null) {
141             msgId = icicle.getLong("message_id");
142         }
143 
144         if (msgId == 0L) {
145             msgId = intent.getLongExtra("message_id", 0L);
146         }
147 
148         return msgId;
149     }
150 
getMessageType(Bundle icicle, Intent intent)151     private String getMessageType(Bundle icicle, Intent intent) {
152         String msgType = null;
153 
154         if (icicle != null) {
155             msgType = icicle.getString("message_type");
156         }
157 
158         if (msgType == null) {
159             msgType = intent.getStringExtra("message_type");
160         }
161 
162         return msgType;
163     }
164 
getReportItems()165     private List<DeliveryReportItem> getReportItems() {
166         if (mMessageType.equals("sms")) {
167             return getSmsReportItems();
168         } else {
169             return getMmsReportItems();
170         }
171     }
172 
getSmsReportItems()173     private List<DeliveryReportItem> getSmsReportItems() {
174         String selection = "_id = " + mMessageId;
175         Cursor c = SqliteWrapper.query(this, getContentResolver(), Sms.CONTENT_URI,
176                               SMS_REPORT_STATUS_PROJECTION, selection, null, null);
177         if (c == null) {
178             return null;
179         }
180 
181         try {
182             if (c.getCount() <= 0) {
183                 return null;
184             }
185 
186             List<DeliveryReportItem> items = new ArrayList<DeliveryReportItem>();
187             while (c.moveToNext()) {
188                 // For sent messages with delivery reports, we stick the delivery time in the
189                 // date_sent column (see MessageStatusReceiver).
190                 String deliveryDateString = null;
191                 long deliveryDate = c.getLong(COLUMN_DATE_SENT);
192                 int messageType = c.getInt(COLUMN_MESSAGE_TYPE);
193                 if (messageType == Sms.MESSAGE_TYPE_SENT && deliveryDate > 0) {
194                     deliveryDateString = getString(R.string.delivered_label) +
195                             MessageUtils.formatTimeStampString(this,
196                                     deliveryDate, true);
197                 }
198 
199                 items.add(new DeliveryReportItem(
200                                 getString(R.string.recipient_label) + c.getString(COLUMN_RECIPIENT),
201                                 getString(R.string.status_label) +
202                                         getSmsStatusText(c.getInt(COLUMN_DELIVERY_STATUS)),
203                                         deliveryDateString));
204             }
205             return items;
206         } finally {
207             c.close();
208         }
209     }
210 
getMmsReportStatusText( MmsReportRequest request, Map<String, MmsReportStatus> reportStatus)211     private String getMmsReportStatusText(
212             MmsReportRequest request,
213             Map<String, MmsReportStatus> reportStatus) {
214         if (reportStatus == null) {
215             // haven't received any reports.
216             return getString(R.string.status_pending);
217         }
218 
219         String recipient = request.getRecipient();
220         recipient = (Mms.isEmailAddress(recipient))?
221                 Mms.extractAddrSpec(recipient): PhoneNumberUtils.stripSeparators(recipient);
222         MmsReportStatus status = queryStatusByRecipient(reportStatus, recipient);
223         if (status == null) {
224             // haven't received any reports.
225             return getString(R.string.status_pending);
226         }
227 
228         if (request.isReadReportRequested()) {
229             if (status.readStatus != 0) {
230                 switch (status.readStatus) {
231                     case PduHeaders.READ_STATUS_READ:
232                         return getString(R.string.status_read);
233                     case PduHeaders.READ_STATUS__DELETED_WITHOUT_BEING_READ:
234                         return getString(R.string.status_unread);
235                 }
236             }
237         }
238 
239         switch (status.deliveryStatus) {
240             case 0: // No delivery report received so far.
241                 return getString(R.string.status_pending);
242             case PduHeaders.STATUS_FORWARDED:
243             case PduHeaders.STATUS_RETRIEVED:
244                 return getString(R.string.status_received);
245             case PduHeaders.STATUS_REJECTED:
246                 return getString(R.string.status_rejected);
247             default:
248                 return getString(R.string.status_failed);
249         }
250     }
251 
queryStatusByRecipient( Map<String, MmsReportStatus> status, String recipient)252     private static MmsReportStatus queryStatusByRecipient(
253             Map<String, MmsReportStatus> status, String recipient) {
254         Set<String> recipientSet = status.keySet();
255         Iterator<String> iterator = recipientSet.iterator();
256         while (iterator.hasNext()) {
257             String r = iterator.next();
258             if (Mms.isEmailAddress(recipient)) {
259                 if (TextUtils.equals(r, recipient)) {
260                     return status.get(r);
261                 }
262             }
263             else if (PhoneNumberUtils.compare(r, recipient)) {
264                 return status.get(r);
265             }
266         }
267         return null;
268     }
269 
getMmsReportItems()270     private List<DeliveryReportItem> getMmsReportItems() {
271         List<MmsReportRequest> reportReqs = getMmsReportRequests();
272         if (null == reportReqs) {
273             return null;
274         }
275 
276         if (reportReqs.size() == 0) {
277             return null;
278         }
279 
280         Map<String, MmsReportStatus> reportStatus = getMmsReportStatus();
281         List<DeliveryReportItem> items = new ArrayList<DeliveryReportItem>();
282         for (MmsReportRequest reportReq : reportReqs) {
283             String statusText = getString(R.string.status_label) +
284                 getMmsReportStatusText(reportReq, reportStatus);
285             items.add(new DeliveryReportItem(getString(R.string.recipient_label) +
286                     reportReq.getRecipient(), statusText, null));
287         }
288         return items;
289     }
290 
getMmsReportStatus()291     private Map<String, MmsReportStatus> getMmsReportStatus() {
292         Uri uri = Uri.withAppendedPath(Mms.REPORT_STATUS_URI,
293                                        String.valueOf(mMessageId));
294         Cursor c = SqliteWrapper.query(this, getContentResolver(), uri,
295                        MMS_REPORT_STATUS_PROJECTION, null, null, null);
296 
297         if (c == null) {
298             return null;
299         }
300 
301         try {
302             Map<String, MmsReportStatus> statusMap =
303                     new HashMap<String, MmsReportStatus>();
304 
305             while (c.moveToNext()) {
306                 String recipient = c.getString(COLUMN_RECIPIENT);
307                 recipient = (Mms.isEmailAddress(recipient))?
308                                         Mms.extractAddrSpec(recipient):
309                                             PhoneNumberUtils.stripSeparators(recipient);
310                 MmsReportStatus status = new MmsReportStatus(
311                                         c.getInt(COLUMN_DELIVERY_STATUS),
312                                         c.getInt(COLUMN_READ_STATUS));
313                 statusMap.put(recipient, status);
314             }
315             return statusMap;
316         } finally {
317             c.close();
318         }
319     }
320 
getMmsReportRequests()321     private List<MmsReportRequest> getMmsReportRequests() {
322         Uri uri = Uri.withAppendedPath(Mms.REPORT_REQUEST_URI,
323                                        String.valueOf(mMessageId));
324         Cursor c = SqliteWrapper.query(this, getContentResolver(), uri,
325                       MMS_REPORT_REQUEST_PROJECTION, null, null, null);
326 
327         if (c == null) {
328             return null;
329         }
330 
331         try {
332             if (c.getCount() <= 0) {
333                 return null;
334             }
335 
336             List<MmsReportRequest> reqList = new ArrayList<MmsReportRequest>();
337             while (c.moveToNext()) {
338                 reqList.add(new MmsReportRequest(
339                                 c.getString(COLUMN_RECIPIENT),
340                                 c.getInt(COLUMN_DELIVERY_REPORT),
341                                 c.getInt(COLUMN_READ_REPORT)));
342             }
343             return reqList;
344         } finally {
345             c.close();
346         }
347     }
348 
getSmsStatusText(int status)349     private String getSmsStatusText(int status) {
350         if (status == Sms.STATUS_NONE) {
351             // No delivery report requested
352             return getString(R.string.status_none);
353         } else if (status >= Sms.STATUS_FAILED) {
354             // Failure
355             return getString(R.string.status_failed);
356         } else if (status >= Sms.STATUS_PENDING) {
357             // Pending
358             return getString(R.string.status_pending);
359         } else {
360             // Success
361             return getString(R.string.status_received);
362         }
363     }
364 
365     private static final class MmsReportStatus {
366         final int deliveryStatus;
367         final int readStatus;
368 
MmsReportStatus(int drStatus, int rrStatus)369         public MmsReportStatus(int drStatus, int rrStatus) {
370             deliveryStatus = drStatus;
371             readStatus = rrStatus;
372         }
373     }
374 
375     private static final class MmsReportRequest {
376         private final String mRecipient;
377         private final boolean mIsDeliveryReportRequsted;
378         private final boolean mIsReadReportRequested;
379 
MmsReportRequest(String recipient, int drValue, int rrValue)380         public MmsReportRequest(String recipient, int drValue, int rrValue) {
381             mRecipient = recipient;
382             mIsDeliveryReportRequsted = drValue == PduHeaders.VALUE_YES;
383             mIsReadReportRequested = rrValue == PduHeaders.VALUE_YES;
384         }
385 
getRecipient()386         public String getRecipient() {
387             return mRecipient;
388         }
389 
isDeliveryReportRequested()390         public boolean isDeliveryReportRequested() {
391             return mIsDeliveryReportRequsted;
392         }
393 
isReadReportRequested()394         public boolean isReadReportRequested() {
395             return mIsReadReportRequested;
396         }
397     }
398 }
399