diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c57c8280b58afc10875034b3ec6b06122d528a0f..320e150803fd2c4bccfcc986922d134f09fcd1f1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,10 +54,18 @@
android:resource="@xml/badge_widget_provider"/>
+
+
+
+
+
+
+
@@ -339,6 +347,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ android:layout_height="@dimen/media_bubble_height"/>
+
diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml
index bb7b07cc82d9b7ccda376cdd65eec6a3b983bff1..156d60fa7350d4d827e0907f853ff5163f12afb2 100644
--- a/res/layout/conversation_item_sent.xml
+++ b/res/layout/conversation_item_sent.xml
@@ -38,18 +38,11 @@
android:background="@drawable/sent_bubble"
android:orientation="vertical">
-
+ android:layout="@layout/conversation_item_sent_thumbnail"/>
+
diff --git a/res/layout/view_identity_activity.xml b/res/layout/view_identity_activity.xml
index 1bc52c3ac209fd0e84e8df9be8e539ac272a9a9a..71477d189c52f2f63beb7964921997a099b830b6 100644
--- a/res/layout/view_identity_activity.xml
+++ b/res/layout/view_identity_activity.xml
@@ -10,14 +10,13 @@
android:layout_gravity="center"
android:orientation="vertical">
-
+
diff --git a/res/menu/conversation_insecure_no_push.xml b/res/menu/conversation_insecure_no_push.xml
index 5c5b77c4dbc428ce2bdbee6dfeae5ab3e168c545..5a78c17d86ed412240bfdc626dd41f6a7fbab436 100644
--- a/res/menu/conversation_insecure_no_push.xml
+++ b/res/menu/conversation_insecure_no_push.xml
@@ -8,6 +8,10 @@
+ -
+
+
diff --git a/res/menu/conversation_secure_identity.xml b/res/menu/conversation_secure_identity.xml
index a1623b622ec3b5298688e0b8e9422ac33f25507b..75722bb965c7ec0fba8a306453bd9d7294ce7155 100644
--- a/res/menu/conversation_secure_identity.xml
+++ b/res/menu/conversation_secure_identity.xml
@@ -6,7 +6,12 @@
app:showAsAction="ifRoom">
diff --git a/res/menu/conversation_secure_sms.xml b/res/menu/conversation_secure_sms.xml
index d30655c66af45412cc866fb902c57376573f35d4..ce947958c312e40ef126385e992a8e136d9473f4 100644
--- a/res/menu/conversation_secure_sms.xml
+++ b/res/menu/conversation_secure_sms.xml
@@ -1,5 +1,15 @@
-
\ No newline at end of file
+ android:id="@+id/menu_abort_session" />
+ -
+
+
+
+ -
+
+
+
diff --git a/res/menu/text_secure_normal.xml b/res/menu/text_secure_normal.xml
index 1114e9163dff428e2d91f1680719a154e744ad7e..44f72c1b5227514f45d5cd0dfa8c8229cde4488a 100644
--- a/res/menu/text_secure_normal.xml
+++ b/res/menu/text_secure_normal.xml
@@ -26,6 +26,12 @@
android:id="@+id/menu_my_identity"
android:icon="@android:drawable/ic_menu_view" />
+ -
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 63a9a46c31e21b2d46971e84a26e580559753736..c36b9533a33045ceb45a4d24135efcba0d5c67bd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -212,6 +212,11 @@
Today
Yesterday
+
+ New SIM card detected
+ A new key has been generated.
+ A new key has been generated for that SIM card.
+
Share with
@@ -357,6 +362,10 @@
Draft:
Media message
+
+ SMS messages disabled
+ No SIM card found
+
You do not have an identity key.
Recipient has no identity key.
@@ -439,6 +448,11 @@
Silence
New message
+
+ Slot %1$s
+ %1$s (slot %2$s)
+
+
Old passphrase
New passphrase
diff --git a/res/xml/automotive_app_desc.xml b/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2603bffcbc2836d4ef24caced41a6c7e41c667e1
--- /dev/null
+++ b/res/xml/automotive_app_desc.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/org/smssecure/smssecure/ApplicationContext.java b/src/org/smssecure/smssecure/ApplicationContext.java
index c18c1d80eada2a8e6a525bf3bcab06959cce58a4..5eb4786a3824de30ed33a51f37a5ce4987cf80cd 100644
--- a/src/org/smssecure/smssecure/ApplicationContext.java
+++ b/src/org/smssecure/smssecure/ApplicationContext.java
@@ -18,6 +18,7 @@ package org.smssecure.smssecure;
import android.app.Application;
import android.content.Context;
+import android.util.Log;
import org.smssecure.smssecure.crypto.PRNGFixes;
import org.smssecure.smssecure.dependencies.InjectableType;
@@ -26,6 +27,7 @@ import org.smssecure.smssecure.jobs.requirements.MasterSecretRequirementProvider
import org.smssecure.smssecure.jobs.requirements.MediaNetworkRequirementProvider;
import org.smssecure.smssecure.jobs.requirements.ServiceRequirementProvider;
import org.smssecure.smssecure.util.SilencePreferences;
+import org.smssecure.smssecure.util.dualsim.SimChangedReceiver;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.jobqueue.dependencies.DependencyInjector;
import org.whispersystems.jobqueue.requirements.NetworkRequirementProvider;
@@ -45,6 +47,7 @@ import dagger.ObjectGraph;
* @author Moxie Marlinspike
*/
public class ApplicationContext extends Application implements DependencyInjector {
+ private static final String TAG = ApplicationContext.class.getSimpleName();
private JobManager jobManager;
private ObjectGraph objectGraph;
@@ -61,6 +64,7 @@ public class ApplicationContext extends Application implements DependencyInjecto
initializeRandomNumberFix();
initializeLogging();
initializeJobManager();
+ checkSimState();
}
@Override
@@ -99,4 +103,8 @@ public class ApplicationContext extends Application implements DependencyInjecto
mediaNetworkRequirementProvider.notifyMediaControlEvent();
}
+ private void checkSimState() {
+ SimChangedReceiver.checkSimState(this);
+ }
+
}
diff --git a/src/org/smssecure/smssecure/ConversationActivity.java b/src/org/smssecure/smssecure/ConversationActivity.java
index 88d7984279053b7a40f0e474bc4fea2ff6018d83..09afd50271f21c6431311498ed559b7b661fdaf4 100644
--- a/src/org/smssecure/smssecure/ConversationActivity.java
+++ b/src/org/smssecure/smssecure/ConversationActivity.java
@@ -47,6 +47,7 @@ import android.util.Pair;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.SubMenu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
@@ -105,7 +106,6 @@ import org.smssecure.smssecure.recipients.Recipients.RecipientsModifiedListener;
import org.smssecure.smssecure.service.KeyCachingService;
import org.smssecure.smssecure.sms.MessageSender;
import org.smssecure.smssecure.sms.OutgoingEncryptedMessage;
-import org.smssecure.smssecure.sms.OutgoingEndSessionMessage;
import org.smssecure.smssecure.sms.OutgoingTextMessage;
import org.smssecure.smssecure.util.concurrent.AssertedSuccessListener;
import org.smssecure.smssecure.util.CharacterCalculator.CharacterState;
@@ -120,6 +120,7 @@ import org.smssecure.smssecure.util.Util;
import org.smssecure.smssecure.util.ViewUtil;
import org.smssecure.smssecure.util.concurrent.ListenableFuture;
import org.smssecure.smssecure.util.concurrent.SettableFuture;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -193,6 +194,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
+ private List activeSubscriptions;
+
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
@@ -203,6 +206,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
Log.w(TAG, "onCreate()");
this.masterSecret = masterSecret;
+ this.activeSubscriptions = SubscriptionManagerCompat.from(this).getActiveSubscriptionInfoList();
supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
setContentView(R.layout.conversation_activity);
@@ -347,14 +351,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater();
menu.clear();
+ boolean isEncryptedForAllSubscriptionIdsConversation = SessionUtil.hasSession(this, masterSecret, recipients.getPrimaryRecipient().getNumber(), activeSubscriptions);
+
if (isSingleConversation() && isEncryptedConversation) {
inflater.inflate(R.menu.conversation_secure_identity, menu);
+ inflateSubMenuVerifyIdentity(menu);
inflater.inflate(R.menu.conversation_secure_sms, menu.findItem(R.id.menu_security).getSubMenu());
- } else if (isSingleConversation()) {
+ inflateSubMenuAbortSecureSession(menu);
+ } else if (isSingleConversation() && !isEncryptedConversation) {
inflater.inflate(R.menu.conversation_insecure_no_push, menu);
inflater.inflate(R.menu.conversation_insecure, menu);
}
+ if (isSingleConversation() && !isEncryptedForAllSubscriptionIdsConversation) {
+ inflateSubMenuStartSecureSession(menu);
+ } else {
+ MenuItem item = menu.findItem(R.id.menu_start_secure_session);
+ if (item != null) item.setVisible(false);
+ }
+
if (isSingleConversation()) {
inflater.inflate(R.menu.conversation_callable, menu);
} else if (isGroupConversation()) {
@@ -390,23 +405,26 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
- case R.id.menu_call: handleDial(getRecipients().getPrimaryRecipient()); return true;
- case R.id.menu_delete_conversation: handleDeleteConversation(); return true;
- case R.id.menu_archive_conversation: handleArchiveConversation(); return true;
- case R.id.menu_add_attachment: handleAddAttachment(); return true;
- case R.id.menu_view_media: handleViewMedia(); return true;
- case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
- case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
- case R.id.menu_abort_session: handleAbortSecureSession(); return true;
- case R.id.menu_verify_identity: handleVerifyIdentity(); return true;
- case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
- case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true;
- case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true;
- case R.id.menu_invite: handleInviteLink(); return true;
- case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
- case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true;
- case R.id.menu_conversation_settings: handleConversationSettings(); return true;
- case android.R.id.home: handleReturnToConversationList(); return true;
+ case R.id.menu_call: handleDial(getRecipients().getPrimaryRecipient()); return true;
+ case R.id.menu_delete_conversation: handleDeleteConversation(); return true;
+ case R.id.menu_archive_conversation: handleArchiveConversation(); return true;
+ case R.id.menu_add_attachment: handleAddAttachment(); return true;
+ case R.id.menu_view_media: handleViewMedia(); return true;
+ case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
+ case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
+ case R.id.menu_start_secure_session_dual_sim: handleStartSecureSession(); return true;
+ case R.id.menu_abort_session: handleAbortSecureSession(); return true;
+ case R.id.menu_abort_session_dual_sim: handleAbortSecureSession(); return true;
+ case R.id.menu_verify_identity: handleVerifyIdentity(); return true;
+ case R.id.menu_verify_identity_dual_sim: handleVerifyIdentity(); return true;
+ case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
+ case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true;
+ case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true;
+ case R.id.menu_invite: handleInviteLink(); return true;
+ case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
+ case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true;
+ case R.id.menu_conversation_settings: handleConversationSettings(); return true;
+ case android.R.id.home: handleReturnToConversationList(); return true;
}
return false;
@@ -424,6 +442,79 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
emojiToggle.setToEmoji();
}
+ private void inflateSubMenuVerifyIdentity(Menu menu) {
+ if (Build.VERSION.SDK_INT >= 22 && activeSubscriptions.size() > 1) {
+ menu.findItem(R.id.menu_verify_identity).setVisible(false);
+ SubMenu identitiesMenu = menu.findItem(R.id.menu_verify_identity_dual_sim).getSubMenu();
+
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ final int subscriptionId = subscriptionInfo.getSubscriptionId();
+ identitiesMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, subscriptionInfo.getDisplayName())
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ handleVerifyIdentity(subscriptionId);
+ return true;
+ }
+ });
+ }
+ } else {
+ menu.findItem(R.id.menu_verify_identity_dual_sim).setVisible(false);
+ }
+ }
+
+ private void inflateSubMenuStartSecureSession(Menu menu) {
+ if (Build.VERSION.SDK_INT >= 22 && activeSubscriptions.size() > 1) {
+ menu.findItem(R.id.menu_start_secure_session).setVisible(false);
+ SubMenu startSecureSessionMenu = menu.findItem(R.id.menu_start_secure_session_dual_sim).getSubMenu();
+
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ final int subscriptionId = subscriptionInfo.getSubscriptionId();
+
+ if (!SessionUtil.hasSession(this, masterSecret, recipients.getPrimaryRecipient().getNumber(), subscriptionId)) {
+
+ startSecureSessionMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, subscriptionInfo.getDisplayName())
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ handleStartSecureSession(subscriptionId);
+ return true;
+ }
+ });
+ }
+ }
+ } else {
+ menu.findItem(R.id.menu_start_secure_session_dual_sim).setVisible(false);
+ }
+ }
+
+ private void inflateSubMenuAbortSecureSession(Menu menu) {
+ if (Build.VERSION.SDK_INT >= 22 && activeSubscriptions.size() > 1) {
+ menu.findItem(R.id.menu_abort_session).setVisible(false);
+ SubMenu abortSecureSessionMenu = menu.findItem(R.id.menu_abort_session_dual_sim).getSubMenu();
+
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ final int subscriptionId = subscriptionInfo.getSubscriptionId();
+ Log.w(TAG, "inflateSubMenuAbortSecureSession( " + subscriptionId + " )");
+
+ if (SessionUtil.hasSession(this, masterSecret, recipients.getPrimaryRecipient().getNumber(), subscriptionId)) {
+ Log.w(TAG, "Subscription ID " + subscriptionId + " has a secure session.");
+
+ abortSecureSessionMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, subscriptionInfo.getDisplayName())
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ handleAbortSecureSession(subscriptionId);
+ return true;
+ }
+ });
+ }
+ }
+ } else {
+ menu.findItem(R.id.menu_abort_session).setVisible(false);
+ }
+ }
+
//////// Event Handlers
private void handleReturnToConversationList() {
@@ -497,12 +588,27 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleVerifyIdentity() {
+ if (activeSubscriptions.size() < 2) {
+ int subscriptionId = activeSubscriptions.get(0).getSubscriptionId();
+ handleVerifyIdentity(subscriptionId);
+ }
+ }
+
+ private void handleVerifyIdentity(int subscriptionId) {
Intent verifyIdentityIntent = new Intent(this, VerifyIdentityActivity.class);
+ verifyIdentityIntent.putExtra("subscription_id", subscriptionId);
verifyIdentityIntent.putExtra("recipient", getRecipients().getPrimaryRecipient().getRecipientId());
startActivity(verifyIdentityIntent);
}
private void handleStartSecureSession() {
+ if (activeSubscriptions.size() < 2) {
+ int subscriptionId = activeSubscriptions.get(0).getSubscriptionId();
+ handleStartSecureSession(subscriptionId);
+ }
+ }
+
+ private void handleStartSecureSession(final int subscriptionId) {
if (getRecipients() == null) {
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
Toast.LENGTH_LONG).show();
@@ -517,7 +623,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
final Recipients recipients = getRecipients();
final Recipient recipient = recipients.getPrimaryRecipient();
- final int subscriptionId = sendButton.getSelectedTransport().getSimSubscriptionId().or(-1);
String recipientName = (recipient.getName() == null ? recipient.getNumber() : recipient.getName());
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -529,9 +634,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- if (!isEncryptedConversation){
- KeyExchangeInitiator.initiate(ConversationActivity.this, masterSecret, recipients, true, subscriptionId);
- }
+ KeyExchangeInitiator.initiate(ConversationActivity.this, masterSecret, recipients, true, subscriptionId);
long allocatedThreadId;
if (threadId == -1) {
allocatedThreadId = DatabaseFactory.getThreadDatabase(getApplicationContext()).getThreadIdFor(recipients);
@@ -548,6 +651,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleAbortSecureSession() {
+ if (activeSubscriptions.size() < 2) {
+ int subscriptionId = activeSubscriptions.get(0).getSubscriptionId();
+ handleAbortSecureSession(subscriptionId);
+ }
+ }
+
+ private void handleAbortSecureSession(final int subscriptionId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_abort_secure_session_confirmation);
builder.setIconAttribute(R.attr.dialog_alert_icon);
@@ -556,23 +666,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- if (isSingleConversation() && isEncryptedConversation) {
- final Context context = getApplicationContext();
-
- OutgoingEndSessionMessage endSessionMessage =
- new OutgoingEndSessionMessage(new OutgoingTextMessage(getRecipients(), "TERMINATE", -1));
-
- new AsyncTask() {
- @Override
- protected Long doInBackground(OutgoingEndSessionMessage... messages) {
- return MessageSender.send(context, masterSecret, messages[0], threadId, false);
- }
-
- @Override
- protected void onPostExecute(Long result) {
- sendComplete(result);
- }
- }.execute(endSessionMessage);
+ if (isSingleConversation()) {
+ Recipients recipients = getRecipients();
+ KeyExchangeInitiator.abort(ConversationActivity.this, masterSecret, recipients, subscriptionId);
+
+ long allocatedThreadId;
+ if (threadId == -1) {
+ allocatedThreadId = DatabaseFactory.getThreadDatabase(getApplicationContext()).getThreadIdFor(recipients);
+ } else {
+ allocatedThreadId = threadId;
+ }
+ Log.w(TAG, "Refreshing thread "+allocatedThreadId+"...");
+ sendComplete(allocatedThreadId);
}
}
});
@@ -757,7 +862,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean isMediaMessage = !recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent();
- isSecureSmsDestination = isSingleConversation() && SessionUtil.hasSession(this, masterSecret, primaryRecipient);
+ isSecureSmsDestination = isSingleConversation() && SessionUtil.hasAtLeastOneSession(this, masterSecret, primaryRecipient.getNumber(), activeSubscriptions);
if (isSecureSmsDestination) {
this.isEncryptedConversation = true;
@@ -769,6 +874,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (!isSecureSmsDestination ) sendButton.disableTransport(Type.SECURE_SMS);
if (recipients.isGroupRecipient()) sendButton.disableTransport(Type.INSECURE_SMS);
+ if (Build.VERSION.SDK_INT >= 22) {
+ sendButton.disableTransport(Type.SECURE_SMS, SessionUtil.getSubscriptionIdWithoutSession(this, masterSecret, primaryRecipient.getNumber(), activeSubscriptions));
+ }
+
if (isSecureSmsDestination) {
sendButton.setDefaultTransport(Type.SECURE_SMS);
} else {
@@ -1184,6 +1293,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void sendMessage() {
+ TransportOption transportOption = sendButton.getSelectedTransport();
+
+ if (transportOption == null || transportOption.getType() == Type.DISABLED) return;
+
try {
Recipients recipients = getRecipients();
diff --git a/src/org/smssecure/smssecure/ConversationItem.java b/src/org/smssecure/smssecure/ConversationItem.java
index 03a6524d2902ee12a07097e6839b4a332d31dc7a..fcdb0f449e40a54fda6c58043500703715d73c93 100644
--- a/src/org/smssecure/smssecure/ConversationItem.java
+++ b/src/org/smssecure/smssecure/ConversationItem.java
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PorterDuff;
+import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
@@ -40,10 +41,10 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
+import org.smssecure.smssecure.components.AlertView;
import org.smssecure.smssecure.components.AudioView;
import org.smssecure.smssecure.components.AvatarImageView;
import org.smssecure.smssecure.components.DeliveryStatusView;
-import org.smssecure.smssecure.components.AlertView;
import org.smssecure.smssecure.components.ThumbnailView;
import org.smssecure.smssecure.crypto.KeyExchangeInitiator;
import org.smssecure.smssecure.crypto.MasterSecret;
@@ -66,16 +67,17 @@ import org.smssecure.smssecure.recipients.Recipient;
import org.smssecure.smssecure.recipients.RecipientFactory;
import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.util.DateUtils;
+import org.smssecure.smssecure.util.DynamicTheme;
+import org.smssecure.smssecure.util.SilencePreferences;
+import org.smssecure.smssecure.util.Util;
import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
-import org.smssecure.smssecure.util.views.Stub;
-import org.smssecure.smssecure.util.DynamicTheme;
import org.smssecure.smssecure.util.TelephonyUtil;
-import org.smssecure.smssecure.util.Util;
-import org.smssecure.smssecure.util.SilencePreferences;
+import org.smssecure.smssecure.util.views.Stub;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
@@ -113,7 +115,7 @@ public class ConversationItem extends LinearLayout
private @NonNull Set batchSelected = new HashSet<>();
private @Nullable Recipients conversationRecipients;
- private @NonNull ThumbnailView mediaThumbnail;
+ private @NonNull Stub mediaThumbnailStub;
private @NonNull Stub audioViewStub;
private @NonNull Button mmsDownloadButton;
private @NonNull TextView mmsDownloadingLabel;
@@ -179,17 +181,12 @@ public class ConversationItem extends LinearLayout
this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading);
this.contactPhoto = (AvatarImageView) findViewById(R.id.contact_photo);
this.bodyBubble = findViewById(R.id.body_bubble);
- this.mediaThumbnail = (ThumbnailView) findViewById(R.id.image_view);
+ this.mediaThumbnailStub = new Stub<>((ViewStub) findViewById(R.id.image_view_stub));
this.audioViewStub = new Stub<>((ViewStub) findViewById(R.id.audio_view_stub));
-
setOnClickListener(new ClickListener(null));
mmsDownloadButton.setOnClickListener(mmsDownloadClickListener);
- mediaThumbnail.setThumbnailClickListener(new ThumbnailClickListener());
- mediaThumbnail.setDownloadClickListener(downloadClickListener);
- mediaThumbnail.setOnLongClickListener(passthroughClickListener);
- mediaThumbnail.setOnClickListener(passthroughClickListener);
bodyText.setOnLongClickListener(passthroughClickListener);
bodyText.setOnClickListener(passthroughClickListener);
}
@@ -250,14 +247,13 @@ public class ConversationItem extends LinearLayout
private void setBubbleState(MessageRecord messageRecord, Recipient recipient) {
if (messageRecord.isOutgoing()) {
bodyBubble.getBackground().setColorFilter(defaultBubbleColor, PorterDuff.Mode.MULTIPLY);
- mediaThumbnail.setBackgroundColorHint(defaultBubbleColor);
+ if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setBackgroundColorHint(defaultBubbleColor);
} else {
int color = recipient.getColor().toConversationColor(context);
bodyBubble.getBackground().setColorFilter(color, PorterDuff.Mode.MULTIPLY);
- mediaThumbnail.setBackgroundColorHint(color);
+ if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setBackgroundColorHint(color);
}
-
if (audioViewStub.resolved()) {
setAudioViewTint(messageRecord, conversationRecipients);
}
@@ -277,9 +273,12 @@ public class ConversationItem extends LinearLayout
private void setInteractionState(MessageRecord messageRecord) {
setSelected(batchSelected.contains(messageRecord));
- mediaThumbnail.setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
- mediaThumbnail.setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
- mediaThumbnail.setLongClickable(batchSelected.isEmpty());
+
+ if (mediaThumbnailStub.resolved()) {
+ mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
+ mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
+ mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty());
+ }
if (audioViewStub.resolved()) {
audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
@@ -339,14 +338,14 @@ public class ConversationItem extends LinearLayout
boolean showControls = !messageRecord.isFailed() && (!messageRecord.isOutgoing() || messageRecord.isPending());
if (messageRecord.isMmsNotification()) {
- mediaThumbnail.setVisibility(View.GONE);
+ if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
setNotificationMmsAttributes((NotificationMmsMessageRecord) messageRecord);
} else if (hasAudio(messageRecord)) {
audioViewStub.get().setVisibility(View.VISIBLE);
- mediaThumbnail.setVisibility(View.GONE);
+ if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
audioViewStub.get().setAudio(masterSecret, ((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls);
@@ -355,17 +354,22 @@ public class ConversationItem extends LinearLayout
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
} else if (hasThumbnail(messageRecord)) {
- mediaThumbnail.setVisibility(View.VISIBLE);
+ mediaThumbnailStub.get().setVisibility(View.VISIBLE);
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
//noinspection ConstantConditions
- mediaThumbnail.setImageResource(masterSecret,
- ((MediaMmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
- showControls);
+ mediaThumbnailStub.get().setImageResource(masterSecret,
+ ((MediaMmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide(),
+ showControls);
+ mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener());
+ mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener);
+ mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener);
+ mediaThumbnailStub.get().setOnClickListener(passthroughClickListener);
+
bodyText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} else {
- mediaThumbnail.setVisibility(View.GONE);
- if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
+ if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
+ if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
bodyText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
}
@@ -398,9 +402,9 @@ public class ConversationItem extends LinearLayout
}
private void setSimInfo(MessageRecord messageRecord) {
- SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context);
+ SubscriptionManagerCompat subscriptionManager = SubscriptionManagerCompat.from(context);
- if (messageRecord.getSubscriptionId() == -1 || subscriptionManager.getActiveSubscriptionInfoList().size() < 2) {
+ if (subscriptionManager.getActiveSubscriptionInfoList().size() < 2) {
simInfoText.setVisibility(View.GONE);
} else {
Optional subscriptionInfo = subscriptionManager.getActiveSubscriptionInfo(messageRecord.getSubscriptionId());
@@ -517,7 +521,7 @@ public class ConversationItem extends LinearLayout
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- KeyExchangeInitiator.initiate(context, masterSecret, recipients, true, subscriptionId);
+ KeyExchangeInitiator.initiate(context, masterSecret, recipients, true);
}
});
builder.show();
@@ -557,15 +561,28 @@ public class ConversationItem extends LinearLayout
}
@Override
- public void onModified(Recipients recipient) {
- onModified(recipient.getPrimaryRecipient());
+ public void onModified(final Recipients recipients) {
+ Util.runOnMain(new Runnable() {
+ @Override
+ public void run() {
+ setAudioViewTint(messageRecord, recipients);
+ }
+ });
}
private class AttachmentDownloadClickListener implements SlideClickListener {
- @Override public void onClick(View v, final Slide slide) {
- DatabaseFactory.getAttachmentDatabase(context).setTransferState(messageRecord.getId(),
- slide.asAttachment(),
- AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
+ @Override
+ public void onClick(View v, final Slide slide) {
+ if (messageRecord.isMmsNotification()) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new MmsDownloadJob(context, messageRecord.getId(),
+ messageRecord.getThreadId(), false));
+ } else {
+ DatabaseFactory.getAttachmentDatabase(context).setTransferState(messageRecord.getId(),
+ slide.asAttachment(),
+ AttachmentDatabase.TRANSFER_PROGRESS_STARTED);
+ }
}
}
@@ -596,7 +613,7 @@ public class ConversationItem extends LinearLayout
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, messageRecord.getThreadId());
context.startActivity(intent);
- } else {
+ } else if (slide.getUri() != null) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.ConversationItem_view_secure_media_question);
builder.setIconAttribute(R.attr.dialog_alert_icon);
diff --git a/src/org/smssecure/smssecure/ConversationListActivity.java b/src/org/smssecure/smssecure/ConversationListActivity.java
index 12d289a1a2c765a7502ade2015ace24f5f4ffc94..d18b077faa94a8c6767abcc217eae4ca2c5e4043 100644
--- a/src/org/smssecure/smssecure/ConversationListActivity.java
+++ b/src/org/smssecure/smssecure/ConversationListActivity.java
@@ -19,16 +19,18 @@ package org.smssecure.smssecure;
import android.content.Intent;
import android.database.ContentObserver;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
-import android.util.Log;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.SubMenu;
import org.smssecure.smssecure.crypto.MasterSecret;
import org.smssecure.smssecure.database.DatabaseFactory;
@@ -36,10 +38,14 @@ import org.smssecure.smssecure.notifications.MessageNotifier;
import org.smssecure.smssecure.recipients.RecipientFactory;
import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.service.KeyCachingService;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.DynamicLanguage;
import org.smssecure.smssecure.util.DynamicTheme;
import org.smssecure.smssecure.util.SilencePreferences;
+import java.util.List;
+
public class ConversationListActivity extends PassphraseRequiredActionBarActivity
implements ConversationListFragment.ConversationSelectedListener
{
@@ -52,6 +58,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
private ContentObserver observer;
private MasterSecret masterSecret;
+ private List activeSubscriptions;
+
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
@@ -61,6 +69,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
@Override
protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
this.masterSecret = masterSecret;
+ this.activeSubscriptions = SubscriptionManagerCompat.from(this).getActiveSubscriptionInfoList();
getSupportActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
getSupportActionBar().setTitle(R.string.app_name);
@@ -91,6 +100,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
menu.findItem(R.id.menu_clear_passphrase).setVisible(!SilencePreferences.isPasswordDisabled(this));
+ inflateViewIdentities(menu);
+
inflater.inflate(R.menu.conversation_list, menu);
MenuItem menuItem = menu.findItem(R.id.menu_search);
initializeSearch(menuItem);
@@ -99,6 +110,27 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
return true;
}
+ private void inflateViewIdentities(Menu menu) {
+ if (Build.VERSION.SDK_INT >= 22 && activeSubscriptions.size() > 1) {
+ menu.findItem(R.id.menu_my_identity).setVisible(false);
+ MenuItem menuItem = menu.findItem(R.id.menu_my_identity_dual_sim);
+ SubMenu identitiesMenu = menuItem.getSubMenu();
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ final int subscriptionId = subscriptionInfo.getSubscriptionId();
+ identitiesMenu.add(Menu.NONE, Menu.NONE, Menu.NONE, subscriptionInfo.getDisplayName())
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ handleMyIdentity(subscriptionId);
+ return true;
+ }
+ });
+ }
+ } else {
+ menu.findItem(R.id.menu_my_identity_dual_sim).setVisible(false);
+ }
+ }
+
private void initializeSearch(MenuItem searchViewItem) {
SearchView searchView = (SearchView)MenuItemCompat.getActionView(searchViewItem);
searchView.setQueryHint(getString(R.string.ConversationListActivity_search));
@@ -197,7 +229,16 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
private void handleMyIdentity() {
- startActivity(new Intent(this, ViewLocalIdentityActivity.class));
+ if (activeSubscriptions.size() < 2) {
+ int subscriptionId = activeSubscriptions.get(0).getSubscriptionId();
+ handleMyIdentity(subscriptionId);
+ }
+ }
+
+ private void handleMyIdentity(int subscriptionId) {
+ Intent intent = new Intent(this, ViewIdentityActivity.class);
+ intent.putExtra("subscription_id", subscriptionId);
+ startActivity(intent);
}
private void handleMarkAllRead() {
diff --git a/src/org/smssecure/smssecure/ConversationListFragment.java b/src/org/smssecure/smssecure/ConversationListFragment.java
index b21b20e88f101412c78cb96b811887c4520601f6..27cd77565afb13ab7945202c3768e8dfcedeca90 100644
--- a/src/org/smssecure/smssecure/ConversationListFragment.java
+++ b/src/org/smssecure/smssecure/ConversationListFragment.java
@@ -78,6 +78,7 @@ import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.sms.MessageSender;
import org.smssecure.smssecure.sms.OutgoingEncryptedMessage;
import org.smssecure.smssecure.sms.OutgoingTextMessage;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.Util;
import org.smssecure.smssecure.util.ViewUtil;
import org.smssecure.smssecure.util.task.SnackbarAsyncTask;
@@ -348,8 +349,9 @@ public class ConversationListFragment extends Fragment
recipients = getListAdapter().getRecipientsFromThreadId(threadId);
if (recipients != null) {
+ int subscriptionId = SubscriptionManagerCompat.getDefaultMessagingSubscriptionId().or(-1);
isSingleConversation = recipients.isSingleRecipient() && !recipients.isGroupRecipient();
- isSecureDestination = isSingleConversation && SessionUtil.hasSession(context, masterSecret, recipients.getPrimaryRecipient());
+ isSecureDestination = isSingleConversation && SessionUtil.hasSession(context, masterSecret, recipients.getPrimaryRecipient().getNumber(), subscriptionId);
Log.w(TAG, "Number of drafts: " + drafts.size());
if (drafts.size() > 1 && !drafts.get(1).getType().equals(DraftDatabase.Draft.TEXT)) {
diff --git a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java
index bc5fed0bd073b8baca502acd3d1ad58cf05cf52d..3a31a1433b6f4cf223a385bf35e1c1f121c4cce0 100644
--- a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java
+++ b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java
@@ -21,28 +21,26 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
-import org.smssecure.smssecure.crypto.IdentityKeyUtil;
import org.smssecure.smssecure.crypto.MasterSecret;
import org.smssecure.smssecure.database.DatabaseFactory;
-import org.smssecure.smssecure.database.MmsDatabase;
-import org.smssecure.smssecure.database.MmsDatabase.Reader;
-import org.smssecure.smssecure.database.EncryptingSmsDatabase;
-import org.smssecure.smssecure.database.model.MessageRecord;
-import org.smssecure.smssecure.database.SmsDatabase;
-import org.smssecure.smssecure.database.model.SmsMessageRecord;
-import org.smssecure.smssecure.jobs.SmsDecryptJob;
import org.smssecure.smssecure.notifications.MessageNotifier;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.ParcelUtil;
import org.smssecure.smssecure.util.Util;
import org.smssecure.smssecure.util.VersionTracker;
+import org.smssecure.smssecure.util.SilencePreferences;
import org.whispersystems.jobqueue.EncryptionKeys;
-import java.io.File;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -50,23 +48,10 @@ import java.util.TreeSet;
public class DatabaseUpgradeActivity extends BaseActivity {
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
- public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
- public static final int MMS_BODY_VERSION = 46;
- public static final int TOFU_IDENTITIES_VERSION = 50;
- public static final int CURVE25519_VERSION = 63;
- public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 73;
- public static final int NO_V1_VERSION = 83;
- public static final int SIGNED_PREKEY_VERSION = 83;
- public static final int NO_DECRYPT_QUEUE_VERSION = 84;
+ public static final int MULTI_SIM_MULTI_KEYS_VERSION = 200;
private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{
- add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
- add(TOFU_IDENTITIES_VERSION);
- add(CURVE25519_VERSION);
- add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
- add(NO_V1_VERSION);
- add(SIGNED_PREKEY_VERSION);
- add(NO_DECRYPT_QUEUE_VERSION);
+ add(MULTI_SIM_MULTI_KEYS_VERSION);
}};
private MasterSecret masterSecret;
@@ -77,7 +62,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
this.masterSecret = getIntent().getParcelableExtra("master_secret");
if (needsUpgradeTask()) {
- Log.w("DatabaseUpgradeActivity", "Upgrading...");
+ Log.w(TAG, "Upgrading...");
setContentView(R.layout.database_upgrade_activity);
ProgressBar indeterminateProgress = (ProgressBar)findViewById(R.id.indeterminate_progress);
@@ -101,13 +86,13 @@ public class DatabaseUpgradeActivity extends BaseActivity {
int currentVersionCode = Util.getCurrentApkReleaseVersion(this);
int lastSeenVersion = VersionTracker.getLastSeenVersion(this);
- Log.w("DatabaseUpgradeActivity", "LastSeenVersion: " + lastSeenVersion);
+ Log.w(TAG, "LastSeenVersion: " + lastSeenVersion);
if (lastSeenVersion >= currentVersionCode)
return false;
for (int version : UPGRADE_VERSIONS) {
- Log.w("DatabaseUpgradeActivity", "Comparing: " + version);
+ Log.w(TAG, "Comparing: " + version);
if (lastSeenVersion < version)
return true;
}
@@ -156,50 +141,31 @@ public class DatabaseUpgradeActivity extends BaseActivity {
protected Void doInBackground(Integer... params) {
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
- Log.w("DatabaseUpgradeActivity", "Running background upgrade..");
+ Log.w(TAG, "Running background upgrade..");
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
- if (params[0] < CURVE25519_VERSION) {
- if (!IdentityKeyUtil.hasCurve25519IdentityKeys(context)) {
- IdentityKeyUtil.generateCurve25519IdentityKeys(context, masterSecret);
- }
- }
-
- if (params[0] < NO_V1_VERSION) {
- File v1sessions = new File(context.getFilesDir(), "sessions");
-
- if (v1sessions.exists() && v1sessions.isDirectory()) {
- File[] contents = v1sessions.listFiles();
-
- if (contents != null) {
- for (File session : contents) {
- session.delete();
+ if (params[0] < MULTI_SIM_MULTI_KEYS_VERSION) {
+ if (Build.VERSION.SDK_INT >= 22) {
+ /*
+ * getDefaultSubscriptionId() is available for API 24+ only, so we
+ * move keys and sessions to SIM card in the first available slot,
+ * not to the default one.
+ */
+ List subscriptionInfoList = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList();
+ int smallerSlot = -1;
+ int eligibleDeviceSubscriptionId = -1;
+
+ for (SubscriptionInfoCompat subscriptionInfo : subscriptionInfoList) {
+ if (smallerSlot == -1 || subscriptionInfo.getIccSlot() < smallerSlot) {
+ smallerSlot = subscriptionInfo.getIccSlot();
+ eligibleDeviceSubscriptionId = subscriptionInfo.getDeviceSubscriptionId();
}
}
- v1sessions.delete();
- }
- }
-
- if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
- EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(getApplicationContext());
-
- SmsDatabase.Reader smsReader = null;
-
- SmsMessageRecord record;
-
- try {
- smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret);
-
- while ((record = smsReader.getNext()) != null) {
- ApplicationContext.getInstance(getApplicationContext())
- .getJobManager()
- .add(new SmsDecryptJob(getApplicationContext(), record.getId()));
- }
- } finally {
- if (smsReader != null)
- smsReader.close();
+ DualSimUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, eligibleDeviceSubscriptionId);
+ DualSimUtil.generateKeysIfDoNotExist(context, masterSecret, subscriptionInfoList);
+ SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList();
}
}
diff --git a/src/org/smssecure/smssecure/KeyScanningActivity.java b/src/org/smssecure/smssecure/KeyScanningActivity.java
index bfcd9082ec7101adee3d06ea400fc562a5b8823f..07600c232dec0390825da775276fdf509e7a3f07 100644
--- a/src/org/smssecure/smssecure/KeyScanningActivity.java
+++ b/src/org/smssecure/smssecure/KeyScanningActivity.java
@@ -119,8 +119,14 @@ public abstract class KeyScanningActivity extends PassphraseRequiredActionBarAct
}
protected void initiateDisplay() {
- IntentIntegrator intentIntegrator = getIntentIntegrator();
- intentIntegrator.shareText(Base64.encodeBytes(getIdentityKeyToDisplay().serialize()));
+ IdentityKey identityKey = getIdentityKeyToDisplay();
+ if (identityKey != null) {
+ IntentIntegrator intentIntegrator = getIntentIntegrator();
+ intentIntegrator.shareText(Base64.encodeBytes(identityKey.serialize()));
+ } else {
+ Toast.makeText(this, R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key,
+ Toast.LENGTH_LONG).show();
+ }
}
protected void initiateShare() {
diff --git a/src/org/smssecure/smssecure/PassphraseCreateActivity.java b/src/org/smssecure/smssecure/PassphraseCreateActivity.java
index f6235472ccccd50a2e8500622a7a6a6f8d81261e..7c0f756da9df437de4c472824c224269924655fe 100644
--- a/src/org/smssecure/smssecure/PassphraseCreateActivity.java
+++ b/src/org/smssecure/smssecure/PassphraseCreateActivity.java
@@ -17,15 +17,21 @@
package org.smssecure.smssecure;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import org.smssecure.smssecure.crypto.IdentityKeyUtil;
import org.smssecure.smssecure.crypto.MasterSecret;
import org.smssecure.smssecure.crypto.MasterSecretUtil;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.SilencePreferences;
import org.smssecure.smssecure.util.VersionTracker;
+import java.util.List;
+
/**
* Activity for creating a user's local encryption passphrase.
*
@@ -53,7 +59,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
}
private class SecretGenerator extends AsyncTask {
- private MasterSecret masterSecret;
+ private MasterSecret masterSecret;
@Override
protected void onPreExecute() {
@@ -66,7 +72,16 @@ public class PassphraseCreateActivity extends PassphraseActivity {
passphrase);
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
- IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret);
+
+ SubscriptionManagerCompat subscriptionManagerCompat = SubscriptionManagerCompat.from(PassphraseCreateActivity.this);
+
+ if (Build.VERSION.SDK_INT >= 22) {
+ List activeSubscriptions = subscriptionManagerCompat.getActiveSubscriptionInfoList();
+ DualSimUtil.generateKeysIfDoNotExist(PassphraseCreateActivity.this, masterSecret, activeSubscriptions, false);
+ } else {
+ IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret, -1, false);
+ subscriptionManagerCompat.updateActiveSubscriptionInfoList();
+ }
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
SilencePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
diff --git a/src/org/smssecure/smssecure/ReceiveKeyDialog.java b/src/org/smssecure/smssecure/ReceiveKeyDialog.java
index 3f45af841b521a270aac84e8deb503b5d0950fac..cec56b428dac1a8287828c636b521ad787d8cb62 100644
--- a/src/org/smssecure/smssecure/ReceiveKeyDialog.java
+++ b/src/org/smssecure/smssecure/ReceiveKeyDialog.java
@@ -77,7 +77,7 @@ public class ReceiveKeyDialog extends AlertDialog {
final IncomingKeyExchangeMessage message = getMessage(messageRecord);
final IdentityKey identityKey = getIdentityKey(message);
- if (isTrusted(masterSecret, identityKey, messageRecord.getIndividualRecipient())){
+ if (isTrusted(masterSecret, identityKey, messageRecord.getIndividualRecipient(), messageRecord.getSubscriptionId())){
setMessage(context.getString(R.string.ReceiveKeyActivity_the_signature_on_this_key_exchange_is_trusted_but));
} else {
setUntrustedText(messageRecord, identityKey);
@@ -121,8 +121,8 @@ public class ReceiveKeyDialog extends AlertDialog {
setMessage(spannableString);
}
- private boolean isTrusted(MasterSecret masterSecret, IdentityKey identityKey, Recipient recipient) {
- IdentityKeyStore identityKeyStore = new SilenceIdentityKeyStore(getContext(), masterSecret);
+ private boolean isTrusted(MasterSecret masterSecret, IdentityKey identityKey, Recipient recipient, int subscriptionId) {
+ IdentityKeyStore identityKeyStore = new SilenceIdentityKeyStore(getContext(), masterSecret, subscriptionId);
return identityKeyStore.isTrustedIdentity(new SignalProtocolAddress(recipient.getNumber(), 1), identityKey);
}
@@ -134,7 +134,8 @@ public class ReceiveKeyDialog extends AlertDialog {
IncomingTextMessage message = new IncomingTextMessage(messageRecord.getIndividualRecipient().getNumber(),
messageRecord.getRecipientDeviceId(),
System.currentTimeMillis(),
- messageRecord.getBody().getBody());
+ messageRecord.getBody().getBody(),
+ messageRecord.getSubscriptionId());
if (messageRecord.isBundleKeyExchange()) {
return new IncomingPreKeyBundleMessage(message, message.getMessageBody());
diff --git a/src/org/smssecure/smssecure/TransportOption.java b/src/org/smssecure/smssecure/TransportOption.java
index cadbe05363e71b05511fd382170182e4e0485307..68a63470722f643a43b24cab447909baeda4cb54 100644
--- a/src/org/smssecure/smssecure/TransportOption.java
+++ b/src/org/smssecure/smssecure/TransportOption.java
@@ -12,6 +12,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
public class TransportOption {
public enum Type {
+ DISABLED,
INSECURE_SMS,
SECURE_SMS
}
@@ -25,17 +26,6 @@ public class TransportOption {
private final @NonNull Optional simName;
private final @NonNull Optional simSubscriptionId;
- public TransportOption(@NonNull Type type,
- @DrawableRes int drawable,
- int backgroundColor,
- @NonNull String text,
- @NonNull String composeHint,
- @NonNull CharacterCalculator characterCalculator)
- {
- this(type, drawable, backgroundColor, text, composeHint, characterCalculator,
- Optional.absent(), Optional.absent());
- }
-
public TransportOption(@NonNull Type type,
@DrawableRes int drawable,
int backgroundColor,
diff --git a/src/org/smssecure/smssecure/TransportOptions.java b/src/org/smssecure/smssecure/TransportOptions.java
index f6b3edd519318eca89da69aa9b59c8fbdcae3650..c51058c38fbae82d052cdcec2a76e73d7117b241 100644
--- a/src/org/smssecure/smssecure/TransportOptions.java
+++ b/src/org/smssecure/smssecure/TransportOptions.java
@@ -6,6 +6,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.smssecure.smssecure.util.CharacterCalculator;
+import org.smssecure.smssecure.util.DummyCharacterCalculator;
import org.smssecure.smssecure.util.MmsCharacterCalculator;
import org.smssecure.smssecure.util.SmsCharacterCalculator;
import org.smssecure.smssecure.util.EncryptedSmsCharacterCalculator;
@@ -97,7 +98,7 @@ public class TransportOptions {
}
}
- throw new AssertionError("No options of default type!");
+ return getDefaultTransportOption();
}
public void disableTransport(Type type) {
@@ -111,6 +112,17 @@ public class TransportOptions {
}
}
+ public void disableTransport(Type type, int subscriptionId) {
+ List options = find(type);
+
+ for (TransportOption option : options) {
+ if (option.getSimSubscriptionId().or(-1) == subscriptionId) enabledTransports.remove(option);
+ if (selectedOption.isPresent() && selectedOption.get().getType() == type && selectedOption.get().getSimSubscriptionId().or(-1) == subscriptionId) {
+ setSelectedTransport(null);
+ }
+ }
+ }
+
public List getEnabledTransports() {
return enabledTransports;
}
@@ -157,27 +169,18 @@ public class TransportOptions {
@NonNull CharacterCalculator characterCalculator)
{
List results = new LinkedList<>();
- SubscriptionManagerCompat subscriptionManager = new SubscriptionManagerCompat(context);
+ SubscriptionManagerCompat subscriptionManager = SubscriptionManagerCompat.from(context);
List subscriptions = subscriptionManager.getActiveSubscriptionInfoList();
- if (subscriptions.size() < 2) {
+ for (SubscriptionInfoCompat subscriptionInfo : subscriptions) {
results.add(new TransportOption(type,
drawable,
backgroundColor,
text,
composeHint,
- characterCalculator));
- } else {
- for (SubscriptionInfoCompat subscriptionInfo : subscriptions) {
- results.add(new TransportOption(type,
- drawable,
- backgroundColor,
- text,
- composeHint,
- characterCalculator,
- Optional.of(subscriptionInfo.getDisplayName()),
- Optional.of(subscriptionInfo.getSubscriptionId())));
- }
+ characterCalculator,
+ Optional.of(subscriptionInfo.getDisplayName()),
+ Optional.of(subscriptionInfo.getSubscriptionId())));
}
return results;
@@ -210,4 +213,15 @@ public class TransportOptions {
public interface OnTransportChangedListener {
public void onChange(TransportOption newTransport, boolean manuallySelected);
}
+
+ private TransportOption getDefaultTransportOption() {
+ return new TransportOption(Type.DISABLED,
+ R.drawable.ic_send_insecure_white_24dp,
+ context.getResources().getColor(R.color.grey_600),
+ context.getString(R.string.TransportOptions_sms_disabled),
+ context.getString(R.string.TransportOptions_no_sim_card_found),
+ new DummyCharacterCalculator(),
+ Optional.of((CharSequence) ""),
+ Optional.of(-1));
+ }
}
diff --git a/src/org/smssecure/smssecure/VerifyIdentityActivity.java b/src/org/smssecure/smssecure/VerifyIdentityActivity.java
index ec915c16dae7a45e9fa380da6f54d95350ba3149..e87e24096251e7cbea2e99eadd7502f7021a47ba 100644
--- a/src/org/smssecure/smssecure/VerifyIdentityActivity.java
+++ b/src/org/smssecure/smssecure/VerifyIdentityActivity.java
@@ -29,6 +29,7 @@ import org.smssecure.smssecure.crypto.MasterSecret;
import org.smssecure.smssecure.crypto.storage.SilenceSessionStore;
import org.smssecure.smssecure.recipients.Recipient;
import org.smssecure.smssecure.recipients.RecipientFactory;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.Hex;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.IdentityKey;
@@ -70,12 +71,14 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
}
private void initializeFingerprints() {
- if (!IdentityKeyUtil.hasIdentityKey(this)) {
+ int subscriptionId = getIntent().getIntExtra("subscription_id", SubscriptionManagerCompat.getDefaultMessagingSubscriptionId().or(-1));
+
+ if (!IdentityKeyUtil.hasIdentityKey(this, subscriptionId)) {
localIdentityFingerprint.setText(R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key);
return;
}
- localIdentityFingerprint.setText(Hex.toString(IdentityKeyUtil.getIdentityKey(this).serialize()));
+ localIdentityFingerprint.setText(Hex.toString(IdentityKeyUtil.getIdentityKey(this, subscriptionId).serialize()));
IdentityKey identityKey = getRemoteIdentityKey(masterSecret, recipient);
@@ -88,7 +91,9 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
@Override
protected void initiateDisplay() {
- if (!IdentityKeyUtil.hasIdentityKey(this)) {
+ int subscriptionId = SubscriptionManagerCompat.getDefaultMessagingSubscriptionId().or(-1);
+
+ if (!IdentityKeyUtil.hasIdentityKey(this, subscriptionId)) {
Toast.makeText(this,
R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key,
Toast.LENGTH_LONG).show();
@@ -127,7 +132,9 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
@Override
protected IdentityKey getIdentityKeyToDisplay() {
- return IdentityKeyUtil.getIdentityKey(this);
+ int subscriptionId = SubscriptionManagerCompat.getDefaultMessagingSubscriptionId().or(-1);
+
+ return IdentityKeyUtil.getIdentityKey(this, subscriptionId);
}
@Override
@@ -151,13 +158,14 @@ public class VerifyIdentityActivity extends KeyScanningActivity {
}
private @Nullable IdentityKey getRemoteIdentityKey(MasterSecret masterSecret, Recipient recipient) {
+ int subscriptionId = SubscriptionManagerCompat.getDefaultMessagingSubscriptionId().or(-1);
IdentityKeyParcelable identityKeyParcelable = getIntent().getParcelableExtra("remote_identity");
if (identityKeyParcelable != null) {
return identityKeyParcelable.get();
}
- SessionStore sessionStore = new SilenceSessionStore(this, masterSecret);
+ SessionStore sessionStore = new SilenceSessionStore(this, masterSecret, subscriptionId);
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(recipient.getNumber(), 1);
SessionRecord record = sessionStore.loadSession(axolotlAddress);
diff --git a/src/org/smssecure/smssecure/ViewIdentityActivity.java b/src/org/smssecure/smssecure/ViewIdentityActivity.java
index 18b3bc951335461c4c763664dfc2a4c71cf61b1c..5e671845510e8205b30328a240a1eebbfb0d6075 100644
--- a/src/org/smssecure/smssecure/ViewIdentityActivity.java
+++ b/src/org/smssecure/smssecure/ViewIdentityActivity.java
@@ -19,11 +19,17 @@ package org.smssecure.smssecure;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.widget.TextView;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import org.smssecure.smssecure.crypto.IdentityKeyParcelable;
+import org.smssecure.smssecure.crypto.IdentityKeyUtil;
import org.smssecure.smssecure.crypto.MasterSecret;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.Hex;
+
import org.whispersystems.libsignal.IdentityKey;
-import org.smssecure.smssecure.crypto.IdentityKeyParcelable;
/**
* Activity for displaying an identity key.
@@ -39,13 +45,41 @@ public class ViewIdentityActivity extends KeyScanningActivity {
private IdentityKey identityKey;
@Override
- protected void onCreate(Bundle state, @NonNull MasterSecret masterSecret) {
+ protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
+ int subscriptionId = getIntent().getIntExtra("subscription_id", SubscriptionManagerCompat.getDefaultMessagingSubscriptionId().or(-1));
+
+ getIntent().putExtra(ViewIdentityActivity.IDENTITY_KEY,
+ new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this, subscriptionId)));
+ getIntent().putExtra(ViewIdentityActivity.TITLE,
+ getString(R.string.ViewIdentityActivity_your_identity_fingerprint));
+
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.view_identity_activity);
initialize();
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+
+ MenuInflater inflater = this.getMenuInflater();
+ inflater.inflate(R.menu.local_identity, menu);
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ super.onOptionsItemSelected(item);
+
+ switch (item.getItemId()) {
+ case android.R.id.home:finish(); return true;
+ }
+
+ return false;
+ }
+
protected void initialize() {
initializeResources();
initializeFingerprint();
diff --git a/src/org/smssecure/smssecure/ViewLocalIdentityActivity.java b/src/org/smssecure/smssecure/ViewLocalIdentityActivity.java
deleted file mode 100644
index 6ecb1ccc9423346021d254e1b3e40ae6889901a7..0000000000000000000000000000000000000000
--- a/src/org/smssecure/smssecure/ViewLocalIdentityActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2011 Whisper Systems
- * Copyright (C) 2013 Open Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.smssecure.smssecure;
-
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-
-import org.smssecure.smssecure.crypto.IdentityKeyUtil;
-import org.smssecure.smssecure.crypto.IdentityKeyParcelable;
-import org.smssecure.smssecure.crypto.MasterSecret;
-
-/**
- * Activity that displays the local identity key and offers the option to regenerate it.
- *
- * @author Moxie Marlinspike
- */
-public class ViewLocalIdentityActivity extends ViewIdentityActivity {
-
- @Override
- protected void onCreate(Bundle icicle, @NonNull MasterSecret masterSecret) {
- getIntent().putExtra(ViewIdentityActivity.IDENTITY_KEY,
- new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this)));
- getIntent().putExtra(ViewIdentityActivity.TITLE,
- getString(R.string.ViewIdentityActivity_your_identity_fingerprint));
- super.onCreate(icicle, masterSecret);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- MenuInflater inflater = this.getMenuInflater();
- inflater.inflate(R.menu.local_identity, menu);
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- super.onOptionsItemSelected(item);
-
- switch (item.getItemId()) {
- case android.R.id.home:finish(); return true;
- }
-
- return false;
- }
-}
diff --git a/src/org/smssecure/smssecure/components/AvatarImageView.java b/src/org/smssecure/smssecure/components/AvatarImageView.java
index ad1b4595a2250107916a907472bf06fe4d74fc49..c2e9e0265ef9c2c56e2a386e0723d9aaea501322 100644
--- a/src/org/smssecure/smssecure/components/AvatarImageView.java
+++ b/src/org/smssecure/smssecure/components/AvatarImageView.java
@@ -24,6 +24,10 @@ import org.smssecure.smssecure.recipients.Recipient;
import org.smssecure.smssecure.recipients.RecipientFactory;
import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.service.KeyCachingService;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
+
+import java.util.List;
public class AvatarImageView extends ImageView {
@@ -92,17 +96,19 @@ public class AvatarImageView extends ImageView {
private class BadgeResolutionTask extends AsyncTask> {
private final Context context;
- private MasterSecret masterSecret;
+ private MasterSecret masterSecret;
+ private final List activeSubscriptions;
public BadgeResolutionTask(Context context, MasterSecret masterSecret) {
this.context = context;
this.masterSecret = masterSecret;
+ this.activeSubscriptions = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList();
}
@Override
protected Pair doInBackground(Recipients... recipients) {
Boolean isSecureSmsDestination = masterSecret != null &&
- SessionUtil.hasSession(context, masterSecret, recipients[0].getPrimaryRecipient());
+ SessionUtil.hasAtLeastOneSession(context, masterSecret, recipients[0].getPrimaryRecipient().getNumber(), activeSubscriptions);
return new Pair<>(recipients[0], isSecureSmsDestination);
}
diff --git a/src/org/smssecure/smssecure/components/SendButton.java b/src/org/smssecure/smssecure/components/SendButton.java
index 94e4b60c875f708b82f729862949a372928f03aa..b920259e88be83c99010584bed2811f981beaf40 100644
--- a/src/org/smssecure/smssecure/components/SendButton.java
+++ b/src/org/smssecure/smssecure/components/SendButton.java
@@ -12,6 +12,8 @@ import org.smssecure.smssecure.TransportOptionsPopup;
import org.smssecure.smssecure.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Optional;
+import java.util.List;
+
public class SendButton extends ImageButton
implements TransportOptions.OnTransportChangedListener,
TransportOptionsPopup.SelectedListener,
@@ -79,6 +81,16 @@ public class SendButton extends ImageButton
transportOptions.disableTransport(type);
}
+ public void disableTransport(TransportOption.Type type, int subscriptionId) {
+ transportOptions.disableTransport(type, subscriptionId);
+ }
+
+ public void disableTransport(TransportOption.Type type, List subscriptionIds) {
+ for (int subscriptionId : subscriptionIds) {
+ transportOptions.disableTransport(type, subscriptionId);
+ }
+ }
+
public void setDefaultTransport(TransportOption.Type type) {
transportOptions.setDefaultTransport(type);
}
diff --git a/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java b/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java
index 2b69230d04b5bf3e21ec17263c74a880b4b12cc0..33a507b13bf9821b20e7d2781fbd54a79c10d096 100644
--- a/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java
+++ b/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java
@@ -1,4 +1,4 @@
-/**
+/**
* Copyright (C) 2011 Whisper Systems
* Copyright (C) 2013 Open Whisper Systems
*
@@ -20,9 +20,11 @@ package org.smssecure.smssecure.crypto;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.os.Build;
import android.util.Log;
import org.smssecure.smssecure.util.Base64;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
@@ -39,43 +41,45 @@ import java.io.IOException;
*/
public class IdentityKeyUtil {
+ private final static String TAG = IdentityKeyUtil.class.getSimpleName();
private static final String IDENTITY_PUBLIC_KEY_DJB_PREF = "pref_identity_public_curve25519";
private static final String IDENTITY_PRIVATE_KEY_DJB_PREF = "pref_identity_private_curve25519";
- public static boolean hasIdentityKey(Context context) {
+ public static boolean hasIdentityKey(Context context, int subscriptionId) {
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
return
- preferences.contains(IDENTITY_PUBLIC_KEY_DJB_PREF) &&
- preferences.contains(IDENTITY_PRIVATE_KEY_DJB_PREF);
+ preferences.contains(getIdentityPublicKeyDjbPref(subscriptionId)) &&
+ preferences.contains(getIdentityPrivateKeyDjbPref(subscriptionId));
}
- public static IdentityKey getIdentityKey(Context context) {
- if (!hasIdentityKey(context)) return null;
+ public static IdentityKey getIdentityKey(Context context, int subscriptionId) {
+ if (!hasIdentityKey(context, subscriptionId)) return null;
try {
- byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF));
+ byte[] publicKeyBytes = Base64.decode(retrieve(context, getIdentityPublicKeyDjbPref(subscriptionId)));
return new IdentityKey(publicKeyBytes, 0);
} catch (IOException ioe) {
- Log.w("IdentityKeyUtil", ioe);
+ Log.w(TAG, ioe);
return null;
} catch (InvalidKeyException e) {
- Log.w("IdentityKeyUtil", e);
+ Log.w(TAG, e);
return null;
}
}
public static IdentityKeyPair getIdentityKeyPair(Context context,
- MasterSecret masterSecret)
+ MasterSecret masterSecret,
+ int subscriptionId)
{
- if (!hasIdentityKey(context))
+ if (!hasIdentityKey(context, subscriptionId))
return null;
try {
MasterCipher masterCipher = new MasterCipher(masterSecret);
- IdentityKey publicKey = getIdentityKey(context);
- ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF)));
+ IdentityKey publicKey = getIdentityKey(context, subscriptionId);
+ ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, getIdentityPrivateKeyDjbPref(subscriptionId))));
return new IdentityKeyPair(publicKey, privateKey);
} catch (IOException | InvalidKeyException e) {
@@ -83,31 +87,38 @@ public class IdentityKeyUtil {
}
}
- public static void generateIdentityKeys(Context context, MasterSecret masterSecret) {
+ public static void generateIdentityKeys(Context context, MasterSecret masterSecret, int subscriptionId) {
+ generateIdentityKeys(context, masterSecret, subscriptionId, true);
+ }
+
+ public static void generateIdentityKeys(Context context, MasterSecret masterSecret, int subscriptionId, boolean displayNotification) {
+ Log.w(TAG, "Generating identity keys for subscription ID " + subscriptionId);
ECKeyPair djbKeyPair = Curve.generateKeyPair();
MasterCipher masterCipher = new MasterCipher(masterSecret);
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
- save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
- save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey));
+ save(context, getIdentityPublicKeyDjbPref(subscriptionId), Base64.encodeBytes(djbIdentityKey.serialize()));
+ save(context, getIdentityPrivateKeyDjbPref(subscriptionId), Base64.encodeBytes(djbPrivateKey));
+
+ if (displayNotification) DualSimUtil.displayNotification(context);
}
- public static boolean hasCurve25519IdentityKeys(Context context) {
+ public static boolean hasCurve25519IdentityKeys(Context context, int subscriptionId) {
return
- retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF) != null &&
- retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF) != null;
+ retrieve(context, getIdentityPublicKeyDjbPref(subscriptionId)) != null &&
+ retrieve(context, getIdentityPrivateKeyDjbPref(subscriptionId)) != null;
}
- public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret) {
+ public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret, int subscriptionId) {
MasterCipher masterCipher = new MasterCipher(masterSecret);
ECKeyPair djbKeyPair = Curve.generateKeyPair();
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
- save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
- save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey));
+ save(context, getIdentityPublicKeyDjbPref(subscriptionId), Base64.encodeBytes(djbIdentityKey.serialize()));
+ save(context, getIdentityPrivateKeyDjbPref(subscriptionId), Base64.encodeBytes(djbPrivateKey));
}
public static String retrieve(Context context, String key) {
@@ -122,4 +133,28 @@ public class IdentityKeyUtil {
preferencesEditor.putString(key, value);
if (!preferencesEditor.commit()) throw new AssertionError("failed to save identity key/value to shared preferences");
}
+
+ public static void remove(Context context, String key) {
+ SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
+ Editor preferencesEditor = preferences.edit();
+
+ preferencesEditor.remove(key);
+ if (!preferencesEditor.commit()) throw new AssertionError("failed to remove identity key/value to shared preferences");
+ }
+
+ public static String getIdentityPublicKeyDjbPref(int subscriptionId) {
+ if (Build.VERSION.SDK_INT >= 22 && subscriptionId != -1) {
+ return IDENTITY_PUBLIC_KEY_DJB_PREF + "_" + subscriptionId;
+ } else {
+ return IDENTITY_PUBLIC_KEY_DJB_PREF;
+ }
+ }
+
+ public static String getIdentityPrivateKeyDjbPref(int subscriptionId) {
+ if (Build.VERSION.SDK_INT >= 22 && subscriptionId != -1) {
+ return IDENTITY_PRIVATE_KEY_DJB_PREF + "_" + subscriptionId;
+ } else {
+ return IDENTITY_PRIVATE_KEY_DJB_PREF;
+ }
+ }
}
diff --git a/src/org/smssecure/smssecure/crypto/KeyExchangeInitiator.java b/src/org/smssecure/smssecure/crypto/KeyExchangeInitiator.java
index 9230ebcc3308abd595230d1bfbce60fdfa9d06de..3aa5d973860a3ed6f8e4411b555da742b6fc85ce 100644
--- a/src/org/smssecure/smssecure/crypto/KeyExchangeInitiator.java
+++ b/src/org/smssecure/smssecure/crypto/KeyExchangeInitiator.java
@@ -20,6 +20,10 @@ package org.smssecure.smssecure.crypto;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.os.Build;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.widget.Toast;
import org.smssecure.smssecure.R;
import org.smssecure.smssecure.crypto.SessionBuilder;
@@ -31,7 +35,9 @@ import org.smssecure.smssecure.recipients.Recipient;
import org.smssecure.smssecure.recipients.RecipientFactory;
import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.sms.MessageSender;
+import org.smssecure.smssecure.sms.OutgoingEndSessionMessage;
import org.smssecure.smssecure.sms.OutgoingKeyExchangeMessage;
+import org.smssecure.smssecure.sms.OutgoingTextMessage;
import org.smssecure.smssecure.util.Base64;
import org.smssecure.smssecure.util.ResUtil;
import org.whispersystems.libsignal.SignalProtocolAddress;
@@ -41,10 +47,28 @@ import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SessionStore;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
+import java.util.List;
+
public class KeyExchangeInitiator {
+ public static void abort(final Context context, final MasterSecret masterSecret, final Recipients recipients, final int subscriptionId) {
+ OutgoingEndSessionMessage endSessionMessage = new OutgoingEndSessionMessage(new OutgoingTextMessage(recipients, "TERMINATE", subscriptionId));
+ MessageSender.send(context, masterSecret, endSessionMessage, -1, false);
+ }
+
+ public static void initiate(final Context context, final MasterSecret masterSecret, final Recipients recipients, boolean promptOnExisting) {
+ if (Build.VERSION.SDK_INT >= 22) {
+ List listSubscriptionInfo = SubscriptionManager.from(context).getActiveSubscriptionInfoList();
+ for (SubscriptionInfo subscriptionInfo : listSubscriptionInfo) {
+ initiate(context, masterSecret, recipients, promptOnExisting, subscriptionInfo.getSubscriptionId());
+ }
+ } else {
+ initiate(context, masterSecret, recipients, promptOnExisting, -1);
+ }
+ }
+
public static void initiate(final Context context, final MasterSecret masterSecret, final Recipients recipients, boolean promptOnExisting, final int subscriptionId) {
- if (promptOnExisting && hasInitiatedSession(context, masterSecret, recipients)) {
+ if (promptOnExisting && hasInitiatedSession(context, masterSecret, recipients, subscriptionId)) {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(R.string.KeyExchangeInitiator_initiate_despite_existing_request_question);
dialog.setMessage(R.string.KeyExchangeInitiator_youve_already_sent_a_session_initiation_request_to_this_recipient_are_you_sure);
@@ -62,28 +86,33 @@ public class KeyExchangeInitiator {
}
}
- private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipients recipients, int subscriptionId) {
+ public static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipients recipients, int subscriptionId) {
Recipient recipient = recipients.getPrimaryRecipient();
- SessionStore sessionStore = new SilenceSessionStore(context, masterSecret);
- PreKeyStore preKeyStore = new SilencePreKeyStore(context, masterSecret);
- SignedPreKeyStore signedPreKeyStore = new SilencePreKeyStore(context, masterSecret);
- IdentityKeyStore identityKeyStore = new SilenceIdentityKeyStore(context, masterSecret);
+ SessionStore sessionStore = new SilenceSessionStore(context, masterSecret, subscriptionId);
+ PreKeyStore preKeyStore = new SilencePreKeyStore(context, masterSecret, subscriptionId);
+ SignedPreKeyStore signedPreKeyStore = new SilencePreKeyStore(context, masterSecret, subscriptionId);
+ IdentityKeyStore identityKeyStore = new SilenceIdentityKeyStore(context, masterSecret, subscriptionId);
SessionBuilder sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
identityKeyStore, new SignalProtocolAddress(recipient.getNumber(), 1));
- KeyExchangeMessage keyExchangeMessage = sessionBuilder.process();
- String serializedMessage = Base64.encodeBytesWithoutPadding(keyExchangeMessage.serialize());
- OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipients, serializedMessage, subscriptionId);
+ if (identityKeyStore.getIdentityKeyPair() != null) {
+ KeyExchangeMessage keyExchangeMessage = sessionBuilder.process();
+ String serializedMessage = Base64.encodeBytesWithoutPadding(keyExchangeMessage.serialize());
+ OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipients, serializedMessage, subscriptionId);
- MessageSender.send(context, masterSecret, textMessage, -1, false);
+ MessageSender.send(context, masterSecret, textMessage, -1, false);
+ } else {
+ Toast.makeText(context, R.string.VerifyIdentityActivity_you_do_not_have_an_identity_key,
+ Toast.LENGTH_LONG).show();
+ }
}
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
- Recipients recipients)
+ Recipients recipients, int subscriptionId)
{
Recipient recipient = recipients.getPrimaryRecipient();
- SessionStore sessionStore = new SilenceSessionStore(context, masterSecret);
+ SessionStore sessionStore = new SilenceSessionStore(context, masterSecret, subscriptionId);
SessionRecord sessionRecord = sessionStore.loadSession(new SignalProtocolAddress(recipient.getNumber(), 1));
return sessionRecord.getSessionState().hasPendingKeyExchange();
diff --git a/src/org/smssecure/smssecure/crypto/PreKeyUtil.java b/src/org/smssecure/smssecure/crypto/PreKeyUtil.java
deleted file mode 100644
index 66baa3f1a7b033e167da3fdc337cf255f90525d8..0000000000000000000000000000000000000000
--- a/src/org/smssecure/smssecure/crypto/PreKeyUtil.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/**
- * Copyright (C) 2013 Open Whisper Systems
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.smssecure.smssecure.crypto;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import org.smssecure.smssecure.crypto.storage.SilencePreKeyStore;
-import org.smssecure.smssecure.util.JsonUtils;
-import org.smssecure.smssecure.util.Util;
-import org.whispersystems.libsignal.IdentityKeyPair;
-import org.whispersystems.libsignal.InvalidKeyException;
-import org.whispersystems.libsignal.InvalidKeyIdException;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECKeyPair;
-import org.whispersystems.libsignal.state.PreKeyRecord;
-import org.whispersystems.libsignal.state.PreKeyStore;
-import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.state.SignedPreKeyStore;
-import org.whispersystems.libsignal.util.Medium;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.LinkedList;
-import java.util.List;
-
-public class PreKeyUtil {
-
- public static final int BATCH_SIZE = 100;
-
- public static List generatePreKeys(Context context, MasterSecret masterSecret) {
- PreKeyStore preKeyStore = new SilencePreKeyStore(context, masterSecret);
- List records = new LinkedList<>();
- int preKeyIdOffset = getNextPreKeyId(context);
-
- for (int i=0;i activeSubscriptions) {
+ if (Build.VERSION.SDK_INT >= 22) {
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ if (!hasSession(context, masterSecret, number, subscriptionInfo.getSubscriptionId())) return false;
+ }
+ return true;
+ } else {
+ return hasSession(context, masterSecret, number, -1);
+ }
+ }
+
+ public static boolean hasAtLeastOneSession(Context context, MasterSecret masterSecret, @NonNull String number, List activeSubscriptions) {
+ if (Build.VERSION.SDK_INT >= 22) {
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ if (hasSession(context, masterSecret, number, subscriptionInfo.getSubscriptionId())) return true;
+ }
+ return false;
+ } else {
+ return hasSession(context, masterSecret, number, -1);
+ }
+ }
+
+ @TargetApi(22)
+ public static List getSubscriptionIdWithoutSession(Context context, MasterSecret masterSecret, @NonNull String number, List activeSubscriptions) {
+ LinkedList list = new LinkedList();
+
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ int subscriptionId = subscriptionInfo.getSubscriptionId();
+ if (!hasSession(context, masterSecret, number, subscriptionId)) list.add(subscriptionId);
+ }
+ return list;
+ }
}
diff --git a/src/org/smssecure/smssecure/crypto/storage/SilenceIdentityKeyStore.java b/src/org/smssecure/smssecure/crypto/storage/SilenceIdentityKeyStore.java
index fbbe947c8d5205fe278c7769e2355069f51fb830..f2eee3b8239ec8c5413efe7a59f626e8d1b2a598 100644
--- a/src/org/smssecure/smssecure/crypto/storage/SilenceIdentityKeyStore.java
+++ b/src/org/smssecure/smssecure/crypto/storage/SilenceIdentityKeyStore.java
@@ -16,15 +16,17 @@ public class SilenceIdentityKeyStore implements IdentityKeyStore {
private final Context context;
private final MasterSecret masterSecret;
+ private final int subscriptionId;
- public SilenceIdentityKeyStore(Context context, MasterSecret masterSecret) {
- this.context = context;
- this.masterSecret = masterSecret;
+ public SilenceIdentityKeyStore(Context context, MasterSecret masterSecret, int subscriptionId) {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ this.subscriptionId = subscriptionId;
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
- return IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ return IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, subscriptionId);
}
@Override
diff --git a/src/org/smssecure/smssecure/crypto/storage/SilencePreKeyStore.java b/src/org/smssecure/smssecure/crypto/storage/SilencePreKeyStore.java
index 16a2807e1fe88c7e6ffa140f3b3658c47cf65987..bb79457b06c5cfc56a60490efd9453472c316d3e 100644
--- a/src/org/smssecure/smssecure/crypto/storage/SilencePreKeyStore.java
+++ b/src/org/smssecure/smssecure/crypto/storage/SilencePreKeyStore.java
@@ -34,10 +34,12 @@ public class SilencePreKeyStore implements PreKeyStore, SignedPreKeyStore {
private final Context context;
private final MasterSecret masterSecret;
+ private final int subscriptionId;
- public SilencePreKeyStore(Context context, MasterSecret masterSecret) {
- this.context = context;
- this.masterSecret = masterSecret;
+ public SilencePreKeyStore(Context context, MasterSecret masterSecret, int subscriptionId) {
+ this.context = context;
+ this.masterSecret = masterSecret;
+ this.subscriptionId = subscriptionId;
}
@Override
@@ -156,11 +158,13 @@ public class SilencePreKeyStore implements PreKeyStore, SignedPreKeyStore {
}
private File getPreKeyFile(int preKeyId) {
- return new File(getPreKeyDirectory(), String.valueOf(preKeyId));
+ String subscriptionFile = subscriptionId != -1 ? subscriptionId + "" : "";
+ return new File(getPreKeyDirectory(), String.valueOf(preKeyId) + subscriptionFile);
}
private File getSignedPreKeyFile(int signedPreKeyId) {
- return new File(getSignedPreKeyDirectory(), String.valueOf(signedPreKeyId));
+ String subscriptionFile = subscriptionId != -1 ? subscriptionId + "" : "";
+ return new File(getSignedPreKeyDirectory(), String.valueOf(signedPreKeyId) + subscriptionFile);
}
private File getPreKeyDirectory() {
diff --git a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java
index 742b423502653513d0b783778805c9ec3215844b..2336cdd19f06333f96da2f3883aab84bf7bd923f 100644
--- a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java
+++ b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java
@@ -1,6 +1,7 @@
package org.smssecure.smssecure.crypto.storage;
import android.content.Context;
+import android.os.Build;
import android.util.Log;
import org.smssecure.smssecure.crypto.MasterCipher;
@@ -37,10 +38,15 @@ public class SilenceSessionStore implements SessionStore {
private final Context context;
private final MasterSecret masterSecret;
+ private final int subscriptionId;
- public SilenceSessionStore(Context context, MasterSecret masterSecret) {
- this.context = context.getApplicationContext();
- this.masterSecret = masterSecret;
+ public SilenceSessionStore(Context context, MasterSecret masterSecret, int subscriptionId) {
+ Log.w(TAG, "SilenceSessionStore for subscription ID " + subscriptionId);
+ if (subscriptionId == -1) Log.w(TAG, "Subscription ID should not be -1!");
+
+ this.context = context.getApplicationContext();
+ this.masterSecret = masterSecret;
+ this.subscriptionId = subscriptionId;
}
@Override
@@ -143,10 +149,15 @@ public class SilenceSessionStore implements SessionStore {
}
private File getSessionFile(SignalProtocolAddress address) {
- return new File(getSessionDirectory(), getSessionName(address));
+ String sessionName = getSessionName(address);
+ return new File(getSessionDirectory(), sessionName);
}
private File getSessionDirectory() {
+ return getSessionDirectory(context);
+ }
+
+ public static File getSessionDirectory(Context context) {
File directory = new File(context.getFilesDir(), SESSIONS_DIRECTORY_V2);
if (!directory.exists()) {
@@ -159,12 +170,10 @@ public class SilenceSessionStore implements SessionStore {
}
private String getSessionName(SignalProtocolAddress axolotlAddress) {
- Recipient recipient = RecipientFactory.getRecipientsFromString(context, axolotlAddress.getName(), true)
- .getPrimaryRecipient();
+ Recipient recipient = RecipientFactory.getRecipientsFromString(context, axolotlAddress.getName(), true).getPrimaryRecipient();
long recipientId = recipient.getRecipientId();
- int deviceId = axolotlAddress.getDeviceId();
- return recipientId + (deviceId == 1 ? "" : "." + deviceId);
+ return recipientId + ((Build.VERSION.SDK_INT < 22 || subscriptionId == -1) ? "" : "." + subscriptionId);
}
private byte[] readBlob(FileInputStream in) throws IOException {
diff --git a/src/org/smssecure/smssecure/crypto/storage/SilenceSignalProtocolStore.java b/src/org/smssecure/smssecure/crypto/storage/SilenceSignalProtocolStore.java
index 4c3e62fcd88ee73fc2ec4cd28b768e212023f23e..9c8917503aa618f541a20fc32484e09490f4d368 100644
--- a/src/org/smssecure/smssecure/crypto/storage/SilenceSignalProtocolStore.java
+++ b/src/org/smssecure/smssecure/crypto/storage/SilenceSignalProtocolStore.java
@@ -24,12 +24,14 @@ public class SilenceSignalProtocolStore implements SignalProtocolStore {
private final SignedPreKeyStore signedPreKeyStore;
private final IdentityKeyStore identityKeyStore;
private final SessionStore sessionStore;
-
- public SilenceSignalProtocolStore(Context context, MasterSecret masterSecret) {
- this.preKeyStore = new SilencePreKeyStore(context, masterSecret);
- this.signedPreKeyStore = new SilencePreKeyStore(context, masterSecret);
- this.identityKeyStore = new SilenceIdentityKeyStore(context, masterSecret);
- this.sessionStore = new SilenceSessionStore(context, masterSecret);
+ private final int subscriptionId;
+
+ public SilenceSignalProtocolStore(Context context, MasterSecret masterSecret, int subscriptionId) {
+ this.preKeyStore = new SilencePreKeyStore(context, masterSecret, subscriptionId);
+ this.signedPreKeyStore = new SilencePreKeyStore(context, masterSecret, subscriptionId);
+ this.identityKeyStore = new SilenceIdentityKeyStore(context, masterSecret, subscriptionId);
+ this.sessionStore = new SilenceSessionStore(context, masterSecret, subscriptionId);
+ this.subscriptionId = subscriptionId;
}
@Override
diff --git a/src/org/smssecure/smssecure/database/DatabaseFactory.java b/src/org/smssecure/smssecure/database/DatabaseFactory.java
index 91c225d7ad03449cfbb8004c4e30a3e672c6a901..d4df5f1b76828effc36a7e41be865d32e9866b11 100644
--- a/src/org/smssecure/smssecure/database/DatabaseFactory.java
+++ b/src/org/smssecure/smssecure/database/DatabaseFactory.java
@@ -207,281 +207,7 @@ public class DatabaseFactory {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.beginTransaction();
- if (fromVersion < DatabaseUpgradeActivity.NO_MORE_KEY_EXCHANGE_PREFIX_VERSION) {
- String KEY_EXCHANGE = "?SilenceKeyExchange";
- String PROCESSED_KEY_EXCHANGE = "?SilenceKeyExchangd";
- String STALE_KEY_EXCHANGE = "?SilenceKeyExchangs";
- int ROW_LIMIT = 500;
-
- MasterCipher masterCipher = new MasterCipher(masterSecret);
- int smsCount = 0;
- int threadCount = 0;
- int skip = 0;
-
- Cursor cursor = db.query("sms", new String[] {"COUNT(*)"}, "type & " + 0x80000000 + " != 0",
- null, null, null, null);
-
- if (cursor != null && cursor.moveToFirst()) {
- smsCount = cursor.getInt(0);
- cursor.close();
- }
-
- cursor = db.query("thread", new String[] {"COUNT(*)"}, "snippet_type & " + 0x80000000 + " != 0",
- null, null, null, null);
-
- if (cursor != null && cursor.moveToFirst()) {
- threadCount = cursor.getInt(0);
- cursor.close();
- }
-
- Cursor smsCursor = null;
-
- Log.w(TAG, "Upgrade count: " + (smsCount + threadCount));
-
- do {
- Log.w(TAG, "Looping SMS cursor...");
- if (smsCursor != null)
- smsCursor.close();
-
- smsCursor = db.query("sms", new String[] {"_id", "type", "body"},
- "type & " + 0x80000000 + " != 0",
- null, null, null, "_id", skip + "," + ROW_LIMIT);
-
- while (smsCursor != null && smsCursor.moveToNext()) {
- listener.setProgress(smsCursor.getPosition() + skip, smsCount + threadCount);
-
- try {
- String eBody = smsCursor.getString(smsCursor.getColumnIndexOrThrow("body"));
- String body = eBody != null ? masterCipher.decryptBody(eBody) : "";
- long type = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("type"));
- long id = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("_id"));
-
- if (body.startsWith(KEY_EXCHANGE)) {
- body = body.substring(KEY_EXCHANGE.length());
- body = masterCipher.encryptBody(body);
- type |= 0x8000;
-
- db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
- new String[] {body, type+"", id+""});
- } else if (body.startsWith(PROCESSED_KEY_EXCHANGE)) {
- body = body.substring(PROCESSED_KEY_EXCHANGE.length());
- body = masterCipher.encryptBody(body);
- type |= (0x8000 | 0x2000);
-
- db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
- new String[] {body, type+"", id+""});
- } else if (body.startsWith(STALE_KEY_EXCHANGE)) {
- body = body.substring(STALE_KEY_EXCHANGE.length());
- body = masterCipher.encryptBody(body);
- type |= (0x8000 | 0x4000);
-
- db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?",
- new String[] {body, type+"", id+""});
- }
- } catch (InvalidMessageException e) {
- Log.w(TAG, e);
- }
- }
-
- skip += ROW_LIMIT;
- } while (smsCursor != null && smsCursor.getCount() > 0);
-
-
-
- Cursor threadCursor = null;
- skip = 0;
-
- do {
- Log.w(TAG, "Looping thread cursor...");
-
- if (threadCursor != null)
- threadCursor.close();
-
- threadCursor = db.query("thread", new String[] {"_id", "snippet_type", "snippet"},
- "snippet_type & " + 0x80000000 + " != 0",
- null, null, null, "_id", skip + "," + ROW_LIMIT);
-
- while (threadCursor != null && threadCursor.moveToNext()) {
- listener.setProgress(smsCount + threadCursor.getPosition(), smsCount + threadCount);
-
- try {
- String snippet = threadCursor.getString(threadCursor.getColumnIndexOrThrow("snippet"));
- long snippetType = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("snippet_type"));
- long id = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id"));
-
- if (!TextUtils.isEmpty(snippet)) {
- snippet = masterCipher.decryptBody(snippet);
- }
-
- if (snippet.startsWith(KEY_EXCHANGE)) {
- snippet = snippet.substring(KEY_EXCHANGE.length());
- snippet = masterCipher.encryptBody(snippet);
- snippetType |= 0x8000;
-
- db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
- new String[] {snippet, snippetType+"", id+""});
- } else if (snippet.startsWith(PROCESSED_KEY_EXCHANGE)) {
- snippet = snippet.substring(PROCESSED_KEY_EXCHANGE.length());
- snippet = masterCipher.encryptBody(snippet);
- snippetType |= (0x8000 | 0x2000);
-
- db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
- new String[] {snippet, snippetType+"", id+""});
- } else if (snippet.startsWith(STALE_KEY_EXCHANGE)) {
- snippet = snippet.substring(STALE_KEY_EXCHANGE.length());
- snippet = masterCipher.encryptBody(snippet);
- snippetType |= (0x8000 | 0x4000);
-
- db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?",
- new String[] {snippet, snippetType+"", id+""});
- }
- } catch (InvalidMessageException e) {
- Log.w(TAG, e);
- }
- }
-
- skip += ROW_LIMIT;
- } while (threadCursor != null && threadCursor.getCount() > 0);
-
- if (smsCursor != null)
- smsCursor.close();
-
- if (threadCursor != null)
- threadCursor.close();
- }
-
- if (fromVersion < DatabaseUpgradeActivity.MMS_BODY_VERSION) {
- Log.w(TAG, "Update MMS bodies...");
- MasterCipher masterCipher = new MasterCipher(masterSecret);
- Cursor mmsCursor = db.query("mms", new String[] {"_id"},
- "msg_box & " + 0x80000000L + " != 0",
- null, null, null, null);
-
- Log.w(TAG, "Got MMS rows: " + (mmsCursor == null ? "null" : mmsCursor.getCount()));
-
- while (mmsCursor != null && mmsCursor.moveToNext()) {
- listener.setProgress(mmsCursor.getPosition(), mmsCursor.getCount());
-
- long mmsId = mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow("_id"));
- String body = null;
- int partCount = 0;
- Cursor partCursor = db.query("part", new String[] {"_id", "ct", "_data", "encrypted"},
- "mid = ?", new String[] {mmsId+""}, null, null, null);
-
- while (partCursor != null && partCursor.moveToNext()) {
- String contentType = partCursor.getString(partCursor.getColumnIndexOrThrow("ct"));
-
- if (ContentType.isTextType(contentType)) {
- try {
- long partId = partCursor.getLong(partCursor.getColumnIndexOrThrow("_id"));
- String dataLocation = partCursor.getString(partCursor.getColumnIndexOrThrow("_data"));
- boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1;
- File dataFile = new File(dataLocation);
-
- InputStream is;
-
- if (encrypted) is = new DecryptingPartInputStream(dataFile, masterSecret);
- else is = new FileInputStream(dataFile);
-
- body = (body == null) ? Util.readFullyAsString(is) : body + " " + Util.readFullyAsString(is);
-
- //noinspection ResultOfMethodCallIgnored
- dataFile.delete();
- db.delete("part", "_id = ?", new String[] {partId+""});
- } catch (IOException e) {
- Log.w(TAG, e);
- }
- } else if (ContentType.isAudioType(contentType) ||
- ContentType.isImageType(contentType) ||
- ContentType.isVideoType(contentType))
- {
- partCount++;
- }
- }
-
- if (!TextUtils.isEmpty(body)) {
- body = masterCipher.encryptBody(body);
- db.execSQL("UPDATE mms SET body = ?, part_count = ? WHERE _id = ?",
- new String[] {body, partCount+"", mmsId+""});
- } else {
- db.execSQL("UPDATE mms SET part_count = ? WHERE _id = ?",
- new String[] {partCount+"", mmsId+""});
- }
-
- Log.w(TAG, "Updated body: " + body + " and part_count: " + partCount);
- }
- }
-
- if (fromVersion < DatabaseUpgradeActivity.TOFU_IDENTITIES_VERSION) {
- File sessionDirectory = new File(context.getFilesDir() + File.separator + "sessions");
-
- if (sessionDirectory.exists() && sessionDirectory.isDirectory()) {
- File[] sessions = sessionDirectory.listFiles();
-
- if (sessions != null) {
- for (File session : sessions) {
- String name = session.getName();
-
- if (name.matches("[0-9]+")) {
- long recipientId = Long.parseLong(name);
- IdentityKey identityKey = null;
- // NOTE (4/21/14) -- At this moment in time, we're forgetting the ability to parse
- // V1 session records. Despite our usual attempts to avoid using shared code in the
- // upgrade path, this is too complex to put here directly. Thus, unfortunately
- // this operation is now lost to the ages. From the git log, it seems to have been
- // almost exactly a year since this went in, so hopefully the bulk of people have
- // already upgraded.
-// IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId);
-
- if (identityKey != null) {
- MasterCipher masterCipher = new MasterCipher(masterSecret);
- String identityKeyString = Base64.encodeBytes(identityKey.serialize());
- String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
- identityKeyString));
-
- db.execSQL("REPLACE INTO identities (recipient, key, mac) VALUES (?, ?, ?)",
- new String[] {recipientId+"", identityKeyString, macString});
- }
- }
- }
- }
- }
- }
-
- if (fromVersion < DatabaseUpgradeActivity.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) {
- if (!MasterSecretUtil.hasAsymmericMasterSecret(context)) {
- MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret);
-
- MasterCipher masterCipher = new MasterCipher(masterSecret);
- Cursor cursor = null;
-
- try {
- cursor = db.query(SmsDatabase.TABLE_NAME,
- new String[] {SmsDatabase.ID, SmsDatabase.BODY, SmsDatabase.TYPE},
- SmsDatabase.TYPE + " & ? == 0",
- new String[] {String.valueOf(SmsDatabase.Types.ENCRYPTION_MASK)},
- null, null, null);
-
- while (cursor.moveToNext()) {
- long id = cursor.getLong(0);
- String body = cursor.getString(1);
- long type = cursor.getLong(2);
-
- String encryptedBody = masterCipher.encryptBody(body);
-
- ContentValues update = new ContentValues();
- update.put(SmsDatabase.BODY, encryptedBody);
- update.put(SmsDatabase.TYPE, type | SmsDatabase.Types.ENCRYPTION_SYMMETRIC_BIT);
-
- db.update(SmsDatabase.TABLE_NAME, update, SmsDatabase.ID + " = ?",
- new String[] {String.valueOf(id)});
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
- }
- }
+ // Do stuff here
db.setTransactionSuccessful();
db.endTransaction();
diff --git a/src/org/smssecure/smssecure/database/MmsSmsDatabase.java b/src/org/smssecure/smssecure/database/MmsSmsDatabase.java
index 6f79bc22fe5789153b194bab6ef616797a6299c4..f30630248bf58e670d2b12237336ad7319a0c0f8 100644
--- a/src/org/smssecure/smssecure/database/MmsSmsDatabase.java
+++ b/src/org/smssecure/smssecure/database/MmsSmsDatabase.java
@@ -23,7 +23,6 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.util.Log;
import org.smssecure.smssecure.crypto.MasterSecret;
import org.smssecure.smssecure.database.model.MessageRecord;
@@ -34,8 +33,6 @@ import java.util.Set;
public class MmsSmsDatabase extends Database {
- private static final String TAG = MmsSmsDatabase.class.getSimpleName();
-
public static final String TRANSPORT = "transport_type";
public static final String MMS_TRANSPORT = "mms";
public static final String SMS_TRANSPORT = "sms";
@@ -246,7 +243,6 @@ public class MmsSmsDatabase extends Database {
@SuppressWarnings("deprecation")
String query = outerQueryBuilder.buildQuery(projection, null, null, null, null, null, null);
- Log.w("MmsSmsDatabase", "Executing query: " + query);
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.rawQuery(query, null);
}
diff --git a/src/org/smssecure/smssecure/database/SmsDatabase.java b/src/org/smssecure/smssecure/database/SmsDatabase.java
index ff077a644abee4d77aecf247d8f21d5d06ec2cd0..c8e5825abd649bf220f0312a4f629cc0b6f06e97 100644
--- a/src/org/smssecure/smssecure/database/SmsDatabase.java
+++ b/src/org/smssecure/smssecure/database/SmsDatabase.java
@@ -396,7 +396,7 @@ public class SmsDatabase extends MessagingDatabase {
if (groupRecipients == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
- ContentValues values = new ContentValues(6);
+ ContentValues values = new ContentValues();
values.put(ADDRESS, message.getSender());
values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
values.put(DATE_RECEIVED, System.currentTimeMillis());
diff --git a/src/org/smssecure/smssecure/jobs/GenerateKeysJob.java b/src/org/smssecure/smssecure/jobs/GenerateKeysJob.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3939a05a67a67e43f86d35d74fe518097cce446
--- /dev/null
+++ b/src/org/smssecure/smssecure/jobs/GenerateKeysJob.java
@@ -0,0 +1,47 @@
+package org.smssecure.smssecure.jobs;
+
+import android.content.Context;
+import android.util.Log;
+
+import org.smssecure.smssecure.crypto.MasterSecret;
+import org.smssecure.smssecure.jobs.requirements.MasterSecretRequirement;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
+import org.whispersystems.jobqueue.JobParameters;
+
+
+import java.util.List;
+
+public class GenerateKeysJob extends MasterSecretJob {
+ private static final String TAG = GenerateKeysJob.class.getSimpleName();
+
+ private List activeSubscriptions;
+
+ public GenerateKeysJob(Context context) {
+ super(context, JobParameters.newBuilder()
+ .withPersistence()
+ .withRequirement(new MasterSecretRequirement(context))
+ .create());
+
+ this.activeSubscriptions = activeSubscriptions;
+ }
+
+ @Override
+ public void onAdded() {}
+
+ @Override
+ public void onRun(MasterSecret masterSecret) {
+ Log.w(TAG, "onRun()");
+ List activeSubscriptions = SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList();
+ DualSimUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptions);
+ }
+
+ @Override
+ public boolean onShouldRetryThrowable(Exception exception) {
+ return false;
+ }
+
+ @Override
+ public void onCanceled() {}
+}
diff --git a/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java b/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java
index cfd295f789d9d5594cfa1d185eb6a6381ade3cb9..6580f87f75e43fde193c718e985cd18518c96089 100644
--- a/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java
+++ b/src/org/smssecure/smssecure/jobs/MmsDownloadJob.java
@@ -119,7 +119,7 @@ public class MmsDownloadJob extends MasterSecretJob {
}
if (retrieveConf.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieveConf.getSubject().getString())) {
- MmsCipher mmsCipher = new MmsCipher(new SilenceSignalProtocolStore(context, masterSecret));
+ MmsCipher mmsCipher = new MmsCipher(new SilenceSignalProtocolStore(context, masterSecret, notification.get().second));
RetrieveConf plaintextPdu = (RetrieveConf) mmsCipher.decrypt(context, retrieveConf);
storeRetrievedMms(masterSecret, contentLocation, messageId, threadId, plaintextPdu, true, notification.get().second);
diff --git a/src/org/smssecure/smssecure/jobs/MmsSendJob.java b/src/org/smssecure/smssecure/jobs/MmsSendJob.java
index 2ae857ac54c332a9c26f73e65749249404c97629..9dd887993af49999e2f48f15a6981fd93c6557d3 100644
--- a/src/org/smssecure/smssecure/jobs/MmsSendJob.java
+++ b/src/org/smssecure/smssecure/jobs/MmsSendJob.java
@@ -23,6 +23,7 @@ import org.smssecure.smssecure.recipients.Recipient;
import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.recipients.RecipientFormattingException;
import org.smssecure.smssecure.transport.UndeliverableMessageException;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
import org.smssecure.smssecure.util.Hex;
import org.smssecure.smssecure.util.NumberUtil;
import org.smssecure.smssecure.util.SmilUtil;
@@ -84,14 +85,14 @@ public class MmsSendJob extends SendJob {
if (message.isSecure()) {
Log.w(TAG, "Encrypting MMS...");
- pdu = getEncryptedMessage(masterSecret, pdu);
+ pdu = getEncryptedMessage(masterSecret, pdu, message.getSubscriptionId());
upgradedSecure = true;
}
validateDestinations(message, pdu);
final byte[] pduBytes = getPduBytes(masterSecret, pdu);
- final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, message.getSubscriptionId());
+ final SendConf sendConf = new CompatMmsConnection(context).send(pduBytes, DualSimUtil.getSubscriptionIdFromAppSubscriptionId(context, message.getSubscriptionId()));
final MmsSendResult result = getSendResult(sendConf, pdu, upgradedSecure);
database.markAsSent(messageId, result.isUpgradedSecure());
@@ -148,11 +149,11 @@ public class MmsSendJob extends SendJob {
}
}
- private SendReq getEncryptedMessage(MasterSecret masterSecret, SendReq pdu)
+ private SendReq getEncryptedMessage(MasterSecret masterSecret, SendReq pdu, int subscriptionId)
throws UndeliverableMessageException
{
try {
- MmsCipher cipher = new MmsCipher(new SilenceSignalProtocolStore(context, masterSecret));
+ MmsCipher cipher = new MmsCipher(new SilenceSignalProtocolStore(context, masterSecret, subscriptionId));
return cipher.encrypt(context, pdu);
} catch (NoSessionException e) {
throw new UndeliverableMessageException(e);
diff --git a/src/org/smssecure/smssecure/jobs/SmsDecryptJob.java b/src/org/smssecure/smssecure/jobs/SmsDecryptJob.java
index c927ded0e1085f40bc1dc2c19d4b13b8c4758ce3..e253326742d16754581c5250896db94c1563aff3 100644
--- a/src/org/smssecure/smssecure/jobs/SmsDecryptJob.java
+++ b/src/org/smssecure/smssecure/jobs/SmsDecryptJob.java
@@ -1,6 +1,7 @@
package org.smssecure.smssecure.jobs;
import android.content.Context;
+import android.os.Build;
import android.util.Log;
import org.smssecure.smssecure.crypto.AsymmetricMasterCipher;
@@ -28,6 +29,8 @@ import org.smssecure.smssecure.sms.IncomingTextMessage;
import org.smssecure.smssecure.sms.IncomingXmppExchangeMessage;
import org.smssecure.smssecure.sms.MessageSender;
import org.smssecure.smssecure.sms.OutgoingKeyExchangeMessage;
+import org.smssecure.smssecure.util.dualsim.SubscriptionInfoCompat;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import org.smssecure.smssecure.util.SilencePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libsignal.DuplicateMessageException;
@@ -40,6 +43,7 @@ import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
+import java.util.List;
public class SmsDecryptJob extends MasterSecretJob {
@@ -127,7 +131,7 @@ public class SmsDecryptJob extends MasterSecretJob {
InvalidMessageException, LegacyMessageException
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
- SmsCipher cipher = new SmsCipher(new SilenceSignalProtocolStore(context, masterSecret));
+ SmsCipher cipher = new SmsCipher(new SilenceSignalProtocolStore(context, masterSecret, message.getSubscriptionId()));
IncomingTextMessage plaintext = cipher.decrypt(context, message);
database.updateMessageBody(masterSecret, messageId, plaintext.getMessageBody());
@@ -143,7 +147,7 @@ public class SmsDecryptJob extends MasterSecretJob {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
try {
- SmsCipher smsCipher = new SmsCipher(new SilenceSignalProtocolStore(context, masterSecret));
+ SmsCipher smsCipher = new SmsCipher(new SilenceSignalProtocolStore(context, masterSecret, message.getSubscriptionId()));
IncomingEncryptedMessage plaintext = smsCipher.decrypt(context, message);
database.updateBundleMessageBody(masterSecret, messageId, plaintext.getMessageBody());
@@ -158,12 +162,12 @@ public class SmsDecryptJob extends MasterSecretJob {
}
private void handleKeyExchangeMessage(MasterSecret masterSecret, long messageId, long threadId,
- IncomingKeyExchangeMessage message)
+ IncomingKeyExchangeMessage message)
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
try {
- SmsCipher cipher = new SmsCipher(new SilenceSignalProtocolStore(context, masterSecret));
+ SmsCipher cipher = new SmsCipher(new SilenceSignalProtocolStore(context, masterSecret, message.getSubscriptionId()));
OutgoingKeyExchangeMessage response = cipher.process(context, message);
if (shouldSend()) {
@@ -209,7 +213,7 @@ public class SmsDecryptJob extends MasterSecretJob {
database.markAsXmppExchange(messageId);
}
- private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
+ private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body, int subscriptionId)
throws InvalidMessageException
{
try {
@@ -228,13 +232,14 @@ public class SmsDecryptJob extends MasterSecretJob {
String plaintextBody = record.getBody().getBody();
if (record.isAsymmetricEncryption()) {
- plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
+ plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody(), record.getSubscriptionId());
}
IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(),
record.getRecipientDeviceId(),
record.getDateSent(),
- plaintextBody);
+ plaintextBody,
+ record.getSubscriptionId());
if (record.isEndSession()) {
return new IncomingEndSessionMessage(message);
diff --git a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java
index 2ae066c71759bf4c746de21326f95108428f3c1f..82c0a3480a50257ec047c996caaeaa14b4fbf642 100644
--- a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java
+++ b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java
@@ -17,6 +17,7 @@ import org.smssecure.smssecure.recipients.Recipients;
import org.smssecure.smssecure.service.KeyCachingService;
import org.smssecure.smssecure.sms.IncomingTextMessage;
import org.smssecure.smssecure.sms.MultipartSmsMessageHandler;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -40,8 +41,11 @@ public class SmsReceiveJob extends ContextJob {
.withWakeLock(true)
.create());
+ Log.w(TAG, "subscriptionId: " + subscriptionId);
+ Log.w(TAG, "Found app subscription ID: " + DualSimUtil.getSubscriptionIdFromDeviceSubscriptionId(context, subscriptionId));
+
this.pdus = pdus;
- this.subscriptionId = subscriptionId;
+ this.subscriptionId = DualSimUtil.getSubscriptionIdFromDeviceSubscriptionId(context, subscriptionId);
}
@Override
@@ -63,6 +67,12 @@ public class SmsReceiveJob extends ContextJob {
{
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
+
+ if (incomingTextMessage.getSender() != null) {
+ Recipients recipients = RecipientFactory.getRecipientsFromString(context, incomingTextMessage.getSender(), false);
+ DatabaseFactory.getRecipientPreferenceDatabase(context)
+ .setDefaultSubscriptionId(recipients, incomingTextMessage.getSubscriptionId());
+ }
} else if (message.isPresent()) {
Log.w(TAG, "*** Received blocked SMS, ignoring...");
}
diff --git a/src/org/smssecure/smssecure/jobs/SmsSendJob.java b/src/org/smssecure/smssecure/jobs/SmsSendJob.java
index 2cae055eec06eddf0a3dbc1dcef8664c006ba587..a37efcfd45fb170dae4b6b12cc2bd857c724ba3a 100644
--- a/src/org/smssecure/smssecure/jobs/SmsSendJob.java
+++ b/src/org/smssecure/smssecure/jobs/SmsSendJob.java
@@ -26,6 +26,7 @@ import org.smssecure.smssecure.service.SmsDeliveryListener;
import org.smssecure.smssecure.sms.MultipartSmsMessageHandler;
import org.smssecure.smssecure.sms.OutgoingTextMessage;
import org.smssecure.smssecure.transport.UndeliverableMessageException;
+import org.smssecure.smssecure.util.dualsim.DualSimUtil;
import org.smssecure.smssecure.util.NumberUtil;
import org.smssecure.smssecure.util.SilencePreferences;
import org.whispersystems.jobqueue.JobParameters;
@@ -82,18 +83,9 @@ public class SmsSendJob extends SendJob {
private void deliver(MasterSecret masterSecret, SmsMessageRecord message)
throws UndeliverableMessageException
- {
- if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
- deliverSecureMessage(masterSecret, message);
- } else {
- deliverPlaintextMessage(message);
- }
- }
-
- private void deliverSecureMessage(MasterSecret masterSecret, SmsMessageRecord message)
- throws UndeliverableMessageException
{
String recipient = message.getIndividualRecipient().getNumber();
+ ArrayList messages;
// See issue #1516 for bug report, and discussion on commits related to #4833 for problems
// related to the original fix to #1516. This still may not be a correct fix if networks allow
@@ -103,74 +95,42 @@ public class SmsSendJob extends SendJob {
recipient = PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(recipient));
}
- MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
- OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
-
- if (message.isSecure() || message.isEndSession()) {
- transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
+ if (!NumberUtil.isValidSmsOrEmail(recipient)) {
+ throw new UndeliverableMessageException("Not a valid SMS destination! " + recipient);
}
- ArrayList messages = SmsManager.getDefault().divideMessage(multipartMessageHandler.getEncodedMessage(transportMessage));
- ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
- ArrayList deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
-
- Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
-
- try {
- getSmsManagerFor(message.getSubscriptionId()).sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
- } catch (NullPointerException npe) {
- Log.w(TAG, npe);
- Log.w(TAG, "Recipient: " + recipient);
- Log.w(TAG, "Message Parts: " + messages.size());
- throw new UndeliverableMessageException(npe);
- } catch (IllegalArgumentException iae) {
- Log.w(TAG, iae);
- throw new UndeliverableMessageException(iae);
- }
- }
-
- private void deliverPlaintextMessage(SmsMessageRecord message)
- throws UndeliverableMessageException
- {
- String recipient = message.getIndividualRecipient().getNumber();
+ if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
+ MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
+ OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
- // See issue #1516 for bug report, and discussion on commits related to #4833 for problems
- // related to the original fix to #1516. This still may not be a correct fix if networks allow
- // SMS/MMS sending to alphanumeric recipients other than email addresses, but should also
- // help to fix issue #3099.
- if (!NumberUtil.isValidEmail(recipient)) {
- recipient = PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(recipient));
- }
+ if (!message.isKeyExchange()) {
+ transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
+ }
- if (!NumberUtil.isValidSmsOrEmail(recipient)) {
- throw new UndeliverableMessageException("Not a valid SMS destination! " + recipient);
+ messages = SmsManager.getDefault().divideMessage(multipartMessageHandler.getEncodedMessage(transportMessage));
+ } else {
+ messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
}
- ArrayList messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
- ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
+ ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
ArrayList deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
+ int deviceSubscriptionId = DualSimUtil.getSubscriptionIdFromAppSubscriptionId(context, message.getSubscriptionId());
+
// NOTE 11/04/14 -- There's apparently a bug where for some unknown recipients
// and messages, this will throw an NPE. We have no idea why, so we're just
// catching it and marking the message as a failure. That way at least it doesn't
// repeatedly crash every time you start the app.
try {
- getSmsManagerFor(message.getSubscriptionId()).sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
+ getSmsManagerFor(deviceSubscriptionId).sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
} catch (NullPointerException npe) {
Log.w(TAG, npe);
Log.w(TAG, "Recipient: " + recipient);
Log.w(TAG, "Message Parts: " + messages.size());
-
- try {
- for (int i=0;i= 22 && subscriptionId != -1) {
return SmsManager.getSmsManagerForSubscriptionId(subscriptionId);
} else {
diff --git a/src/org/smssecure/smssecure/jobs/SmsSentJob.java b/src/org/smssecure/smssecure/jobs/SmsSentJob.java
index b04c5c2eb6ba6fb8f820fd8a066a331ecdfe410e..24b0bfcc7551a6f91919e851bdc24dba426d904d 100644
--- a/src/org/smssecure/smssecure/jobs/SmsSentJob.java
+++ b/src/org/smssecure/smssecure/jobs/SmsSentJob.java
@@ -94,7 +94,7 @@ public class SmsSentJob extends MasterSecretJob {
if (record != null && record.isEndSession()) {
Log.w(TAG, "Ending session...");
- SessionStore sessionStore = new SilenceSessionStore(context, masterSecret);
+ SessionStore sessionStore = new SilenceSessionStore(context, masterSecret, record.getSubscriptionId());
sessionStore.deleteAllSessions(record.getIndividualRecipient().getNumber());
SecurityEvent.broadcastSecurityUpdateEvent(context, record.getThreadId());
}
diff --git a/src/org/smssecure/smssecure/notifications/AndroidAutoHeardReceiver.java b/src/org/smssecure/smssecure/notifications/AndroidAutoHeardReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..0ff6025eadd2733aaf4e6a55e990d86d66a39741
--- /dev/null
+++ b/src/org/smssecure/smssecure/notifications/AndroidAutoHeardReceiver.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (C) 2011 Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.smssecure.smssecure.notifications;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.support.annotation.Nullable;
+import android.support.v4.app.NotificationManagerCompat;
+import android.util.Log;
+
+import org.smssecure.smssecure.crypto.MasterSecret;
+import org.smssecure.smssecure.database.DatabaseFactory;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Marks an Android Auto as read after the driver have listened to it
+ */
+public class AndroidAutoHeardReceiver extends MasterSecretBroadcastReceiver {
+
+ public static final String TAG = AndroidAutoHeardReceiver.class.getSimpleName();
+ public static final String HEARD_ACTION = "org.smssecure.smssecure.notifications.ANDROID_AUTO_HEARD";
+ public static final String THREAD_IDS_EXTRA = "car_heard_thread_ids";
+ public static final String NOTIFICATION_ID_EXTRA = "car_notification_id";
+
+ @Override
+ protected void onReceive(final Context context, Intent intent,
+ @Nullable final MasterSecret masterSecret)
+ {
+ if (!HEARD_ACTION.equals(intent.getAction()))
+ return;
+
+ final long[] threadIds = intent.getLongArrayExtra(THREAD_IDS_EXTRA);
+
+ if (threadIds != null) {
+ int notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1);
+ NotificationManagerCompat.from(context).cancel(notificationId);
+
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ for (long threadId : threadIds) {
+ Log.i(TAG, "Marking message as read: " + threadId);
+ DatabaseFactory.getThreadDatabase(context).setRead(threadId);
+ //DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
+ }
+
+ MessageNotifier.updateNotification(context, masterSecret);
+ return null;
+ }
+ }.execute();
+ }
+ }
+}
diff --git a/src/org/smssecure/smssecure/notifications/AndroidAutoReplyReceiver.java b/src/org/smssecure/smssecure/notifications/AndroidAutoReplyReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb49e85e1bcd29744e8265b2e7f09d9156e76114
--- /dev/null
+++ b/src/org/smssecure/smssecure/notifications/AndroidAutoReplyReceiver.java
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2011 Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.smssecure.smssecure.notifications;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.RemoteInput;
+import android.util.Log;
+
+import org.smssecure.smssecure.attachments.Attachment;
+import org.smssecure.smssecure.crypto.MasterSecret;
+import org.smssecure.smssecure.crypto.SessionUtil;
+import org.smssecure.smssecure.database.DatabaseFactory;
+import org.smssecure.smssecure.database.MessagingDatabase;
+import org.smssecure.smssecure.database.RecipientPreferenceDatabase.RecipientsPreferences;
+import org.smssecure.smssecure.mms.OutgoingMediaMessage;
+import org.smssecure.smssecure.recipients.RecipientFactory;
+import org.smssecure.smssecure.recipients.Recipients;
+import org.smssecure.smssecure.sms.MessageSender;
+import org.smssecure.smssecure.sms.OutgoingEncryptedMessage;
+import org.smssecure.smssecure.sms.OutgoingTextMessage;
+import org.whispersystems.libsignal.util.guava.Optional;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Get the response text from the Android Auto and sends an message as a reply
+ */
+public class AndroidAutoReplyReceiver extends MasterSecretBroadcastReceiver {
+
+ public static final String TAG = AndroidAutoReplyReceiver.class.getSimpleName();
+ public static final String REPLY_ACTION = "org.smssecure.smssecure.notifications.ANDROID_AUTO_REPLY";
+ public static final String RECIPIENT_IDS_EXTRA = "car_recipient_ids";
+ public static final String VOICE_REPLY_KEY = "car_voice_reply_key";
+ public static final String THREAD_ID_EXTRA = "car_reply_thread_id";
+
+ @Override
+ protected void onReceive(final Context context, Intent intent,
+ final @Nullable MasterSecret masterSecret)
+ {
+ if (!REPLY_ACTION.equals(intent.getAction())) return;
+
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+
+ if (remoteInput == null) return;
+
+ final long[] recipientIds = intent.getLongArrayExtra(RECIPIENT_IDS_EXTRA);
+ final long threadId = intent.getLongExtra(THREAD_ID_EXTRA, -1);
+ final CharSequence responseText = getMessageText(intent);
+ final Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientIds, false);
+
+ if (responseText != null) {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... params) {
+
+ long replyThreadId;
+
+ Optional preferences = DatabaseFactory.getRecipientPreferenceDatabase(context).getRecipientsPreferences(recipientIds);
+ int subscriptionId = preferences.isPresent() ? preferences.get().getDefaultSubscriptionId().or(-1) : -1;
+
+ if (recipients.isGroupRecipient()) {
+ Log.i(TAG, "GroupRecipient, Sending media message");
+ OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList(), System.currentTimeMillis(), subscriptionId, 0);
+ replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false);
+ } else {
+ Log.i(TAG, "Sending regular message");
+ boolean secure = SessionUtil.hasSession(context, masterSecret, recipients.getPrimaryRecipient().getNumber(), subscriptionId);
+
+ OutgoingTextMessage reply;
+ if (!secure) {
+ reply = new OutgoingTextMessage(recipients, responseText.toString(), subscriptionId);
+ } else {
+ reply = new OutgoingEncryptedMessage(recipients, responseText.toString(), subscriptionId);
+ }
+
+ replyThreadId = MessageSender.send(context, masterSecret, reply, threadId, false);
+ }
+
+ DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId);
+ //DatabaseFactory.getThreadDatabase(context).setLastSeen(replyThreadId);
+ MessageNotifier.updateNotification(context, masterSecret);
+
+ return null;
+ }
+ }.execute();
+ }
+ }
+
+ private CharSequence getMessageText(Intent intent) {
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ return remoteInput.getCharSequence(VOICE_REPLY_KEY);
+ }
+ return null;
+ }
+
+}
diff --git a/src/org/smssecure/smssecure/notifications/MessageNotifier.java b/src/org/smssecure/smssecure/notifications/MessageNotifier.java
index df873880253d34cd017a4b32da1f7619647f3d70..ad6284eba501a10c14f4f309b69ab60341e10e26 100644
--- a/src/org/smssecure/smssecure/notifications/MessageNotifier.java
+++ b/src/org/smssecure/smssecure/notifications/MessageNotifier.java
@@ -296,6 +296,8 @@ public class MessageNotifier {
notificationState.getMarkAsReadIntent(context, notificationId),
notificationState.getQuickReplyIntent(context, notifications.get(0).getRecipients()),
notificationState.getRemoteReplyIntent(context, notifications.get(0).getRecipients()));
+ builder.addAndroidAutoAction(notificationState.getAndroidAutoReplyIntent(context, notifications.get(0).getRecipients()),
+ notificationState.getAndroidAutoHeardIntent(context, notificationId), notifications.get(0).getTimestamp());
ListIterator iterator = notifications.listIterator(notifications.size());
diff --git a/src/org/smssecure/smssecure/notifications/NotificationState.java b/src/org/smssecure/smssecure/notifications/NotificationState.java
index e2c6b99acd688b7d23661472f1d810f20b19a4a2..bf553bd7cfccee3dba6fc1f7171af0ca69a4ccac 100644
--- a/src/org/smssecure/smssecure/notifications/NotificationState.java
+++ b/src/org/smssecure/smssecure/notifications/NotificationState.java
@@ -127,6 +127,39 @@ public class NotificationState {
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
+ public PendingIntent getAndroidAutoReplyIntent(Context context, Recipients recipients) {
+ if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!");
+
+ Intent intent = new Intent(AndroidAutoReplyReceiver.REPLY_ACTION);
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ intent.setClass(context, AndroidAutoReplyReceiver.class);
+ intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
+ intent.putExtra(AndroidAutoReplyReceiver.RECIPIENT_IDS_EXTRA, recipients.getIds());
+ intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]);
+ intent.setPackage(context.getPackageName());
+
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ public PendingIntent getAndroidAutoHeardIntent(Context context, int notificationId) {
+ long[] threadArray = new long[threads.size()];
+ int index = 0;
+ for (long thread : threads) {
+ Log.w("NotificationState", "getAndroidAutoHeardIntent Added thread: " + thread);
+ threadArray[index++] = thread;
+ }
+
+ Intent intent = new Intent(AndroidAutoHeardReceiver.HEARD_ACTION);
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ intent.setClass(context, AndroidAutoHeardReceiver.class);
+ intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
+ intent.putExtra(AndroidAutoHeardReceiver.THREAD_IDS_EXTRA, threadArray);
+ intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId);
+ intent.setPackage(context.getPackageName());
+
+ return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
public PendingIntent getQuickReplyIntent(Context context, Recipients recipients) {
if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size());
diff --git a/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java b/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java
index d44eb62ce268f4d2f0f4d4b478523ce6f227d590..d94d118b3b6f0fede43df631fab717c695895acd 100644
--- a/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java
+++ b/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java
@@ -75,7 +75,7 @@ public class RemoteReplyReceiver extends MasterSecretBroadcastReceiver {
OutgoingMediaMessage reply = new OutgoingMediaMessage(recipients, responseText.toString(), new LinkedList(), System.currentTimeMillis(), subscriptionId, 0);
threadId = MessageSender.send(context, masterSecret, reply, -1, false);
} else {
- boolean secure = SessionUtil.hasSession(context, masterSecret, recipients.getPrimaryRecipient());
+ boolean secure = SessionUtil.hasSession(context, masterSecret, recipients.getPrimaryRecipient().getNumber(), subscriptionId);
OutgoingTextMessage reply;
if (!secure) {
diff --git a/src/org/smssecure/smssecure/notifications/SingleRecipientNotificationBuilder.java b/src/org/smssecure/smssecure/notifications/SingleRecipientNotificationBuilder.java
index 8f4d0e0eacd5c339b7d6ab5841452133ffcbc9bd..1e103ea2e1444893e6320a8a17c847d417fe8e63 100644
--- a/src/org/smssecure/smssecure/notifications/SingleRecipientNotificationBuilder.java
+++ b/src/org/smssecure/smssecure/notifications/SingleRecipientNotificationBuilder.java
@@ -99,6 +99,27 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
}
}
+ public void addAndroidAutoAction(@NonNull PendingIntent androidAutoReplyIntent,
+ @NonNull PendingIntent androidAutoHeardIntent, long timestamp)
+ {
+
+ if (mContentTitle == null || mContentText == null)
+ return;
+
+ RemoteInput remoteInput = new RemoteInput.Builder(AndroidAutoReplyReceiver.VOICE_REPLY_KEY)
+ .setLabel(context.getString(R.string.MessageNotifier_reply))
+ .build();
+
+ NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
+ new NotificationCompat.CarExtender.UnreadConversation.Builder(mContentTitle.toString())
+ .addMessage(mContentText.toString())
+ .setLatestTimestamp(timestamp)
+ .setReadPendingIntent(androidAutoHeardIntent)
+ .setReplyAction(androidAutoReplyIntent, remoteInput);
+
+ extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build()));
+ }
+
public void addActions(@Nullable MasterSecret masterSecret,
@NonNull PendingIntent markReadIntent,
@NonNull PendingIntent quickReplyIntent,
diff --git a/src/org/smssecure/smssecure/protocol/AutoInitiate.java b/src/org/smssecure/smssecure/protocol/AutoInitiate.java
index b0063be90fd8851445713aaa643f6c8ac7615fc2..be6cf1986071550eb4ecc537877149d907e7b336 100644
--- a/src/org/smssecure/smssecure/protocol/AutoInitiate.java
+++ b/src/org/smssecure/smssecure/protocol/AutoInitiate.java
@@ -14,6 +14,7 @@ import org.smssecure.smssecure.crypto.MasterSecret;
import org.smssecure.smssecure.crypto.SessionUtil;
import org.smssecure.smssecure.recipients.Recipient;
import org.smssecure.smssecure.recipients.Recipients;
+import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat;
import java.util.Locale;
@@ -89,7 +90,7 @@ public class AutoInitiate {
MasterSecret masterSecret,
Recipient recipient)
{
- return !SessionUtil.hasSession(context, masterSecret, recipient);
+ return !SessionUtil.hasSession(context, masterSecret, recipient.getNumber(), SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList());
}
}
diff --git a/src/org/smssecure/smssecure/recipients/Recipients.java b/src/org/smssecure/smssecure/recipients/Recipients.java
index 06d68faeb94a5bd9cc60922230dd58e907a9aad8..da4b2437bf254ef559384922d1a2f5f83d321aec 100644
--- a/src/org/smssecure/smssecure/recipients/Recipients.java
+++ b/src/org/smssecure/smssecure/recipients/Recipients.java
@@ -50,11 +50,12 @@ public class Recipients implements Iterable, RecipientModifiedListene
private final Set listeners = Collections.newSetFromMap(new WeakHashMap());
private final List recipients;
- private Uri ringtone = null;
- private long mutedUntil = 0;
- private boolean blocked = false;
- private VibrateState vibrate = VibrateState.DEFAULT;
- private boolean stale = false;
+ private Uri ringtone = null;
+ private long mutedUntil = 0;
+ private boolean blocked = false;
+ private VibrateState vibrate = VibrateState.DEFAULT;
+ private boolean stale = false;
+ private int defaultSubscriptionId = -1;
Recipients() {
this(new LinkedList(), null);
@@ -64,10 +65,11 @@ public class Recipients implements Iterable, RecipientModifiedListene
this.recipients = recipients;
if (preferences != null) {
- ringtone = preferences.getRingtone();
- mutedUntil = preferences.getMuteUntil();
- vibrate = preferences.getVibrateState();
- blocked = preferences.isBlocked();
+ ringtone = preferences.getRingtone();
+ mutedUntil = preferences.getMuteUntil();
+ vibrate = preferences.getVibrateState();
+ blocked = preferences.isBlocked();
+ defaultSubscriptionId = preferences.getDefaultSubscriptionId().or(-1);
}
}
@@ -78,10 +80,11 @@ public class Recipients implements Iterable, RecipientModifiedListene
this.recipients = recipients;
if (stale != null) {
- ringtone = stale.ringtone;
- mutedUntil = stale.mutedUntil;
- vibrate = stale.vibrate;
- blocked = stale.blocked;
+ ringtone = stale.ringtone;
+ mutedUntil = stale.mutedUntil;
+ vibrate = stale.vibrate;
+ blocked = stale.blocked;
+ defaultSubscriptionId = stale.defaultSubscriptionId;
}
preferences.addListener(new FutureTaskListener() {
@@ -92,10 +95,11 @@ public class Recipients implements Iterable, RecipientModifiedListene
Set localListeners;
synchronized (Recipients.this) {
- ringtone = result.getRingtone();
- mutedUntil = result.getMuteUntil();
- vibrate = result.getVibrateState();
- blocked = result.isBlocked();
+ ringtone = result.getRingtone();
+ mutedUntil = result.getMuteUntil();
+ vibrate = result.getVibrateState();
+ blocked = result.isBlocked();
+ defaultSubscriptionId = result.getDefaultSubscriptionId().or(-1);
localListeners = new HashSet<>(listeners);
}
@@ -161,6 +165,18 @@ public class Recipients implements Iterable, RecipientModifiedListene
notifyListeners();
}
+ public synchronized int getDefaultSubscriptionId() {
+ return defaultSubscriptionId;
+ }
+
+ public void setDefaultSubscriptionId(int defaultSubscriptionId) {
+ synchronized (this) {
+ this.defaultSubscriptionId = defaultSubscriptionId;
+ }
+
+ notifyListeners();
+ }
+
public @NonNull ContactPhoto getContactPhoto() {
if (recipients.size() == 1) return recipients.get(0).getContactPhoto();
else return ContactPhotoFactory.getDefaultGroupPhoto();
diff --git a/src/org/smssecure/smssecure/sms/IncomingTextMessage.java b/src/org/smssecure/smssecure/sms/IncomingTextMessage.java
index efe5df40d4ae8ddc2dbc3632b5cfdb60c06cbf5b..68603b7e07dc8c799755193cba45d8d0f30043cf 100644
--- a/src/org/smssecure/smssecure/sms/IncomingTextMessage.java
+++ b/src/org/smssecure/smssecure/sms/IncomingTextMessage.java
@@ -54,7 +54,7 @@ public class IncomingTextMessage implements Parcelable {
this.receivedWhenLocked = receivedWhenLocked;
}
- public IncomingTextMessage(String sender, int senderDeviceId, long sentTimestampMillis, String encodedBody) {
+ public IncomingTextMessage(String sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, int subscriptionId) {
this.message = encodedBody;
this.sender = sender;
this.senderDeviceId = senderDeviceId;
@@ -64,8 +64,8 @@ public class IncomingTextMessage implements Parcelable {
this.pseudoSubject = "";
this.sentTimestampMillis = sentTimestampMillis;
this.push = true;
- this.subscriptionId = -1;
- this.groupId = null;
+ this.subscriptionId = subscriptionId;
+ this.groupId = null;
this.receivedWhenLocked = false;
}
diff --git a/src/org/smssecure/smssecure/sms/MessageSender.java b/src/org/smssecure/smssecure/sms/MessageSender.java
index ec0ec156ca19cab08e2d848e86f32ecafbbc7edf..37acb33ebba90af65231f460567447cf0427a6d0 100644
--- a/src/org/smssecure/smssecure/sms/MessageSender.java
+++ b/src/org/smssecure/smssecure/sms/MessageSender.java
@@ -55,7 +55,6 @@ public class MessageSender {
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
Recipients recipients = message.getRecipients();
- boolean keyExchange = message.isKeyExchange();
long allocatedThreadId;
diff --git a/src/org/smssecure/smssecure/util/DummyCharacterCalculator.java b/src/org/smssecure/smssecure/util/DummyCharacterCalculator.java
new file mode 100644
index 0000000000000000000000000000000000000000..7921f475062fd2cf74944133e74af4901f9858d3
--- /dev/null
+++ b/src/org/smssecure/smssecure/util/DummyCharacterCalculator.java
@@ -0,0 +1,9 @@
+package org.smssecure.smssecure.util;
+
+public class DummyCharacterCalculator extends CharacterCalculator {
+
+ @Override
+ public CharacterState calculateCharacters(String messageBody) {
+ return new CharacterState(0, 0, 0);
+ }
+}
diff --git a/src/org/smssecure/smssecure/util/SilencePreferences.java b/src/org/smssecure/smssecure/util/SilencePreferences.java
index ed04c9f2757566ad0ed453bc3f8ff21982674a63..6ccf2e2bb68d5b908b1116ba1d3fa5fa082c4ced 100644
--- a/src/org/smssecure/smssecure/util/SilencePreferences.java
+++ b/src/org/smssecure/smssecure/util/SilencePreferences.java
@@ -97,9 +97,14 @@ public class SilencePreferences {
private static final String MEDIA_DOWNLOAD_ROAMING_PREF = "pref_media_download_roaming";
public static final String SYSTEM_EMOJI_PREF = "pref_system_emoji";
-
public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard";
+ private static final String APP_SUBSCRIPTION_ID_FOR_DEVICE_SUBSCRIPTION_ID_PREF = "app_subscription_id_for_device_subscription_id";
+ private static final String LAST_APP_SUBSCRIPTION_ID_PREF = "last_app_subscription_id";
+ private static final String NUMBER_FOR_APP_SUBSCRIPTION_ID_PREF = "number_for_app_subscription_id";
+ private static final String ICC_ID_FOR_APP_SUBSCRIPTION_ID_PREF = "icc_id_for_app_subscription_id";
+ private static final String SUBSCRIPTIONS_PREF = "pref_subscriptions";
+
public static boolean isIncognitoKeyboardEnabled(Context context) {
return getBooleanPreference(context, INCOGNITO_KEYBORAD_PREF, true);
}
@@ -159,7 +164,7 @@ public class SilencePreferences {
public static void setGcmRegistrationId(Context context, String registrationId) {
setStringPreference(context, GCM_REGISTRATION_ID_PREF, registrationId);
- setIntegerPrefrence(context, GCM_REGISTRATION_ID_VERSION_PREF, Util.getCurrentApkReleaseVersion(context));
+ setIntegerPreference(context, GCM_REGISTRATION_ID_VERSION_PREF, Util.getCurrentApkReleaseVersion(context));
}
public static String getGcmRegistrationId(Context context) {
@@ -217,7 +222,7 @@ public class SilencePreferences {
}
public static void setLocalRegistrationId(Context context, int registrationId) {
- setIntegerPrefrence(context, LOCAL_REGISTRATION_ID_PREF, registrationId);
+ setIntegerPreference(context, LOCAL_REGISTRATION_ID_PREF, registrationId);
}
public static boolean isInThreadNotifications(Context context) {
@@ -385,7 +390,7 @@ public class SilencePreferences {
}
public static void setLastVersionCode(Context context, int versionCode) throws IOException {
- if (!setIntegerPrefrenceBlocking(context, LAST_VERSION_CODE_PREF, versionCode)) {
+ if (!setIntegerPreferenceBlocking(context, LAST_VERSION_CODE_PREF, versionCode)) {
throw new IOException("couldn't write version code to sharedpreferences");
}
}
@@ -436,7 +441,7 @@ public class SilencePreferences {
}
public static void setPassphraseTimeoutInterval(Context context, int interval) {
- setIntegerPrefrence(context, PASSPHRASE_TIMEOUT_INTERVAL_PREF, interval);
+ setIntegerPreference(context, PASSPHRASE_TIMEOUT_INTERVAL_PREF, interval);
}
public static String getLanguage(Context context) {
@@ -559,6 +564,46 @@ public class SilencePreferences {
return getBooleanPreference(context, HIDE_UNREAD_MESSAGE_DIVIDER, false);
}
+ public static int getLastAppSubscriptionId(Context context) {
+ return getIntegerPreference(context, LAST_APP_SUBSCRIPTION_ID_PREF, 0);
+ }
+
+ public static void setLastAppSubscriptionId(Context context, int appSubscriptionId) {
+ setIntegerPreference(context, LAST_APP_SUBSCRIPTION_ID_PREF, appSubscriptionId);
+ }
+ public static int getAppSubscriptionId(Context context, int deviceSubscriptionId) {
+ return getIntegerPreference(context, APP_SUBSCRIPTION_ID_FOR_DEVICE_SUBSCRIPTION_ID_PREF + "_" + deviceSubscriptionId, -1);
+ }
+
+ public static void setAppSubscriptionId(Context context, int deviceSubscriptionId, int appSubscriptionId) {
+ setIntegerPreference(context, APP_SUBSCRIPTION_ID_FOR_DEVICE_SUBSCRIPTION_ID_PREF + "_" + appSubscriptionId, deviceSubscriptionId);
+ if (appSubscriptionId > getLastAppSubscriptionId(context)) setLastAppSubscriptionId(context, appSubscriptionId);
+ }
+
+ public static void setNumberForSubscriptionId(Context context, int subscriptionId, String number) {
+ setStringPreference(context, NUMBER_FOR_APP_SUBSCRIPTION_ID_PREF + "_" + subscriptionId, number);
+ }
+
+ public static String getNumberForSubscriptionId(Context context, int subscriptionId) {
+ return getStringPreference(context, NUMBER_FOR_APP_SUBSCRIPTION_ID_PREF + "_" + subscriptionId, null);
+ }
+
+ public static void setIccIdForSubscriptionId(Context context, int subscriptionId, String iccId) {
+ setStringPreference(context, ICC_ID_FOR_APP_SUBSCRIPTION_ID_PREF + "_" + subscriptionId, iccId);
+ }
+
+ public static String getIccIdForSubscriptionId(Context context, int subscriptionId) {
+ return getStringPreference(context, ICC_ID_FOR_APP_SUBSCRIPTION_ID_PREF + "_" + subscriptionId, null);
+ }
+
+ public static void setDeviceSubscriptions(Context context, String subscriptions) {
+ setStringPreference(context, SUBSCRIPTIONS_PREF, subscriptions);
+ }
+
+ public static String getDeviceSubscriptions(Context context) {
+ return getStringPreference(context, SUBSCRIPTIONS_PREF, "");
+ }
+
public static void setBooleanPreference(Context context, String key, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}
@@ -579,11 +624,11 @@ public class SilencePreferences {
return PreferenceManager.getDefaultSharedPreferences(context).getInt(key, defaultValue);
}
- private static void setIntegerPrefrence(Context context, String key, int value) {
+ private static void setIntegerPreference(Context context, String key, int value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(key, value).apply();
}
- private static boolean setIntegerPrefrenceBlocking(Context context, String key, int value) {
+ private static boolean setIntegerPreferenceBlocking(Context context, String key, int value) {
return PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(key, value).commit();
}
diff --git a/src/org/smssecure/smssecure/util/VersionTracker.java b/src/org/smssecure/smssecure/util/VersionTracker.java
index 8c45a32f005a790da8011a345501ebbac301d428..f9816313eb09c79201757154e3b0ded8ce35ccc8 100644
--- a/src/org/smssecure/smssecure/util/VersionTracker.java
+++ b/src/org/smssecure/smssecure/util/VersionTracker.java
@@ -1,12 +1,13 @@
package org.smssecure.smssecure.util;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.pm.PackageInfo;
+import android.util.Log;
import java.io.IOException;
public class VersionTracker {
-
+ private static final String TAG = VersionTracker.class.getSimpleName();
public static int getLastSeenVersion(Context context) {
return SilencePreferences.getLastVersionCode(context);
@@ -20,4 +21,16 @@ public class VersionTracker {
throw new AssertionError(ioe);
}
}
+
+ public static boolean isDbUpdated(Context context) {
+ try {
+ PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+ if (packageInfo == null) return true;
+
+ return SilencePreferences.getLastVersionCode(context) >= packageInfo.versionCode;
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ return true;
+ }
+ }
}
diff --git a/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..a67701fb1fa9b9374bab030e93cd632c0bfdfcbc
--- /dev/null
+++ b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java
@@ -0,0 +1,131 @@
+package org.smssecure.smssecure.util.dualsim;
+
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.support.v4.app.NotificationCompat;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import org.smssecure.smssecure.crypto.IdentityKeyUtil;
+import org.smssecure.smssecure.crypto.MasterSecret;
+import org.smssecure.smssecure.crypto.MasterSecretUtil;
+import org.smssecure.smssecure.crypto.storage.SilenceSessionStore;
+import org.smssecure.smssecure.R;
+import org.smssecure.smssecure.util.ServiceUtil;
+import org.smssecure.smssecure.util.SilencePreferences;
+import org.whispersystems.libsignal.util.guava.Optional;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DualSimUtil {
+ private static final String TAG = DualSimUtil.class.getSimpleName();
+
+ private static final int NOTIFICATION_ID = 1340;
+
+ public static void moveIdentityKeysAndSessionsToSubscriptionId(Context context, int originalSubscriptionId, int subscriptionId) {
+ Log.w(TAG, "moveIdentityKeysMasterSecretAndSessionsToSubscriptionId(" + originalSubscriptionId + ", " + subscriptionId + ")");
+
+ moveIdentityKeysToSubscriptionId(context, originalSubscriptionId, subscriptionId);
+ moveSessionsToSubscriptionId(context, originalSubscriptionId, subscriptionId);
+ }
+
+ private static void moveIdentityKeysToSubscriptionId(Context context, int originalSubscriptionId, int subscriptionId) {
+ String originalIdentityPublicPref = IdentityKeyUtil.getIdentityPublicKeyDjbPref(originalSubscriptionId);
+ String identityPublicPref = IdentityKeyUtil.getIdentityPublicKeyDjbPref(subscriptionId);
+ String originalIdentityPrivatePref = IdentityKeyUtil.getIdentityPrivateKeyDjbPref(originalSubscriptionId);
+ String identityPrivatePref = IdentityKeyUtil.getIdentityPrivateKeyDjbPref(subscriptionId);
+
+ Log.w(TAG, "Moving " + originalIdentityPublicPref + " to " + identityPublicPref);
+ Log.w(TAG, "Moving " + originalIdentityPrivatePref + " to " + identityPrivatePref);
+
+ String identityPublicKey = IdentityKeyUtil.retrieve(context, originalIdentityPublicPref);
+ String identityPrivateKey = IdentityKeyUtil.retrieve(context, originalIdentityPrivatePref);
+
+ IdentityKeyUtil.save(context, identityPublicPref, identityPublicKey);
+ IdentityKeyUtil.save(context, identityPrivatePref, identityPrivateKey);
+
+ IdentityKeyUtil.remove(context, originalIdentityPublicPref);
+ IdentityKeyUtil.remove(context, originalIdentityPrivatePref);
+ }
+
+ private static void moveSessionsToSubscriptionId(Context context, int originalSubscriptionId, int subscriptionId) {
+ File sessionDirectory = SilenceSessionStore.getSessionDirectory(context);
+
+ File[] sessionList = sessionDirectory.listFiles();
+
+ String destinationSuffix = subscriptionId != -1 ? "." + subscriptionId : "";
+
+ for (File session : sessionList){
+ if (session.isFile()){
+ String absolutePath = session.getAbsolutePath();
+ String newSessionName = null;
+
+ if (originalSubscriptionId != -1 && absolutePath.endsWith("." + originalSubscriptionId)) {
+ newSessionName = absolutePath.replaceAll("/\\." + originalSubscriptionId + "/g", destinationSuffix);
+ } else if (originalSubscriptionId == -1) {
+ newSessionName = absolutePath + destinationSuffix;
+ }
+
+ if (newSessionName != null) {
+ Log.w(TAG, "Moving session " + absolutePath + " to " + newSessionName);
+ File newFile = new File(newSessionName);
+ if (session.renameTo(newFile)) {
+ Log.w(TAG, "Done!");
+ } else {
+ Log.w(TAG, "Failed!");
+ }
+ }
+
+ }
+ }
+ }
+
+ public static void generateKeysIfDoNotExist(Context context, MasterSecret masterSecret, List activeSubscriptions) {
+ generateKeysIfDoNotExist(context, masterSecret, activeSubscriptions, true);
+ }
+
+ public static void generateKeysIfDoNotExist(Context context, MasterSecret masterSecret, List activeSubscriptions, boolean displayNotification) {
+ for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) {
+ int subscriptionId = subscriptionInfo.getSubscriptionId();
+
+ if (!IdentityKeyUtil.hasIdentityKey(context, subscriptionId))
+ IdentityKeyUtil.generateIdentityKeys(context, masterSecret, subscriptionId, displayNotification);
+ }
+ }
+
+ public static int getSubscriptionIdFromAppSubscriptionId(Context context, int appSubscriptionId) {
+ Optional subscriptionInfo = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfo(appSubscriptionId);
+ if (subscriptionInfo.isPresent()) return subscriptionInfo.get().getDeviceSubscriptionId();
+ else return -1;
+ }
+
+ public static int getSubscriptionIdFromDeviceSubscriptionId(Context context, int deviceSubscriptionId) {
+ Optional subscriptionInfo = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoFromDeviceSubscriptionId(deviceSubscriptionId);
+ if (subscriptionInfo.isPresent()) return subscriptionInfo.get().getSubscriptionId();
+ else return -1;
+ }
+
+ public static void displayNotification(Context context) {
+ Intent targetIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
+ Notification notification = new NotificationCompat.Builder(context)
+ .setSmallIcon(R.drawable.icon_notification)
+ .setColor(context.getResources().getColor(R.color.silence_primary))
+ .setContentTitle(context.getString(R.string.DualSimUtil__new_sim_card_detected))
+ .setContentText(context.getString(R.string.DualSimUtil__a_new_key_has_been_generated))
+ .setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.DualSimUtil__a_new_key_has_been_generated_for_that_new_sim_card)))
+ .setAutoCancel(true)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setContentIntent(PendingIntent.getActivity(context.getApplicationContext(), 0,
+ targetIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT))
+ .build();
+ ServiceUtil.getNotificationManager(context).notify(NOTIFICATION_ID, notification);
+ }
+}
diff --git a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..96ae380b7c48b02c5af0dd94ce5bfc43d2d9c499
--- /dev/null
+++ b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java
@@ -0,0 +1,85 @@
+package org.smssecure.smssecure.util.dualsim;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import org.smssecure.smssecure.ApplicationContext;
+import org.smssecure.smssecure.util.SilencePreferences;
+import org.smssecure.smssecure.util.VersionTracker;
+import org.smssecure.smssecure.jobs.GenerateKeysJob;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class SimChangedReceiver extends BroadcastReceiver {
+ private static final String TAG = SimChangedReceiver.class.getSimpleName();
+
+ private static final String SIM_STATE_CHANGED_EVENT = "android.intent.action.SIM_STATE_CHANGED";
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Log.w(TAG, "onReceive()");
+
+ if (intent.getAction().equals(SIM_STATE_CHANGED_EVENT)) {
+ checkSimState(context);
+ }
+ }
+
+ public static void checkSimState(final Context context) {
+ if (hasDifferentSubscriptions(context) && VersionTracker.isDbUpdated(context)) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new GenerateKeysJob(context));
+ SilencePreferences.setDeviceSubscriptions(context, getDeviceSubscriptions(context));
+ }
+ SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList();
+ }
+
+ private static boolean hasDifferentSubscriptions(Context context) {
+ String subscriptions = getDeviceSubscriptions(context);
+ String registeredSubscriptions = getActiveDeviceSubscriptionIds(context);
+
+ Log.w(TAG, "getDeviceSubscriptions(): " + getDeviceSubscriptions(context));
+ Log.w(TAG, "getActiveDeviceSubscriptionIds(): " + getActiveDeviceSubscriptionIds(context));
+
+ return !subscriptions.equals(registeredSubscriptions);
+ }
+
+ private static String getDeviceSubscriptions(Context context) {
+ if (Build.VERSION.SDK_INT < 22) return "1";
+
+ SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+ List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList();
+
+ if (activeSubscriptions == null) return "1";
+
+ String[] subscriptions = new String[activeSubscriptions.size()];
+ for(int i=0; i displayNameList;
+ private List compatList;
- public SubscriptionManagerCompat(Context context) {
+ public static SubscriptionManagerCompat from(Context context) {
+ if (instance == null) {
+ instance = new SubscriptionManagerCompat(context);
+ }
+ return instance;
+ }
+
+ private SubscriptionManagerCompat(Context context) {
this.context = context.getApplicationContext();
+ this.displayNameList = new LinkedList();
}
public Optional getActiveSubscriptionInfo(int subscriptionId) {
- if (Build.VERSION.SDK_INT < 22) {
+ if (getActiveSubscriptionInfoList().size() <= 0) {
return Optional.absent();
}
- SubscriptionInfo subscriptionInfo = SubscriptionManager.from(context).getActiveSubscriptionInfo(subscriptionId);
+ for (SubscriptionInfoCompat subscriptionInfo : getActiveSubscriptionInfoList()) {
+ if (subscriptionInfo.getSubscriptionId() == subscriptionId) return Optional.of(subscriptionInfo);
+ }
- if (subscriptionInfo != null) {
- return Optional.of(new SubscriptionInfoCompat(subscriptionId, subscriptionInfo.getDisplayName()));
- } else {
+ return Optional.absent();
+ }
+
+ public Optional getActiveSubscriptionInfoFromDeviceSubscriptionId(int subscriptionId) {
+ if (getActiveSubscriptionInfoList().size() <= 0) {
return Optional.absent();
}
+
+ for (SubscriptionInfoCompat subscriptionInfo : getActiveSubscriptionInfoList()) {
+ if (subscriptionInfo.getDeviceSubscriptionId() == subscriptionId) return Optional.of(subscriptionInfo);
+ }
+
+ return Optional.absent();
+ }
+
+ @TargetApi(22)
+ private void updateDisplayNameList(List activeSubscriptions) {
+ displayNameList = new LinkedList();
+
+ if (activeSubscriptions != null) {
+ for (SubscriptionInfo subscriptionInfo : activeSubscriptions) {
+ displayNameList.add(subscriptionInfo.getDisplayName().toString());
+ }
+ }
+ }
+
+ public boolean knowThisDisplayNameTwice(CharSequence displayName) {
+ if (displayName == null) return false;
+
+ boolean found = false;
+
+ for (String potentialDisplayName : displayNameList) {
+ if (found && potentialDisplayName != null && potentialDisplayName.equals(displayName.toString()))
+ return true;
+ if (potentialDisplayName != null && potentialDisplayName.equals(displayName.toString()))
+ found = true;
+ }
+ return false;
}
public @NonNull List getActiveSubscriptionInfoList() {
+ if (compatList == null) return updateActiveSubscriptionInfoList();
+ return compatList;
+ }
+
+ public @NonNull List updateActiveSubscriptionInfoList() {
+ compatList = new LinkedList<>();
+
if (Build.VERSION.SDK_INT < 22) {
- return new LinkedList<>();
+ TelephonyManager telephonyManager = ServiceUtil.getTelephonyManager(context);
+ compatList.add(new SubscriptionInfoCompat(context,
+ -1,
+ telephonyManager.getSimOperatorName(),
+ telephonyManager.getLine1Number(),
+ telephonyManager.getSimSerialNumber(),
+ 1,
+ false));
+ return compatList;
}
List subscriptionInfos = SubscriptionManager.from(context).getActiveSubscriptionInfoList();
+ updateDisplayNameList(subscriptionInfos);
if (subscriptionInfos == null || subscriptionInfos.isEmpty()) {
- return new LinkedList<>();
+ return compatList;
}
- List compatList = new LinkedList<>();
-
for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
- compatList.add(new SubscriptionInfoCompat(subscriptionInfo.getSubscriptionId(),
- subscriptionInfo.getDisplayName()));
+ compatList.add(new SubscriptionInfoCompat(context,
+ subscriptionInfo.getSubscriptionId(),
+ subscriptionInfo.getDisplayName(),
+ subscriptionInfo.getNumber(),
+ subscriptionInfo.getIccId(),
+ subscriptionInfo.getSimSlotIndex()+1,
+ knowThisDisplayNameTwice(subscriptionInfo.getDisplayName())));
}
return compatList;