Support for dual-sim SMS/MMS functionality

Allow source selection for sending SMS/MMS, and display the
SIM that received SMS/MMS.

Fixes #555
Closes #5199
// FREEBIE
This commit is contained in:
Moxie Marlinspike 2016-02-05 16:10:33 -08:00
parent c1106d98dd
commit 6da86e482d
53 changed files with 727 additions and 281 deletions

View File

@ -113,6 +113,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:layout_gravity="center_vertical"
android:paddingTop="2dp" android:paddingTop="2dp"
android:paddingRight="4dp"
android:paddingEnd="4dp"
android:src="?menu_lock_icon_small" android:src="?menu_lock_icon_small"
android:contentDescription="@string/conversation_item__secure_message_description" android:contentDescription="@string/conversation_item__secure_message_description"
android:visibility="gone" android:visibility="gone"
@ -126,6 +128,19 @@
android:visibility="gone"/> android:visibility="gone"/>
<TextView android:id="@+id/conversation_item_date" <TextView android:id="@+id/conversation_item_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:paddingTop="1dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?conversation_item_received_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:fontFamily="sans-serif-light"
android:autoLink="none"
android:linksClickable="false"
tools:text="Now"/>
<TextView android:id="@+id/sim_info"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="left" android:layout_gravity="left"
@ -137,7 +152,10 @@
android:textSize="@dimen/conversation_item_date_text_size" android:textSize="@dimen/conversation_item_date_text_size"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:autoLink="none" android:autoLink="none"
android:linksClickable="false" /> android:linksClickable="false"
android:visibility="gone"
tools:visibility="visible"
tools:text="from SIM1"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -125,6 +125,25 @@
android:paddingBottom="2dp" android:paddingBottom="2dp"
tools:text="30 mins" /> tools:text="30 mins" />
<TextView android:id="@+id/sim_info"
android:autoLink="none"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="15sp"
android:linksClickable="false"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_gravity="right"
android:fontFamily="sans-serif-light"
android:textColor="?conversation_item_sent_text_secondary_color"
android:textSize="@dimen/conversation_item_date_text_size"
android:paddingTop="1dip"
android:paddingBottom="2dp"
android:paddingLeft="4dp"
android:paddingStart="4dp"
android:visibility="gone"
tools:visibility="visible"
tools:text="to SIM1"/>
<org.thoughtcrime.securesms.components.DeliveryStatusView <org.thoughtcrime.securesms.components.DeliveryStatusView
android:id="@+id/delivery_status" android:id="@+id/delivery_status"
android:layout_width="20dp" android:layout_width="20dp"

View File

@ -8,6 +8,7 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingLeft="10dp" android:paddingLeft="10dp"
android:paddingRight="10dp"> android:paddingRight="10dp">
<ImageView android:id="@+id/icon" <ImageView android:id="@+id/icon"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -16,11 +17,28 @@
android:contentDescription="@string/transport_selection_list_item__transport_icon" android:contentDescription="@string/transport_selection_list_item__transport_icon"
tools:src="@drawable/ic_send_push_white_24dp" tools:src="@drawable/ic_send_push_white_24dp"
tools:backgroundTint="@color/textsecure_primary" /> tools:backgroundTint="@color/textsecure_primary" />
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingLeft="10dp">
<TextView android:id="@+id/text" <TextView android:id="@+id/text"
android:paddingLeft="10dp"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:textSize="16sp" android:textSize="16sp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="TextSecure" /> tools:text="TextSecure" />
<TextView android:id="@+id/subtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textSize="12dp"
android:visibility="gone"
tools:text="From Home"
tools:visibility="visible"/>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -100,6 +100,8 @@
<string name="ConversationItem_click_to_approve_unencrypted_mms_dialog_title">Fallback to unencrypted MMS?</string> <string name="ConversationItem_click_to_approve_unencrypted_mms_dialog_title">Fallback to unencrypted MMS?</string>
<string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because the recipient is no longer a Signal user.\n\nSend unsecured message?</string> <string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because the recipient is no longer a Signal user.\n\nSend unsecured message?</string>
<string name="ConversationItem_unable_to_open_media">Can\'t find an app able to open this media.</string> <string name="ConversationItem_unable_to_open_media">Can\'t find an app able to open this media.</string>
<string name="ConversationItem_from_s">from %s</string>
<string name="ConversationItem_to_s">to %s</string>
<!-- ConversationActivity --> <!-- ConversationActivity -->
<string name="ConversationActivity_reset_secure_session_question">Reset secure session?</string> <string name="ConversationActivity_reset_secure_session_question">Reset secure session?</string>

View File

@ -535,7 +535,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
final Context context = getApplicationContext(); final Context context = getApplicationContext();
OutgoingEndSessionMessage endSessionMessage = OutgoingEndSessionMessage endSessionMessage =
new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE")); new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE", -1));
new AsyncTask<OutgoingEndSessionMessage, Void, Long>() { new AsyncTask<OutgoingEndSessionMessage, Void, Long>() {
@Override @Override
@ -823,22 +823,43 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void onSecurityUpdated() { private void onSecurityUpdated() {
updateInviteReminder(); updateRecipientPreferences();
} }
protected void updateInviteReminder() { private void updateRecipientPreferences() {
if (TextSecurePreferences.isPushRegistered(this) && if (recipients.getPrimaryRecipient() != null &&
!isSecureText &&
recipients.isSingleRecipient() &&
recipients.getPrimaryRecipient() != null &&
recipients.getPrimaryRecipient().getContactUri() != null) recipients.getPrimaryRecipient().getContactUri() != null)
{ {
new ShowInviteReminderTask().execute(recipients); new RecipientPreferencesTask().execute(recipients);
}
}
protected void updateInviteReminder(boolean seenInvite) {
Log.w(TAG, "updateInviteReminder(" + seenInvite+")");
if (TextSecurePreferences.isPushRegistered(this) &&
!isSecureText &&
!seenInvite &&
recipients.isSingleRecipient())
{
InviteReminder reminder = new InviteReminder(this, recipients);
reminder.setOkListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleInviteLink();
reminderView.requestDismiss();
}
});
reminderView.showReminder(reminder);
} else { } else {
reminderView.hide(); reminderView.hide();
} }
} }
private void updateDefaultSubscriptionId(Optional<Integer> defaultSubscriptionId) {
Log.w(TAG, "updateDefaultSubscriptionId(" + defaultSubscriptionId.orNull() + ")");
sendButton.setDefaultSubscriptionId(defaultSubscriptionId);
}
private void initializeMmsEnabledCheck() { private void initializeMmsEnabledCheck() {
new AsyncTask<Void, Void, Boolean>() { new AsyncTask<Void, Void, Boolean>() {
@Override @Override
@ -896,11 +917,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendButton.setEnabled(true); sendButton.setEnabled(true);
sendButton.addOnTransportChangedListener(new OnTransportChangedListener() { sendButton.addOnTransportChangedListener(new OnTransportChangedListener() {
@Override @Override
public void onChange(TransportOption newTransport) { public void onChange(TransportOption newTransport, boolean manuallySelected) {
calculateCharactersRemaining(); calculateCharactersRemaining();
composeText.setTransport(newTransport); composeText.setTransport(newTransport);
buttonToggle.getBackground().setColorFilter(newTransport.getBackgroundColor(), Mode.MULTIPLY); buttonToggle.getBackground().setColorFilter(newTransport.getBackgroundColor(), Mode.MULTIPLY);
buttonToggle.getBackground().invalidateSelf(); buttonToggle.getBackground().invalidateSelf();
if (manuallySelected) recordSubscriptionIdPreference(newTransport.getSimSubscriptionId());
} }
}); });
@ -968,7 +990,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
titleView.setTitle(recipients); titleView.setTitle(recipients);
setBlockedUserState(recipients); setBlockedUserState(recipients);
setActionBarColor(recipients.getColor()); setActionBarColor(recipients.getColor());
updateInviteReminder(); updateRecipientPreferences();
} }
}); });
} }
@ -1244,6 +1266,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
try { try {
Recipients recipients = getRecipients(); Recipients recipients = getRecipients();
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
Log.w(TAG, "isManual Selection: " + sendButton.isManualSelection()); Log.w(TAG, "isManual Selection: " + sendButton.isManualSelection());
Log.w(TAG, "forceSms: " + forceSms); Log.w(TAG, "forceSms: " + forceSms);
@ -1255,9 +1278,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) { if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
handleManualMmsRequired(); handleManualMmsRequired();
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) { } else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
sendMediaMessage(forceSms); sendMediaMessage(forceSms, subscriptionId);
} else { } else {
sendTextMessage(forceSms); sendTextMessage(forceSms, subscriptionId);
} }
} catch (RecipientFormattingException ex) { } catch (RecipientFormattingException ex) {
Toast.makeText(ConversationActivity.this, Toast.makeText(ConversationActivity.this,
@ -1271,13 +1294,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
} }
private void sendMediaMessage(final boolean forceSms) private void sendMediaMessage(final boolean forceSms, final int subscriptionId)
throws InvalidMessageException throws InvalidMessageException
{ {
sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck()); sendMediaMessage(forceSms, getMessage(), attachmentManager.buildSlideDeck(), subscriptionId);
} }
private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck) private ListenableFuture<Void> sendMediaMessage(final boolean forceSms, String body, SlideDeck slideDeck, final int subscriptionId)
throws InvalidMessageException throws InvalidMessageException
{ {
final SettableFuture<Void> future = new SettableFuture<>(); final SettableFuture<Void> future = new SettableFuture<>();
@ -1286,6 +1309,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
slideDeck, slideDeck,
body, body,
System.currentTimeMillis(), System.currentTimeMillis(),
subscriptionId,
distributionType); distributionType);
if (isSecureText && !forceSms) { if (isSecureText && !forceSms) {
@ -1311,7 +1335,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return future; return future;
} }
private void sendTextMessage(final boolean forceSms) private void sendTextMessage(final boolean forceSms, final int subscriptionId)
throws InvalidMessageException throws InvalidMessageException
{ {
final Context context = getApplicationContext(); final Context context = getApplicationContext();
@ -1320,7 +1344,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (isSecureText && !forceSms) { if (isSecureText && !forceSms) {
message = new OutgoingEncryptedMessage(recipients, getMessage()); message = new OutgoingEncryptedMessage(recipients, getMessage());
} else { } else {
message = new OutgoingTextMessage(recipients, getMessage()); message = new OutgoingTextMessage(recipients, getMessage(), subscriptionId);
} }
this.composeText.setText(""); this.composeText.setText("");
@ -1348,6 +1372,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
} }
private void recordSubscriptionIdPreference(final Optional<Integer> subscriptionId) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.setDefaultSubscriptionId(recipients, subscriptionId.or(-1));
return null;
}
}.execute();
}
@Override @Override
public void onAttachmentDrawerStateChanged(DrawerState drawerState) { public void onAttachmentDrawerStateChanged(DrawerState drawerState) {
if (drawerState == DrawerState.FULL_EXPANDED) { if (drawerState == DrawerState.FULL_EXPANDED) {
@ -1399,11 +1434,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public void onSuccess(final @NonNull Pair<Uri, Long> result) { public void onSuccess(final @NonNull Pair<Uri, Long> result) {
try { try {
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms(); boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, ContentType.AUDIO_AAC); AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, ContentType.AUDIO_AAC);
SlideDeck slideDeck = new SlideDeck(); SlideDeck slideDeck = new SlideDeck();
slideDeck.addSlide(audioSlide); slideDeck.addSlide(audioSlide);
sendMediaMessage(forceSms, "", slideDeck).addListener(new AssertedSuccessListener<Void>() { sendMediaMessage(forceSms, "", slideDeck, subscriptionId).addListener(new AssertedSuccessListener<Void>() {
@Override @Override
public void onSuccess(Void nothing) { public void onSuccess(Void nothing) {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@ -1568,30 +1604,23 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
updateToggleButtonState(); updateToggleButtonState();
} }
private class ShowInviteReminderTask extends AsyncTask<Recipients, Void, Pair<Recipients,Boolean>> { private class RecipientPreferencesTask extends AsyncTask<Recipients, Void, Pair<Recipients,RecipientsPreferences>> {
@Override @Override
protected Pair<Recipients, Boolean> doInBackground(Recipients... recipients) { protected Pair<Recipients, RecipientsPreferences> doInBackground(Recipients... recipients) {
if (recipients.length != 1 || recipients[0] == null) throw new AssertionError("task needs exactly one Recipients object"); if (recipients.length != 1 || recipients[0] == null) {
throw new AssertionError("task needs exactly one Recipients object");
}
Optional<RecipientsPreferences> prefs = DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this) Optional<RecipientsPreferences> prefs = DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
.getRecipientsPreferences(recipients[0].getIds()); .getRecipientsPreferences(recipients[0].getIds());
return new Pair<>(recipients[0], prefs.isPresent() && prefs.get().hasSeenInviteReminder()); return new Pair<>(recipients[0], prefs.orNull());
} }
@Override @Override
protected void onPostExecute(Pair<Recipients, Boolean> result) { protected void onPostExecute(@NonNull Pair<Recipients, RecipientsPreferences> result) {
if (!result.second && result.first == recipients) { if (result.first == recipients) {
InviteReminder reminder = new InviteReminder(ConversationActivity.this, result.first); updateInviteReminder(result.second != null && result.second.hasSeenInviteReminder());
reminder.setOkListener(new OnClickListener() { updateDefaultSubscriptionId(result.second != null ? result.second.getDefaultSubscriptionId() : Optional.<Integer>absent());
@Override
public void onClick(View v) {
handleInviteLink();
reminderView.requestDismiss();
}
});
reminderView.showReminder(reminder);
} else {
reminderView.hide();
} }
} }
} }

View File

@ -65,6 +65,9 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -93,6 +96,7 @@ public class ConversationItem extends LinearLayout
private View bodyBubble; private View bodyBubble;
private TextView bodyText; private TextView bodyText;
private TextView dateText; private TextView dateText;
private TextView simInfoText;
private TextView indicatorText; private TextView indicatorText;
private TextView groupStatusText; private TextView groupStatusText;
private ImageView secureImage; private ImageView secureImage;
@ -135,6 +139,7 @@ public class ConversationItem extends LinearLayout
this.bodyText = (TextView) findViewById(R.id.conversation_item_body); this.bodyText = (TextView) findViewById(R.id.conversation_item_body);
this.dateText = (TextView) findViewById(R.id.conversation_item_date); this.dateText = (TextView) findViewById(R.id.conversation_item_date);
this.simInfoText = (TextView) findViewById(R.id.sim_info);
this.indicatorText = (TextView) findViewById(R.id.indicator_text); this.indicatorText = (TextView) findViewById(R.id.indicator_text);
this.groupStatusText = (TextView) findViewById(R.id.group_message_status); this.groupStatusText = (TextView) findViewById(R.id.group_message_status);
this.secureImage = (ImageView) findViewById(R.id.secure_indicator); this.secureImage = (ImageView) findViewById(R.id.secure_indicator);
@ -188,6 +193,7 @@ public class ConversationItem extends LinearLayout
setGroupMessageStatus(messageRecord, recipient); setGroupMessageStatus(messageRecord, recipient);
setMinimumWidth(); setMinimumWidth();
setMediaAttributes(messageRecord); setMediaAttributes(messageRecord);
setSimInfo(messageRecord);
} }
private void initializeAttributes() { private void initializeAttributes() {
@ -327,6 +333,25 @@ public class ConversationItem extends LinearLayout
} }
} }
private void setSimInfo(MessageRecord messageRecord) {
if (messageRecord.getSubscriptionId() == -1) {
simInfoText.setVisibility(View.GONE);
} else {
SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context);
Optional<SubscriptionInfoCompat> subscriptionInfo = subscriptionManager.getActiveSubscriptionInfo(messageRecord.getSubscriptionId());
if (subscriptionInfo.isPresent() && messageRecord.isOutgoing()) {
simInfoText.setText(getContext().getString(R.string.ConversationItem_from_s, subscriptionInfo.get().getDisplayName()));
simInfoText.setVisibility(View.VISIBLE);
} else if (subscriptionInfo.isPresent()) {
simInfoText.setText(getContext().getString(R.string.ConversationItem_to_s, subscriptionInfo.get().getDisplayName()));
simInfoText.setVisibility(View.VISIBLE);
} else {
simInfoText.setVisibility(View.GONE);
}
}
}
private void setFailedStatusIcons() { private void setFailedStatusIcons() {
alertView.setFailed(); alertView.setFailed();
deliveryStatusIndicator.setNone(); deliveryStatusIndicator.setNone();

View File

@ -121,7 +121,7 @@ public class ConversationPopupActivity extends ConversationActivity {
} }
@Override @Override
protected void updateInviteReminder() { protected void updateInviteReminder(boolean seenInvite) {
reminderView.setVisibility(View.GONE); reminderView.setVisibility(View.GONE);
} }
} }

View File

@ -28,13 +28,15 @@ import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener; import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -227,9 +229,14 @@ public class InviteActivity extends PassphraseRequiredActionBarActivity implemen
if (context == null) return null; if (context == null) return null;
for (String number : numbers) { for (String number : numbers) {
final Recipients recipients = RecipientFactory.getRecipientsFromString(context, number, false); Recipients recipients = RecipientFactory.getRecipientsFromString(context, number, false);
if (recipients != null && recipients.getPrimaryRecipient() != null) {
MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipients, message), -1L, true); if (recipients.getPrimaryRecipient() != null) {
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipients.getIds());
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
MessageSender.send(context, masterSecret, new OutgoingTextMessage(recipients, message, subscriptionId), -1L, true);
if (recipients.getPrimaryRecipient().getContactUri() != null) { if (recipients.getPrimaryRecipient().getContactUri() != null) {
DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true); DatabaseFactory.getRecipientPreferenceDatabase(context).setSeenInviteReminder(recipients, true);
} }

View File

@ -1,9 +1,11 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.support.annotation.DrawableRes; import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.util.CharacterCalculator; import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState; import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.whispersystems.libaxolotl.util.guava.Optional;
public class TransportOption { public class TransportOption {
@ -12,19 +14,34 @@ public class TransportOption {
TEXTSECURE TEXTSECURE
} }
private int drawable; private final int drawable;
private int backgroundColor; private final int backgroundColor;
private String text; private final @NonNull String text;
private Type type; private final @NonNull Type type;
private String composeHint; private final @NonNull String composeHint;
private CharacterCalculator characterCalculator; private final @NonNull CharacterCalculator characterCalculator;
private final @NonNull Optional<CharSequence> simName;
private final @NonNull Optional<Integer> simSubscriptionId;
public TransportOption(Type type, public TransportOption(@NonNull Type type,
@DrawableRes int drawable, @DrawableRes int drawable,
int backgroundColor, int backgroundColor,
String text, @NonNull String text,
String composeHint, @NonNull String composeHint,
CharacterCalculator characterCalculator) @NonNull CharacterCalculator characterCalculator)
{
this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
Optional.<CharSequence>absent(), Optional.<Integer>absent());
}
public TransportOption(@NonNull Type type,
@DrawableRes int drawable,
int backgroundColor,
@NonNull String text,
@NonNull String composeHint,
@NonNull CharacterCalculator characterCalculator,
@NonNull Optional<CharSequence> simName,
@NonNull Optional<Integer> simSubscriptionId)
{ {
this.type = type; this.type = type;
this.drawable = drawable; this.drawable = drawable;
@ -32,9 +49,12 @@ public class TransportOption {
this.text = text; this.text = text;
this.composeHint = composeHint; this.composeHint = composeHint;
this.characterCalculator = characterCalculator; this.characterCalculator = characterCalculator;
this.simName = simName;
this.simSubscriptionId = simSubscriptionId;
} }
public Type getType() {
public @NonNull Type getType() {
return type; return type;
} }
@ -58,11 +78,22 @@ public class TransportOption {
return backgroundColor; return backgroundColor;
} }
public String getComposeHint() { public @NonNull String getComposeHint() {
return composeHint; return composeHint;
} }
public String getDescription() { public @NonNull String getDescription() {
return text; return text;
} }
@NonNull
public Optional<CharSequence> getSimName() {
return simName;
}
@NonNull
public Optional<Integer> getSimSubscriptionId() {
return simSubscriptionId;
}
} }

View File

@ -1,10 +1,15 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.util.CharacterCalculator;
import org.thoughtcrime.securesms.util.MmsCharacterCalculator; import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
import org.thoughtcrime.securesms.util.PushCharacterCalculator; import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.SmsCharacterCalculator; import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionInfoCompat;
import org.thoughtcrime.securesms.util.dualsim.SubscriptionManagerCompat;
import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.LinkedList; import java.util.LinkedList;
@ -20,49 +25,76 @@ public class TransportOptions {
private final Context context; private final Context context;
private final List<TransportOption> enabledTransports; private final List<TransportOption> enabledTransports;
private Type selectedType; private Type defaultTransportType = Type.SMS;
private boolean manuallySelected; private Optional<Integer> defaultSubscriptionId = Optional.absent();
private Optional<TransportOption> selectedOption = Optional.absent();
public TransportOptions(Context context, boolean media) { public TransportOptions(Context context, boolean media) {
this.context = context; this.context = context;
this.enabledTransports = initializeAvailableTransports(media); this.enabledTransports = initializeAvailableTransports(media);
setDefaultTransport(Type.SMS);
} }
public void reset(boolean media) { public void reset(boolean media) {
List<TransportOption> transportOptions = initializeAvailableTransports(media); List<TransportOption> transportOptions = initializeAvailableTransports(media);
this.enabledTransports.clear(); this.enabledTransports.clear();
this.enabledTransports.addAll(transportOptions); this.enabledTransports.addAll(transportOptions);
if (!find(selectedType).isPresent()) { if (selectedOption.isPresent() && !isEnabled(selectedOption.get())) {
this.manuallySelected = false; setSelectedTransport(null);
setTransport(Type.SMS);
} else { } else {
this.defaultTransportType = Type.SMS;
this.defaultSubscriptionId = Optional.absent();
notifyTransportChangeListeners(); notifyTransportChangeListeners();
} }
} }
public void setDefaultTransport(Type type) { public void setDefaultTransport(Type type) {
if (!this.manuallySelected) { this.defaultTransportType = type;
setTransport(type);
if (!selectedOption.isPresent()) {
notifyTransportChangeListeners();
} }
} }
public void setSelectedTransport(Type type) { public void setDefaultSubscriptionId(Optional<Integer> subscriptionId) {
this.manuallySelected= true; this.defaultSubscriptionId = subscriptionId;
setTransport(type);
if (!selectedOption.isPresent()) {
notifyTransportChangeListeners();
}
}
public void setSelectedTransport(@Nullable TransportOption transportOption) {
this.selectedOption = Optional.fromNullable(transportOption);
notifyTransportChangeListeners();
} }
public boolean isManualSelection() { public boolean isManualSelection() {
return manuallySelected; return this.selectedOption.isPresent();
} }
public TransportOption getSelectedTransport() { public @NonNull TransportOption getSelectedTransport() {
Optional<TransportOption> option = find(selectedType); if (selectedOption.isPresent()) return selectedOption.get();
if (option.isPresent()) return option.get(); if (defaultSubscriptionId.isPresent()) {
else throw new AssertionError("Selected type isn't present!"); for (TransportOption transportOption : enabledTransports) {
if (transportOption.getType() == defaultTransportType &&
(int)defaultSubscriptionId.get() == transportOption.getSimSubscriptionId().or(-1))
{
return transportOption;
}
}
}
for (TransportOption transportOption : enabledTransports) {
if (transportOption.getType() == defaultTransportType) {
return transportOption;
}
}
throw new AssertionError("No options of default type!");
} }
public void disableTransport(Type type) { public void disableTransport(Type type) {
@ -71,8 +103,8 @@ public class TransportOptions {
if (option.isPresent()) { if (option.isPresent()) {
enabledTransports.remove(option.get()); enabledTransports.remove(option.get());
if (manuallySelected && type == selectedType) { if (selectedOption.isPresent() && selectedOption.get().getType() == type) {
manuallySelected = false; setSelectedTransport(null);
} }
} }
} }
@ -89,15 +121,11 @@ public class TransportOptions {
List<TransportOption> results = new LinkedList<>(); List<TransportOption> results = new LinkedList<>();
if (isMediaMessage) { if (isMediaMessage) {
results.add(new TransportOption(Type.SMS, R.drawable.ic_send_sms_white_24dp, results.addAll(getTransportOptionsForSimCards(context.getString(R.string.ConversationActivity_transport_insecure_mms),
context.getResources().getColor(R.color.grey_600),
context.getString(R.string.ConversationActivity_transport_insecure_mms),
context.getString(R.string.conversation_activity__type_message_mms_insecure), context.getString(R.string.conversation_activity__type_message_mms_insecure),
new MmsCharacterCalculator())); new MmsCharacterCalculator()));
} else { } else {
results.add(new TransportOption(Type.SMS, R.drawable.ic_send_sms_white_24dp, results.addAll(getTransportOptionsForSimCards(context.getString(R.string.ConversationActivity_transport_insecure_sms),
context.getResources().getColor(R.color.grey_600),
context.getString(R.string.ConversationActivity_transport_insecure_sms),
context.getString(R.string.conversation_activity__type_message_sms_insecure), context.getString(R.string.conversation_activity__type_message_sms_insecure),
new SmsCharacterCalculator())); new SmsCharacterCalculator()));
} }
@ -111,16 +139,34 @@ public class TransportOptions {
return results; return results;
} }
private @NonNull List<TransportOption> getTransportOptionsForSimCards(@NonNull String text,
@NonNull String composeHint,
@NonNull CharacterCalculator characterCalculator)
{
List<TransportOption> results = new LinkedList<>();
SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context);
List<SubscriptionInfoCompat> subscriptions = subscriptionManager.getActiveSubscriptionInfoList();
private void setTransport(Type type) { if (subscriptions.size() < 2) {
this.selectedType = type; results.add(new TransportOption(Type.SMS, R.drawable.ic_send_sms_white_24dp,
context.getResources().getColor(R.color.grey_600),
text, composeHint, characterCalculator));
} else {
for (SubscriptionInfoCompat subscriptionInfo : subscriptions) {
results.add(new TransportOption(Type.SMS, R.drawable.ic_send_sms_white_24dp,
context.getResources().getColor(R.color.grey_600),
text, composeHint, characterCalculator,
Optional.of(subscriptionInfo.getDisplayName()),
Optional.of(subscriptionInfo.getSubscriptionId())));
}
}
notifyTransportChangeListeners(); return results;
} }
private void notifyTransportChangeListeners() { private void notifyTransportChangeListeners() {
for (OnTransportChangedListener listener : listeners) { for (OnTransportChangedListener listener : listeners) {
listener.onChange(getSelectedTransport()); listener.onChange(getSelectedTransport(), selectedOption.isPresent());
} }
} }
@ -134,7 +180,15 @@ public class TransportOptions {
return Optional.absent(); return Optional.absent();
} }
private boolean isEnabled(TransportOption transportOption) {
for (TransportOption option : enabledTransports) {
if (option.equals(transportOption)) return true;
}
return false;
}
public interface OnTransportChangedListener { public interface OnTransportChangedListener {
public void onChange(TransportOption newTransport); public void onChange(TransportOption newTransport, boolean manuallySelected);
} }
} }

View File

@ -10,6 +10,8 @@ import android.widget.BaseAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.List; import java.util.List;
public class TransportOptionsAdapter extends BaseAdapter { public class TransportOptionsAdapter extends BaseAdapter {
@ -52,13 +54,21 @@ public class TransportOptionsAdapter extends BaseAdapter {
} }
TransportOption transport = (TransportOption) getItem(position); TransportOption transport = (TransportOption) getItem(position);
ImageView imageView = (ImageView) convertView.findViewById(R.id.icon); ImageView imageView = ViewUtil.findById(convertView, R.id.icon);
TextView textView = (TextView) convertView.findViewById(R.id.text); TextView textView = ViewUtil.findById(convertView, R.id.text);
TextView subtextView = ViewUtil.findById(convertView, R.id.subtext);
imageView.getBackground().setColorFilter(transport.getBackgroundColor(), Mode.MULTIPLY); imageView.getBackground().setColorFilter(transport.getBackgroundColor(), Mode.MULTIPLY);
imageView.setImageResource(transport.getDrawable()); imageView.setImageResource(transport.getDrawable());
textView.setText(transport.getDescription()); textView.setText(transport.getDescription());
if (transport.getSimName().isPresent()) {
subtextView.setText(transport.getSimName().get());
subtextView.setVisibility(View.VISIBLE);
} else {
subtextView.setVisibility(View.GONE);
}
return convertView; return convertView;
} }
} }

View File

@ -3,9 +3,11 @@ package org.thoughtcrime.securesms.components;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.InputType; import android.text.InputType;
import android.text.Spannable; import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextUtils.TruncateAt; import android.text.TextUtils.TruncateAt;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
@ -17,7 +19,9 @@ import org.thoughtcrime.securesms.components.emoji.EmojiEditText;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class ComposeText extends EmojiEditText { public class ComposeText extends EmojiEditText {
private SpannableString hint; private SpannableString hint;
private SpannableString subHint;
public ComposeText(Context context) { public ComposeText(Context context) {
super(context); super(context);
@ -31,12 +35,20 @@ public class ComposeText extends EmojiEditText {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom); super.onLayout(changed, left, top, right, bottom);
if (!TextUtils.isEmpty(hint)) { if (!TextUtils.isEmpty(hint)) {
if (!TextUtils.isEmpty(subHint)) {
setHint(new SpannableStringBuilder().append(ellipsizeToWidth(hint))
.append("\n")
.append(ellipsizeToWidth(subHint)));
} else {
setHint(ellipsizeToWidth(hint)); setHint(ellipsizeToWidth(hint));
} }
} }
}
private CharSequence ellipsizeToWidth(CharSequence text) { private CharSequence ellipsizeToWidth(CharSequence text) {
return TextUtils.ellipsize(text, return TextUtils.ellipsize(text,
@ -45,11 +57,25 @@ public class ComposeText extends EmojiEditText {
TruncateAt.END); TruncateAt.END);
} }
public void setHint(@NonNull String hint) { public void setHint(@NonNull String hint, @Nullable CharSequence subHint) {
this.hint = new SpannableString(hint); this.hint = new SpannableString(hint);
this.hint.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); this.hint.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
if (subHint != null) {
this.subHint = new SpannableString(subHint);
this.subHint.setSpan(new RelativeSizeSpan(0.8f), 0, subHint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
} else {
this.subHint = null;
}
if (this.subHint != null) {
super.setHint(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint))
.append("\n")
.append(ellipsizeToWidth(this.subHint)));
} else {
super.setHint(ellipsizeToWidth(this.hint)); super.setHint(ellipsizeToWidth(this.hint));
} }
}
public void appendInvite(String invite) { public void appendInvite(String invite) {
if (!TextUtils.isEmpty(getText()) && !getText().toString().equals(" ")) { if (!TextUtils.isEmpty(getText()) && !getText().toString().equals(" ")) {
@ -88,6 +114,6 @@ public class ComposeText extends EmojiEditText {
setInputType(inputType); setInputType(inputType);
setImeOptions(imeOptions); setImeOptions(imeOptions);
setHint(transport.getComposeHint()); setHint(transport.getComposeHint(), transport.getSimName().isPresent() ? "From " + transport.getSimName().get() : null);
} }
} }

View File

@ -83,14 +83,18 @@ public class SendButton extends ImageButton
transportOptions.setDefaultTransport(type); transportOptions.setDefaultTransport(type);
} }
public void setDefaultSubscriptionId(Optional<Integer> subscriptionId) {
transportOptions.setDefaultSubscriptionId(subscriptionId);
}
@Override @Override
public void onSelected(TransportOption option) { public void onSelected(TransportOption option) {
transportOptions.setSelectedTransport(option.getType()); transportOptions.setSelectedTransport(option);
getTransportOptionsPopup().dismiss(); getTransportOptionsPopup().dismiss();
} }
@Override @Override
public void onChange(TransportOption newTransport) { public void onChange(TransportOption newTransport, boolean isManualSelection) {
setImageResource(newTransport.getDrawable()); setImageResource(newTransport.getDrawable());
setContentDescription(newTransport.getDescription()); setContentDescription(newTransport.getDescription());
} }

View File

@ -71,7 +71,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_ARCHIVE_VERSION = 24; private static final int INTRODUCED_ARCHIVE_VERSION = 24;
private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25;
private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26; private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26;
private static final int DATABASE_VERSION = 26; private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 27;
private static final int DATABASE_VERSION = 27;
private static final String DATABASE_NAME = "messages.db"; private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object(); private static final Object lock = new Object();
@ -813,6 +814,12 @@ public class DatabaseFactory {
} }
} }
if (oldVersion < INTRODUCED_SUBSCRIPTION_ID_VERSION) {
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN default_subscription_id INTEGER DEFAULT -1");
db.execSQL("ALTER TABLE sms ADD COLUMN subscription_id INTEGER DEFAULT -1");
db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
} }

View File

@ -20,27 +20,24 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret; import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.database.model.DisplayRecord; import org.thoughtcrime.securesms.database.model.DisplayRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.LRUCache;
import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.InvalidMessageException;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
public class EncryptingSmsDatabase extends SmsDatabase { public class EncryptingSmsDatabase extends SmsDatabase {

View File

@ -24,7 +24,6 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.telephony.TelephonyManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -110,7 +109,8 @@ public class MmsDatabase extends MessagingDatabase {
"retr_txt" + " TEXT, " + "retr_txt_cs" + " INTEGER, " + "read_status" + " INTEGER, " + "retr_txt" + " TEXT, " + "retr_txt_cs" + " INTEGER, " + "read_status" + " INTEGER, " +
"ct_cls" + " INTEGER, " + "resp_txt" + " TEXT, " + "d_tm" + " INTEGER, " + "ct_cls" + " INTEGER, " + "resp_txt" + " TEXT, " + "d_tm" + " INTEGER, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + RECEIPT_COUNT + " INTEGER DEFAULT 0, " + MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " +
NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER);"; NETWORK_FAILURE + " TEXT DEFAULT NULL," + "d_rpt" + " INTEGER, " +
SUBSCRIPTION_ID + " INTEGER DEFAULT -1);";
public static final String[] CREATE_INDEXS = { 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_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -129,7 +129,7 @@ public class MmsDatabase extends MessagingDatabase {
CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE, CONTENT_LOCATION, EXPIRY, MESSAGE_TYPE,
MESSAGE_SIZE, STATUS, TRANSACTION_ID, MESSAGE_SIZE, STATUS, TRANSACTION_ID,
BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, RECEIPT_COUNT, MISMATCHED_IDENTITIES, NETWORK_FAILURE, SUBSCRIPTION_ID,
AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS, AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + " AS " + AttachmentDatabase.ATTACHMENT_ID_ALIAS,
AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.UNIQUE_ID,
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
@ -478,7 +478,7 @@ public class MmsDatabase extends MessagingDatabase {
return new Pair<>(messageId, threadId); return new Pair<>(messageId, threadId);
} }
public Optional<NotificationInd> getNotification(long messageId) { public Optional<Pair<NotificationInd, Integer>> getNotification(long messageId) {
Cursor cursor = null; Cursor cursor = null;
try { try {
@ -493,7 +493,8 @@ public class MmsDatabase extends MessagingDatabase {
builder.addLong(MESSAGE_SIZE, PduHeaders.MESSAGE_SIZE); builder.addLong(MESSAGE_SIZE, PduHeaders.MESSAGE_SIZE);
builder.addText(TRANSACTION_ID, PduHeaders.TRANSACTION_ID); builder.addText(TRANSACTION_ID, PduHeaders.TRANSACTION_ID);
return Optional.of(new NotificationInd(headers)); return Optional.of(new Pair<>(new NotificationInd(headers),
cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID))));
} else { } else {
return Optional.absent(); return Optional.absent();
} }
@ -517,6 +518,7 @@ public class MmsDatabase extends MessagingDatabase {
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX)); long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY)); String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT)); long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(messageId)); List<Attachment> attachments = new LinkedList<Attachment>(attachmentDatabase.getAttachmentsForMessage(messageId));
MmsAddresses addresses = addr.getAddressesForId(messageId); MmsAddresses addresses = addr.getAddressesForId(messageId);
List<String> destinations = new LinkedList<>(); List<String> destinations = new LinkedList<>();
@ -532,7 +534,7 @@ public class MmsDatabase extends MessagingDatabase {
return new OutgoingGroupMediaMessage(recipients, body, attachments, timestamp); return new OutgoingGroupMediaMessage(recipients, body, attachments, timestamp);
} }
OutgoingMediaMessage message = new OutgoingMediaMessage(recipients, body, attachments, timestamp, OutgoingMediaMessage message = new OutgoingMediaMessage(recipients, body, attachments, timestamp, subscriptionId,
!addresses.getBcc().isEmpty() ? ThreadDatabase.DistributionTypes.BROADCAST : !addresses.getBcc().isEmpty() ? ThreadDatabase.DistributionTypes.BROADCAST :
ThreadDatabase.DistributionTypes.DEFAULT); ThreadDatabase.DistributionTypes.DEFAULT);
if (Types.isSecureType(outboxType)) { if (Types.isSecureType(outboxType)) {
@ -615,6 +617,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED); contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp()); contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(PART_COUNT, retrieved.getAttachments().size()); contentValues.put(PART_COUNT, retrieved.getAttachments().size());
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
contentValues.put(READ, 0); contentValues.put(READ, 0);
if (!contentValues.containsKey(DATE_SENT)) { if (!contentValues.containsKey(DATE_SENT)) {
@ -673,7 +676,7 @@ public class MmsDatabase extends MessagingDatabase {
return insertMessageInbox(masterSecret, retrieved, "", threadId, type); return insertMessageInbox(masterSecret, retrieved, "", threadId, type);
} }
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification) { public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context); MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
long threadId = getThreadIdFor(notification); long threadId = getThreadIdFor(notification);
@ -702,6 +705,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED); contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp()); contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(READ, Util.isDefaultSmsProvider(context) ? 0 : 1); contentValues.put(READ, Util.isDefaultSmsProvider(context) ? 0 : 1);
contentValues.put(SUBSCRIPTION_ID, subscriptionId);
if (!contentValues.containsKey(DATE_SENT)) if (!contentValues.containsKey(DATE_SENT))
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)); contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
@ -761,6 +765,7 @@ public class MmsDatabase extends MessagingDatabase {
contentValues.put(THREAD_ID, threadId); contentValues.put(THREAD_ID, threadId);
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
if (message.getRecipients().isSingleRecipient()) { if (message.getRecipients().isSingleRecipient()) {
try { try {
@ -1021,6 +1026,7 @@ public class MmsDatabase extends MessagingDatabase {
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY)); long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT)); int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
byte[]contentLocationBytes = null; byte[]contentLocationBytes = null;
byte[]transactionIdBytes = null; byte[]transactionIdBytes = null;
@ -1035,7 +1041,7 @@ public class MmsDatabase extends MessagingDatabase {
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(), return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
addressDeviceId, dateSent, dateReceived, receiptCount, threadId, addressDeviceId, dateSent, dateReceived, receiptCount, threadId,
contentLocationBytes, messageSize, expiry, status, contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox); transactionIdBytes, mailbox, subscriptionId);
} }
private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) { private MediaMmsMessageRecord getMediaMmsMessageRecord(Cursor cursor) {
@ -1051,6 +1057,7 @@ public class MmsDatabase extends MessagingDatabase {
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT)); int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES)); String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.MISMATCHED_IDENTITIES));
String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE)); String networkDocument = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.NETWORK_FAILURE));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.SUBSCRIPTION_ID));
Recipients recipients = getRecipientsFor(address); Recipients recipients = getRecipientsFor(address);
List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument); List<IdentityKeyMismatch> mismatches = getMismatchedIdentities(mismatchDocument);
@ -1059,7 +1066,8 @@ public class MmsDatabase extends MessagingDatabase {
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(), return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
addressDeviceId, dateSent, dateReceived, receiptCount, addressDeviceId, dateSent, dateReceived, receiptCount,
threadId, body, slideDeck, partCount, box, mismatches, networkFailures); threadId, body, slideDeck, partCount, box, mismatches,
networkFailures, subscriptionId);
} }
private Recipients getRecipientsFor(String address) { private Recipients getRecipientsFor(String address) {

View File

@ -14,6 +14,7 @@ public interface MmsSmsColumns {
public static final String RECEIPT_COUNT = "delivery_receipt_count"; public static final String RECEIPT_COUNT = "delivery_receipt_count";
public static final String MISMATCHED_IDENTITIES = "mismatched_identities"; public static final String MISMATCHED_IDENTITIES = "mismatched_identities";
public static final String UNIQUE_ROW_ID = "unique_row_id"; public static final String UNIQUE_ROW_ID = "unique_row_id";
public static final String SUBSCRIPTION_ID = "subscription_id";
public static class Types { public static class Types {
protected static final long TOTAL_MASK = 0xFFFFFFFF; protected static final long TOTAL_MASK = 0xFFFFFFFF;

View File

@ -52,7 +52,8 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT,
MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsDatabase.NETWORK_FAILURE,
MmsSmsColumns.SUBSCRIPTION_ID, TRANSPORT,
AttachmentDatabase.ATTACHMENT_ID_ALIAS, AttachmentDatabase.ATTACHMENT_ID_ALIAS,
AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.UNIQUE_ID,
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
@ -132,7 +133,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsSmsColumns.SUBSCRIPTION_ID, MmsDatabase.NETWORK_FAILURE, TRANSPORT,
AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.UNIQUE_ID,
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
AttachmentDatabase.SIZE, AttachmentDatabase.SIZE,
@ -156,6 +157,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID, MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS, MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES, MmsSmsColumns.RECEIPT_COUNT, MmsSmsColumns.MISMATCHED_IDENTITIES,
MmsSmsColumns.SUBSCRIPTION_ID,
MmsDatabase.NETWORK_FAILURE, TRANSPORT, MmsDatabase.NETWORK_FAILURE, TRANSPORT,
AttachmentDatabase.UNIQUE_ID, AttachmentDatabase.UNIQUE_ID,
AttachmentDatabase.MMS_ID, AttachmentDatabase.MMS_ID,
@ -192,6 +194,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID); mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT); mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES); mmsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
mmsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE); mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX); mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
mmsColumnsPresent.add(MmsDatabase.DATE_SENT); mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
@ -222,6 +225,7 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID); smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT); smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES); smsColumnsPresent.add(MmsSmsColumns.MISMATCHED_IDENTITIES);
smsColumnsPresent.add(MmsSmsColumns.SUBSCRIPTION_ID);
smsColumnsPresent.add(SmsDatabase.TYPE); smsColumnsPresent.add(SmsDatabase.TYPE);
smsColumnsPresent.add(SmsDatabase.SUBJECT); smsColumnsPresent.add(SmsDatabase.SUBJECT);
smsColumnsPresent.add(SmsDatabase.DATE_SENT); smsColumnsPresent.add(SmsDatabase.DATE_SENT);

View File

@ -32,6 +32,7 @@ public class RecipientPreferenceDatabase extends Database {
private static final String MUTE_UNTIL = "mute_until"; private static final String MUTE_UNTIL = "mute_until";
private static final String COLOR = "color"; private static final String COLOR = "color";
private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder"; private static final String SEEN_INVITE_REMINDER = "seen_invite_reminder";
private static final String DEFAULT_SUBSCRIPTION_ID = "default_subscription_id";
public enum VibrateState { public enum VibrateState {
DEFAULT(0), ENABLED(1), DISABLED(2); DEFAULT(0), ENABLED(1), DISABLED(2);
@ -60,7 +61,8 @@ public class RecipientPreferenceDatabase extends Database {
VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " + VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " +
MUTE_UNTIL + " INTEGER DEFAULT 0, " + MUTE_UNTIL + " INTEGER DEFAULT 0, " +
COLOR + " TEXT DEFAULT NULL, " + COLOR + " TEXT DEFAULT NULL, " +
SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0);"; SEEN_INVITE_REMINDER + " INTEGER DEFAULT 0, " +
DEFAULT_SUBSCRIPTION_ID + " INTEGER DEFAULT -1);";
public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) { public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) {
super(context, databaseHelper); super(context, databaseHelper);
@ -95,6 +97,7 @@ public class RecipientPreferenceDatabase extends Database {
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR));
Uri notificationUri = notification == null ? null : Uri.parse(notification); Uri notificationUri = notification == null ? null : Uri.parse(notification);
boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1; boolean seenInviteReminder = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)) == 1;
int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID));
MaterialColor color; MaterialColor color;
@ -109,7 +112,8 @@ public class RecipientPreferenceDatabase extends Database {
return Optional.of(new RecipientsPreferences(blocked, muteUntil, return Optional.of(new RecipientsPreferences(blocked, muteUntil,
VibrateState.fromId(vibrateState), VibrateState.fromId(vibrateState),
notificationUri, color, seenInviteReminder)); notificationUri, color, seenInviteReminder,
defaultSubscriptionId));
} }
return Optional.absent(); return Optional.absent();
@ -124,6 +128,13 @@ public class RecipientPreferenceDatabase extends Database {
updateOrInsert(recipients, values); updateOrInsert(recipients, values);
} }
public void setDefaultSubscriptionId(@NonNull Recipients recipients, int defaultSubscriptionId) {
ContentValues values = new ContentValues();
values.put(DEFAULT_SUBSCRIPTION_ID, defaultSubscriptionId);
updateOrInsert(recipients, values);
}
public void setBlocked(Recipients recipients, boolean blocked) { public void setBlocked(Recipients recipients, boolean blocked) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(BLOCK, blocked ? 1 : 0); values.put(BLOCK, blocked ? 1 : 0);
@ -181,12 +192,14 @@ public class RecipientPreferenceDatabase extends Database {
private final Uri notification; private final Uri notification;
private final MaterialColor color; private final MaterialColor color;
private final boolean seenInviteReminder; private final boolean seenInviteReminder;
private final int defaultSubscriptionId;
public RecipientsPreferences(boolean blocked, long muteUntil, public RecipientsPreferences(boolean blocked, long muteUntil,
@NonNull VibrateState vibrateState, @NonNull VibrateState vibrateState,
@Nullable Uri notification, @Nullable Uri notification,
@Nullable MaterialColor color, @Nullable MaterialColor color,
boolean seenInviteReminder) boolean seenInviteReminder,
int defaultSubscriptionId)
{ {
this.blocked = blocked; this.blocked = blocked;
this.muteUntil = muteUntil; this.muteUntil = muteUntil;
@ -194,6 +207,7 @@ public class RecipientPreferenceDatabase extends Database {
this.notification = notification; this.notification = notification;
this.color = color; this.color = color;
this.seenInviteReminder = seenInviteReminder; this.seenInviteReminder = seenInviteReminder;
this.defaultSubscriptionId = defaultSubscriptionId;
} }
public @Nullable MaterialColor getColor() { public @Nullable MaterialColor getColor() {
@ -219,5 +233,9 @@ public class RecipientPreferenceDatabase extends Database {
public boolean hasSeenInviteReminder() { public boolean hasSeenInviteReminder() {
return seenInviteReminder; return seenInviteReminder;
} }
public Optional<Integer> getDefaultSubscriptionId() {
return defaultSubscriptionId != -1 ? Optional.of(defaultSubscriptionId) : Optional.<Integer>absent();
}
} }
} }

View File

@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.LRUCache;
import org.whispersystems.jobqueue.JobManager; import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.textsecure.api.util.InvalidNumberException; import org.whispersystems.textsecure.api.util.InvalidNumberException;
@ -78,7 +77,7 @@ public class SmsDatabase extends MessagingDatabase {
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " + DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " + STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " + RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT);"; MISMATCHED_IDENTITIES + " TEXT DEFAULT NULL, " + SERVICE_CENTER + " TEXT, " + SUBSCRIPTION_ID + " INTEGER DEFAULT -1);";
public static final String[] CREATE_INDEXS = { 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_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
@ -95,7 +94,7 @@ public class SmsDatabase extends MessagingDatabase {
DATE_SENT + " AS " + NORMALIZED_DATE_SENT, DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
PROTOCOL, READ, STATUS, TYPE, PROTOCOL, READ, STATUS, TYPE,
REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT, REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT,
MISMATCHED_IDENTITIES MISMATCHED_IDENTITIES, SUBSCRIPTION_ID
}; };
private static final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache(); private static final EarlyReceiptCache earlyReceiptCache = new EarlyReceiptCache();
@ -451,6 +450,7 @@ public class SmsDatabase extends MessagingDatabase {
values.put(DATE_SENT, message.getSentTimestampMillis()); values.put(DATE_SENT, message.getSentTimestampMillis());
values.put(PROTOCOL, message.getProtocol()); values.put(PROTOCOL, message.getProtocol());
values.put(READ, unread ? 0 : 1); values.put(READ, unread ? 0 : 1);
values.put(SUBSCRIPTION_ID, message.getSubscriptionId());
if (!TextUtils.isEmpty(message.getPseudoSubject())) if (!TextUtils.isEmpty(message.getPseudoSubject()))
values.put(SUBJECT, message.getPseudoSubject()); values.put(SUBJECT, message.getPseudoSubject());
@ -497,6 +497,7 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(DATE_SENT, date); contentValues.put(DATE_SENT, date);
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(TYPE, type); contentValues.put(TYPE, type);
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
try { try {
contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(date, canonicalizeNumber(context, address))); contentValues.put(RECEIPT_COUNT, earlyReceiptCache.remove(date, canonicalizeNumber(context, address)));
@ -663,6 +664,7 @@ public class SmsDatabase extends MessagingDatabase {
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS)); int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT)); int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES)); String mismatchDocument = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.MISMATCHED_IDENTITIES));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.SUBSCRIPTION_ID));
List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument); List<IdentityKeyMismatch> mismatches = getMismatches(mismatchDocument);
Recipients recipients = getRecipientsFor(address); Recipients recipients = getRecipientsFor(address);
@ -672,7 +674,7 @@ public class SmsDatabase extends MessagingDatabase {
recipients.getPrimaryRecipient(), recipients.getPrimaryRecipient(),
addressDeviceId, addressDeviceId,
dateSent, dateReceived, receiptCount, type, dateSent, dateReceived, receiptCount, type,
threadId, status, mismatches); threadId, status, mismatches, subscriptionId);
} }
private Recipients getRecipientsFor(String address) { private Recipients getRecipientsFor(String address) {

View File

@ -21,8 +21,8 @@ import android.support.annotation.NonNull;
import android.text.SpannableString; import android.text.SpannableString;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase.Status;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
@ -53,10 +53,10 @@ public class MediaMmsMessageRecord extends MessageRecord {
@NonNull SlideDeck slideDeck, @NonNull SlideDeck slideDeck,
int partCount, long mailbox, int partCount, long mailbox,
List<IdentityKeyMismatch> mismatches, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> failures) List<NetworkFailure> failures, int subscriptionId)
{ {
super(context, id, body, recipients, individualRecipient, recipientDeviceId, dateSent, super(context, id, body, recipients, individualRecipient, recipientDeviceId, dateSent,
dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures); dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, mismatches, failures, subscriptionId);
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.partCount = partCount; this.partCount = partCount;

View File

@ -50,13 +50,15 @@ public abstract class MessageRecord extends DisplayRecord {
private final long id; private final long id;
private final List<IdentityKeyMismatch> mismatches; private final List<IdentityKeyMismatch> mismatches;
private final List<NetworkFailure> networkFailures; private final List<NetworkFailure> networkFailures;
private final int subscriptionId;
MessageRecord(Context context, long id, Body body, Recipients recipients, MessageRecord(Context context, long id, Body body, Recipients recipients,
Recipient individualRecipient, int recipientDeviceId, Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, long threadId, long dateSent, long dateReceived, long threadId,
int deliveryStatus, int receiptCount, long type, int deliveryStatus, int receiptCount, long type,
List<IdentityKeyMismatch> mismatches, List<IdentityKeyMismatch> mismatches,
List<NetworkFailure> networkFailures) List<NetworkFailure> networkFailures,
int subscriptionId)
{ {
super(context, body, recipients, dateSent, dateReceived, threadId, deliveryStatus, receiptCount, super(context, body, recipients, dateSent, dateReceived, threadId, deliveryStatus, receiptCount,
type); type);
@ -65,6 +67,7 @@ public abstract class MessageRecord extends DisplayRecord {
this.recipientDeviceId = recipientDeviceId; this.recipientDeviceId = recipientDeviceId;
this.mismatches = mismatches; this.mismatches = mismatches;
this.networkFailures = networkFailures; this.networkFailures = networkFailures;
this.subscriptionId = subscriptionId;
} }
public abstract boolean isMms(); public abstract boolean isMms();
@ -195,4 +198,7 @@ public abstract class MessageRecord extends DisplayRecord {
return (int)getId(); return (int)getId();
} }
public int getSubscriptionId() {
return subscriptionId;
}
} }

View File

@ -49,11 +49,12 @@ public class NotificationMmsMessageRecord extends MessageRecord {
Recipient individualRecipient, int recipientDeviceId, Recipient individualRecipient, int recipientDeviceId,
long dateSent, long dateReceived, int receiptCount, long dateSent, long dateReceived, int receiptCount,
long threadId, byte[] contentLocation, long messageSize, long threadId, byte[] contentLocation, long messageSize,
long expiry, int status, byte[] transactionId, long mailbox) long expiry, int status, byte[] transactionId, long mailbox,
int subscriptionId)
{ {
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId, super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox, dateSent, dateReceived, threadId, Status.STATUS_NONE, receiptCount, mailbox,
new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>()); new LinkedList<IdentityKeyMismatch>(), new LinkedList<NetworkFailure>(), subscriptionId);
this.contentLocation = contentLocation; this.contentLocation = contentLocation;
this.messageSize = messageSize; this.messageSize = messageSize;

View File

@ -47,11 +47,12 @@ public class SmsMessageRecord extends MessageRecord {
long dateSent, long dateReceived, long dateSent, long dateReceived,
int receiptCount, int receiptCount,
long type, long threadId, long type, long threadId,
int status, List<IdentityKeyMismatch> mismatches) int status, List<IdentityKeyMismatch> mismatches,
int subscriptionId)
{ {
super(context, id, body, recipients, individualRecipient, recipientDeviceId, super(context, id, body, recipients, individualRecipient, recipientDeviceId,
dateSent, dateReceived, threadId, status, receiptCount, type, dateSent, dateReceived, threadId, status, receiptCount, type,
mismatches, new LinkedList<NetworkFailure>()); mismatches, new LinkedList<NetworkFailure>(), subscriptionId);
} }
public long getType() { public long getType() {

View File

@ -9,9 +9,9 @@ import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.UriAttachment; import org.thoughtcrime.securesms.attachments.UriAttachment;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion; import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.ApnUnavailableException; import org.thoughtcrime.securesms.mms.ApnUnavailableException;
import org.thoughtcrime.securesms.mms.CompatMmsConnection; import org.thoughtcrime.securesms.mms.CompatMmsConnection;
@ -35,7 +35,6 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.MmsException; import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.EncodedStringValue; import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.NotificationInd; import ws.com.google.android.mms.pdu.NotificationInd;
@ -76,7 +75,7 @@ public class MmsDownloadJob extends MasterSecretJob {
@Override @Override
public void onRun(MasterSecret masterSecret) { public void onRun(MasterSecret masterSecret) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Optional<NotificationInd> notification = database.getNotification(messageId); Optional<Pair<NotificationInd, Integer>> notification = database.getNotification(messageId);
if (!notification.isPresent()) { if (!notification.isPresent()) {
Log.w(TAG, "No notification for ID: " + messageId); Log.w(TAG, "No notification for ID: " + messageId);
@ -84,22 +83,24 @@ public class MmsDownloadJob extends MasterSecretJob {
} }
try { try {
if (notification.get().getContentLocation() == null) { if (notification.get().first.getContentLocation() == null) {
throw new MmsException("Notification content location was null."); throw new MmsException("Notification content location was null.");
} }
database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING); database.markDownloadState(messageId, MmsDatabase.Status.DOWNLOAD_CONNECTING);
String contentLocation = new String(notification.get().getContentLocation()); String contentLocation = new String(notification.get().first.getContentLocation());
byte[] transactionId = notification.get().getTransactionId(); byte[] transactionId = notification.get().first.getTransactionId();
Log.w(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost()); Log.w(TAG, "Downloading mms at " + Uri.parse(contentLocation).getHost());
RetrieveConf retrieveConf = new CompatMmsConnection(context).retrieve(contentLocation, transactionId); RetrieveConf retrieveConf = new CompatMmsConnection(context).retrieve(contentLocation, transactionId, notification.get().second);
if (retrieveConf == null) { if (retrieveConf == null) {
throw new MmsException("RetrieveConf was null"); throw new MmsException("RetrieveConf was null");
} }
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieveConf);
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, retrieveConf, notification.get().second);
} catch (ApnUnavailableException e) { } catch (ApnUnavailableException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE, handleDownloadError(masterSecret, messageId, threadId, MmsDatabase.Status.DOWNLOAD_APN_UNAVAILABLE,
@ -146,7 +147,8 @@ public class MmsDownloadJob extends MasterSecretJob {
} }
private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation, private void storeRetrievedMms(MasterSecret masterSecret, String contentLocation,
long messageId, long threadId, RetrieveConf retrieved) long messageId, long threadId, RetrieveConf retrieved,
int subscriptionId)
throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException, throws MmsException, NoSessionException, DuplicateMessageException, InvalidMessageException,
LegacyMessageException LegacyMessageException
{ {
@ -192,7 +194,7 @@ public class MmsDownloadJob extends MasterSecretJob {
IncomingMediaMessage message = new IncomingMediaMessage(from, to, cc, body, retrieved.getDate() * 1000L, attachments); IncomingMediaMessage message = new IncomingMediaMessage(from, to, cc, body, retrieved.getDate() * 1000L, attachments, subscriptionId);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret), Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
message, contentLocation, threadId); message, contentLocation, threadId);

View File

@ -19,16 +19,20 @@ import ws.com.google.android.mms.pdu.PduParser;
public class MmsReceiveJob extends ContextJob { public class MmsReceiveJob extends ContextJob {
private static final long serialVersionUID = 1L;
private static final String TAG = MmsReceiveJob.class.getSimpleName(); private static final String TAG = MmsReceiveJob.class.getSimpleName();
private final byte[] data; private final byte[] data;
private final int subscriptionId;
public MmsReceiveJob(Context context, byte[] data) { public MmsReceiveJob(Context context, byte[] data, int subscriptionId) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
.withWakeLock(true) .withWakeLock(true)
.withPersistence().create()); .withPersistence().create());
this.data = data; this.data = data;
this.subscriptionId = subscriptionId;
} }
@Override @Override
@ -54,7 +58,7 @@ public class MmsReceiveJob extends ContextJob {
if (isNotification(pdu) && !isBlocked(pdu)) { if (isNotification(pdu) && !isBlocked(pdu)) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu); Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu, subscriptionId);
Log.w(TAG, "Inserted received MMS notification..."); Log.w(TAG, "Inserted received MMS notification...");

View File

@ -17,7 +17,6 @@ import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
@ -25,12 +24,9 @@ import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.SmilUtil; import org.thoughtcrime.securesms.util.SmilUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil; import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.textsecure.api.util.InvalidNumberException;
import org.whispersystems.textsecure.api.util.PhoneNumberFormatter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -83,7 +79,7 @@ public class MmsSendJob extends SendJob {
validateDestinations(message, pdu); validateDestinations(message, pdu);
final byte[] pduBytes = getPduBytes(pdu); final byte[] pduBytes = getPduBytes(pdu);
final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes); final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, message.getSubscriptionId());
final MmsSendResult result = getSendResult(sendConf, pdu); final MmsSendResult result = getSendResult(sendConf, pdu);
database.markAsSent(messageId); database.markAsSent(messageId);

View File

@ -261,7 +261,7 @@ public class PushDecryptJob extends ContextJob {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
String localNumber = TextSecurePreferences.getLocalNumber(context); String localNumber = TextSecurePreferences.getLocalNumber(context);
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(), IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterSecret, envelope.getSource(),
localNumber, message.getTimestamp(), localNumber, message.getTimestamp(), -1,
Optional.fromNullable(envelope.getRelay()), Optional.fromNullable(envelope.getRelay()),
message.getBody(), message.getBody(),
message.getGroupInfo(), message.getGroupInfo(),
@ -293,7 +293,7 @@ public class PushDecryptJob extends ContextJob {
Recipients recipients = getSyncMessageDestination(message); Recipients recipients = getSyncMessageDestination(message);
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(), OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
PointerAttachment.forPointers(masterSecret, message.getMessage().getAttachments()), PointerAttachment.forPointers(masterSecret, message.getMessage().getAttachments()),
message.getTimestamp(), ThreadDatabase.DistributionTypes.DEFAULT); message.getTimestamp(), -1, ThreadDatabase.DistributionTypes.DEFAULT);
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage); mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
@ -350,7 +350,7 @@ public class PushDecryptJob extends ContextJob {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
Recipients recipients = getSyncMessageDestination(message); Recipients recipients = getSyncMessageDestination(message);
String body = message.getMessage().getBody().or(""); String body = message.getMessage().getBody().or("");
OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipients, body); OutgoingTextMessage outgoingTextMessage = new OutgoingTextMessage(recipients, body, -1);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp()); long messageId = database.insertMessageOutbox(masterSecret, threadId, outgoingTextMessage, false, message.getTimestamp());

View File

@ -23,17 +23,21 @@ import java.util.List;
public class SmsReceiveJob extends ContextJob { public class SmsReceiveJob extends ContextJob {
private static final long serialVersionUID = 1L;
private static final String TAG = SmsReceiveJob.class.getSimpleName(); private static final String TAG = SmsReceiveJob.class.getSimpleName();
private final Object[] pdus; private final Object[] pdus;
private final int subscriptionId;
public SmsReceiveJob(Context context, Object[] pdus) { public SmsReceiveJob(Context context, Object[] pdus, int subscriptionId) {
super(context, JobParameters.newBuilder() super(context, JobParameters.newBuilder()
.withPersistence() .withPersistence()
.withWakeLock(true) .withWakeLock(true)
.create()); .create());
this.pdus = pdus; this.pdus = pdus;
this.subscriptionId = subscriptionId;
} }
@Override @Override
@ -41,7 +45,7 @@ public class SmsReceiveJob extends ContextJob {
@Override @Override
public void onRun() { public void onRun() {
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus); Optional<IncomingTextMessage> message = assembleMessageFragments(pdus, subscriptionId);
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
MasterSecretUnion masterSecretUnion; MasterSecretUnion masterSecretUnion;
@ -95,11 +99,11 @@ public class SmsReceiveJob extends ContextJob {
return messageAndThreadId; return messageAndThreadId;
} }
private Optional<IncomingTextMessage> assembleMessageFragments(Object[] pdus) { private Optional<IncomingTextMessage> assembleMessageFragments(Object[] pdus, int subscriptionId) {
List<IncomingTextMessage> messages = new LinkedList<>(); List<IncomingTextMessage> messages = new LinkedList<>();
for (Object pdu : pdus) { for (Object pdu : pdus) {
messages.add(new IncomingTextMessage(SmsMessage.createFromPdu((byte[])pdu))); messages.add(new IncomingTextMessage(SmsMessage.createFromPdu((byte[])pdu), subscriptionId));
} }
if (messages.isEmpty()) { if (messages.isEmpty()) {

View File

@ -4,6 +4,7 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager; import android.telephony.SmsManager;
import android.util.Log; import android.util.Log;
@ -105,7 +106,7 @@ public class SmsSendJob extends SendJob {
// catching it and marking the message as a failure. That way at least it doesn't // catching it and marking the message as a failure. That way at least it doesn't
// repeatedly crash every time you start the app. // repeatedly crash every time you start the app.
try { try {
SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents); getSmsManagerFor(message.getSubscriptionId()).sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
} catch (NullPointerException npe) { } catch (NullPointerException npe) {
Log.w(TAG, npe); Log.w(TAG, npe);
Log.w(TAG, "Recipient: " + recipient); Log.w(TAG, "Recipient: " + recipient);
@ -113,7 +114,7 @@ public class SmsSendJob extends SendJob {
try { try {
for (int i=0;i<messages.size();i++) { for (int i=0;i<messages.size();i++) {
SmsManager.getDefault().sendTextMessage(recipient, null, messages.get(i), getSmsManagerFor(message.getSubscriptionId()).sendTextMessage(recipient, null, messages.get(i),
sentIntents.get(i), sentIntents.get(i),
deliveredIntents == null ? null : deliveredIntents.get(i)); deliveredIntents == null ? null : deliveredIntents.get(i));
} }
@ -179,6 +180,14 @@ public class SmsSendJob extends SendJob {
return pending; return pending;
} }
private SmsManager getSmsManagerFor(int subscriptionId) {
if (Build.VERSION.SDK_INT >= 22 && subscriptionId != -1) {
return SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
return SmsManager.getDefault();
}
}
private static JobParameters constructParameters(Context context, String name) { private static JobParameters constructParameters(Context context, String name) {
JobParameters.Builder builder = JobParameters.newBuilder() JobParameters.Builder builder = JobParameters.newBuilder()
.withPersistence() .withPersistence()

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.mms; package org.thoughtcrime.securesms.mms;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -26,38 +27,46 @@ public class CompatMmsConnection implements OutgoingMmsConnection, IncomingMmsCo
@Nullable @Nullable
@Override @Override
public SendConf send(@NonNull byte[] pduBytes) public SendConf send(@NonNull byte[] pduBytes, int subscriptionId)
throws UndeliverableMessageException throws UndeliverableMessageException
{ {
try { if (subscriptionId == -1 || VERSION.SDK_INT < 22) {
Log.w(TAG, "Sending via legacy connection"); Log.w(TAG, "Sending via legacy connection");
return new OutgoingLegacyMmsConnection(context).send(pduBytes); try {
return new OutgoingLegacyMmsConnection(context).send(pduBytes, subscriptionId);
} catch (UndeliverableMessageException | ApnUnavailableException e) { } catch (UndeliverableMessageException | ApnUnavailableException e) {
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { Log.w(TAG, e);
Log.w(TAG, "Falling back to try sending via Lollipop API");
return new OutgoingLollipopMmsConnection(context).send(pduBytes);
} else {
throw new UndeliverableMessageException(e);
} }
} }
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
return new OutgoingLollipopMmsConnection(context).send(pduBytes, subscriptionId);
} else {
throw new UndeliverableMessageException("Lollipop API not available to try...");
}
} }
@Nullable @Nullable
@Override @Override
public RetrieveConf retrieve(@NonNull String contentLocation, public RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId) byte[] transactionId,
int subscriptionId)
throws MmsException, MmsRadioException, ApnUnavailableException, IOException throws MmsException, MmsRadioException, ApnUnavailableException, IOException
{ {
try { if (VERSION.SDK_INT < 22 || subscriptionId == -1) {
Log.w(TAG, "Receiving via legacy connection"); Log.w(TAG, "Receiving via legacy connection");
return new IncomingLegacyMmsConnection(context).retrieve(contentLocation, transactionId); try {
} catch (MmsRadioException | IOException | ApnUnavailableException e) { return new IncomingLegacyMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
} catch (MmsRadioException | ApnUnavailableException | IOException e) {
Log.w(TAG, e);
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
Log.w(TAG, "Falling back to try receiving via Lollipop API"); Log.w(TAG, "Falling back to try receiving via Lollipop API");
return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId); return new IncomingLollipopMmsConnection(context).retrieve(contentLocation, transactionId, subscriptionId);
} else { } else {
throw e; throw new IOException("Not able to use Lollipop APIs, giving up...");
}
} }
} }
} }

View File

@ -60,7 +60,7 @@ public class IncomingLegacyMmsConnection extends LegacyMmsConnection implements
@Override @Override
public @Nullable RetrieveConf retrieve(@NonNull String contentLocation, public @Nullable RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId) byte[] transactionId, int subscriptionId)
throws MmsRadioException, ApnUnavailableException, IOException throws MmsRadioException, ApnUnavailableException, IOException
{ {
MmsRadio radio = MmsRadio.getInstance(context); MmsRadio radio = MmsRadio.getInstance(context);

View File

@ -39,6 +39,7 @@ import ws.com.google.android.mms.pdu.PduParser;
import ws.com.google.android.mms.pdu.RetrieveConf; import ws.com.google.android.mms.pdu.RetrieveConf;
public class IncomingLollipopMmsConnection extends LollipopMmsConnection implements IncomingMmsConnection { public class IncomingLollipopMmsConnection extends LollipopMmsConnection implements IncomingMmsConnection {
public static final String ACTION = IncomingLollipopMmsConnection.class.getCanonicalName() + "MMS_DOWNLOADED_ACTION"; public static final String ACTION = IncomingLollipopMmsConnection.class.getCanonicalName() + "MMS_DOWNLOADED_ACTION";
private static final String TAG = IncomingLollipopMmsConnection.class.getSimpleName(); private static final String TAG = IncomingLollipopMmsConnection.class.getSimpleName();
@ -58,7 +59,8 @@ public class IncomingLollipopMmsConnection extends LollipopMmsConnection impleme
@Override @Override
@TargetApi(VERSION_CODES.LOLLIPOP) @TargetApi(VERSION_CODES.LOLLIPOP)
public synchronized @Nullable RetrieveConf retrieve(@NonNull String contentLocation, public synchronized @Nullable RetrieveConf retrieve(@NonNull String contentLocation,
byte[] transactionId) throws MmsException byte[] transactionId,
int subscriptionId) throws MmsException
{ {
beginTransaction(); beginTransaction();
@ -66,7 +68,16 @@ public class IncomingLollipopMmsConnection extends LollipopMmsConnection impleme
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext()); MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
Log.w(TAG, "downloading multimedia from " + contentLocation + " to " + pointer.getUri()); Log.w(TAG, "downloading multimedia from " + contentLocation + " to " + pointer.getUri());
SmsManager.getDefault().downloadMultimediaMessage(getContext(),
SmsManager smsManager;
if (VERSION.SDK_INT >= 22 && subscriptionId != -1) {
smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
smsManager = SmsManager.getDefault();
}
smsManager.downloadMultimediaMessage(getContext(),
contentLocation, contentLocation,
pointer.getUri(), pointer.getUri(),
null, null,

View File

@ -19,6 +19,7 @@ public class IncomingMediaMessage {
private final String groupId; private final String groupId;
private final boolean push; private final boolean push;
private final long sentTimeMillis; private final long sentTimeMillis;
private final int subscriptionId;
private final List<String> to = new LinkedList<>(); private final List<String> to = new LinkedList<>();
private final List<String> cc = new LinkedList<>(); private final List<String> cc = new LinkedList<>();
@ -26,13 +27,14 @@ public class IncomingMediaMessage {
public IncomingMediaMessage(String from, List<String> to, List<String> cc, public IncomingMediaMessage(String from, List<String> to, List<String> cc,
String body, long sentTimeMillis, String body, long sentTimeMillis,
List<Attachment> attachments) List<Attachment> attachments, int subscriptionId)
{ {
this.from = from; this.from = from;
this.sentTimeMillis = sentTimeMillis; this.sentTimeMillis = sentTimeMillis;
this.body = body; this.body = body;
this.groupId = null; this.groupId = null;
this.push = false; this.push = false;
this.subscriptionId = subscriptionId;
this.to.addAll(to); this.to.addAll(to);
this.cc.addAll(cc); this.cc.addAll(cc);
@ -43,6 +45,7 @@ public class IncomingMediaMessage {
String from, String from,
String to, String to,
long sentTimeMillis, long sentTimeMillis,
int subscriptionId,
Optional<String> relay, Optional<String> relay,
Optional<String> body, Optional<String> body,
Optional<TextSecureGroup> group, Optional<TextSecureGroup> group,
@ -52,6 +55,7 @@ public class IncomingMediaMessage {
this.from = from; this.from = from;
this.sentTimeMillis = sentTimeMillis; this.sentTimeMillis = sentTimeMillis;
this.body = body.orNull(); this.body = body.orNull();
this.subscriptionId = subscriptionId;
if (group.isPresent()) this.groupId = GroupUtil.getEncodedId(group.get().getGroupId()); if (group.isPresent()) this.groupId = GroupUtil.getEncodedId(group.get().getGroupId());
else this.groupId = null; else this.groupId = null;
@ -60,6 +64,10 @@ public class IncomingMediaMessage {
this.attachments.addAll(PointerAttachment.forPointers(masterSecret, attachments)); this.attachments.addAll(PointerAttachment.forPointers(masterSecret, attachments));
} }
public int getSubscriptionId() {
return subscriptionId;
}
public String getBody() { public String getBody() {
return body; return body;
} }

View File

@ -9,5 +9,5 @@ import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.RetrieveConf; import ws.com.google.android.mms.pdu.RetrieveConf;
public interface IncomingMmsConnection { public interface IncomingMmsConnection {
@Nullable RetrieveConf retrieve(@NonNull String contentLocation, byte[] transactionId) throws MmsException, MmsRadioException, ApnUnavailableException, IOException; @Nullable RetrieveConf retrieve(@NonNull String contentLocation, byte[] transactionId, int subscriptionId) throws MmsException, MmsRadioException, ApnUnavailableException, IOException;
} }

View File

@ -72,7 +72,7 @@ public class OutgoingLegacyMmsConnection extends LegacyMmsConnection implements
} }
@Override @Override
public @Nullable SendConf send(@NonNull byte[] pduBytes) throws UndeliverableMessageException { public @Nullable SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException {
try { try {
MmsRadio radio = MmsRadio.getInstance(context); MmsRadio radio = MmsRadio.getInstance(context);

View File

@ -59,13 +59,23 @@ public class OutgoingLollipopMmsConnection extends LollipopMmsConnection impleme
@Override @Override
@TargetApi(VERSION_CODES.LOLLIPOP) @TargetApi(VERSION_CODES.LOLLIPOP)
public @Nullable synchronized SendConf send(@NonNull byte[] pduBytes) throws UndeliverableMessageException { public @Nullable synchronized SendConf send(@NonNull byte[] pduBytes, int subscriptionId)
throws UndeliverableMessageException
{
beginTransaction(); beginTransaction();
try { try {
MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext()); MmsBodyProvider.Pointer pointer = MmsBodyProvider.makeTemporaryPointer(getContext());
Util.copy(new ByteArrayInputStream(pduBytes), pointer.getOutputStream()); Util.copy(new ByteArrayInputStream(pduBytes), pointer.getOutputStream());
SmsManager.getDefault().sendMultimediaMessage(getContext(), SmsManager smsManager;
if (VERSION.SDK_INT >= 22 && subscriptionId != -1) {
smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
smsManager = SmsManager.getDefault();
}
smsManager.sendMultimediaMessage(getContext(),
pointer.getUri(), pointer.getUri(),
null, null,
null, null,

View File

@ -14,9 +14,11 @@ public class OutgoingMediaMessage {
protected final List<Attachment> attachments; protected final List<Attachment> attachments;
private final long sentTimeMillis; private final long sentTimeMillis;
private final int distributionType; private final int distributionType;
private final int subscriptionId;
public OutgoingMediaMessage(Recipients recipients, String message, public OutgoingMediaMessage(Recipients recipients, String message,
List<Attachment> attachments, long sentTimeMillis, List<Attachment> attachments, long sentTimeMillis,
int subscriptionId,
int distributionType) int distributionType)
{ {
this.recipients = recipients; this.recipients = recipients;
@ -24,14 +26,15 @@ public class OutgoingMediaMessage {
this.sentTimeMillis = sentTimeMillis; this.sentTimeMillis = sentTimeMillis;
this.distributionType = distributionType; this.distributionType = distributionType;
this.attachments = attachments; this.attachments = attachments;
this.subscriptionId = subscriptionId;
} }
public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int distributionType) public OutgoingMediaMessage(Recipients recipients, SlideDeck slideDeck, String message, long sentTimeMillis, int subscriptionId, int distributionType)
{ {
this(recipients, this(recipients,
buildMessage(slideDeck, message), buildMessage(slideDeck, message),
slideDeck.asAttachments(), slideDeck.asAttachments(),
sentTimeMillis, sentTimeMillis, subscriptionId,
distributionType); distributionType);
} }
@ -41,6 +44,7 @@ public class OutgoingMediaMessage {
this.distributionType = that.distributionType; this.distributionType = that.distributionType;
this.attachments = that.attachments; this.attachments = that.attachments;
this.sentTimeMillis = that.sentTimeMillis; this.sentTimeMillis = that.sentTimeMillis;
this.subscriptionId = that.subscriptionId;
} }
public Recipients getRecipients() { public Recipients getRecipients() {
@ -71,6 +75,10 @@ public class OutgoingMediaMessage {
return sentTimeMillis; return sentTimeMillis;
} }
public int getSubscriptionId() {
return subscriptionId;
}
private static String buildMessage(SlideDeck slideDeck, String message) { private static String buildMessage(SlideDeck slideDeck, String message) {
if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) { if (!TextUtils.isEmpty(message) && !TextUtils.isEmpty(slideDeck.getBody())) {
return slideDeck.getBody() + "\n\n" + message; return slideDeck.getBody() + "\n\n" + message;

View File

@ -8,5 +8,5 @@ import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import ws.com.google.android.mms.pdu.SendConf; import ws.com.google.android.mms.pdu.SendConf;
public interface OutgoingMmsConnection { public interface OutgoingMmsConnection {
@Nullable SendConf send(@NonNull byte[] pduBytes) throws UndeliverableMessageException; @Nullable SendConf send(@NonNull byte[] pduBytes, int subscriptionId) throws UndeliverableMessageException;
} }

View File

@ -16,7 +16,7 @@ public class OutgoingSecureMediaMessage extends OutgoingMediaMessage {
long sentTimeMillis, long sentTimeMillis,
int distributionType) int distributionType)
{ {
super(recipients, body, attachments, sentTimeMillis, distributionType); super(recipients, body, attachments, sentTimeMillis, -1, distributionType);
} }
public OutgoingSecureMediaMessage(OutgoingMediaMessage base) { public OutgoingSecureMediaMessage(OutgoingMediaMessage base) {

View File

@ -27,16 +27,16 @@ import android.support.v4.app.RemoteInput;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.LinkedList; import java.util.LinkedList;
import ws.com.google.android.mms.pdu.PduBody;
/** /**
* Get the response text from the Wearable Device and sends an message as a reply * Get the response text from the Wearable Device and sends an message as a reply
*/ */
@ -66,11 +66,14 @@ public class WearReplyReceiver extends MasterSecretBroadcastReceiver {
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
long threadId; long threadId;
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipientIds);
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
if (recipients.isGroupRecipient()) { if (recipients.isGroupRecipient()) {
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), 0); OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList<Attachment>(), System.currentTimeMillis(), subscriptionId, 0);
threadId = MessageSender.send(context, masterSecret, reply, -1, false); threadId = MessageSender.send(context, masterSecret, reply, -1, false);
} else { } else {
OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString()); OutgoingTextMessage reply = new OutgoingTextMessage(recipients, responseText.toString(), subscriptionId);
threadId = MessageSender.send(context, masterSecret, reply, -1, false); threadId = MessageSender.send(context, masterSecret, reply, -1, false);
} }

View File

@ -66,7 +66,7 @@ public class RecipientFactory {
return provider.getRecipients(context, recipientIds, asynchronous); return provider.getRecipients(context, recipientIds, asynchronous);
} }
public static Recipients getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) { public static @NonNull Recipients getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) {
StringTokenizer tokenizer = new StringTokenizer(rawText, ","); StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
List<String> ids = new LinkedList<>(); List<String> ids = new LinkedList<>();
@ -81,7 +81,7 @@ public class RecipientFactory {
return getRecipientsForIds(context, ids, asynchronous); return getRecipientsForIds(context, ids, asynchronous);
} }
public static Recipients getRecipientsFromStrings(@NonNull Context context, @NonNull List<String> numbers, boolean asynchronous) { public static @NonNull Recipients getRecipientsFromStrings(@NonNull Context context, @NonNull List<String> numbers, boolean asynchronous) {
List<String> ids = new LinkedList<>(); List<String> ids = new LinkedList<>();
for (String number : numbers) { for (String number : numbers) {
@ -95,7 +95,7 @@ public class RecipientFactory {
return getRecipientsForIds(context, ids, asynchronous); return getRecipientsForIds(context, ids, asynchronous);
} }
private static Recipients getRecipientsForIds(Context context, List<String> idStrings, boolean asynchronous) { private static @NonNull Recipients getRecipientsForIds(Context context, List<String> idStrings, boolean asynchronous) {
long[] ids = new long[idStrings.size()]; long[] ids = new long[idStrings.size()];
int i = 0; int i = 0;

View File

@ -63,9 +63,11 @@ public class MmsListener extends BroadcastReceiver {
isRelevant(context, intent))) isRelevant(context, intent)))
{ {
Log.w(TAG, "Relevant!"); Log.w(TAG, "Relevant!");
int subscriptionId = intent.getExtras().getInt("subscription", -1);
ApplicationContext.getInstance(context) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new MmsReceiveJob(context, intent.getByteArrayExtra("data"))); .add(new MmsReceiveJob(context, intent.getByteArrayExtra("data"), subscriptionId));
abortBroadcast(); abortBroadcast();
} }

View File

@ -9,6 +9,8 @@ import android.widget.Toast;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
@ -17,6 +19,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.Rfc5724Uri; import org.thoughtcrime.securesms.util.Rfc5724Uri;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLDecoder; import java.net.URLDecoder;
@ -49,14 +52,17 @@ public class QuickResponseService extends MasterSecretIntentService {
if(numbers.contains("%")){ if(numbers.contains("%")){
numbers = URLDecoder.decode(numbers); numbers = URLDecoder.decode(numbers);
} }
Recipients recipients = RecipientFactory.getRecipientsFromString(this, numbers, false); Recipients recipients = RecipientFactory.getRecipientsFromString(this, numbers, false);
Optional<RecipientsPreferences> preferences = DatabaseFactory.getRecipientPreferenceDatabase(this).getRecipientsPreferences(recipients.getIds());
int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
if (recipients.isSingleRecipient()) { if (recipients.isSingleRecipient()) {
MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content), -1, false); MessageSender.send(this, masterSecret, new OutgoingTextMessage(recipients, content, subscriptionId), -1, false);
} else { } else {
MessageSender.send(this, masterSecret, new OutgoingMediaMessage(recipients, new SlideDeck(), content, System.currentTimeMillis(), MessageSender.send(this, masterSecret, new OutgoingMediaMessage(recipients, new SlideDeck(), content, System.currentTimeMillis(),
ThreadDatabase.DistributionTypes.DEFAULT), -1, false); subscriptionId, ThreadDatabase.DistributionTypes.DEFAULT), -1, false);
} }
} }
} catch (URISyntaxException e) { } catch (URISyntaxException e) {

View File

@ -84,16 +84,6 @@ public class SmsListener extends BroadcastReceiver {
return bodyBuilder.toString(); return bodyBuilder.toString();
} }
// private ArrayList<IncomingTextMessage> getAsTextMessages(Intent intent) {
// Object[] pdus = (Object[])intent.getExtras().get("pdus");
// ArrayList<IncomingTextMessage> messages = new ArrayList<IncomingTextMessage>(pdus.length);
//
// for (int i=0;i<pdus.length;i++)
// messages.add(new IncomingTextMessage(SmsMessage.createFromPdu((byte[])pdus[i])));
//
// return messages;
// }
private boolean isRelevant(Context context, Intent intent) { private boolean isRelevant(Context context, Intent intent) {
SmsMessage message = getSmsMessageFromIntent(intent); SmsMessage message = getSmsMessageFromIntent(intent);
String messageBody = getSmsMessageBodyFromIntent(intent); String messageBody = getSmsMessageBodyFromIntent(intent);
@ -165,13 +155,9 @@ public class SmsListener extends BroadcastReceiver {
(intent.getAction().equals(SMS_RECEIVED_ACTION)) && isRelevant(context, intent)) (intent.getAction().equals(SMS_RECEIVED_ACTION)) && isRelevant(context, intent))
{ {
Object[] pdus = (Object[]) intent.getExtras().get("pdus"); Object[] pdus = (Object[]) intent.getExtras().get("pdus");
ApplicationContext.getInstance(context).getJobManager().add(new SmsReceiveJob(context, pdus)); int subscriptionId = intent.getExtras().getInt("subscription", -1);
// Intent receivedIntent = new Intent(context, SendReceiveService.class); ApplicationContext.getInstance(context).getJobManager().add(new SmsReceiveJob(context, pdus, subscriptionId));
// receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
// receivedIntent.putExtra("ResultCode", this.getResultCode());
// receivedIntent.putParcelableArrayListExtra("text_messages",getAsTextMessages(intent));
// context.startService(receivedIntent);
abortBroadcast(); abortBroadcast();
} }

View File

@ -35,8 +35,9 @@ public class IncomingTextMessage implements Parcelable {
private final long sentTimestampMillis; private final long sentTimestampMillis;
private final String groupId; private final String groupId;
private final boolean push; private final boolean push;
private final int subscriptionId;
public IncomingTextMessage(SmsMessage message) { public IncomingTextMessage(SmsMessage message, int subscriptionId) {
this.message = message.getDisplayMessageBody(); this.message = message.getDisplayMessageBody();
this.sender = message.getDisplayOriginatingAddress(); this.sender = message.getDisplayOriginatingAddress();
this.senderDeviceId = TextSecureAddress.DEFAULT_DEVICE_ID; this.senderDeviceId = TextSecureAddress.DEFAULT_DEVICE_ID;
@ -45,6 +46,7 @@ public class IncomingTextMessage implements Parcelable {
this.replyPathPresent = message.isReplyPathPresent(); this.replyPathPresent = message.isReplyPathPresent();
this.pseudoSubject = message.getPseudoSubject(); this.pseudoSubject = message.getPseudoSubject();
this.sentTimestampMillis = message.getTimestampMillis(); this.sentTimestampMillis = message.getTimestampMillis();
this.subscriptionId = subscriptionId;
this.groupId = null; this.groupId = null;
this.push = false; this.push = false;
} }
@ -61,6 +63,7 @@ public class IncomingTextMessage implements Parcelable {
this.pseudoSubject = ""; this.pseudoSubject = "";
this.sentTimestampMillis = sentTimestampMillis; this.sentTimestampMillis = sentTimestampMillis;
this.push = true; this.push = true;
this.subscriptionId = -1;
if (group.isPresent()) { if (group.isPresent()) {
this.groupId = GroupUtil.getEncodedId(group.get().getGroupId()); this.groupId = GroupUtil.getEncodedId(group.get().getGroupId());
@ -80,6 +83,7 @@ public class IncomingTextMessage implements Parcelable {
this.sentTimestampMillis = in.readLong(); this.sentTimestampMillis = in.readLong();
this.groupId = in.readString(); this.groupId = in.readString();
this.push = (in.readInt() == 1); this.push = (in.readInt() == 1);
this.subscriptionId = in.readInt();
} }
public IncomingTextMessage(IncomingTextMessage base, String newBody) { public IncomingTextMessage(IncomingTextMessage base, String newBody) {
@ -93,6 +97,7 @@ public class IncomingTextMessage implements Parcelable {
this.sentTimestampMillis = base.getSentTimestampMillis(); this.sentTimestampMillis = base.getSentTimestampMillis();
this.groupId = base.getGroupId(); this.groupId = base.getGroupId();
this.push = base.isPush(); this.push = base.isPush();
this.subscriptionId = base.getSubscriptionId();
} }
public IncomingTextMessage(List<IncomingTextMessage> fragments) { public IncomingTextMessage(List<IncomingTextMessage> fragments) {
@ -112,6 +117,7 @@ public class IncomingTextMessage implements Parcelable {
this.sentTimestampMillis = fragments.get(0).getSentTimestampMillis(); this.sentTimestampMillis = fragments.get(0).getSentTimestampMillis();
this.groupId = fragments.get(0).getGroupId(); this.groupId = fragments.get(0).getGroupId();
this.push = fragments.get(0).isPush(); this.push = fragments.get(0).isPush();
this.subscriptionId = fragments.get(0).getSubscriptionId();
} }
protected IncomingTextMessage(String sender, String groupId) protected IncomingTextMessage(String sender, String groupId)
@ -126,6 +132,11 @@ public class IncomingTextMessage implements Parcelable {
this.sentTimestampMillis = System.currentTimeMillis(); this.sentTimestampMillis = System.currentTimeMillis();
this.groupId = groupId; this.groupId = groupId;
this.push = true; this.push = true;
this.subscriptionId = -1;
}
public int getSubscriptionId() {
return subscriptionId;
} }
public long getSentTimestampMillis() { public long getSentTimestampMillis() {
@ -209,5 +220,6 @@ public class IncomingTextMessage implements Parcelable {
out.writeLong(sentTimestampMillis); out.writeLong(sentTimestampMillis);
out.writeString(groupId); out.writeString(groupId);
out.writeInt(push ? 1 : 0); out.writeInt(push ? 1 : 0);
out.writeInt(subscriptionId);
} }
} }

View File

@ -74,7 +74,8 @@ public class MessageSender {
allocatedThreadId = threadId; allocatedThreadId = threadId;
} }
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId, message, forceSms, System.currentTimeMillis()); long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId,
message, forceSms, System.currentTimeMillis());
sendTextMessage(context, recipients, forceSms, keyExchange, messageId); sendTextMessage(context, recipients, forceSms, keyExchange, messageId);

View File

@ -6,7 +6,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
public class OutgoingEncryptedMessage extends OutgoingTextMessage { public class OutgoingEncryptedMessage extends OutgoingTextMessage {
public OutgoingEncryptedMessage(Recipients recipients, String body) { public OutgoingEncryptedMessage(Recipients recipients, String body) {
super(recipients, body); super(recipients, body, -1);
} }
private OutgoingEncryptedMessage(OutgoingEncryptedMessage base, String body) { private OutgoingEncryptedMessage(OutgoingEncryptedMessage base, String body) {

View File

@ -5,7 +5,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
public class OutgoingKeyExchangeMessage extends OutgoingTextMessage { public class OutgoingKeyExchangeMessage extends OutgoingTextMessage {
public OutgoingKeyExchangeMessage(Recipients recipients, String message) { public OutgoingKeyExchangeMessage(Recipients recipients, String message) {
super(recipients, message); super(recipients, message, -1);
} }
private OutgoingKeyExchangeMessage(OutgoingKeyExchangeMessage base, String body) { private OutgoingKeyExchangeMessage(OutgoingKeyExchangeMessage base, String body) {

View File

@ -7,17 +7,24 @@ public class OutgoingTextMessage {
private final Recipients recipients; private final Recipients recipients;
private final String message; private final String message;
private final int subscriptionId;
public OutgoingTextMessage(Recipients recipients, String message) { public OutgoingTextMessage(Recipients recipients, String message, int subscriptionId) {
this.recipients = recipients; this.recipients = recipients;
this.message = message; this.message = message;
this.subscriptionId = subscriptionId;
} }
protected OutgoingTextMessage(OutgoingTextMessage base, String body) { protected OutgoingTextMessage(OutgoingTextMessage base, String body) {
this.recipients = base.getRecipients(); this.recipients = base.getRecipients();
this.subscriptionId = base.getSubscriptionId();
this.message = body; this.message = body;
} }
public int getSubscriptionId() {
return subscriptionId;
}
public String getMessageBody() { public String getMessageBody() {
return message; return message;
} }
@ -48,9 +55,9 @@ public class OutgoingTextMessage {
} else if (record.isKeyExchange()) { } else if (record.isKeyExchange()) {
return new OutgoingKeyExchangeMessage(record.getRecipients(), record.getBody().getBody()); return new OutgoingKeyExchangeMessage(record.getRecipients(), record.getBody().getBody());
} else if (record.isEndSession()) { } else if (record.isEndSession()) {
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody())); return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody(), -1));
} else { } else {
return new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody()); return new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody(), record.getSubscriptionId());
} }
} }

View File

@ -0,0 +1,23 @@
package org.thoughtcrime.securesms.util.dualsim;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class SubscriptionInfoCompat {
private final int subscriptionId;
private final @Nullable CharSequence displayName;
public SubscriptionInfoCompat(int subscriptionId, @Nullable CharSequence displayName) {
this.subscriptionId = subscriptionId;
this.displayName = displayName;
}
public @NonNull CharSequence getDisplayName() {
return displayName != null ? displayName : "";
}
public int getSubscriptionId() {
return subscriptionId;
}
}

View File

@ -0,0 +1,57 @@
package org.thoughtcrime.securesms.util.dualsim;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.LinkedList;
import java.util.List;
public class SubscriptionManagerCompat {
private final Context context;
public SubscriptionManagerCompat(Context context) {
this.context = context.getApplicationContext();
}
public Optional<SubscriptionInfoCompat> getActiveSubscriptionInfo(int subscriptionId) {
if (Build.VERSION.SDK_INT < 22) {
return Optional.absent();
}
SubscriptionInfo subscriptionInfo = SubscriptionManager.from(context).getActiveSubscriptionInfo(subscriptionId);
if (subscriptionInfo != null) {
return Optional.of(new SubscriptionInfoCompat(subscriptionId, subscriptionInfo.getDisplayName()));
} else {
return Optional.absent();
}
}
public @NonNull List<SubscriptionInfoCompat> getActiveSubscriptionInfoList() {
if (Build.VERSION.SDK_INT < 22) {
return new LinkedList<>();
}
List<SubscriptionInfo> subscriptionInfos = SubscriptionManager.from(context).getActiveSubscriptionInfoList();
if (subscriptionInfos == null || subscriptionInfos.isEmpty()) {
return new LinkedList<>();
}
List<SubscriptionInfoCompat> compatList = new LinkedList<>();
for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
compatList.add(new SubscriptionInfoCompat(subscriptionInfo.getSubscriptionId(),
subscriptionInfo.getDisplayName()));
}
return compatList;
}
}