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