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