Add support for "delivery notifications." Currently SMS-only.

This commit is contained in:
Moxie Marlinspike 2013-01-06 21:38:36 -08:00
parent 118560cf0d
commit 5cb02445e8
19 changed files with 174 additions and 61 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -96,6 +96,13 @@
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="left"> android:gravity="left">
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dip"
android:src="@drawable/ic_sms_mms_delivered"
android:visibility="gone" />
<TextView android:id="@+id/group_message_status" <TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -105,8 +112,7 @@
android:textColor="#ffcccccc" android:textColor="#ffcccccc"
android:visibility="gone" android:visibility="gone"
android:layout_marginRight="8dip" android:layout_marginRight="8dip"
android:paddingTop="1dip"/> android:paddingTop="1dip"/>
<TextView android:id="@+id/conversation_item_date" <TextView android:id="@+id/conversation_item_date"
android:autoLink="all" android:autoLink="all"

View File

@ -118,6 +118,13 @@
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="right"> android:gravity="right">
<ImageView android:id="@+id/delivered_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="3dip"
android:src="@drawable/ic_sms_mms_delivered"
android:visibility="gone" />
<TextView android:id="@+id/group_message_status" <TextView android:id="@+id/group_message_status"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -1,22 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (C) 2007-2008 Esmertec AG.
* Copyright (C) 2007-2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/preferences__use_settings"> <PreferenceCategory android:title="@string/preferences__use_settings">
<CheckBoxPreference android:defaultValue="true" <CheckBoxPreference android:defaultValue="true"
@ -31,6 +14,19 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="Delivery Reports">
<CheckBoxPreference android:defaultValue="false"
android:key="pref_delivery_report_sms"
android:summary="Request a delivery report for each SMS message you send"
android:title="SMS delivery reports" />
<CheckBoxPreference android:defaultValue="false"
android:key="pref_delivery_report_mms"
android:summary="Request a delivery report for each MMS message you send"
android:enabled="false"
android:title="MMS delivery reports" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/preferences__input_settings"> <PreferenceCategory android:title="@string/preferences__input_settings">
<CheckBoxPreference android:defaultValue="false" <CheckBoxPreference android:defaultValue="false"
android:key="pref_enter_sends" android:key="pref_enter_sends"

View File

@ -80,6 +80,9 @@ public class ApplicationPreferencesActivity extends SherlockPreferenceActivity {
public static final String MMSC_PROXY_HOST_PREF = "pref_apn_mms_proxy"; public static final String MMSC_PROXY_HOST_PREF = "pref_apn_mms_proxy";
public static final String MMSC_PROXY_PORT_PREF = "pref_apn_mms_proxy_port"; public static final String MMSC_PROXY_PORT_PREF = "pref_apn_mms_proxy_port";
public static final String SMS_DELIVERY_REPORT_PREF = "pref_delivery_report_sms";
public static final String MMS_DELIVERY_REPORT_PREF = "pref_delivery_report_mms";
@Override @Override
protected void onCreate(Bundle icicle) { protected void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);

View File

@ -208,6 +208,7 @@ public class ConversationAdapter extends CursorAdapter {
long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsDatabase.DATE_RECEIVED)); long dateReceived = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsDatabase.DATE_RECEIVED));
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsDatabase.DATE_SENT)); long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsDatabase.DATE_SENT));
long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.TYPE));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)); String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS)); String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
Recipient recipient = getIndividualRecipientFor(address); Recipient recipient = getIndividualRecipientFor(address);
@ -226,7 +227,7 @@ public class ConversationAdapter extends CursorAdapter {
SmsMessageRecord messageRecord = new SmsMessageRecord(context, messageId, recipients, SmsMessageRecord messageRecord = new SmsMessageRecord(context, messageId, recipients,
recipient, dateSent, dateReceived, recipient, dateSent, dateReceived,
type, threadId, groupData); type, threadId, status, groupData);
if (body == null) { if (body == null) {
body = ""; body = "";

View File

@ -84,6 +84,7 @@ public class ConversationItem extends LinearLayout {
private ImageView failedImage; private ImageView failedImage;
private ImageView keyImage; private ImageView keyImage;
private ImageView contactPhoto; private ImageView contactPhoto;
private ImageView deliveredImage;
private ImageView mmsThumbnail; private ImageView mmsThumbnail;
private Button mmsDownloadButton; private Button mmsDownloadButton;
@ -118,6 +119,7 @@ public class ConversationItem extends LinearLayout {
this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button); this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button);
this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading); this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading);
this.contactPhoto = (ImageView)findViewById(R.id.contact_photo); this.contactPhoto = (ImageView)findViewById(R.id.contact_photo);
this.deliveredImage = (ImageView)findViewById(R.id.delivered_indicator);
setOnClickListener(clickListener); setOnClickListener(clickListener);
this.failedImage.setOnClickListener(failedIconClickListener); this.failedImage.setOnClickListener(failedIconClickListener);
@ -182,6 +184,7 @@ public class ConversationItem extends LinearLayout {
failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE); failedImage.setVisibility(messageRecord.isFailed() ? View.VISIBLE : View.GONE);
secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE); secureImage.setVisibility(messageRecord.isSecure() ? View.VISIBLE : View.GONE);
keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE); keyImage.setVisibility(messageRecord.isKeyExchange() ? View.VISIBLE : View.GONE);
deliveredImage.setVisibility(!messageRecord.isKeyExchange() && messageRecord.isDelivered() ? View.VISIBLE : View.GONE);
mmsThumbnail.setVisibility(View.GONE); mmsThumbnail.setVisibility(View.GONE);
mmsDownloadButton.setVisibility(View.GONE); mmsDownloadButton.setVisibility(View.GONE);

View File

@ -93,7 +93,7 @@ public class MmsSmsDatabase extends Database {
String[] projection = {"_id", "body", "type", "address", "subject", String[] projection = {"_id", "body", "type", "address", "subject",
"normalized_date_sent AS date_sent", "normalized_date_sent AS date_sent",
"normalized_date_received AS date_received", "normalized_date_received AS date_received",
"m_type", "msg_box", "transport_type"}; "m_type", "msg_box", "status", "transport_type"};
String order = "normalized_date_received ASC"; String order = "normalized_date_received ASC";
String selection = "thread_id = " + threadId; String selection = "thread_id = " + threadId;
@ -135,8 +135,8 @@ public class MmsSmsDatabase extends Database {
} }
private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) { private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
String[] mmsProjection = {"date * 1000 AS normalized_date_sent", "date_received * 1000 AS normalized_date_received", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "transport_type"}; String[] mmsProjection = {"date * 1000 AS normalized_date_sent", "date_received * 1000 AS normalized_date_received", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "status", "transport_type"};
String[] smsProjection = {"date_sent * 1 AS normalized_date_sent", "date * 1 AS normalized_date_received", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "transport_type"}; String[] smsProjection = {"date_sent * 1 AS normalized_date_sent", "date * 1 AS normalized_date_received", "_id", "body", "read", "thread_id", "type", "address", "subject", "date", "m_type", "msg_box", "status", "transport_type"};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
@ -166,6 +166,7 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add("date"); smsColumnsPresent.add("date");
smsColumnsPresent.add("read"); smsColumnsPresent.add("read");
smsColumnsPresent.add("thread_id"); smsColumnsPresent.add("thread_id");
smsColumnsPresent.add("status");
String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery("transport_type", mmsProjection, mmsColumnsPresent, 2, "mms", selection, null, null, null); String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery("transport_type", mmsProjection, mmsColumnsPresent, 2, "mms", selection, null, null, null);
String smsSubQuery = smsQueryBuilder.buildUnionSubQuery("transport_type", smsProjection, smsColumnsPresent, 2, "sms", selection, null, null, null); String smsSubQuery = smsQueryBuilder.buildUnionSubQuery("transport_type", smsProjection, smsColumnsPresent, 2, "sms", selection, null, null, null);

View File

@ -170,6 +170,16 @@ public class SmsDatabase extends Database {
updateType(id, Types.SENT_TYPE); updateType(id, Types.SENT_TYPE);
} }
public void markStatus(long id, int status) {
Log.w("MessageDatabase", "Updating ID: " + id + " to status: " + status);
ContentValues contentValues = new ContentValues();
contentValues.put(STATUS, status);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {id+""});
notifyConversationListeners(getThreadIdForMessage(id));
}
public void markAsSentFailed(long id) { public void markAsSentFailed(long id) {
updateType(id, Types.FAILED_TYPE); updateType(id, Types.FAILED_TYPE);
} }
@ -317,6 +327,13 @@ public class SmsDatabase extends Database {
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
} }
public static class Status {
public static final int STATUS_NONE = -1;
public static final int STATUS_COMPLETE = 0;
public static final int STATUS_PENDING = 32;
public static final int STATUS_FAILED = 64;
}
public static class Types { public static class Types {
public static final int INBOX_TYPE = 1; public static final int INBOX_TYPE = 1;
public static final int SENT_TYPE = 2; public static final int SENT_TYPE = 2;

View File

@ -46,7 +46,8 @@ public class MediaMmsMessageRecord extends MessageRecord {
long threadId, SlideDeck slideDeck, long mailbox, long threadId, SlideDeck slideDeck, long mailbox,
GroupData groupData) GroupData groupData)
{ {
super(id, recipients, individualRecipient, dateSent, dateReceived, threadId, groupData); super(id, recipients, individualRecipient, dateSent, dateReceived,
threadId, DELIVERY_STATUS_NONE, groupData);
this.slideDeck = slideDeck; this.slideDeck = slideDeck;
this.mailbox = mailbox; this.mailbox = mailbox;

View File

@ -29,18 +29,26 @@ import org.thoughtcrime.securesms.recipients.Recipients;
*/ */
public abstract class MessageRecord extends DisplayRecord { public abstract class MessageRecord extends DisplayRecord {
public static final int DELIVERY_STATUS_NONE = 0;
public static final int DELIVERY_STATUS_RECEIVED = 1;
public static final int DELIVERY_STATUS_PENDING = 2;
public static final int DELIVERY_STATUS_FAILED = 3;
private final Recipient individualRecipient; private final Recipient individualRecipient;
private final long id; private final long id;
private final int deliveryStatus;
private final GroupData groupData; private final GroupData groupData;
public MessageRecord(long id, Recipients recipients, public MessageRecord(long id, Recipients recipients,
Recipient individualRecipient, Recipient individualRecipient,
long dateSent, long dateReceived, long dateSent, long dateReceived,
long threadId, GroupData groupData) long threadId, int deliveryStatus,
GroupData groupData)
{ {
super(recipients, dateSent, dateReceived, threadId); super(recipients, dateSent, dateReceived, threadId);
this.id = id; this.id = id;
this.individualRecipient = individualRecipient; this.individualRecipient = individualRecipient;
this.deliveryStatus = deliveryStatus;
this.groupData = groupData; this.groupData = groupData;
} }
@ -58,6 +66,14 @@ public abstract class MessageRecord extends DisplayRecord {
return id; return id;
} }
public int getDeliveryStatus() {
return deliveryStatus;
}
public boolean isDelivered() {
return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED;
}
public boolean isStaleKeyExchange() { public boolean isStaleKeyExchange() {
return this.staleKeyExchange; return this.staleKeyExchange;
} }

View File

@ -41,7 +41,9 @@ public class NotificationMmsMessageRecord extends MessageRecord {
byte[] contentLocation, long messageSize, long expiry, byte[] contentLocation, long messageSize, long expiry,
int status, byte[] transactionId) int status, byte[] transactionId)
{ {
super(id, recipients, individualRecipient, dateSent, dateReceived, threadId, null); super(id, recipients, individualRecipient, dateSent, dateReceived,
threadId, DELIVERY_STATUS_NONE, null);
this.contentLocation = contentLocation; this.contentLocation = contentLocation;
this.messageSize = messageSize; this.messageSize = messageSize;
this.expiry = expiry; this.expiry = expiry;

View File

@ -36,19 +36,18 @@ public class SmsMessageRecord extends MessageRecord {
private final Context context; private final Context context;
private final long type; private final long type;
private final long dateSent;
public SmsMessageRecord(Context context, long id, public SmsMessageRecord(Context context, long id,
Recipients recipients, Recipients recipients,
Recipient individualRecipient, Recipient individualRecipient,
long dateSent, long dateReceived, long dateSent, long dateReceived,
long type, long threadId, long type, long threadId,
GroupData groupData) int status, GroupData groupData)
{ {
super(id, recipients, individualRecipient, dateSent, dateReceived, threadId, groupData); super(id, recipients, individualRecipient, dateSent, dateReceived,
threadId, getGenericDeliveryStatus(status), groupData);
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.type = type; this.type = type;
this.dateSent = dateSent;
} }
public long getType() { public long getType() {
@ -76,7 +75,8 @@ public class SmsMessageRecord extends MessageRecord {
@Override @Override
public boolean isFailed() { public boolean isFailed() {
return SmsDatabase.Types.isFailedMessageType(getType()); return SmsDatabase.Types.isFailedMessageType(getType()) ||
getDeliveryStatus() == DELIVERY_STATUS_FAILED;
} }
@Override @Override
@ -99,4 +99,15 @@ public class SmsMessageRecord extends MessageRecord {
return false; return false;
} }
private static int getGenericDeliveryStatus(int status) {
if (status == SmsDatabase.Status.STATUS_NONE) {
return MessageRecord.DELIVERY_STATUS_NONE;
} else if (status >= SmsDatabase.Status.STATUS_FAILED) {
return MessageRecord.DELIVERY_STATUS_FAILED;
} else if (status >= SmsDatabase.Status.STATUS_PENDING) {
return MessageRecord.DELIVERY_STATUS_PENDING;
} else {
return MessageRecord.DELIVERY_STATUS_RECEIVED;
}
}
} }

View File

@ -47,6 +47,7 @@ public class SendReceiveService extends Service {
public static final String SEND_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_SMS_ACTION"; public static final String SEND_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_SMS_ACTION";
public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION"; public static final String SENT_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SENT_SMS_ACTION";
public static final String DELIVERED_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.DELIVERED_SMS_ACTION";
public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION"; public static final String RECEIVE_SMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.RECEIVE_SMS_ACTION";
public static final String SEND_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION"; public static final String SEND_MMS_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_ACTION";
public static final String SEND_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION"; public static final String SEND_MMS_CONNECTIVITY_ACTION = "org.thoughtcrime.securesms.SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION";
@ -93,6 +94,8 @@ public class SendReceiveService extends Service {
scheduleIntent(RECEIVE_SMS, intent); scheduleIntent(RECEIVE_SMS, intent);
else if (intent.getAction().equals(SENT_SMS_ACTION)) else if (intent.getAction().equals(SENT_SMS_ACTION))
scheduleIntent(RECEIVE_SMS, intent); scheduleIntent(RECEIVE_SMS, intent);
else if (intent.getAction().equals(DELIVERED_SMS_ACTION))
scheduleIntent(RECEIVE_SMS, intent);
else if (intent.getAction().equals(SEND_MMS_ACTION) || intent.getAction().equals(SEND_MMS_CONNECTIVITY_ACTION)) else if (intent.getAction().equals(SEND_MMS_ACTION) || intent.getAction().equals(SEND_MMS_CONNECTIVITY_ACTION))
scheduleSecretRequiredIntent(SEND_MMS, intent); scheduleSecretRequiredIntent(SEND_MMS, intent);
else if (intent.getAction().equals(RECEIVE_MMS_ACTION)) else if (intent.getAction().equals(RECEIVE_MMS_ACTION))
@ -191,8 +194,8 @@ public class SendReceiveService extends Service {
public void run() { public void run() {
switch (what) { switch (what) {
case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return; case RECEIVE_SMS: smsReceiver.process(masterSecret, intent); return;
case SEND_SMS: smsSender.process(masterSecret, intent); return; case SEND_SMS: smsSender.process(masterSecret, intent); return;
case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return; case RECEIVE_MMS: mmsReceiver.process(masterSecret, intent); return;
case SEND_MMS: mmsSender.process(masterSecret, intent); return; case SEND_MMS: mmsSender.process(masterSecret, intent); return;
case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return; case DOWNLOAD_MMS: mmsDownloader.process(masterSecret, intent); return;

View File

@ -1,6 +1,6 @@
/** /**
* Copyright (C) 2011 Whisper Systems * Copyright (C) 2011 Whisper Systems
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
@ -10,14 +10,12 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms.service; package org.thoughtcrime.securesms.service;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -26,6 +24,8 @@ import android.preference.PreferenceManager;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.protocol.WirePrefix;
public class SmsListener extends BroadcastReceiver { public class SmsListener extends BroadcastReceiver {
private static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED"; private static final String SMS_RECEIVED_ACTION = "android.provider.Telephony.SMS_RECEIVED";
@ -40,58 +40,58 @@ public class SmsListener extends BroadcastReceiver {
if (messageBody.startsWith("Sparebank1://otp?")) { if (messageBody.startsWith("Sparebank1://otp?")) {
return true; return true;
} }
// Sprint Visual Voicemail // Sprint Visual Voicemail
return return
message.getOriginatingAddress().length() < 7 && message.getOriginatingAddress().length() < 7 &&
(messageBody.startsWith("//ANDROID:") || messageBody.startsWith("//Android:") || (messageBody.startsWith("//ANDROID:") || messageBody.startsWith("//Android:") ||
messageBody.startsWith("//android:") || messageBody.startsWith("//BREW:")); messageBody.startsWith("//android:") || messageBody.startsWith("//BREW:"));
} }
private SmsMessage getSmsMessageFromIntent(Intent intent) { private SmsMessage getSmsMessageFromIntent(Intent intent) {
Bundle bundle = intent.getExtras(); Bundle bundle = intent.getExtras();
Object[] pdus = (Object[])bundle.get("pdus"); Object[] pdus = (Object[])bundle.get("pdus");
if (pdus == null || pdus.length == 0) if (pdus == null || pdus.length == 0)
return null; return null;
return SmsMessage.createFromPdu((byte[])pdus[0]); return SmsMessage.createFromPdu((byte[])pdus[0]);
} }
private String getSmsMessageBodyFromIntent(Intent intent) { private String getSmsMessageBodyFromIntent(Intent intent) {
Bundle bundle = intent.getExtras(); Bundle bundle = intent.getExtras();
Object[] pdus = (Object[])bundle.get("pdus"); Object[] pdus = (Object[])bundle.get("pdus");
StringBuilder bodyBuilder = new StringBuilder(); StringBuilder bodyBuilder = new StringBuilder();
if (pdus == null) if (pdus == null)
return null; return null;
for (int i=0;i<pdus.length;i++) for (int i=0;i<pdus.length;i++)
bodyBuilder.append(SmsMessage.createFromPdu((byte[])pdus[i]).getDisplayMessageBody()); bodyBuilder.append(SmsMessage.createFromPdu((byte[])pdus[i]).getDisplayMessageBody());
return bodyBuilder.toString(); return bodyBuilder.toString();
} }
private boolean isRelevent(Context context, Intent intent) { private boolean isRelevent(Context context, Intent intent) {
SmsMessage message = getSmsMessageFromIntent(intent); SmsMessage message = getSmsMessageFromIntent(intent);
String messageBody = getSmsMessageBodyFromIntent(intent); String messageBody = getSmsMessageBodyFromIntent(intent);
if (message == null && messageBody == null) if (message == null && messageBody == null)
return false; return false;
if (isExemption(message, messageBody)) if (isExemption(message, messageBody))
return false; return false;
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pref_all_sms", true)) if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pref_all_sms", true))
return true; return true;
return WirePrefix.isEncryptedMessage(messageBody) || WirePrefix.isKeyExchange(messageBody); return WirePrefix.isEncryptedMessage(messageBody) || WirePrefix.isKeyExchange(messageBody);
} }
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
Log.w("SMSListener", "Got SMS broadcast..."); Log.w("SMSListener", "Got SMS broadcast...");
if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isRelevent(context, intent)) { if (intent.getAction().equals(SMS_RECEIVED_ACTION) && isRelevent(context, intent)) {
intent.setAction(SendReceiveService.RECEIVE_SMS_ACTION); intent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
intent.putExtra("ResultCode", this.getResultCode()); intent.putExtra("ResultCode", this.getResultCode());
@ -103,6 +103,10 @@ public class SmsListener extends BroadcastReceiver {
intent.putExtra("ResultCode", this.getResultCode()); intent.putExtra("ResultCode", this.getResultCode());
intent.setClass(context, SendReceiveService.class); intent.setClass(context, SendReceiveService.class);
context.startService(intent); context.startService(intent);
} else if (intent.getAction().equals(SendReceiveService.DELIVERED_SMS_ACTION)) {
intent.putExtra("ResultCode", this.getResultCode());
intent.setClass(context, SendReceiveService.class);
context.startService(intent);
} }
} }
} }

View File

@ -176,10 +176,26 @@ public class SmsReceiver {
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId); DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
} }
private void handleDeliveredMessage(Intent intent) {
long messageId = intent.getLongExtra("message_id", -1);
long type = intent.getLongExtra("type", -1);
byte[] pdu = intent.getByteArrayExtra("pdu");
String format = intent.getStringExtra("format");
SmsMessage message = SmsMessage.createFromPdu(pdu);
if (message == null) {
return;
}
DatabaseFactory.getSmsDatabase(context).markStatus(messageId, message.getStatus());
}
public void process(MasterSecret masterSecret, Intent intent) { public void process(MasterSecret masterSecret, Intent intent) {
if (intent.getAction().equals(SendReceiveService.RECEIVE_SMS_ACTION)) if (intent.getAction().equals(SendReceiveService.RECEIVE_SMS_ACTION))
handleReceiveMessage(masterSecret, intent); handleReceiveMessage(masterSecret, intent);
else if (intent.getAction().equals(SendReceiveService.SENT_SMS_ACTION)) else if (intent.getAction().equals(SendReceiveService.SENT_SMS_ACTION))
handleSentMessage(intent); handleSentMessage(intent);
else if (intent.getAction().equals(SendReceiveService.DELIVERED_SMS_ACTION))
handleDeliveredMessage(intent);
} }
} }

View File

@ -21,9 +21,11 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import android.telephony.SmsManager; import android.telephony.SmsManager;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionCipher; import org.thoughtcrime.securesms.crypto.SessionCipher;
@ -111,15 +113,36 @@ public class SmsSender {
return sentIntents; return sentIntents;
} }
private ArrayList<PendingIntent> constructDeliveredIntents(long messageId, long type, ArrayList<String> messages) {
if (!PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(ApplicationPreferencesActivity.SMS_DELIVERY_REPORT_PREF, false))
{
return null;
}
ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(messages.size());
for (int i=0;i<messages.size();i++) {
Intent pending = new Intent(SendReceiveService.DELIVERED_SMS_ACTION, Uri.parse("custom://" + messageId + System.currentTimeMillis()), context, SmsListener.class);
pending.putExtra("type", type);
pending.putExtra("message_id", messageId);
deliveredIntents.add(PendingIntent.getBroadcast(context, 0, pending, 0));
}
return deliveredIntents;
}
private void deliverGSMTransportTextMessage(String recipient, String text, long messageId, long type) { private void deliverGSMTransportTextMessage(String recipient, String text, long messageId, long type) {
ArrayList<String> messages = SmsManager.getDefault().divideMessage(text); ArrayList<String> messages = SmsManager.getDefault().divideMessage(text);
ArrayList<PendingIntent> sentIntents = constructSentIntents(messageId, type, messages); ArrayList<PendingIntent> sentIntents = constructSentIntents(messageId, type, messages);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(messageId, type, messages);
// XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients // XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
// and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking // and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking
// the message as a failure. That way at least it doesn't repeatedly crash every time you start // the message as a failure. That way at least it doesn't repeatedly crash every time you start
// the app. // the app.
try { try {
SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, null); SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
} catch (NullPointerException npe) { } catch (NullPointerException npe) {
Log.w("SmsSender", npe); Log.w("SmsSender", npe);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId); DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
@ -142,15 +165,18 @@ public class SmsSender {
return; return;
} }
ArrayList<String> messages = multipartMessageHandler.divideMessage(recipient, text, prefix); ArrayList<String> messages = multipartMessageHandler.divideMessage(recipient, text, prefix);
ArrayList<PendingIntent> sentIntents = constructSentIntents(messageId, type, messages); ArrayList<PendingIntent> sentIntents = constructSentIntents(messageId, type, messages);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(messageId, type, messages);
for (int i=0;i<messages.size();i++) { for (int i=0;i<messages.size();i++) {
// XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients // XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
// and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking // and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking
// the message as a failure. That way at least it doesn't repeatedly crash every time you start // the message as a failure. That way at least it doesn't repeatedly crash every time you start
// the app. // the app.
try { try {
SmsManager.getDefault().sendTextMessage(recipient, null, messages.get(i), sentIntents.get(i), null); SmsManager.getDefault().sendTextMessage(recipient, null, messages.get(i), sentIntents.get(i),
deliveredIntents == null ? null : deliveredIntents.get(i));
} catch (NullPointerException npe) { } catch (NullPointerException npe) {
Log.w("SmsSender", npe); Log.w("SmsSender", npe);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId); DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);