mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-11 21:41:46 +00:00
Implement delivery receipts.
1) Support a "receipt" push message type. 2) Identify messages by timestamp. 3) Introduce a JobManager to handle the queue for network dependent jobs.
This commit is contained in:
80
src/org/thoughtcrime/securesms/ApplicationContext.java
Normal file
80
src/org/thoughtcrime/securesms/ApplicationContext.java
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.path.android.jobqueue.JobManager;
|
||||
import com.path.android.jobqueue.config.Configuration;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
||||
import org.thoughtcrime.securesms.jobs.ContextInjector;
|
||||
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.JobLogger;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
/**
|
||||
* Will be called once when the TextSecure process is created.
|
||||
*
|
||||
* We're using this as an insertion point to patch up the Android PRNG disaster,
|
||||
* to initialize the job manager, and to check for GCM registration freshness.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ApplicationContext extends Application {
|
||||
|
||||
private JobManager jobManager;
|
||||
|
||||
public static ApplicationContext getInstance(Context context) {
|
||||
return (ApplicationContext)context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
initializeRandomNumberFix();
|
||||
initializeJobManager();
|
||||
initializeGcmCheck();
|
||||
}
|
||||
|
||||
public JobManager getJobManager() {
|
||||
return jobManager;
|
||||
}
|
||||
|
||||
private void initializeRandomNumberFix() {
|
||||
PRNGFixes.apply();
|
||||
}
|
||||
|
||||
private void initializeJobManager() {
|
||||
Configuration configuration = new Configuration.Builder(this)
|
||||
.minConsumerCount(1)
|
||||
.injector(new ContextInjector(this))
|
||||
.customLogger(new JobLogger())
|
||||
.build();
|
||||
|
||||
this.jobManager = new JobManager(this, configuration);
|
||||
}
|
||||
|
||||
private void initializeGcmCheck() {
|
||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
TextSecurePreferences.getGcmRegistrationId(this) == null)
|
||||
{
|
||||
this.jobManager.addJob(new GcmRefreshJob());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
||||
|
||||
/**
|
||||
* Will be called once when the TextSecure process is created.
|
||||
*
|
||||
* We're using this as an insertion point to patch up the Android PRNG disaster.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ApplicationListener extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
PRNGFixes.apply();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -61,7 +61,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Trimmer;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.push.AuthorizationFailedException;
|
||||
import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -35,9 +35,9 @@ import org.thoughtcrime.securesms.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.util.Dialogs;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.push.ExpectationFailedException;
|
||||
import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.RateLimitException;
|
||||
import org.whispersystems.textsecure.push.exceptions.RateLimitException;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.GcmRegistrationService;
|
||||
import org.thoughtcrime.securesms.service.PreKeyService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
|
||||
@@ -122,8 +120,6 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
|
||||
final ConversationParameters parameters = getConversationParameters();
|
||||
final Intent intent;
|
||||
|
||||
scheduleRefreshActions();
|
||||
|
||||
if (isShareAction()) intent = getShareIntent(parameters);
|
||||
else if (parameters.recipients != null) intent = getConversationIntent(parameters);
|
||||
else intent = getConversationListIntent();
|
||||
@@ -173,20 +169,20 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
|
||||
return intent;
|
||||
}
|
||||
|
||||
private void scheduleRefreshActions() {
|
||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
TextSecurePreferences.getGcmRegistrationId(this) == null)
|
||||
{
|
||||
Intent intent = new Intent(this, GcmRegistrationService.class);
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
!TextSecurePreferences.isSignedPreKeyRegistered(this))
|
||||
{
|
||||
PreKeyService.initiateCreateSigned(this, masterSecret);
|
||||
}
|
||||
}
|
||||
// private void scheduleRefreshActions() {
|
||||
// if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
// TextSecurePreferences.getGcmRegistrationId(this) == null)
|
||||
// {
|
||||
// Intent intent = new Intent(this, GcmRegistrationService.class);
|
||||
// startService(intent);
|
||||
// }
|
||||
//
|
||||
// if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
// !TextSecurePreferences.isSignedPreKeyRegistered(this))
|
||||
// {
|
||||
// PreKeyService.initiateCreateSigned(this, masterSecret);
|
||||
// }
|
||||
// }
|
||||
|
||||
private int getApplicationState() {
|
||||
if (!MasterSecretUtil.isPassphraseInitialized(this))
|
||||
|
||||
@@ -55,7 +55,9 @@ public class DatabaseFactory {
|
||||
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
|
||||
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
||||
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
|
||||
private static final int DATABASE_VERSION = 12;
|
||||
private static final int INTRODUCED_DELIVERY_RECEIPTS = 13;
|
||||
private static final int DATABASE_VERSION = 13;
|
||||
|
||||
|
||||
private static final String DATABASE_NAME = "messages.db";
|
||||
private static final Object lock = new Object();
|
||||
@@ -702,6 +704,13 @@ public class DatabaseFactory {
|
||||
db.execSQL("DROP TABLE push_backup;");
|
||||
}
|
||||
|
||||
if (oldVersion < INTRODUCED_DELIVERY_RECEIPTS) {
|
||||
db.execSQL("ALTER TABLE sms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;");
|
||||
db.execSQL("ALTER TABLE mms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;");
|
||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS sms_date_sent_index ON sms (date_sent);");
|
||||
db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import ws.com.google.android.mms.pdu.EncodedStringValue;
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class MmsAddressDatabase extends Database {
|
||||
|
||||
@@ -106,6 +108,25 @@ public class MmsAddressDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getAddressesForId(long messageId) {
|
||||
List<String> results = new LinkedList<String>();
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {messageId+""}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.add(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void deleteAddressesForId(long messageId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Trimmer;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
@@ -122,13 +123,14 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + RETRIEVE_STATUS + " INTEGER, " +
|
||||
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
|
||||
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
|
||||
DELIVERY_REPORT + " INTEGER);";
|
||||
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + DELIVERY_REPORT + " INTEGER);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_read_index ON " + TABLE_NAME + " (" + READ + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + THREAD_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_message_box_index ON " + TABLE_NAME + " (" + MESSAGE_BOX + ");"
|
||||
"CREATE INDEX IF NOT EXISTS mms_message_box_index ON " + TABLE_NAME + " (" + MESSAGE_BOX + ");",
|
||||
"CREATE INDEX IF NOT EXISTS mms_date_sent_index ON " + TABLE_NAME + " (" + DATE_SENT + ");"
|
||||
};
|
||||
|
||||
private static final String[] MMS_PROJECTION = new String[] {
|
||||
@@ -139,6 +141,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
|
||||
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||
RECEIPT_COUNT
|
||||
};
|
||||
|
||||
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
|
||||
@@ -166,6 +169,43 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void incrementDeliveryReceiptCount(String address, long timestamp) {
|
||||
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID}, DATE_SENT + " = ?", new String[] {String.valueOf(timestamp / 1000)}, null, null, null, null);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
List<String> addresses = addressDatabase.getAddressesForId(cursor.getLong(cursor.getColumnIndexOrThrow(ID)));
|
||||
|
||||
for (String storedAddress : addresses) {
|
||||
try {
|
||||
String ourAddress = org.thoughtcrime.securesms.util.Util.canonicalizeNumber(context, address);
|
||||
String theirAddress = org.thoughtcrime.securesms.util.Util.canonicalizeNumber(context, storedAddress);
|
||||
|
||||
if (ourAddress.equals(theirAddress)) {
|
||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
|
||||
|
||||
database.execSQL("UPDATE " + TABLE_NAME + " SET " +
|
||||
RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?",
|
||||
new String[] {String.valueOf(id)});
|
||||
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w("MmsDatabase", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public long getThreadIdForMessage(long id) {
|
||||
String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?";
|
||||
String[] sqlArgs = new String[] {id+""};
|
||||
@@ -418,6 +458,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
|
||||
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
|
||||
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
||||
PduHeaders headers = getHeadersFromCursor(cursor);
|
||||
addr.getAddressesForId(messageId, headers);
|
||||
|
||||
@@ -433,7 +474,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
Log.w("MmsDatabase", e);
|
||||
}
|
||||
|
||||
requests[i++] = new SendReq(headers, body, messageId, outboxType);
|
||||
requests[i++] = new SendReq(headers, body, messageId, outboxType, timestamp);
|
||||
}
|
||||
|
||||
return requests;
|
||||
@@ -902,6 +943,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
|
||||
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
|
||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
|
||||
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
|
||||
|
||||
byte[]contentLocationBytes = null;
|
||||
byte[]transactionIdBytes = null;
|
||||
@@ -914,7 +956,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
|
||||
|
||||
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||
addressDeviceId, dateSent, dateReceived, threadId,
|
||||
addressDeviceId, dateSent, dateReceived, receiptCount, threadId,
|
||||
contentLocationBytes, messageSize, expiry, status,
|
||||
transactionIdBytes, mailbox);
|
||||
}
|
||||
@@ -927,6 +969,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
|
||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
|
||||
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
|
||||
DisplayRecord.Body body = getBody(cursor);
|
||||
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
@@ -934,8 +977,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
||||
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(masterSecret, id);
|
||||
|
||||
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||
addressDeviceId, dateSent, dateReceived, threadId, body,
|
||||
slideDeck, partCount, box);
|
||||
addressDeviceId, dateSent, dateReceived, receiptCount,
|
||||
threadId, body, slideDeck, partCount, box);
|
||||
}
|
||||
|
||||
private Recipients getRecipientsFor(String address) {
|
||||
|
||||
@@ -10,6 +10,7 @@ public interface MmsSmsColumns {
|
||||
public static final String BODY = "body";
|
||||
public static final String ADDRESS = "address";
|
||||
public static final String ADDRESS_DEVICE_ID = "address_device_id";
|
||||
public static final String RECEIPT_COUNT = "delivery_receipt_count";
|
||||
|
||||
public static class Types {
|
||||
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
||||
|
||||
@@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -49,7 +49,7 @@ public class MmsSmsDatabase extends Database {
|
||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, TRANSPORT};
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
|
||||
@@ -71,7 +71,7 @@ public class MmsSmsDatabase extends Database {
|
||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, TRANSPORT};
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
@@ -89,7 +89,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||
MmsDatabase.STATUS, TRANSPORT};
|
||||
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
String selection = MmsSmsColumns.READ + " = 0";
|
||||
@@ -104,6 +104,11 @@ public class MmsSmsDatabase extends Database {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void incrementDeliveryReceiptCount(String address, long timestamp) {
|
||||
DatabaseFactory.getSmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
|
||||
DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
|
||||
}
|
||||
|
||||
private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
|
||||
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
@@ -112,7 +117,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
TRANSPORT};
|
||||
MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
@@ -121,7 +126,7 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||
TRANSPORT};
|
||||
MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||
|
||||
|
||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||
@@ -140,6 +145,7 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsSmsColumns.BODY);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
||||
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
||||
@@ -158,6 +164,7 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||
smsColumnsPresent.add(MmsSmsColumns.READ);
|
||||
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
||||
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||
smsColumnsPresent.add(SmsDatabase.TYPE);
|
||||
smsColumnsPresent.add(SmsDatabase.SUBJECT);
|
||||
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
|
||||
|
||||
@@ -36,13 +36,18 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Trimmer;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
|
||||
|
||||
/**
|
||||
* Database for storage of SMS messages.
|
||||
*
|
||||
@@ -66,13 +71,15 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " +
|
||||
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
|
||||
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
|
||||
SUBJECT + " TEXT, " + BODY + " TEXT, " + SERVICE_CENTER + " TEXT);";
|
||||
RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
|
||||
SERVICE_CENTER + " TEXT);";
|
||||
|
||||
public static final String[] CREATE_INDEXS = {
|
||||
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS sms_read_index ON " + TABLE_NAME + " (" + READ + ");",
|
||||
"CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + THREAD_ID + ");",
|
||||
"CREATE INDEX IF NOT EXISTS sms_type_index ON " + TABLE_NAME + " (" + TYPE + ");"
|
||||
"CREATE INDEX IF NOT EXISTS sms_type_index ON " + TABLE_NAME + " (" + TYPE + ");",
|
||||
"CREATE INDEX IF NOT EXISTS sms_date_sent_index ON " + TABLE_NAME + " (" + DATE_SENT + ");"
|
||||
};
|
||||
|
||||
private static final String[] MESSAGE_PROJECTION = new String[] {
|
||||
@@ -80,7 +87,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
|
||||
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
||||
PROTOCOL, READ, STATUS, TYPE,
|
||||
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER
|
||||
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT
|
||||
};
|
||||
|
||||
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
@@ -240,6 +247,38 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_FAILED_TYPE);
|
||||
}
|
||||
|
||||
public void incrementDeliveryReceiptCount(String address, long timestamp) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS},
|
||||
DATE_SENT + " = ?", new String[] {String.valueOf(timestamp)},
|
||||
null, null, null, null);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
try {
|
||||
String theirAddress = canonicalizeNumber(context, address);
|
||||
String ourAddress = canonicalizeNumber(context, cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
|
||||
|
||||
if (ourAddress.equals(theirAddress)) {
|
||||
database.execSQL("UPDATE " + TABLE_NAME +
|
||||
" SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " +
|
||||
ID + " = ?",
|
||||
new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
|
||||
|
||||
notifyConversationListeners(cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)));
|
||||
}
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w("SmsDatabase", e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMessagesRead(long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
@@ -539,13 +578,14 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
|
||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
|
||||
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
|
||||
Recipients recipients = getRecipientsFor(address);
|
||||
DisplayRecord.Body body = getBody(cursor);
|
||||
|
||||
return new SmsMessageRecord(context, messageId, body, recipients,
|
||||
recipients.getPrimaryRecipient(),
|
||||
addressDeviceId,
|
||||
dateSent, dateReceived, type,
|
||||
dateSent, dateReceived, receiptCount, type,
|
||||
threadId, status);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,12 +43,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
||||
|
||||
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived, long threadId, Body body,
|
||||
long dateSent, long dateReceived, int deliveredCount,
|
||||
long threadId, Body body,
|
||||
ListenableFutureTask<SlideDeck> slideDeck,
|
||||
int partCount, long mailbox)
|
||||
{
|
||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
|
||||
dateSent, dateReceived, threadId, deliveredCount, DELIVERY_STATUS_NONE, mailbox);
|
||||
|
||||
this.context = context.getApplicationContext();
|
||||
this.partCount = partCount;
|
||||
|
||||
@@ -48,17 +48,19 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
private final int recipientDeviceId;
|
||||
private final long id;
|
||||
private final int deliveryStatus;
|
||||
private final int receiptCount;
|
||||
|
||||
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived,
|
||||
long threadId, int deliveryStatus, long type)
|
||||
long dateSent, long dateReceived, long threadId,
|
||||
int deliveryStatus, int receiptCount, long type)
|
||||
{
|
||||
super(context, body, recipients, dateSent, dateReceived, threadId, type);
|
||||
this.id = id;
|
||||
this.individualRecipient = individualRecipient;
|
||||
this.recipientDeviceId = recipientDeviceId;
|
||||
this.deliveryStatus = deliveryStatus;
|
||||
this.receiptCount = receiptCount;
|
||||
}
|
||||
|
||||
public abstract boolean isMms();
|
||||
@@ -110,7 +112,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
}
|
||||
|
||||
public boolean isDelivered() {
|
||||
return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED;
|
||||
return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED || receiptCount > 0;
|
||||
}
|
||||
|
||||
public boolean isPush() {
|
||||
|
||||
@@ -42,12 +42,12 @@ public class NotificationMmsMessageRecord extends MessageRecord {
|
||||
|
||||
public NotificationMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||
Recipient individualRecipient, int recipientDeviceId,
|
||||
long dateSent, long dateReceived, long threadId,
|
||||
byte[] contentLocation, long messageSize, long expiry,
|
||||
int status, byte[] transactionId, long mailbox)
|
||||
long dateSent, long dateReceived, int receiptCount,
|
||||
long threadId, byte[] contentLocation, long messageSize,
|
||||
long expiry, int status, byte[] transactionId, long mailbox)
|
||||
{
|
||||
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
|
||||
dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
|
||||
@@ -43,11 +43,12 @@ public class SmsMessageRecord extends MessageRecord {
|
||||
Recipient individualRecipient,
|
||||
int recipientDeviceId,
|
||||
long dateSent, long dateReceived,
|
||||
int receiptCount,
|
||||
long type, long threadId,
|
||||
int status)
|
||||
{
|
||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||
dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type);
|
||||
dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type);
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
|
||||
@@ -6,7 +6,10 @@ import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.gms.gcm.GoogleCloudMessaging;
|
||||
import com.path.android.jobqueue.JobManager;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
|
||||
import org.thoughtcrime.securesms.service.SendReceiveService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||
@@ -31,38 +34,47 @@ public class GcmBroadcastReceiver extends BroadcastReceiver {
|
||||
if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
|
||||
Log.w(TAG, "GCM message...");
|
||||
|
||||
try {
|
||||
String data = intent.getStringExtra("message");
|
||||
|
||||
if (Util.isEmpty(data))
|
||||
return;
|
||||
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
Log.w(TAG, "Not push registered!");
|
||||
return;
|
||||
}
|
||||
|
||||
String sessionKey = TextSecurePreferences.getSignalingKey(context);
|
||||
IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey);
|
||||
IncomingPushMessage message = encryptedMessage.getIncomingPushMessage();
|
||||
|
||||
if (!isActiveNumber(context, message.getSource())) {
|
||||
Directory directory = Directory.getInstance(context);
|
||||
ContactTokenDetails contactTokenDetails = new ContactTokenDetails();
|
||||
contactTokenDetails.setNumber(message.getSource());
|
||||
|
||||
directory.setNumber(contactTokenDetails, true);
|
||||
}
|
||||
|
||||
Intent service = new Intent(context, SendReceiveService.class);
|
||||
service.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
|
||||
service.putExtra("message", message);
|
||||
context.startService(service);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||
Log.w(TAG, "Not push registered!");
|
||||
return;
|
||||
}
|
||||
|
||||
String messageData = intent.getStringExtra("message");
|
||||
String receiptData = intent.getStringExtra("receipt");
|
||||
|
||||
if (!Util.isEmpty(messageData)) handleReceivedMessage(context, messageData);
|
||||
else if (!Util.isEmpty(receiptData)) handleReceivedMessage(context, receiptData);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReceivedMessage(Context context, String data) {
|
||||
try {
|
||||
String sessionKey = TextSecurePreferences.getSignalingKey(context);
|
||||
IncomingEncryptedPushMessage encrypted = new IncomingEncryptedPushMessage(data, sessionKey);
|
||||
IncomingPushMessage message = encrypted.getIncomingPushMessage();
|
||||
|
||||
if (!isActiveNumber(context, message.getSource())) {
|
||||
Directory directory = Directory.getInstance(context);
|
||||
ContactTokenDetails contactTokenDetails = new ContactTokenDetails();
|
||||
contactTokenDetails.setNumber(message.getSource());
|
||||
|
||||
directory.setNumber(contactTokenDetails, true);
|
||||
}
|
||||
|
||||
Intent receiveService = new Intent(context, SendReceiveService.class);
|
||||
receiveService.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
|
||||
receiveService.putExtra("message", message);
|
||||
context.startService(receiveService);
|
||||
|
||||
if (!message.isReceipt()) {
|
||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
jobManager.addJob(new DeliveryReceiptJob(message.getSource(), message.getTimestampMillis(),
|
||||
message.getRelay()));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
src/org/thoughtcrime/securesms/jobs/ContextInjector.java
Normal file
22
src/org/thoughtcrime/securesms/jobs/ContextInjector.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.path.android.jobqueue.BaseJob;
|
||||
import com.path.android.jobqueue.di.DependencyInjector;
|
||||
|
||||
public class ContextInjector implements DependencyInjector {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public ContextInjector(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inject(BaseJob job) {
|
||||
if (job instanceof ContextJob) {
|
||||
((ContextJob)job).setContext(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/org/thoughtcrime/securesms/jobs/ContextJob.java
Normal file
23
src/org/thoughtcrime/securesms/jobs/ContextJob.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.path.android.jobqueue.Job;
|
||||
import com.path.android.jobqueue.Params;
|
||||
|
||||
public abstract class ContextJob extends Job {
|
||||
|
||||
transient protected Context context;
|
||||
|
||||
protected ContextJob(Params params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
56
src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java
Normal file
56
src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.path.android.jobqueue.Params;
|
||||
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
|
||||
|
||||
public class DeliveryReceiptJob extends ContextJob {
|
||||
|
||||
private static final String TAG = DeliveryReceiptJob.class.getSimpleName();
|
||||
|
||||
private final String destination;
|
||||
private final long timestamp;
|
||||
private final String relay;
|
||||
|
||||
public DeliveryReceiptJob(String destination, long timestamp, String relay) {
|
||||
super(new Params(Priorities.HIGH).requireNetwork().persist());
|
||||
|
||||
this.destination = destination;
|
||||
this.timestamp = timestamp;
|
||||
this.relay = relay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onRun() throws Throwable {
|
||||
Log.w("DeliveryReceiptJob", "Sending delivery receipt...");
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
socket.sendReceipt(destination, timestamp, relay);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel() {
|
||||
Log.w(TAG, "Failed to send receipt after retry exhausted!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldReRunOnThrowable(Throwable throwable) {
|
||||
Log.w(TAG, throwable);
|
||||
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
|
||||
if (throwable instanceof PushNetworkException) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getRetryLimit() {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
61
src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
Normal file
61
src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||
import com.google.android.gms.gcm.GoogleCloudMessaging;
|
||||
import com.path.android.jobqueue.Params;
|
||||
|
||||
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
|
||||
|
||||
public class GcmRefreshJob extends ContextJob {
|
||||
|
||||
private static final String TAG = GcmRefreshJob.class.getSimpleName();
|
||||
|
||||
public static final String REGISTRATION_ID = "312334754206";
|
||||
|
||||
public GcmRefreshJob() {
|
||||
super(new Params(Priorities.NORMAL).requireNetwork());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onRun() throws Exception {
|
||||
String registrationId = TextSecurePreferences.getGcmRegistrationId(context);
|
||||
|
||||
if (registrationId == null) {
|
||||
Log.w(TAG, "GCM registrationId expired, reregistering...");
|
||||
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
|
||||
|
||||
if (result != ConnectionResult.SUCCESS) {
|
||||
Toast.makeText(context, "Unable to register with GCM!", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
String gcmId = GoogleCloudMessaging.getInstance(context).register(REGISTRATION_ID);
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
|
||||
socket.registerGcmId(gcmId);
|
||||
TextSecurePreferences.setGcmRegistrationId(context, gcmId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancel() {
|
||||
Log.w(TAG, "GCM reregistration failed after retry attempt exhaustion!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldReRunOnThrowable(Throwable throwable) {
|
||||
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
27
src/org/thoughtcrime/securesms/jobs/JobLogger.java
Normal file
27
src/org/thoughtcrime/securesms/jobs/JobLogger.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.path.android.jobqueue.log.CustomLogger;
|
||||
|
||||
public class JobLogger implements CustomLogger {
|
||||
@Override
|
||||
public boolean isDebugEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void d(String text, Object... args) {
|
||||
Log.w("JobManager", String.format(text, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void e(Throwable t, String text, Object... args) {
|
||||
Log.w("JobManager", String.format(text, args), t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void e(String text, Object... args) {
|
||||
Log.w("JobManager", String.format(text, args));
|
||||
}
|
||||
}
|
||||
8
src/org/thoughtcrime/securesms/jobs/Priorities.java
Normal file
8
src/org/thoughtcrime/securesms/jobs/Priorities.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
public class Priorities {
|
||||
|
||||
public static final int NORMAL = 500;
|
||||
public static final int HIGH = 1000;
|
||||
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.Release;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingPartDatabase;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||
@@ -16,7 +15,7 @@ import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.push.NotFoundException;
|
||||
import org.whispersystems.textsecure.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageCo
|
||||
|
||||
public class PushReceiver {
|
||||
|
||||
private static final String TAG = PushReceiver.class.getSimpleName();
|
||||
|
||||
public static final int RESULT_OK = 0;
|
||||
public static final int RESULT_NO_SESSION = 1;
|
||||
public static final int RESULT_DECRYPT_FAILED = 2;
|
||||
@@ -97,7 +99,9 @@ public class PushReceiver {
|
||||
|
||||
if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message);
|
||||
else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(masterSecret, message);
|
||||
else handleReceivedMessage(masterSecret, message, false);
|
||||
else if (message.isReceipt()) handleReceivedReceipt(message);
|
||||
else if (message.isPlaintext()) handleReceivedMessage(masterSecret, message, false);
|
||||
else Log.w(TAG, "Received push of unknown type!");
|
||||
}
|
||||
|
||||
private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) {
|
||||
@@ -130,21 +134,21 @@ public class PushReceiver {
|
||||
handleReceivedMessage(masterSecret, bundledMessage, true);
|
||||
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, true);
|
||||
} catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException |
|
||||
RecipientFormattingException | LegacyMessageException e)
|
||||
{
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||
} catch (DuplicateMessageException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
handleReceivedDuplicateMessage(message);
|
||||
} catch (NoSessionException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
handleReceivedMessageForNoSession(masterSecret, message);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
String encoded = Base64.encodeBytes(message.getBody());
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
|
||||
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
|
||||
@@ -163,24 +167,31 @@ public class PushReceiver {
|
||||
PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody());
|
||||
|
||||
if (secure && (messageContent.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0) {
|
||||
Log.w("PushReceiver", "Received end session message...");
|
||||
Log.w(TAG, "Received end session message...");
|
||||
handleEndSessionMessage(masterSecret, message, messageContent);
|
||||
} else if (messageContent.hasGroup() && messageContent.getGroup().getType().getNumber() != Type.DELIVER_VALUE) {
|
||||
Log.w("PushReceiver", "Received push group message...");
|
||||
Log.w(TAG, "Received push group message...");
|
||||
groupReceiver.process(masterSecret, message, messageContent, secure);
|
||||
} else if (messageContent.getAttachmentsCount() > 0) {
|
||||
Log.w("PushReceiver", "Received push media message...");
|
||||
Log.w(TAG, "Received push media message...");
|
||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
|
||||
} else {
|
||||
Log.w("PushReceiver", "Received push text message...");
|
||||
Log.w(TAG, "Received push text message...");
|
||||
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
|
||||
}
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
handleReceivedCorruptedMessage(masterSecret, message, secure);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleReceivedReceipt(IncomingPushMessage message)
|
||||
{
|
||||
Log.w("PushReceiver", String.format("Received receipt: (XXXXX, %d)", message.getTimestampMillis()));
|
||||
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(message.getSource(),
|
||||
message.getTimestampMillis());
|
||||
}
|
||||
|
||||
private void handleEndSessionMessage(MasterSecret masterSecret,
|
||||
IncomingPushMessage message,
|
||||
PushMessageContent messageContent)
|
||||
@@ -200,7 +211,7 @@ public class PushReceiver {
|
||||
|
||||
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
|
||||
} catch (RecipientFormattingException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +242,7 @@ public class PushReceiver {
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
} catch (MmsException e) {
|
||||
Log.w("PushReceiver", e);
|
||||
Log.w(TAG, e);
|
||||
// XXX
|
||||
}
|
||||
}
|
||||
@@ -267,7 +278,7 @@ public class PushReceiver {
|
||||
}
|
||||
|
||||
private void handleReceivedDuplicateMessage(IncomingPushMessage message) {
|
||||
Log.w("PushReceiver", "Received duplicate message: " + message.getSource() + " , " + message.getSourceDevice());
|
||||
Log.w(TAG, "Received duplicate message: " + message.getSource() + " , " + message.getSourceDevice());
|
||||
}
|
||||
|
||||
private void handleReceivedCorruptedKey(MasterSecret masterSecret,
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.util.KeyHelper;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.PreKeyUtil;
|
||||
import org.whispersystems.textsecure.push.ExpectationFailedException;
|
||||
import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import org.whispersystems.textsecure.crypto.AttachmentCipher;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.TransportDetails;
|
||||
import org.whispersystems.textsecure.push.MismatchedDevices;
|
||||
import org.whispersystems.textsecure.push.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecure.push.OutgoingPushMessage;
|
||||
import org.whispersystems.textsecure.push.OutgoingPushMessageList;
|
||||
import org.whispersystems.textsecure.push.PushAddress;
|
||||
@@ -54,7 +54,7 @@ import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.PushTransportDetails;
|
||||
import org.whispersystems.textsecure.push.StaleDevices;
|
||||
import org.whispersystems.textsecure.push.StaleDevicesException;
|
||||
import org.whispersystems.textsecure.push.exceptions.StaleDevicesException;
|
||||
import org.whispersystems.textsecure.push.UnregisteredUserException;
|
||||
import org.whispersystems.textsecure.storage.SessionUtil;
|
||||
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
|
||||
@@ -92,7 +92,7 @@ public class PushTransport extends BaseTransport {
|
||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||
byte[] plaintext = getPlaintextMessage(message);
|
||||
|
||||
deliver(socket, recipient, threadId, plaintext);
|
||||
deliver(socket, recipient, message.getDateSent(), threadId, plaintext);
|
||||
|
||||
if (message.isEndSession()) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
@@ -129,7 +129,7 @@ public class PushTransport extends BaseTransport {
|
||||
|
||||
for (Recipient recipient : recipients.getRecipientsList()) {
|
||||
try {
|
||||
deliver(socket, recipient, threadId, plaintext);
|
||||
deliver(socket, recipient, message.getSentTimestamp(), threadId, plaintext);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w("PushTransport", e);
|
||||
untrustedIdentities.add(e);
|
||||
@@ -144,13 +144,14 @@ public class PushTransport extends BaseTransport {
|
||||
}
|
||||
}
|
||||
|
||||
private void deliver(PushServiceSocket socket, Recipient recipient, long threadId, byte[] plaintext)
|
||||
private void deliver(PushServiceSocket socket, Recipient recipient, long timestamp,
|
||||
long threadId, byte[] plaintext)
|
||||
throws IOException, InvalidNumberException, UntrustedIdentityException
|
||||
{
|
||||
for (int i=0;i<3;i++) {
|
||||
try {
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId,
|
||||
recipient, plaintext);
|
||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId, recipient,
|
||||
timestamp, plaintext);
|
||||
socket.sendMessage(messages);
|
||||
|
||||
return;
|
||||
@@ -300,7 +301,8 @@ public class PushTransport extends BaseTransport {
|
||||
}
|
||||
|
||||
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
|
||||
Recipient recipient, byte[] plaintext)
|
||||
Recipient recipient, long timestamp,
|
||||
byte[] plaintext)
|
||||
throws IOException, InvalidNumberException, UntrustedIdentityException
|
||||
{
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
@@ -319,7 +321,7 @@ public class PushTransport extends BaseTransport {
|
||||
messages.add(new OutgoingPushMessage(device, body));
|
||||
}
|
||||
|
||||
return new OutgoingPushMessageList(e164number, masterDevice.getRelay(), messages);
|
||||
return new OutgoingPushMessageList(e164number, timestamp, masterDevice.getRelay(), messages);
|
||||
}
|
||||
|
||||
private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId,
|
||||
|
||||
Reference in New Issue
Block a user