From 8f6590b7381fec0899f8fae217e2a59639767e3b Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 9 Sep 2013 16:46:03 -0700 Subject: [PATCH] Handle notifications and receiving push when locked. --- .../textsecure/push/IncomingPushMessage.java | 11 ++++ .../whispersystems/textsecure/util/Util.java | 17 +++++ .../securesms/crypto/DecryptingQueue.java | 36 ++++++++-- .../securesms/database/PushDatabase.java | 50 +++++++++++++- .../notifications/MessageNotifier.java | 65 +++++++++++++++++-- .../recipients/RecipientFactory.java | 22 +++++++ .../securesms/service/PushReceiver.java | 43 +++++++++--- .../securesms/service/SmsReceiver.java | 26 +------- .../securesms/transport/PushTransport.java | 9 ++- 9 files changed, 226 insertions(+), 53 deletions(-) diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java index 8de52cd294..93dd829cfc 100644 --- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java +++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java @@ -71,6 +71,17 @@ public class IncomingPushMessage implements PushMessage, Parcelable { this.timestamp = in.readLong(); } + public IncomingPushMessage(int type, String source, + List destinations, + byte[] body, long timestamp) + { + this.type = type; + this.source = source; + this.destinations = destinations; + this.message = body; + this.timestamp = timestamp; + } + public long getTimestampMillis() { return timestamp; } diff --git a/library/src/org/whispersystems/textsecure/util/Util.java b/library/src/org/whispersystems/textsecure/util/Util.java index 800050c631..7bdba4973d 100644 --- a/library/src/org/whispersystems/textsecure/util/Util.java +++ b/library/src/org/whispersystems/textsecure/util/Util.java @@ -13,6 +13,7 @@ import java.io.OutputStream; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Collection; +import java.util.LinkedList; import java.util.List; public class Util { @@ -159,6 +160,22 @@ public class Util { return result.toString(); } + public static List split(String source, String delimiter) { + List results = new LinkedList(); + + if (isEmpty(source)) { + return results; + } + + String[] elements = source.split(delimiter); + + for (String element : elements) { + results.add(element); + } + + return results; + } + public static SecureRandom getSecureRandom() { try { return SecureRandom.getInstance("SHA1PRNG"); diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 6bbefa0406..6f0ea2269f 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; @@ -34,7 +35,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.service.PushDownloader; import org.thoughtcrime.securesms.service.PushReceiver; import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.sms.SmsTransportDetails; @@ -98,18 +98,42 @@ public class DecryptingQueue { public static void schedulePendingDecrypts(Context context, MasterSecret masterSecret) { Log.w("DecryptingQueue", "Processing pending decrypts..."); - EncryptingSmsDatabase.Reader reader = null; + EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context); + PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context); + + EncryptingSmsDatabase.Reader smsReader = null; + PushDatabase.Reader pushReader = null; + SmsMessageRecord record; + IncomingPushMessage message; try { - reader = DatabaseFactory.getEncryptingSmsDatabase(context).getDecryptInProgressMessages(masterSecret); + smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret); + pushReader = pushDatabase.readerFor(pushDatabase.getPending()); - while ((record = reader.getNext()) != null) { + while ((record = smsReader.getNext()) != null) { scheduleDecryptFromCursor(context, masterSecret, record); } + + while ((message = pushReader.getNext()) != null) { + if (message.isPreKeyBundle()) { + Intent intent = new Intent(context, SendReceiveService.class); + intent.setAction(SendReceiveService.RECEIVE_PUSH_ACTION); + intent.putExtra("message", message); + context.startService(intent); + + pushDatabase.delete(pushReader.getCurrentId()); + } else { + scheduleDecryption(context, masterSecret, pushReader.getCurrentId(), message); + } + } + } finally { - if (reader != null) - reader.close(); + if (smsReader != null) + smsReader.close(); + + if (pushReader != null) + pushReader.close(); } } diff --git a/src/org/thoughtcrime/securesms/database/PushDatabase.java b/src/org/thoughtcrime/securesms/database/PushDatabase.java index 0ca927423e..d004f0b9ca 100644 --- a/src/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/src/org/thoughtcrime/securesms/database/PushDatabase.java @@ -2,12 +2,16 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteOpenHelper; -import org.spongycastle.util.encoders.Base64; import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Util; +import java.io.IOException; +import java.util.List; + public class PushDatabase extends Database { private static final String TABLE_NAME = "push"; @@ -30,13 +34,55 @@ public class PushDatabase extends Database { values.put(TYPE, message.getType()); values.put(SOURCE, message.getSource()); values.put(DESTINATIONS, Util.join(message.getDestinations(), ",")); - values.put(BODY, Base64.encode(message.getBody())); + values.put(BODY, Base64.encodeBytes(message.getBody())); values.put(TIMESTAMP, message.getTimestampMillis()); return databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, values); } + public Cursor getPending() { + return databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null); + } + public void delete(long id) { databaseHelper.getWritableDatabase().delete(TABLE_NAME, ID_WHERE, new String[] {id+""}); } + + public Reader readerFor(Cursor cursor) { + return new Reader(cursor); + } + + public static class Reader { + private final Cursor cursor; + + public Reader(Cursor cursor) { + this.cursor = cursor; + } + + public IncomingPushMessage getNext() { + try { + if (cursor == null || !cursor.moveToNext()) + return null; + + int type = cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)); + String source = cursor.getString(cursor.getColumnIndexOrThrow(SOURCE)); + List destinations = Util.split(cursor.getString(cursor.getColumnIndexOrThrow(DESTINATIONS)), ","); + byte[] body = Base64.decode(cursor.getString(cursor.getColumnIndexOrThrow(BODY))); + long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)); + + return new IncomingPushMessage(type, source, destinations, body, timestamp); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + public long getCurrentId() { + return cursor.getLong(cursor.getColumnIndexOrThrow(ID)); + } + + public void close() { + this.cursor.close(); + } + } + } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index a186ae83ff..7b060ee99d 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -39,6 +39,10 @@ import android.util.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.RoutingActivity; +import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; +import org.thoughtcrime.securesms.database.PushDatabase; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsSmsDatabase; @@ -46,6 +50,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.push.IncomingPushMessage; import java.io.IOException; import java.util.List; @@ -116,18 +121,24 @@ public class MessageNotifier { } private static void updateNotification(Context context, MasterSecret masterSecret, boolean signal) { - Cursor cursor = null; + Cursor telcoCursor = null; + Cursor pushCursor = null; try { - cursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread(); + telcoCursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread(); + pushCursor = DatabaseFactory.getPushDatabase(context).getPending(); - if (cursor == null || cursor.isAfterLast()) { + if ((telcoCursor == null || telcoCursor.isAfterLast()) && + (pushCursor == null || pushCursor.isAfterLast())) + { ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)) .cancel(NOTIFICATION_ID); return; } - NotificationState notificationState = constructNotificationState(context, masterSecret, cursor); + NotificationState notificationState = constructNotificationState(context, masterSecret, telcoCursor); + + appendPushNotificationState(context, masterSecret, notificationState, pushCursor); if (notificationState.hasMultipleThreads()) { sendMultipleThreadNotification(context, masterSecret, notificationState, signal); @@ -135,8 +146,8 @@ public class MessageNotifier { sendSingleThreadNotification(context, masterSecret, notificationState, signal); } } finally { - if (cursor != null) - cursor.close(); + if (telcoCursor != null) telcoCursor.close(); + if (pushCursor != null) pushCursor.close(); } } @@ -145,6 +156,12 @@ public class MessageNotifier { NotificationState notificationState, boolean signal) { + if (notificationState.getNotifications().isEmpty()) { + ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)) + .cancel(NOTIFICATION_ID); + return; + } + Listnotifications = notificationState.getNotifications(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context); Recipient recipient = notifications.get(0).getIndividualRecipient(); @@ -261,6 +278,42 @@ public class MessageNotifier { } } + private static void appendPushNotificationState(Context context, + MasterSecret masterSecret, + NotificationState notificationState, + Cursor cursor) + { + if (masterSecret != null) return; + + PushDatabase.Reader reader = null; + IncomingPushMessage message; + + try { + reader = DatabaseFactory.getPushDatabase(context).readerFor(cursor); + + while ((message = reader.getNext()) != null) { + Recipient recipient; + + try { + recipient = RecipientFactory.getRecipientsFromString(context, message.getSource(), false).getPrimaryRecipient(); + } catch (RecipientFormattingException e) { + Log.w("MessageNotifier", e); + recipient = new Recipient("Unknown", "Unknown", null, ContactPhotoFactory.getDefaultContactPhoto(context)); + } + + Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_encrypted_message)); + body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + notificationState.addNotification(new NotificationItem(recipient, recipients, threadId, body, null)); + } + } finally { + if (reader != null) + reader.close(); + } + } + private static NotificationState constructNotificationState(Context context, MasterSecret masterSecret, Cursor cursor) diff --git a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java index 03505ee68f..44740f96d7 100644 --- a/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java +++ b/src/org/thoughtcrime/securesms/recipients/RecipientFactory.java @@ -17,13 +17,18 @@ package org.thoughtcrime.securesms.recipients; import android.content.Context; +import android.util.Log; import org.thoughtcrime.securesms.contacts.ContactPhotoFactory; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.util.NumberUtil; +import org.whispersystems.textsecure.push.IncomingPushMessage; +import org.whispersystems.textsecure.util.Util; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.StringTokenizer; public class RecipientFactory { @@ -70,6 +75,23 @@ public class RecipientFactory { return new Recipients(results); } + public static Recipients getRecipientsFromMessage(Context context, + IncomingPushMessage message, + boolean asynchronous) + { + Set recipients = new HashSet(); + recipients.add(message.getSource()); + recipients.addAll(message.getDestinations()); + + try { + return getRecipientsFromString(context, Util.join(recipients, ","), asynchronous); + } catch (RecipientFormattingException e) { + Log.w("RecipientFactory", e); + return new Recipients(new Recipient("Unknown", "Unknown", null, + ContactPhotoFactory.getDefaultContactPhoto(context))); + } + } + private static Recipient getRecipientFromProviderId(Context context, String recipientId, boolean asynchronous) { String number = DatabaseFactory.getAddressDatabase(context).getAddressFromId(recipientId); return getRecipientForNumber(context, number, asynchronous); diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java index 4750615d20..ce7fc3d9b9 100644 --- a/src/org/thoughtcrime/securesms/service/PushReceiver.java +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -12,7 +12,10 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.mms.IncomingMediaMessage; +import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientFactory; +import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -69,10 +72,22 @@ public class PushReceiver { private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) { long id = DatabaseFactory.getPushDatabase(context).insert(message); - DecryptingQueue.scheduleDecryption(context, masterSecret, id, message); + + if (masterSecret != null) { + DecryptingQueue.scheduleDecryption(context, masterSecret, id, message); + } else { + Recipients recipients = RecipientFactory.getRecipientsFromMessage(context, message, false); + long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); + MessageNotifier.updateNotification(context, masterSecret, threadId); + } } private void handleReceivedPreKeyBundle(MasterSecret masterSecret, IncomingPushMessage message) { + if (masterSecret == null) { + handleReceivedSecureMessage(masterSecret, message); + return; + } + try { Recipient recipient = new Recipient(null, message.getSource(), null, null); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); @@ -105,7 +120,7 @@ public class PushReceiver { try { PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody()); - if (messageContent.getAttachmentsCount() > 0) { + if (messageContent.getAttachmentsCount() > 0 || message.getDestinations().size() > 0) { Log.w("PushReceiver", "Received push media message..."); handleReceivedMediaMessage(masterSecret, message, messageContent, secure); } else { @@ -143,6 +158,7 @@ public class PushReceiver { intent.putExtra("message_id", messageAndThreadId.first); context.startService(intent); + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } catch (MmsException e) { Log.w("PushReceiver", e); // XXX @@ -163,14 +179,18 @@ public class PushReceiver { Pair messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage); database.updateMessageBody(masterSecret, messageAndThreadId.first, messageContent.getBody()); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } private void handleReceivedCorruptedMessage(MasterSecret masterSecret, IncomingPushMessage message, boolean secure) { - long messageId = insertMessagePlaceholder(masterSecret, message, secure); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageId); + Pair messageAndThreadId = insertMessagePlaceholder(masterSecret, message, secure); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsDecryptFailed(messageAndThreadId.first); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } private void handleReceivedCorruptedKey(MasterSecret masterSecret, @@ -183,17 +203,22 @@ public class PushReceiver { if (!invalidVersion) corruptedKeyMessage.setCorrupted(true); else corruptedKeyMessage.setInvalidVersion(true); - DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageInbox(masterSecret, corruptedKeyMessage); + Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) + .insertMessageInbox(masterSecret, + corruptedKeyMessage); + + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } private void handleReceivedMessageForNoSession(MasterSecret masterSecret, IncomingPushMessage message) { - long messageId = insertMessagePlaceholder(masterSecret, message, true); - DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageId); + Pair messageAndThreadId = insertMessagePlaceholder(masterSecret, message, true); + DatabaseFactory.getEncryptingSmsDatabase(context).markAsNoSession(messageAndThreadId.first); + MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); } - private long insertMessagePlaceholder(MasterSecret masterSecret, + private Pair insertMessagePlaceholder(MasterSecret masterSecret, IncomingPushMessage message, boolean secure) { @@ -206,6 +231,6 @@ public class PushReceiver { Pair messageAndThreadId = DatabaseFactory.getEncryptingSmsDatabase(context) .insertMessageInbox(masterSecret, placeholder); - return messageAndThreadId.first; + return messageAndThreadId; } } diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index 418b160989..528da2a4ca 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -33,7 +33,6 @@ import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; -import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; import org.thoughtcrime.securesms.sms.SmsTransportDetails; @@ -42,7 +41,6 @@ import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; -import org.whispersystems.textsecure.push.PushMessage; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import java.io.IOException; @@ -58,23 +56,6 @@ public class SmsReceiver { this.context = context; } - private IncomingTextMessage assembleMessageFragments(List messages, int pushType) { - if (messages.size() != 1) return assembleMessageFragments(messages); - - IncomingTextMessage message = messages.get(0); - - switch (pushType) { - case PushMessage.TYPE_MESSAGE_CIPHERTEXT: - return new IncomingEncryptedMessage(message, message.getMessageBody()); - case PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE: - return new IncomingPreKeyBundleMessage(message, message.getMessageBody()); - case PushMessage.TYPE_MESSAGE_KEY_EXCHANGE: - return new IncomingKeyExchangeMessage(message, message.getMessageBody()); - } - - return message; - } - private IncomingTextMessage assembleMessageFragments(List messages) { IncomingTextMessage message = new IncomingTextMessage(messages); @@ -201,12 +182,7 @@ public class SmsReceiver { private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) { List messagesList = intent.getExtras().getParcelableArrayList("text_messages"); - int pushType = intent.getIntExtra("push_type", -1); - - IncomingTextMessage message; - - if (pushType != -1) message = assembleMessageFragments(messagesList, pushType); - else message = assembleMessageFragments(messagesList); + IncomingTextMessage message = assembleMessageFragments(messagesList); if (message != null) { Pair messageAndThreadId = storeMessage(masterSecret, message); diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index 8fbb04e7d4..ffec138cd4 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -13,12 +13,9 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; -import org.whispersystems.textsecure.crypto.AttachmentCipher; -import org.whispersystems.textsecure.push.PushAttachmentPointer; -import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; -import org.whispersystems.textsecure.push.RawTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; +import org.whispersystems.textsecure.crypto.AttachmentCipher; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.KeyUtil; @@ -28,10 +25,12 @@ import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.push.OutgoingPushMessage; import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PushAttachmentData; +import org.whispersystems.textsecure.push.PushAttachmentPointer; +import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushTransportDetails; import org.whispersystems.textsecure.push.RateLimitException; -import org.whispersystems.textsecure.util.Hex; +import org.whispersystems.textsecure.push.RawTransportDetails; import org.whispersystems.textsecure.util.PhoneNumberFormatter; import java.io.IOException;