From 060277c29b7148422fcd689aa0d8b00a7be7f098 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Tue, 21 Feb 2017 11:56:07 +0100 Subject: [PATCH 01/46] improve multi-SIM support * Each SIM card is now bound to a unique key pair. * Display number or slot index if display name is the same for multiple cards. * Use subcription ID of last received message as default SIM, but do not change it if user is in conversation. * Update app subscription IDs on new phone * Start secure session only for SIM which received message, allow user to select SIM when starting a secure session Fixes #467 Fixes #497 --- AndroidManifest.xml | 5 + res/layout/view_identity_activity.xml | 15 +- res/menu/conversation_insecure_no_push.xml | 4 + res/menu/conversation_secure_identity.xml | 7 +- res/menu/conversation_secure_sms.xml | 14 +- res/menu/text_secure_normal.xml | 6 + res/values/strings.xml | 5 + .../smssecure/ApplicationContext.java | 8 + .../smssecure/ConversationActivity.java | 191 +++++++++--- .../smssecure/smssecure/ConversationItem.java | 4 +- .../smssecure/ConversationListActivity.java | 45 ++- .../smssecure/ConversationListFragment.java | 4 +- .../smssecure/DatabaseUpgradeActivity.java | 92 ++---- .../smssecure/PassphraseCreateActivity.java | 15 +- .../smssecure/smssecure/ReceiveKeyDialog.java | 9 +- .../smssecure/smssecure/TransportOptions.java | 13 +- .../smssecure/VerifyIdentityActivity.java | 18 +- .../smssecure/ViewIdentityActivity.java | 38 ++- .../smssecure/ViewLocalIdentityActivity.java | 66 ----- .../smssecure/components/AvatarImageView.java | 10 +- .../smssecure/components/SendButton.java | 12 + .../smssecure/crypto/IdentityKeyUtil.java | 70 +++-- .../crypto/KeyExchangeInitiator.java | 39 ++- .../smssecure/crypto/PreKeyUtil.java | 210 ------------- .../smssecure/crypto/SessionUtil.java | 48 ++- .../storage/SilenceIdentityKeyStore.java | 10 +- .../crypto/storage/SilencePreKeyStore.java | 14 +- .../crypto/storage/SilenceSessionStore.java | 24 +- .../storage/SilenceSignalProtocolStore.java | 14 +- .../smssecure/database/DatabaseFactory.java | 276 +----------------- .../smssecure/database/SmsDatabase.java | 2 +- .../smssecure/jobs/CheckSimStateJob.java | 49 ++++ .../smssecure/jobs/MmsDownloadJob.java | 2 +- .../smssecure/smssecure/jobs/MmsSendJob.java | 6 +- .../smssecure/jobs/SmsDecryptJob.java | 19 +- .../smssecure/jobs/SmsReceiveJob.java | 6 + .../smssecure/smssecure/jobs/SmsSendJob.java | 80 ++--- .../smssecure/smssecure/jobs/SmsSentJob.java | 2 +- .../notifications/RemoteReplyReceiver.java | 2 +- .../smssecure/protocol/AutoInitiate.java | 3 +- .../smssecure/recipients/Recipients.java | 50 ++-- .../smssecure/sms/IncomingTextMessage.java | 6 +- .../smssecure/sms/MessageSender.java | 1 - .../smssecure/util/SilencePreferences.java | 59 +++- .../util/dualsim/DualSimUpgradeUtil.java | 196 +++++++++++++ .../util/dualsim/SimChangedReceiver.java | 84 ++++++ .../util/dualsim/SubscriptionInfoCompat.java | 63 +++- .../dualsim/SubscriptionManagerCompat.java | 90 +++++- 48 files changed, 1141 insertions(+), 865 deletions(-) delete mode 100644 src/org/smssecure/smssecure/ViewLocalIdentityActivity.java delete mode 100644 src/org/smssecure/smssecure/crypto/PreKeyUtil.java create mode 100644 src/org/smssecure/smssecure/jobs/CheckSimStateJob.java create mode 100644 src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java create mode 100644 src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c57c8280..e532cf7e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -54,6 +54,11 @@ android:resource="@xml/badge_widget_provider"/> + + + + + diff --git a/res/layout/view_identity_activity.xml b/res/layout/view_identity_activity.xml index 1bc52c3a..71477d18 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 5c5b77c4..5a78c17d 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 a1623b62..75722bb9 100644 --- a/res/menu/conversation_secure_identity.xml +++ b/res/menu/conversation_secure_identity.xml @@ -6,7 +6,12 @@ app:showAsAction="ifRoom"> + android:id="@+id/menu_verify_identity" /> + + + + diff --git a/res/menu/conversation_secure_sms.xml b/res/menu/conversation_secure_sms.xml index d30655c6..ce947958 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 1114e916..44f72c1b 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 df4905a5..c14f0600 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -439,6 +439,11 @@ Silence New message + + Slot %1$s + %1$s (slot %2$s) + + Old passphrase New passphrase diff --git a/src/org/smssecure/smssecure/ApplicationContext.java b/src/org/smssecure/smssecure/ApplicationContext.java index e814509b..7821b4b8 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; @@ -60,6 +63,7 @@ public class ApplicationContext extends Application implements DependencyInjecto initializeRandomNumberFix(); initializeLogging(); initializeJobManager(); + checkSimState(); } @Override @@ -98,4 +102,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 88d79842..5cf452a0 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 (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { + int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : 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 (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { + int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : 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 (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { + int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : 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 { diff --git a/src/org/smssecure/smssecure/ConversationItem.java b/src/org/smssecure/smssecure/ConversationItem.java index ed8c9bc2..dc740a80 100644 --- a/src/org/smssecure/smssecure/ConversationItem.java +++ b/src/org/smssecure/smssecure/ConversationItem.java @@ -398,7 +398,7 @@ 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) { simInfoText.setVisibility(View.GONE); @@ -513,7 +513,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(); diff --git a/src/org/smssecure/smssecure/ConversationListActivity.java b/src/org/smssecure/smssecure/ConversationListActivity.java index 12d289a1..9a3e68d0 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 (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { + int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : 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 b21b20e8..27cd7756 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 bc5fed0b..10d3d067 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.DualSimUpgradeUtil; +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 = 129; 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 (params[0] < MULTI_SIM_MULTI_KEYS_VERSION) { + if (Build.VERSION.SDK_INT >= 22) { + SubscriptionManager subscriptionManager = SubscriptionManager.from(context); + List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); - if (v1sessions.exists() && v1sessions.isDirectory()) { - File[] contents = v1sessions.listFiles(); - - if (contents != null) { - for (File session : contents) { - session.delete(); - } + for (SubscriptionInfo subscriptionInfo : activeSubscriptions) { + int subscriptionId = subscriptionInfo.getSubscriptionId(); + SilencePreferences.setAppSubscriptionId(context, subscriptionId, subscriptionId); } - v1sessions.delete(); - } - } - - if (params[0] < NO_DECRYPT_QUEUE_VERSION) { - EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(getApplicationContext()); - - SmsDatabase.Reader smsReader = null; + /* + * getDefaultSubscriptionId() is available for API 24+ only, so we + * move keys and sessions to SIM card in slot 1, not to the default one. + */ + int defaultSubscriptionId = activeSubscriptions.get(0).getSubscriptionId(); - SmsMessageRecord record; + List activeSubscriptionsCompat = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList(); - 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(); + DualSimUpgradeUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, defaultSubscriptionId); + DualSimUpgradeUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptionsCompat); + DualSimUpgradeUtil.bindSubscriptionId(context, activeSubscriptionsCompat); } } diff --git a/src/org/smssecure/smssecure/PassphraseCreateActivity.java b/src/org/smssecure/smssecure/PassphraseCreateActivity.java index f6235472..fc876408 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.DualSimUpgradeUtil; +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. * @@ -66,7 +72,14 @@ public class PassphraseCreateActivity extends PassphraseActivity { passphrase); MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret); - IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret); + + if (Build.VERSION.SDK_INT >= 22) { + List activeSubscriptions = SubscriptionManagerCompat.from(PassphraseCreateActivity.this).getActiveSubscriptionInfoList(); + DualSimUpgradeUtil.generateKeysIfDoNotExist(PassphraseCreateActivity.this, masterSecret, activeSubscriptions); + DualSimUpgradeUtil.bindSubscriptionId(PassphraseCreateActivity.this, activeSubscriptions); + } else { + IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret, -1); + } 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 3f45af84..cec56b42 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/TransportOptions.java b/src/org/smssecure/smssecure/TransportOptions.java index f6b3edd5..32c3ce8f 100644 --- a/src/org/smssecure/smssecure/TransportOptions.java +++ b/src/org/smssecure/smssecure/TransportOptions.java @@ -111,6 +111,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,7 +168,7 @@ 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) { diff --git a/src/org/smssecure/smssecure/VerifyIdentityActivity.java b/src/org/smssecure/smssecure/VerifyIdentityActivity.java index ec915c16..e87e2409 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 18b3bc95..5e671845 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 6ecb1ccc..00000000 --- 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 ad1b4595..c2e9e026 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 94e4b60c..b920259e 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 2b69230d..b4401184 100644 --- a/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java +++ b/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java @@ -20,6 +20,7 @@ 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; @@ -39,43 +40,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 +86,32 @@ public class IdentityKeyUtil { } } - public static void generateIdentityKeys(Context context, MasterSecret masterSecret) { + public static void generateIdentityKeys(Context context, MasterSecret masterSecret, int subscriptionId) { + 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)); } - 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 +126,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 9230ebcc..b51d2bc7 100644 --- a/src/org/smssecure/smssecure/crypto/KeyExchangeInitiator.java +++ b/src/org/smssecure/smssecure/crypto/KeyExchangeInitiator.java @@ -20,6 +20,9 @@ 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 org.smssecure.smssecure.R; import org.smssecure.smssecure.crypto.SessionBuilder; @@ -31,7 +34,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 +46,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,12 +85,12 @@ 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)); @@ -80,10 +103,10 @@ public class KeyExchangeInitiator { } 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 66baa3f1..00000000 --- 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 fbbe947c..f2eee3b8 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 16a2807e..bb79457b 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 742b4235..25040fc7 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,13 @@ 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); + this.context = context.getApplicationContext(); + this.masterSecret = masterSecret; + this.subscriptionId = subscriptionId; } @Override @@ -143,10 +147,16 @@ public class SilenceSessionStore implements SessionStore { } private File getSessionFile(SignalProtocolAddress address) { - return new File(getSessionDirectory(), getSessionName(address)); + String sessionName = getSessionName(address); + Log.w(TAG, "session name: " + sessionName); + 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 +169,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 4c3e62fc..9c891750 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 91c225d7..d4df5f1b 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/SmsDatabase.java b/src/org/smssecure/smssecure/database/SmsDatabase.java index 93b44419..62b57156 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/CheckSimStateJob.java b/src/org/smssecure/smssecure/jobs/CheckSimStateJob.java new file mode 100644 index 00000000..6316915e --- /dev/null +++ b/src/org/smssecure/smssecure/jobs/CheckSimStateJob.java @@ -0,0 +1,49 @@ +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.DualSimUpgradeUtil; +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 CheckSimStateJob extends MasterSecretJob { + private static final String TAG = CheckSimStateJob.class.getSimpleName(); + + private List activeSubscriptions; + + public CheckSimStateJob(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).getActiveSubscriptionInfoList(); + DualSimUpgradeUtil.bindAppSubscriptionId(context, activeSubscriptions); + DualSimUpgradeUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptions); + DualSimUpgradeUtil.bindSubscriptionId(context, 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 cfd295f7..6580f87f 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 2ae857ac..3374a5aa 100644 --- a/src/org/smssecure/smssecure/jobs/MmsSendJob.java +++ b/src/org/smssecure/smssecure/jobs/MmsSendJob.java @@ -84,7 +84,7 @@ 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; } @@ -148,11 +148,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 c927ded0..e2533267 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 2ae066c7..098c7313 100644 --- a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java +++ b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java @@ -63,6 +63,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 2cae055e..4783c92b 100644 --- a/src/org/smssecure/smssecure/jobs/SmsSendJob.java +++ b/src/org/smssecure/smssecure/jobs/SmsSendJob.java @@ -82,18 +82,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,19 +94,26 @@ public class SmsSendJob extends SendJob { recipient = PhoneNumberUtils.stripSeparators(PhoneNumberUtils.convertKeypadLettersToDigits(recipient)); } - MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler(); - OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message); + if (!NumberUtil.isValidSmsOrEmail(recipient)) { + throw new UndeliverableMessageException("Not a valid SMS destination! " + recipient); + } + + if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) { + MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler(); + OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message); - if (message.isSecure() || message.isEndSession()) { - transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage); + if (!message.isKeyExchange()) { + transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage); + } + + messages = SmsManager.getDefault().divideMessage(multipartMessageHandler.getEncodedMessage(transportMessage)); + } else { + messages = SmsManager.getDefault().divideMessage(message.getBody().getBody()); } - 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) { @@ -129,57 +127,12 @@ public class SmsSendJob extends SendJob { } } - private void deliverPlaintextMessage(SmsMessageRecord message) - throws UndeliverableMessageException - { - String recipient = message.getIndividualRecipient().getNumber(); - - // 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 (!NumberUtil.isValidSmsOrEmail(recipient)) { - throw new UndeliverableMessageException("Not a valid SMS destination! " + recipient); - } - - ArrayList messages = SmsManager.getDefault().divideMessage(message.getBody().getBody()); - ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false); - ArrayList deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages); - - // 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); - } 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 b04c5c2e..24b0bfcc 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/RemoteReplyReceiver.java b/src/org/smssecure/smssecure/notifications/RemoteReplyReceiver.java index d44eb62c..d94d118b 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/protocol/AutoInitiate.java b/src/org/smssecure/smssecure/protocol/AutoInitiate.java index b0063be9..be6cf198 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 7dcaa540..22cd3276 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 efe5df40..68603b7e 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 ec0ec156..37acb33e 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/SilencePreferences.java b/src/org/smssecure/smssecure/util/SilencePreferences.java index ed04c9f2..6ccf2e2b 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/dualsim/DualSimUpgradeUtil.java b/src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java new file mode 100644 index 00000000..e146a64b --- /dev/null +++ b/src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java @@ -0,0 +1,196 @@ +package org.smssecure.smssecure.util.dualsim; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +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.util.SilencePreferences; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; + +public class DualSimUpgradeUtil { + private static final String TAG = DualSimUpgradeUtil.class.getSimpleName(); + + 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) { + for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) { + int subscriptionId = subscriptionInfo.getSubscriptionId(); + + if (!IdentityKeyUtil.hasIdentityKey(context, subscriptionId)) + IdentityKeyUtil.generateIdentityKeys(context, masterSecret, subscriptionId); + } + } + + public static void bindSubscriptionId(Context context, List activeSubscriptions) { + List remainingSubscriptions; + remainingSubscriptions = bindSubscriptionIdToPhoneNumber(context, activeSubscriptions); + remainingSubscriptions = bindSubscriptionIdToIccId(context, remainingSubscriptions); + + if (remainingSubscriptions.size() > 0) Log.w(TAG, "WARNING: Cannot bind " + remainingSubscriptions.size() + " subscription(s)!"); + } + + private static List bindSubscriptionIdToPhoneNumber(Context context, List activeSubscriptions) { + List remainingSubscriptions = new LinkedList(activeSubscriptions); + + for (SubscriptionInfoCompat subscriptionInfo : new LinkedList(activeSubscriptions)) { + String number = subscriptionInfo.getNumber(); + if (number != null && !number.equals("")) { + SilencePreferences.setNumberForSubscriptionId(context, subscriptionInfo.getSubscriptionId(), number); + remainingSubscriptions.remove(subscriptionInfo); + } + } + + return remainingSubscriptions; + } + + private static List bindSubscriptionIdToIccId(Context context, List activeSubscriptions) { + List remainingSubscriptions = new LinkedList(activeSubscriptions); + + for (SubscriptionInfoCompat subscriptionInfo : new LinkedList(activeSubscriptions)) { + String iccId = subscriptionInfo.getIccId(); + if (iccId != null && !iccId.equals("")) { + SilencePreferences.setIccIdForSubscriptionId(context, subscriptionInfo.getSubscriptionId(), iccId); + remainingSubscriptions.remove(subscriptionInfo); + } + } + + return remainingSubscriptions; + } + + public static void bindAppSubscriptionId(Context context, List activeSubscriptions) { + if (Build.VERSION.SDK_INT >= 22) { + for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) { + int appSubscriptionId = SilencePreferences.getLastAppSubscriptionId(context) + 1; + if (subscriptionInfo.getSubscriptionId() == -1) SilencePreferences.setAppSubscriptionId(context, subscriptionInfo.getDeviceSubscriptionId(), appSubscriptionId); + } + } + } + + public static void checkAndFixAppSubscriptionIds(Context context) { + if (Build.VERSION.SDK_INT >= 22) { + SubscriptionManagerCompat subscriptionManagerCompat = SubscriptionManagerCompat.from(context); + List activeSubscriptionsCompat = subscriptionManagerCompat.updateActiveSubscriptionInfoList(); + + for (SubscriptionInfoCompat subscriptionInfoCompat : activeSubscriptionsCompat) { + Log.w(TAG, "getDeviceSubscriptionId(): " + subscriptionInfoCompat.getDeviceSubscriptionId()); + Log.w(TAG, "getSubscriptionId(): " + subscriptionInfoCompat.getSubscriptionId()); + loop: + if (subscriptionInfoCompat.getDeviceSubscriptionId() != -1 && subscriptionInfoCompat.getSubscriptionId() == -1) { + fixSubscriptionsIds(context, activeSubscriptionsCompat); + break loop; + } + } + } + } + + @TargetApi(22) + private static void fixSubscriptionsIds(Context context, List activeSubscriptionsCompat) { + SubscriptionManager subscriptionManager = SubscriptionManager.from(context); + List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); + + for (SubscriptionInfoCompat subscriptionInfoCompat : activeSubscriptionsCompat) { + loop: + if (!subscriptionInfoCompat.getNumber().equals("")) { + int subscriptionId = findSubscriptionIdForNumber(context, subscriptionInfoCompat.getNumber(), activeSubscriptions); + Log.w(TAG, "findSubscriptionIdForNumber(" + subscriptionInfoCompat.getNumber() + "): " + subscriptionId); + if (subscriptionId != -1) { + subscriptionInfoCompat.setSubscriptionId(subscriptionId); + break loop; + } + } else { + int subscriptionId = findSubscriptionIdForIccId(context, subscriptionInfoCompat.getIccId(), activeSubscriptions); + Log.w(TAG, "findSubscriptionIdForIccId(" + subscriptionInfoCompat.getIccId() + "): " + subscriptionId); + if (subscriptionId != -1) { + subscriptionInfoCompat.setSubscriptionId(subscriptionId); + break loop; + } + } + int subscriptionId = SilencePreferences.getLastAppSubscriptionId(context) + 1; + subscriptionInfoCompat.setSubscriptionId(subscriptionId); + } + } + + @TargetApi(22) + private static int findSubscriptionIdForNumber(Context context, String number, List activeSubscriptions) { + for (SubscriptionInfo subscriptionInfo : activeSubscriptions) { + String eligibleNumber = subscriptionInfo.getNumber(); + if (eligibleNumber != null && eligibleNumber.equals(number)) return subscriptionInfo.getSubscriptionId(); + } + return -1; + } + + @TargetApi(22) + private static int findSubscriptionIdForIccId(Context context, String iccId, List activeSubscriptions) { + for (SubscriptionInfo subscriptionInfo : activeSubscriptions) { + String eligibleIccId = subscriptionInfo.getIccId(); + if (eligibleIccId != null && eligibleIccId.equals(iccId)) return subscriptionInfo.getSubscriptionId(); + } + return -1; + } +} 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 00000000..7b85bd91 --- /dev/null +++ b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java @@ -0,0 +1,84 @@ +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.jobs.CheckSimStateJob; + +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)) { + DualSimUpgradeUtil.checkAndFixAppSubscriptionIds(context); + ApplicationContext.getInstance(context) + .getJobManager() + .add(new CheckSimStateJob(context)); + SilencePreferences.setDeviceSubscriptions(context, getDeviceSubscriptions(context)); + } + } + + 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 != null && !subscriptions.equals(registeredSubscriptions); + } + + private static String getDeviceSubscriptions(Context context) { + if (Build.VERSION.SDK_INT < 22) return null; + + SubscriptionManager subscriptionManager = SubscriptionManager.from(context); + List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); + + if (activeSubscriptions == null) return null; + + String[] subscriptions = new String[activeSubscriptions.size()]; + for(int i=0; i displayNameList; + private List compatList; - private final Context context; + public static SubscriptionManagerCompat from(Context context) { + Log.w(TAG, "from()"); + + if (instance == null) { + instance = new SubscriptionManagerCompat(context); + } + return instance; + } - public SubscriptionManagerCompat(Context context) { + 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); if (subscriptionInfo != null) { - return Optional.of(new SubscriptionInfoCompat(subscriptionId, subscriptionInfo.getDisplayName())); + return Optional.of(new SubscriptionInfoCompat(context, + subscriptionId, + subscriptionInfo.getDisplayName(), + subscriptionInfo.getNumber(), + subscriptionInfo.getIccId(), + subscriptionInfo.getSimSlotIndex()+1, + knowThisDisplayNameTwice(subscriptionInfo.getDisplayName()))); } else { 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; -- GitLab From 25ae8eda649cd7d9c70761ced7dc922d207e3b5c Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Wed, 22 Feb 2017 19:58:09 +0100 Subject: [PATCH 02/46] support for Android Auto --- AndroidManifest.xml | 18 +++ res/xml/automotive_app_desc.xml | 3 + .../AndroidAutoHeardReceiver.java | 71 +++++++++++ .../AndroidAutoReplyReceiver.java | 118 ++++++++++++++++++ .../notifications/MessageNotifier.java | 2 + .../notifications/NotificationState.java | 33 +++++ .../SingleRecipientNotificationBuilder.java | 21 ++++ 7 files changed, 266 insertions(+) create mode 100644 res/xml/automotive_app_desc.xml create mode 100644 src/org/smssecure/smssecure/notifications/AndroidAutoHeardReceiver.java create mode 100644 src/org/smssecure/smssecure/notifications/AndroidAutoReplyReceiver.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e532cf7e..320e1508 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -63,6 +63,9 @@ + + @@ -344,6 +347,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/org/smssecure/smssecure/notifications/AndroidAutoHeardReceiver.java b/src/org/smssecure/smssecure/notifications/AndroidAutoHeardReceiver.java new file mode 100644 index 00000000..0ff6025e --- /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 00000000..cb49e85e --- /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 df873880..ad6284eb 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 e2c6b99a..bf553bd7 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/SingleRecipientNotificationBuilder.java b/src/org/smssecure/smssecure/notifications/SingleRecipientNotificationBuilder.java index 8f4d0e0e..1e103ea2 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, -- GitLab From 5213bbcf6a2ebde850e15ca8208033a62d87012a Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Fri, 24 Feb 2017 22:53:30 +0100 Subject: [PATCH 03/46] use Stub for thumbnails --- res/layout/conversation_item_received.xml | 13 +-- .../conversation_item_received_thumbnail.xml | 13 +++ res/layout/conversation_item_sent.xml | 13 +-- .../conversation_item_sent_thumbnail.xml | 15 ++++ .../smssecure/smssecure/ConversationItem.java | 83 +++++++++++-------- 5 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 res/layout/conversation_item_received_thumbnail.xml create mode 100644 res/layout/conversation_item_sent_thumbnail.xml diff --git a/res/layout/conversation_item_received.xml b/res/layout/conversation_item_received.xml index 22a4dc2f..a09f7f8f 100644 --- a/res/layout/conversation_item_received.xml +++ b/res/layout/conversation_item_received.xml @@ -48,16 +48,11 @@ android:background="@drawable/received_bubble" android:orientation="vertical"> - + android:layout_height="@dimen/media_bubble_height"/> + diff --git a/res/layout/conversation_item_sent.xml b/res/layout/conversation_item_sent.xml index bb7b07cc..156d60fa 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/src/org/smssecure/smssecure/ConversationItem.java b/src/org/smssecure/smssecure/ConversationItem.java index dc740a80..f7240e1c 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)); } } @@ -553,15 +557,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); + } } } @@ -592,7 +609,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); -- GitLab From de2962a2faa15c5f533c6577579ebf9910beb626 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Mon, 20 Mar 2017 20:46:09 +0100 Subject: [PATCH 04/46] fix keys management if app subscription ID is not equal to device's one --- .../smssecure/ConversationActivity.java | 12 +- .../smssecure/smssecure/ConversationItem.java | 2 +- .../smssecure/ConversationListActivity.java | 4 +- .../smssecure/DatabaseUpgradeActivity.java | 13 +- .../smssecure/PassphraseCreateActivity.java | 12 +- .../smssecure/crypto/IdentityKeyUtil.java | 2 +- .../crypto/storage/SilenceSessionStore.java | 1 + ...kSimStateJob.java => GenerateKeysJob.java} | 14 +- .../smssecure/smssecure/jobs/MmsSendJob.java | 3 +- .../smssecure/smssecure/jobs/SmsSendJob.java | 9 +- .../util/dualsim/DualSimUpgradeUtil.java | 196 ------------------ .../smssecure/util/dualsim/DualSimUtil.java | 96 +++++++++ .../util/dualsim/SimChangedReceiver.java | 11 +- .../util/dualsim/SubscriptionInfoCompat.java | 54 ++++- .../dualsim/SubscriptionManagerCompat.java | 16 +- 15 files changed, 195 insertions(+), 250 deletions(-) rename src/org/smssecure/smssecure/jobs/{CheckSimStateJob.java => GenerateKeysJob.java} (67%) delete mode 100644 src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java create mode 100644 src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java diff --git a/src/org/smssecure/smssecure/ConversationActivity.java b/src/org/smssecure/smssecure/ConversationActivity.java index 5cf452a0..3303e0b0 100644 --- a/src/org/smssecure/smssecure/ConversationActivity.java +++ b/src/org/smssecure/smssecure/ConversationActivity.java @@ -588,8 +588,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleVerifyIdentity() { - if (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { - int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : activeSubscriptions.get(0).getSubscriptionId(); + if (activeSubscriptions.size() < 2) { + int subscriptionId = activeSubscriptions.get(0).getSubscriptionId(); handleVerifyIdentity(subscriptionId); } } @@ -602,8 +602,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleStartSecureSession() { - if (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { - int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : activeSubscriptions.get(0).getSubscriptionId(); + if (activeSubscriptions.size() < 2) { + int subscriptionId = activeSubscriptions.get(0).getSubscriptionId(); handleStartSecureSession(subscriptionId); } } @@ -651,8 +651,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void handleAbortSecureSession() { - if (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { - int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : activeSubscriptions.get(0).getSubscriptionId(); + if (activeSubscriptions.size() < 2) { + int subscriptionId = activeSubscriptions.get(0).getSubscriptionId(); handleAbortSecureSession(subscriptionId); } } diff --git a/src/org/smssecure/smssecure/ConversationItem.java b/src/org/smssecure/smssecure/ConversationItem.java index f7240e1c..dafa6c6b 100644 --- a/src/org/smssecure/smssecure/ConversationItem.java +++ b/src/org/smssecure/smssecure/ConversationItem.java @@ -404,7 +404,7 @@ public class ConversationItem extends LinearLayout private void setSimInfo(MessageRecord messageRecord) { 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()); diff --git a/src/org/smssecure/smssecure/ConversationListActivity.java b/src/org/smssecure/smssecure/ConversationListActivity.java index 9a3e68d0..d18b077f 100644 --- a/src/org/smssecure/smssecure/ConversationListActivity.java +++ b/src/org/smssecure/smssecure/ConversationListActivity.java @@ -229,8 +229,8 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } private void handleMyIdentity() { - if (Build.VERSION.SDK_INT < 22 || activeSubscriptions.size() < 2) { - int subscriptionId = Build.VERSION.SDK_INT < 22 ? -1 : activeSubscriptions.get(0).getSubscriptionId(); + if (activeSubscriptions.size() < 2) { + int subscriptionId = activeSubscriptions.get(0).getSubscriptionId(); handleMyIdentity(subscriptionId); } } diff --git a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java index 10d3d067..d128bbec 100644 --- a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java +++ b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java @@ -32,7 +32,7 @@ import android.widget.ProgressBar; import org.smssecure.smssecure.crypto.MasterSecret; import org.smssecure.smssecure.database.DatabaseFactory; import org.smssecure.smssecure.notifications.MessageNotifier; -import org.smssecure.smssecure.util.dualsim.DualSimUpgradeUtil; +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; @@ -150,11 +150,6 @@ public class DatabaseUpgradeActivity extends BaseActivity { SubscriptionManager subscriptionManager = SubscriptionManager.from(context); List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); - for (SubscriptionInfo subscriptionInfo : activeSubscriptions) { - int subscriptionId = subscriptionInfo.getSubscriptionId(); - SilencePreferences.setAppSubscriptionId(context, subscriptionId, subscriptionId); - } - /* * getDefaultSubscriptionId() is available for API 24+ only, so we * move keys and sessions to SIM card in slot 1, not to the default one. @@ -163,9 +158,9 @@ public class DatabaseUpgradeActivity extends BaseActivity { List activeSubscriptionsCompat = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList(); - DualSimUpgradeUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, defaultSubscriptionId); - DualSimUpgradeUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptionsCompat); - DualSimUpgradeUtil.bindSubscriptionId(context, activeSubscriptionsCompat); + DualSimUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, defaultSubscriptionId); + DualSimUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptionsCompat); + SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList(); } } diff --git a/src/org/smssecure/smssecure/PassphraseCreateActivity.java b/src/org/smssecure/smssecure/PassphraseCreateActivity.java index fc876408..ffb7b17b 100644 --- a/src/org/smssecure/smssecure/PassphraseCreateActivity.java +++ b/src/org/smssecure/smssecure/PassphraseCreateActivity.java @@ -24,7 +24,7 @@ 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.DualSimUpgradeUtil; +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; @@ -59,7 +59,7 @@ public class PassphraseCreateActivity extends PassphraseActivity { } private class SecretGenerator extends AsyncTask { - private MasterSecret masterSecret; + private MasterSecret masterSecret; @Override protected void onPreExecute() { @@ -73,12 +73,14 @@ public class PassphraseCreateActivity extends PassphraseActivity { MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret); + SubscriptionManagerCompat subscriptionManagerCompat = SubscriptionManagerCompat.from(PassphraseCreateActivity.this); + if (Build.VERSION.SDK_INT >= 22) { - List activeSubscriptions = SubscriptionManagerCompat.from(PassphraseCreateActivity.this).getActiveSubscriptionInfoList(); - DualSimUpgradeUtil.generateKeysIfDoNotExist(PassphraseCreateActivity.this, masterSecret, activeSubscriptions); - DualSimUpgradeUtil.bindSubscriptionId(PassphraseCreateActivity.this, activeSubscriptions); + List activeSubscriptions = subscriptionManagerCompat.getActiveSubscriptionInfoList(); + DualSimUtil.generateKeysIfDoNotExist(PassphraseCreateActivity.this, masterSecret, activeSubscriptions); } else { IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret, -1); + subscriptionManagerCompat.updateActiveSubscriptionInfoList(); } VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); SilencePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true); diff --git a/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java b/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java index b4401184..c4da2624 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 * diff --git a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java index 25040fc7..3943a224 100644 --- a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java +++ b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java @@ -42,6 +42,7 @@ public class SilenceSessionStore implements SessionStore { public SilenceSessionStore(Context context, MasterSecret masterSecret, int subscriptionId) { Log.w(TAG, "SilenceSessionStore for subscription ID " + subscriptionId); + if (subscriptionId == -1) throw new AssertionError("Subscription ID cannot be -1 but should be >1"); this.context = context.getApplicationContext(); this.masterSecret = masterSecret; this.subscriptionId = subscriptionId; diff --git a/src/org/smssecure/smssecure/jobs/CheckSimStateJob.java b/src/org/smssecure/smssecure/jobs/GenerateKeysJob.java similarity index 67% rename from src/org/smssecure/smssecure/jobs/CheckSimStateJob.java rename to src/org/smssecure/smssecure/jobs/GenerateKeysJob.java index 6316915e..e3939a05 100644 --- a/src/org/smssecure/smssecure/jobs/CheckSimStateJob.java +++ b/src/org/smssecure/smssecure/jobs/GenerateKeysJob.java @@ -5,7 +5,7 @@ import android.util.Log; import org.smssecure.smssecure.crypto.MasterSecret; import org.smssecure.smssecure.jobs.requirements.MasterSecretRequirement; -import org.smssecure.smssecure.util.dualsim.DualSimUpgradeUtil; +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; @@ -13,12 +13,12 @@ import org.whispersystems.jobqueue.JobParameters; import java.util.List; -public class CheckSimStateJob extends MasterSecretJob { - private static final String TAG = CheckSimStateJob.class.getSimpleName(); +public class GenerateKeysJob extends MasterSecretJob { + private static final String TAG = GenerateKeysJob.class.getSimpleName(); private List activeSubscriptions; - public CheckSimStateJob(Context context) { + public GenerateKeysJob(Context context) { super(context, JobParameters.newBuilder() .withPersistence() .withRequirement(new MasterSecretRequirement(context)) @@ -33,10 +33,8 @@ public class CheckSimStateJob extends MasterSecretJob { @Override public void onRun(MasterSecret masterSecret) { Log.w(TAG, "onRun()"); - List activeSubscriptions = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList(); - DualSimUpgradeUtil.bindAppSubscriptionId(context, activeSubscriptions); - DualSimUpgradeUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptions); - DualSimUpgradeUtil.bindSubscriptionId(context, activeSubscriptions); + List activeSubscriptions = SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList(); + DualSimUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptions); } @Override diff --git a/src/org/smssecure/smssecure/jobs/MmsSendJob.java b/src/org/smssecure/smssecure/jobs/MmsSendJob.java index 3374a5aa..9dd88799 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; @@ -91,7 +92,7 @@ public class MmsSendJob extends SendJob { 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()); diff --git a/src/org/smssecure/smssecure/jobs/SmsSendJob.java b/src/org/smssecure/smssecure/jobs/SmsSendJob.java index 4783c92b..a37efcfd 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; @@ -114,8 +115,14 @@ public class SmsSendJob extends SendJob { 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); diff --git a/src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java b/src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java deleted file mode 100644 index e146a64b..00000000 --- a/src/org/smssecure/smssecure/util/dualsim/DualSimUpgradeUtil.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.smssecure.smssecure.util.dualsim; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -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.util.SilencePreferences; - -import java.io.File; -import java.util.LinkedList; -import java.util.List; - -public class DualSimUpgradeUtil { - private static final String TAG = DualSimUpgradeUtil.class.getSimpleName(); - - 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) { - for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) { - int subscriptionId = subscriptionInfo.getSubscriptionId(); - - if (!IdentityKeyUtil.hasIdentityKey(context, subscriptionId)) - IdentityKeyUtil.generateIdentityKeys(context, masterSecret, subscriptionId); - } - } - - public static void bindSubscriptionId(Context context, List activeSubscriptions) { - List remainingSubscriptions; - remainingSubscriptions = bindSubscriptionIdToPhoneNumber(context, activeSubscriptions); - remainingSubscriptions = bindSubscriptionIdToIccId(context, remainingSubscriptions); - - if (remainingSubscriptions.size() > 0) Log.w(TAG, "WARNING: Cannot bind " + remainingSubscriptions.size() + " subscription(s)!"); - } - - private static List bindSubscriptionIdToPhoneNumber(Context context, List activeSubscriptions) { - List remainingSubscriptions = new LinkedList(activeSubscriptions); - - for (SubscriptionInfoCompat subscriptionInfo : new LinkedList(activeSubscriptions)) { - String number = subscriptionInfo.getNumber(); - if (number != null && !number.equals("")) { - SilencePreferences.setNumberForSubscriptionId(context, subscriptionInfo.getSubscriptionId(), number); - remainingSubscriptions.remove(subscriptionInfo); - } - } - - return remainingSubscriptions; - } - - private static List bindSubscriptionIdToIccId(Context context, List activeSubscriptions) { - List remainingSubscriptions = new LinkedList(activeSubscriptions); - - for (SubscriptionInfoCompat subscriptionInfo : new LinkedList(activeSubscriptions)) { - String iccId = subscriptionInfo.getIccId(); - if (iccId != null && !iccId.equals("")) { - SilencePreferences.setIccIdForSubscriptionId(context, subscriptionInfo.getSubscriptionId(), iccId); - remainingSubscriptions.remove(subscriptionInfo); - } - } - - return remainingSubscriptions; - } - - public static void bindAppSubscriptionId(Context context, List activeSubscriptions) { - if (Build.VERSION.SDK_INT >= 22) { - for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) { - int appSubscriptionId = SilencePreferences.getLastAppSubscriptionId(context) + 1; - if (subscriptionInfo.getSubscriptionId() == -1) SilencePreferences.setAppSubscriptionId(context, subscriptionInfo.getDeviceSubscriptionId(), appSubscriptionId); - } - } - } - - public static void checkAndFixAppSubscriptionIds(Context context) { - if (Build.VERSION.SDK_INT >= 22) { - SubscriptionManagerCompat subscriptionManagerCompat = SubscriptionManagerCompat.from(context); - List activeSubscriptionsCompat = subscriptionManagerCompat.updateActiveSubscriptionInfoList(); - - for (SubscriptionInfoCompat subscriptionInfoCompat : activeSubscriptionsCompat) { - Log.w(TAG, "getDeviceSubscriptionId(): " + subscriptionInfoCompat.getDeviceSubscriptionId()); - Log.w(TAG, "getSubscriptionId(): " + subscriptionInfoCompat.getSubscriptionId()); - loop: - if (subscriptionInfoCompat.getDeviceSubscriptionId() != -1 && subscriptionInfoCompat.getSubscriptionId() == -1) { - fixSubscriptionsIds(context, activeSubscriptionsCompat); - break loop; - } - } - } - } - - @TargetApi(22) - private static void fixSubscriptionsIds(Context context, List activeSubscriptionsCompat) { - SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); - - for (SubscriptionInfoCompat subscriptionInfoCompat : activeSubscriptionsCompat) { - loop: - if (!subscriptionInfoCompat.getNumber().equals("")) { - int subscriptionId = findSubscriptionIdForNumber(context, subscriptionInfoCompat.getNumber(), activeSubscriptions); - Log.w(TAG, "findSubscriptionIdForNumber(" + subscriptionInfoCompat.getNumber() + "): " + subscriptionId); - if (subscriptionId != -1) { - subscriptionInfoCompat.setSubscriptionId(subscriptionId); - break loop; - } - } else { - int subscriptionId = findSubscriptionIdForIccId(context, subscriptionInfoCompat.getIccId(), activeSubscriptions); - Log.w(TAG, "findSubscriptionIdForIccId(" + subscriptionInfoCompat.getIccId() + "): " + subscriptionId); - if (subscriptionId != -1) { - subscriptionInfoCompat.setSubscriptionId(subscriptionId); - break loop; - } - } - int subscriptionId = SilencePreferences.getLastAppSubscriptionId(context) + 1; - subscriptionInfoCompat.setSubscriptionId(subscriptionId); - } - } - - @TargetApi(22) - private static int findSubscriptionIdForNumber(Context context, String number, List activeSubscriptions) { - for (SubscriptionInfo subscriptionInfo : activeSubscriptions) { - String eligibleNumber = subscriptionInfo.getNumber(); - if (eligibleNumber != null && eligibleNumber.equals(number)) return subscriptionInfo.getSubscriptionId(); - } - return -1; - } - - @TargetApi(22) - private static int findSubscriptionIdForIccId(Context context, String iccId, List activeSubscriptions) { - for (SubscriptionInfo subscriptionInfo : activeSubscriptions) { - String eligibleIccId = subscriptionInfo.getIccId(); - if (eligibleIccId != null && eligibleIccId.equals(iccId)) return subscriptionInfo.getSubscriptionId(); - } - return -1; - } -} 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 00000000..86c86b11 --- /dev/null +++ b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java @@ -0,0 +1,96 @@ +package org.smssecure.smssecure.util.dualsim; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +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.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(); + + 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) { + for (SubscriptionInfoCompat subscriptionInfo : activeSubscriptions) { + int subscriptionId = subscriptionInfo.getSubscriptionId(); + + if (!IdentityKeyUtil.hasIdentityKey(context, subscriptionId)) + IdentityKeyUtil.generateIdentityKeys(context, masterSecret, subscriptionId); + } + } + + 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; + } +} diff --git a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java index 7b85bd91..b3b64653 100644 --- a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java +++ b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java @@ -10,7 +10,7 @@ import android.util.Log; import org.smssecure.smssecure.ApplicationContext; import org.smssecure.smssecure.util.SilencePreferences; -import org.smssecure.smssecure.jobs.CheckSimStateJob; +import org.smssecure.smssecure.jobs.GenerateKeysJob; import java.util.Arrays; import java.util.List; @@ -31,10 +31,9 @@ public class SimChangedReceiver extends BroadcastReceiver { public static void checkSimState(final Context context) { if (hasDifferentSubscriptions(context)) { - DualSimUpgradeUtil.checkAndFixAppSubscriptionIds(context); ApplicationContext.getInstance(context) .getJobManager() - .add(new CheckSimStateJob(context)); + .add(new GenerateKeysJob(context)); SilencePreferences.setDeviceSubscriptions(context, getDeviceSubscriptions(context)); } } @@ -46,16 +45,16 @@ public class SimChangedReceiver extends BroadcastReceiver { Log.w(TAG, "getDeviceSubscriptions(): " + getDeviceSubscriptions(context)); Log.w(TAG, "getActiveDeviceSubscriptionIds(): " + getActiveDeviceSubscriptionIds(context)); - return subscriptions != null && !subscriptions.equals(registeredSubscriptions); + return !subscriptions.equals(registeredSubscriptions); } private static String getDeviceSubscriptions(Context context) { - if (Build.VERSION.SDK_INT < 22) return null; + if (Build.VERSION.SDK_INT < 22) return "1"; SubscriptionManager subscriptionManager = SubscriptionManager.from(context); List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); - if (activeSubscriptions == null) return null; + if (activeSubscriptions == null) return "1"; String[] subscriptions = new String[activeSubscriptions.size()]; for(int i=0; i Date: Mon, 20 Mar 2017 21:26:15 +0100 Subject: [PATCH 05/46] notify user if a new card is detected --- res/values/strings.xml | 5 +++ .../smssecure/PassphraseCreateActivity.java | 4 +-- .../smssecure/crypto/IdentityKeyUtil.java | 7 +++++ .../smssecure/util/dualsim/DualSimUtil.java | 31 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c14f0600..db9f9449 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 diff --git a/src/org/smssecure/smssecure/PassphraseCreateActivity.java b/src/org/smssecure/smssecure/PassphraseCreateActivity.java index ffb7b17b..7c0f756d 100644 --- a/src/org/smssecure/smssecure/PassphraseCreateActivity.java +++ b/src/org/smssecure/smssecure/PassphraseCreateActivity.java @@ -77,9 +77,9 @@ public class PassphraseCreateActivity extends PassphraseActivity { if (Build.VERSION.SDK_INT >= 22) { List activeSubscriptions = subscriptionManagerCompat.getActiveSubscriptionInfoList(); - DualSimUtil.generateKeysIfDoNotExist(PassphraseCreateActivity.this, masterSecret, activeSubscriptions); + DualSimUtil.generateKeysIfDoNotExist(PassphraseCreateActivity.this, masterSecret, activeSubscriptions, false); } else { - IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret, -1); + IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret, -1, false); subscriptionManagerCompat.updateActiveSubscriptionInfoList(); } VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this); diff --git a/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java b/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java index c4da2624..33a507b1 100644 --- a/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java +++ b/src/org/smssecure/smssecure/crypto/IdentityKeyUtil.java @@ -24,6 +24,7 @@ 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; @@ -87,6 +88,10 @@ public class IdentityKeyUtil { } 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(); @@ -96,6 +101,8 @@ public class IdentityKeyUtil { 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, int subscriptionId) { diff --git a/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java index 86c86b11..7fbd9435 100644 --- a/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java +++ b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java @@ -1,8 +1,12 @@ 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; @@ -11,6 +15,8 @@ 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; @@ -21,6 +27,8 @@ 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 + ")"); @@ -80,11 +88,15 @@ public class DualSimUtil { } 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); + IdentityKeyUtil.generateIdentityKeys(context, masterSecret, subscriptionId, displayNotification); } } @@ -93,4 +105,21 @@ public class DualSimUtil { if (subscriptionInfo.isPresent()) return subscriptionInfo.get().getDeviceSubscriptionId(); 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, 0, + targetIntent, + PendingIntent.FLAG_UPDATE_CURRENT)) + .build(); + ServiceUtil.getNotificationManager(context).notify(NOTIFICATION_ID, notification); + } } -- GitLab From cc368d37bbd2ed783ed95a7aba30d2722f2894ec Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Mon, 20 Mar 2017 22:04:31 +0100 Subject: [PATCH 06/46] fix DB upgrade --- .../smssecure/DatabaseUpgradeActivity.java | 19 +++++++++++-------- .../util/dualsim/SubscriptionInfoCompat.java | 4 ++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java index d128bbec..95cfef9a 100644 --- a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java +++ b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java @@ -48,7 +48,7 @@ import java.util.TreeSet; public class DatabaseUpgradeActivity extends BaseActivity { private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName(); - public static final int MULTI_SIM_MULTI_KEYS_VERSION = 129; + public static final int MULTI_SIM_MULTI_KEYS_VERSION = 200; private static final SortedSet UPGRADE_VERSIONS = new TreeSet() {{ add(MULTI_SIM_MULTI_KEYS_VERSION); @@ -147,19 +147,22 @@ public class DatabaseUpgradeActivity extends BaseActivity { if (params[0] < MULTI_SIM_MULTI_KEYS_VERSION) { if (Build.VERSION.SDK_INT >= 22) { - SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - List activeSubscriptions = subscriptionManager.getActiveSubscriptionInfoList(); /* * getDefaultSubscriptionId() is available for API 24+ only, so we - * move keys and sessions to SIM card in slot 1, not to the default one. + * move keys and sessions to SIM card in the first available slot, + * not to the default one. */ - int defaultSubscriptionId = activeSubscriptions.get(0).getSubscriptionId(); + List subscriptionInfoList = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList(); + int smallerSlot = -1; + int eligibleDeviceSubscriptionId = -1; - List activeSubscriptionsCompat = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfoList(); + for (SubscriptionInfoCompat subscriptionInfo : subscriptionInfoList) { + if (smallerSlot == -1 || subscriptionInfo.getIccSlot() < smallerSlot) eligibleDeviceSubscriptionId = subscriptionInfo.getDeviceSubscriptionId(); + } - DualSimUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, defaultSubscriptionId); - DualSimUtil.generateKeysIfDoNotExist(context, masterSecret, activeSubscriptionsCompat); + DualSimUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, eligibleDeviceSubscriptionId); + DualSimUtil.generateKeysIfDoNotExist(context, masterSecret, subscriptionInfoList); SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList(); } } diff --git a/src/org/smssecure/smssecure/util/dualsim/SubscriptionInfoCompat.java b/src/org/smssecure/smssecure/util/dualsim/SubscriptionInfoCompat.java index 605190b9..011fdca0 100644 --- a/src/org/smssecure/smssecure/util/dualsim/SubscriptionInfoCompat.java +++ b/src/org/smssecure/smssecure/util/dualsim/SubscriptionInfoCompat.java @@ -56,6 +56,10 @@ public class SubscriptionInfoCompat { return subscriptionId; } + public int getIccSlot() { + return iccSlot; + } + public void setSubscriptionId(int subscriptionId) { SilencePreferences.setAppSubscriptionId(context, deviceSubscriptionId, subscriptionId); this.subscriptionId = subscriptionId; -- GitLab From 8fb1fdd0e69bc5c52afdd6d70f9a3b7ad383cef1 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Tue, 21 Mar 2017 21:13:40 +0100 Subject: [PATCH 07/46] fix subscription ID when receiving a message --- src/org/smssecure/smssecure/jobs/SmsReceiveJob.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java index 098c7313..92b18dca 100644 --- a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java +++ b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java @@ -17,6 +17,8 @@ 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.SubscriptionInfoCompat; +import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.libsignal.util.guava.Optional; @@ -41,7 +43,7 @@ public class SmsReceiveJob extends ContextJob { .create()); this.pdus = pdus; - this.subscriptionId = subscriptionId; + this.subscriptionId = findAppSubscriptionId(context, subscriptionId); } @Override @@ -138,4 +140,10 @@ public class SmsReceiveJob extends ContextJob { return Optional.of(message); } } + + private static int findAppSubscriptionId(Context context, int subscriptionId) { + Optional subscriptionInfo = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfo(subscriptionId); + if (!subscriptionInfo.isPresent()) return -1; + return subscriptionInfo.get().getDeviceSubscriptionId(); + } } -- GitLab From 0bcf6c86403c813a19ba6d8376b39d29ea8273e8 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Tue, 21 Mar 2017 22:47:23 +0100 Subject: [PATCH 08/46] fix key management for dual-sim devices with 1 card inserted --- .../smssecure/smssecure/TransportOption.java | 11 ----------- .../smssecure/smssecure/TransportOptions.java | 17 ++++------------- .../crypto/storage/SilenceSessionStore.java | 3 ++- .../smssecure/smssecure/jobs/SmsReceiveJob.java | 14 +++++--------- .../smssecure/util/dualsim/DualSimUtil.java | 6 ++++++ .../util/dualsim/SubscriptionManagerCompat.java | 12 ++++++++++++ 6 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/org/smssecure/smssecure/TransportOption.java b/src/org/smssecure/smssecure/TransportOption.java index cadbe053..fc777660 100644 --- a/src/org/smssecure/smssecure/TransportOption.java +++ b/src/org/smssecure/smssecure/TransportOption.java @@ -25,17 +25,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 32c3ce8f..508f0b22 100644 --- a/src/org/smssecure/smssecure/TransportOptions.java +++ b/src/org/smssecure/smssecure/TransportOptions.java @@ -171,24 +171,15 @@ public class TransportOptions { 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; diff --git a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java index 3943a224..67aed936 100644 --- a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java +++ b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java @@ -42,7 +42,8 @@ public class SilenceSessionStore implements SessionStore { public SilenceSessionStore(Context context, MasterSecret masterSecret, int subscriptionId) { Log.w(TAG, "SilenceSessionStore for subscription ID " + subscriptionId); - if (subscriptionId == -1) throw new AssertionError("Subscription ID cannot be -1 but should be >1"); + if (subscriptionId == -1) Log.w(TAG, "Subscription ID should not be -1!"); + this.context = context.getApplicationContext(); this.masterSecret = masterSecret; this.subscriptionId = subscriptionId; diff --git a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java index 92b18dca..82c0a348 100644 --- a/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java +++ b/src/org/smssecure/smssecure/jobs/SmsReceiveJob.java @@ -17,8 +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.SubscriptionInfoCompat; -import org.smssecure.smssecure.util.dualsim.SubscriptionManagerCompat; +import org.smssecure.smssecure.util.dualsim.DualSimUtil; import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.libsignal.util.guava.Optional; @@ -42,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 = findAppSubscriptionId(context, subscriptionId); + this.subscriptionId = DualSimUtil.getSubscriptionIdFromDeviceSubscriptionId(context, subscriptionId); } @Override @@ -140,10 +142,4 @@ public class SmsReceiveJob extends ContextJob { return Optional.of(message); } } - - private static int findAppSubscriptionId(Context context, int subscriptionId) { - Optional subscriptionInfo = SubscriptionManagerCompat.from(context).getActiveSubscriptionInfo(subscriptionId); - if (!subscriptionInfo.isPresent()) return -1; - return subscriptionInfo.get().getDeviceSubscriptionId(); - } } diff --git a/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java index 7fbd9435..e7458f3f 100644 --- a/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java +++ b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java @@ -106,6 +106,12 @@ public class DualSimUtil { 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) diff --git a/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java b/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java index 37c8586d..2689940a 100644 --- a/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java +++ b/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java @@ -52,6 +52,18 @@ public class SubscriptionManagerCompat { 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(); -- GitLab From 864aa2b614aa8215484cbc6f0213d85ba4ba3f25 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Thu, 23 Mar 2017 14:00:52 +0100 Subject: [PATCH 09/46] do not log SQL queries --- src/org/smssecure/smssecure/database/MmsSmsDatabase.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/org/smssecure/smssecure/database/MmsSmsDatabase.java b/src/org/smssecure/smssecure/database/MmsSmsDatabase.java index 6f79bc22..f3063024 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); } -- GitLab From 0a84898643610c4df84e4ce5fd1ca26dcb2c606f Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Thu, 23 Mar 2017 14:02:31 +0100 Subject: [PATCH 10/46] do not log session names --- .../smssecure/smssecure/crypto/storage/SilenceSessionStore.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java index 67aed936..2336cdd1 100644 --- a/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java +++ b/src/org/smssecure/smssecure/crypto/storage/SilenceSessionStore.java @@ -150,7 +150,6 @@ public class SilenceSessionStore implements SessionStore { private File getSessionFile(SignalProtocolAddress address) { String sessionName = getSessionName(address); - Log.w(TAG, "session name: " + sessionName); return new File(getSessionDirectory(), sessionName); } -- GitLab From e761b4720e09805535d92d94ffa7dbe5296d6e48 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Thu, 23 Mar 2017 14:03:36 +0100 Subject: [PATCH 11/46] fix SIM detection in upgrade --- src/org/smssecure/smssecure/DatabaseUpgradeActivity.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java index 95cfef9a..3a31a143 100644 --- a/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java +++ b/src/org/smssecure/smssecure/DatabaseUpgradeActivity.java @@ -147,7 +147,6 @@ public class DatabaseUpgradeActivity extends BaseActivity { 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, @@ -158,7 +157,10 @@ public class DatabaseUpgradeActivity extends BaseActivity { int eligibleDeviceSubscriptionId = -1; for (SubscriptionInfoCompat subscriptionInfo : subscriptionInfoList) { - if (smallerSlot == -1 || subscriptionInfo.getIccSlot() < smallerSlot) eligibleDeviceSubscriptionId = subscriptionInfo.getDeviceSubscriptionId(); + if (smallerSlot == -1 || subscriptionInfo.getIccSlot() < smallerSlot) { + smallerSlot = subscriptionInfo.getIccSlot(); + eligibleDeviceSubscriptionId = subscriptionInfo.getDeviceSubscriptionId(); + } } DualSimUtil.moveIdentityKeysAndSessionsToSubscriptionId(context, -1, eligibleDeviceSubscriptionId); -- GitLab From f49e8f956b917d2ee286e43267f27e1f3f573e1f Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Thu, 23 Mar 2017 15:41:24 +0100 Subject: [PATCH 12/46] generate missing keys only if DB upgrade is done --- .../smssecure/util/VersionTracker.java | 17 +++++++++++++++-- .../smssecure/util/dualsim/DualSimUtil.java | 2 +- .../util/dualsim/SimChangedReceiver.java | 3 ++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/org/smssecure/smssecure/util/VersionTracker.java b/src/org/smssecure/smssecure/util/VersionTracker.java index 8c45a32f..f9816313 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 index e7458f3f..a67701fb 100644 --- a/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java +++ b/src/org/smssecure/smssecure/util/dualsim/DualSimUtil.java @@ -122,7 +122,7 @@ public class DualSimUtil { .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, 0, + .setContentIntent(PendingIntent.getActivity(context.getApplicationContext(), 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT)) .build(); diff --git a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java index b3b64653..5746f017 100644 --- a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java +++ b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java @@ -10,6 +10,7 @@ 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; @@ -30,7 +31,7 @@ public class SimChangedReceiver extends BroadcastReceiver { } public static void checkSimState(final Context context) { - if (hasDifferentSubscriptions(context)) { + if (hasDifferentSubscriptions(context) && VersionTracker.isDbUpdated(context)) { ApplicationContext.getInstance(context) .getJobManager() .add(new GenerateKeysJob(context)); -- GitLab From a29e5446433bde2cba1610c727cf07e392977f5f Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Sat, 25 Mar 2017 15:47:22 +0100 Subject: [PATCH 13/46] update subscriptions if a SIM change is detected Avoid having "CARD 1" or "CARD 2" as carrier name. --- src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java index 5746f017..96ae380b 100644 --- a/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java +++ b/src/org/smssecure/smssecure/util/dualsim/SimChangedReceiver.java @@ -37,6 +37,7 @@ public class SimChangedReceiver extends BroadcastReceiver { .add(new GenerateKeysJob(context)); SilencePreferences.setDeviceSubscriptions(context, getDeviceSubscriptions(context)); } + SubscriptionManagerCompat.from(context).updateActiveSubscriptionInfoList(); } private static boolean hasDifferentSubscriptions(Context context) { -- GitLab From 945c62e5bc41aebd93a135802cbf9e017831189f Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Thu, 30 Mar 2017 13:26:51 +0200 Subject: [PATCH 14/46] fix crash if no SIM card is inserted --- res/values/strings.xml | 4 ++++ .../smssecure/smssecure/ConversationActivity.java | 4 ++++ src/org/smssecure/smssecure/TransportOption.java | 1 + src/org/smssecure/smssecure/TransportOptions.java | 14 +++++++++++++- .../smssecure/util/DummyCharacterCalculator.java | 9 +++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/org/smssecure/smssecure/util/DummyCharacterCalculator.java diff --git a/res/values/strings.xml b/res/values/strings.xml index db9f9449..ff1ed79a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -362,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. diff --git a/src/org/smssecure/smssecure/ConversationActivity.java b/src/org/smssecure/smssecure/ConversationActivity.java index 3303e0b0..09afd502 100644 --- a/src/org/smssecure/smssecure/ConversationActivity.java +++ b/src/org/smssecure/smssecure/ConversationActivity.java @@ -1293,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/TransportOption.java b/src/org/smssecure/smssecure/TransportOption.java index fc777660..68a63470 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 } diff --git a/src/org/smssecure/smssecure/TransportOptions.java b/src/org/smssecure/smssecure/TransportOptions.java index 508f0b22..c51058c3 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) { @@ -212,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/util/DummyCharacterCalculator.java b/src/org/smssecure/smssecure/util/DummyCharacterCalculator.java new file mode 100644 index 00000000..7921f475 --- /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); + } +} -- GitLab From 5819c23546e5a18317fd1fb36ca4cab471827702 Mon Sep 17 00:00:00 2001 From: Bastien Le Querrec Date: Mon, 3 Apr 2017 14:04:10 +0200 Subject: [PATCH 15/46] reduce log's verbosity --- .../smssecure/util/dualsim/SubscriptionManagerCompat.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java b/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java index 2689940a..81760ed9 100644 --- a/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java +++ b/src/org/smssecure/smssecure/util/dualsim/SubscriptionManagerCompat.java @@ -8,7 +8,6 @@ import android.telephony.SmsManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.util.Log; import org.smssecure.smssecure.util.ServiceUtil; @@ -18,7 +17,6 @@ import java.util.LinkedList; import java.util.List; public class SubscriptionManagerCompat { - private static final String TAG = SubscriptionManagerCompat.class.getSimpleName(); private static SubscriptionManagerCompat instance; @@ -27,8 +25,6 @@ public class SubscriptionManagerCompat { private List compatList; public static SubscriptionManagerCompat from(Context context) { - Log.w(TAG, "from()"); - if (instance == null) { instance = new SubscriptionManagerCompat(context); } -- GitLab From 8e63b3ad7f594260d62bde6a1bdc543ad5907d99 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 1 Mar 2018 14:07:27 +0100 Subject: [PATCH 16/46] SDK_INT is never <9 because minSdkVersion 9 --- src/org/smssecure/smssecure/service/MmsListener.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/org/smssecure/smssecure/service/MmsListener.java b/src/org/smssecure/smssecure/service/MmsListener.java index 2a27b76e..f6ad6f62 100644 --- a/src/org/smssecure/smssecure/service/MmsListener.java +++ b/src/org/smssecure/smssecure/service/MmsListener.java @@ -39,9 +39,6 @@ public class MmsListener extends BroadcastReceiver { private static final String TAG = MmsListener.class.getSimpleName(); private boolean isRelevant(Context context, Intent intent) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.DONUT) { - return false; - } if (!ApplicationMigrationService.isDatabaseImported(context)) { return false; -- GitLab From 6bd009e46e8bc335b4eb229305c58dade63190af Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 1 Mar 2018 14:13:17 +0100 Subject: [PATCH 17/46] Replace for i with foreach --- .../smssecure/contacts/ArrayListCursor.java | 4 ++-- .../smssecure/contacts/RecipientsEditor.java | 6 +++--- .../database/CanonicalSessionMigrator.java | 12 ++++++------ .../database/EncryptedBackupExporter.java | 16 +++++++--------- .../smssecure/dom/smil/ElementTimeImpl.java | 8 ++++---- .../MultipartSmsTransportMessageFragments.java | 13 ++++++------- src/org/smssecure/smssecure/util/Hex.java | 6 +++--- 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/org/smssecure/smssecure/contacts/ArrayListCursor.java b/src/org/smssecure/smssecure/contacts/ArrayListCursor.java index 4f76eae5..7edbc453 100644 --- a/src/org/smssecure/smssecure/contacts/ArrayListCursor.java +++ b/src/org/smssecure/smssecure/contacts/ArrayListCursor.java @@ -34,8 +34,8 @@ public class ArrayListCursor extends AbstractCursor { int colCount = columnNames.length; boolean foundID = false; // Add an _id column if not in columnNames - for (int i = 0; i < colCount; ++i) { - if (columnNames[i].compareToIgnoreCase("_id") == 0) { + for (String columnName : columnNames) { + if (columnName.compareToIgnoreCase("_id") == 0) { mColumnNames = columnNames; foundID = true; break; diff --git a/src/org/smssecure/smssecure/contacts/RecipientsEditor.java b/src/org/smssecure/smssecure/contacts/RecipientsEditor.java index 9aebf37f..f090b34b 100644 --- a/src/org/smssecure/smssecure/contacts/RecipientsEditor.java +++ b/src/org/smssecure/smssecure/contacts/RecipientsEditor.java @@ -280,9 +280,9 @@ public class RecipientsEditor extends AppCompatMultiAutoCompleteTextView { } private static String getAnnotation(Annotation[] a, String key) { - for (int i = 0; i < a.length; i++) { - if (a[i].getKey().equals(key)) { - return a[i].getValue(); + for (Annotation anA : a) { + if (anA.getKey().equals(key)) { + return anA.getValue(); } } diff --git a/src/org/smssecure/smssecure/database/CanonicalSessionMigrator.java b/src/org/smssecure/smssecure/database/CanonicalSessionMigrator.java index 529eaa10..9035ad78 100644 --- a/src/org/smssecure/smssecure/database/CanonicalSessionMigrator.java +++ b/src/org/smssecure/smssecure/database/CanonicalSessionMigrator.java @@ -55,14 +55,14 @@ public class CanonicalSessionMigrator { String[] files = rootDirectory.list(); - for (int i=0;i beginTimeList = new ArrayList