Remove encrypted SMS transport, simplify transport options.

Closes #2647

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-03-11 14:23:45 -07:00
parent 2011391e65
commit a4e18c515c
57 changed files with 541 additions and 1952 deletions

View File

@ -78,7 +78,7 @@
android:contentDescription="@string/conversation_activity__emoji_toggle_description"
android:padding="10dp"/>
<EditText
<org.thoughtcrime.securesms.components.ComposeText
android:id="@+id/embedded_text_editor"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -89,7 +89,7 @@
android:paddingRight="0dp"
android:imeOptions="actionSend|flagNoEnterAction|flagNoExtractUi"
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
android:maxLength="1000"
android:maxLength="2000"
android:maxLines="4"
android:nextFocusForward="@+id/send_button"
android:nextFocusRight="@+id/send_button"

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:paddingTop="15dp"
android:paddingLeft="15dp"
android:paddingBottom="15dp"
android:paddingRight="10dp">
<CheckBox android:id="@+id/data_users"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:singleLine="true"
android:text="@string/preferences__sms_outgoing_push_users" />
<TextView
style="@style/TextSecureDialogSecondaryText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-5dp"
android:layout_marginLeft="32dp"
android:textSize="12sp"
android:text="@string/preferences__sms_outgoing_push_users_description" />
<CheckBox android:id="@+id/ask_before_fallback_data"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="@string/preferences__sms_fallback_ask_before_sending_sms_mms"
android:layout_marginLeft="25dp" />
<CheckBox android:id="@+id/never_send_mms"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="@string/preferences__sms_fallback_never_send_mms"
android:layout_marginLeft="25dp" />
<CheckBox android:id="@+id/non_data_users"
style="@style/TextSecureDialogPrimaryText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:text="@string/preferences__sms_fallback_non_push_users" />
</LinearLayout>

View File

@ -77,7 +77,8 @@
android:orientation="horizontal">
<Button android:id="@+id/skipButton"
android:text="@string/registration_activity__skip"
android:visibility="gone"
android:text="@android:string/cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="5dip"

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:title="@string/conversation_insecure__security"
android:id="@+id/menu_security"
android:icon="?menu_unlock_icon"
app:showAsAction="ifRoom">
<menu>
<item android:title="@string/conversation_insecure__menu_start_secure_session"
android:id="@+id/menu_start_secure_session" />
</menu>
</item>
</menu>

View File

@ -143,48 +143,6 @@
<item>custom</item>
</string-array>
<string-array name="transport_selection_entries_text">
<item>@string/ConversationActivity_transport_insecure_sms</item>
<item>@string/ConversationActivity_transport_secure_sms</item>
<item>@string/ConversationActivity_transport_textsecure</item>
</string-array>
<string-array name="transport_selection_entries_media">
<item>@string/ConversationActivity_transport_insecure_mms</item>
<item>@string/ConversationActivity_transport_secure_mms</item>
<item>@string/ConversationActivity_transport_textsecure</item>
</string-array>
<string-array name="transport_selection_entries_compose_text">
<item>@string/conversation_activity__type_message_sms_insecure</item>
<item>@string/conversation_activity__type_message_sms_secure</item>
<item>@string/conversation_activity__type_message_push</item>
</string-array>
<string-array name="transport_selection_entries_compose_media">
<item>@string/conversation_activity__type_message_mms_insecure</item>
<item>@string/conversation_activity__type_message_mms_secure</item>
<item>@string/conversation_activity__type_message_push</item>
</string-array>
<string-array name="transport_selection_values">
<item>insecure_sms</item>
<item>secure_sms</item>
<item>textsecure</item>
</string-array>
<string-array name="transport_selection_icons_light">
<item>@drawable/ic_send_sms_insecure</item>
<item>@drawable/ic_send_sms_secure</item>
<item>@drawable/ic_send_push</item>
</string-array>
<string-array name="transport_selection_icons_dark">
<item>@drawable/ic_send_sms_insecure_dark</item>
<item>@drawable/ic_send_sms_secure</item>
<item>@drawable/ic_send_push</item>
</string-array>
<string-array name="pref_repeat_alerts_entries">
<item>@string/preferences__never</item>
<item>@string/preferences__one_time</item>

View File

@ -31,7 +31,8 @@
<attr name="conversation_send_button_sms_secure" format="reference"/>
<attr name="conversation_send_button_sms_insecure" format="reference"/>
<attr name="conversation_delivery_delivered" format="reference"/>
<attr name="conversation_transport_indicators" format="reference"/>
<attr name="conversation_transport_sms_indicator" format="reference"/>
<attr name="conversation_transport_push_indicator" format="reference"/>
<attr name="conversation_transport_popup_background" format="reference"/>
<attr name="conversation_emoji_toggle" format="reference"/>
<attr name="conversation_keyboard_toggle" format="reference"/>

View File

@ -78,15 +78,11 @@
<string name="ConversationItem_error_received_stale_key_exchange_message">Error, received stale key exchange message.</string>
<string name="ConversationItem_received_key_exchange_message_click_to_process">Received key exchange message, click to process.</string>
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
<string name="ConversationItem_click_to_approve_sms">Tap for SMS fallback</string>
<string name="ConversationItem_click_to_approve_mms">Tap for MMS fallback</string>
<string name="ConversationItem_click_for_details">Tap for details</string>
<string name="ConversationItem_click_to_approve_unencrypted">Tap for unsecured fallback</string>
<string name="ConversationItem_click_to_approve_sms_dialog_title">Fallback to SMS?</string>
<string name="ConversationItem_click_to_approve_mms_dialog_title">Fallback to MMS?</string>
<string name="ConversationItem_click_to_approve_unencrypted_sms_dialog_title">Fallback to unencrypted SMS?</string>
<string name="ConversationItem_click_to_approve_unencrypted_mms_dialog_title">Fallback to unencrypted MMS?</string>
<string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because a secure session could not be established.\n\nSend unsecured message?</string>
<string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because the recipient is no longer a TextSecure user.\n\nSend unsecured message?</string>
<string name="ConversationItem_unable_to_open_media">Can\'t find an app able to open this media.</string>
<!-- ConversationActivity -->
@ -117,8 +113,6 @@
<string name="ConversationActivity_are_you_sure_you_want_to_leave_this_group">Are you sure you want to leave this group?</string>
<string name="ConversationActivity_transport_insecure_sms">Insecure SMS</string>
<string name="ConversationActivity_transport_insecure_mms">Insecure MMS</string>
<string name="ConversationActivity_transport_secure_sms">Secure SMS</string>
<string name="ConversationActivity_transport_secure_mms">Secure MMS</string>
<string name="ConversationActivity_transport_textsecure">TextSecure</string>
<string name="ConversationActivity_get_with_it">Get with it: %s</string>
<string name="ConversationActivity_install_textsecure">Install TextSecure: %s</string>
@ -294,10 +288,6 @@
<string name="ReceiveKeyActivity_you_may_wish_to_verify_this_contact">You may wish to verify
this contact.
</string>
<string name="ReceiveKeyActivity_the_signature_on_this_key_exchange_is_trusted_but">The
signature on this key exchange is trusted, but you have the \'automatically complete key
exchanges\' setting disabled.
</string>
<string name="ReceiveKeyActivity_processing">Processing</string>
<string name="ReceiveKeyActivity_processing_key_exchange">Processing key exchange…</string>
@ -453,10 +443,6 @@
<!-- QuickResponseService -->
<string name="QuickResponseService_sorry_quick_response_is_not_yet_supported_by_textsecure">Sorry, Quick Response is not yet supported by TextSecure!</string>
<!-- auto_initiate_activity -->
<string name="auto_initiate_activity__you_have_received_a_message_from_someone_who_supports_textsecure_encrypted_sessions_would_you_like_to_initiate_a_secure_session">You have received a message from someone who supports TextSecure encrypted sessions. Would you like to initiate a secure session?</string>
<string name="auto_initiate_activity__initiate_exchange">Initiate exchange</string>
<!-- change_passphrase_activity -->
<string name="change_passphrase_activity__old_passphrase">OLD PASSPHRASE:</string>
<string name="change_passphrase_activity__new_passphrase">NEW PASSPHRASE:</string>
@ -581,7 +567,7 @@
<!-- registration_activity -->
<string name="registration_activity__textsecure_can_use_instant_messages_to_avoid_sms_charges_when_communicating_with_other_textsecure_users">
Registering is free and increases privacy when messaging other TextSecure users. Please verify your phone number.
Verify your phone number to connect with TextSecure.
</string>
<string name="registration_activity__your_country">YOUR COUNTRY</string>
<string name="registration_activity__your_country_code_and_phone_number">YOUR COUNTRY CODE AND
@ -590,7 +576,6 @@
<string name="registration_activity__phone_number">PHONE NUMBER</string>
<string name="registration_activity__register">Register</string>
<string name="registration_activity__registration_will_transmit_some_contact_information_to_the_server_temporariliy">Registration transmits some contact information to the server. It is not stored.</string>
<string name="registration_activity__skip">Skip</string>
<!-- registration_problems -->
<string name="registration_problems__some_possible_problems_include">Some possible problems
@ -689,7 +674,6 @@
<string name="AndroidManifest__public_identity_key">Public identity key</string>
<string name="AndroidManifest__change_passphrase">Change passphrase</string>
<string name="AndroidManifest__verify_identity">Verify identity</string>
<string name="AndroidManifest__complete_key_exchange">Complete key exchange</string>
<string name="AndroidManifest__log_submit">Submit debug logs</string>
<string name="AndroidManifest__media_preview">Media Preview</string>
<string name="AndroidManifest__media_overview">All images</string>
@ -730,13 +714,11 @@
<string name="preferences__choose_your_contact_entry_from_the_contacts_list">Choose your contact entry from the contacts list.</string>
<string name="preferences__change_passphrase">Change passphrase</string>
<string name="preferences__change_my_passphrase">Change my passphrase</string>
<string name="preferences__complete_key_exchanges">Complete key exchanges</string>
<string name="preferences__enable_passphrase">Enable passphrase</string>
<string name="preferences__passphrase_summary">Passphrase %s</string>
<string name="preferences__enable_local_encryption_of_messages_and_keys">Enable local encryption of messages and keys</string>
<string name="preferences__screen_security">Screen security</string>
<string name="preferences__screen_security_summary">Screen security %s</string>
<string name="preferences__automatically_complete_key_exchanges_for_new_sessions_or_for_existing_sessions_with_the_same_identity_key">Automatically complete key exchanges for new sessions or for existing sessions with the same identity key</string>
<string name="preferences__disable_screen_security_to_allow_screen_shots">Block screenshots in the recents list and inside the app</string>
<string name="preferences__forget_passphrase_from_memory_after_some_interval">Forget passphrase from memory after some interval</string>
<string name="preferences__timeout_passphrase">Timeout passphrase</string>
@ -840,13 +822,6 @@
<!-- refreshing push directory from menu -->
<string name="push_directory__menu_refresh">Refresh contact list</string>
<!-- conversation_button_context -->
<string name="conversation_button_context__send_textsecure_message">Send TextSecure message</string>
<string name="conversation_button_context__send_secure_sms">Send secure SMS</string>
<string name="conversation_button_context__send_insecure_sms">Send unsecured SMS</string>
<string name="conversation_button_context__send_secure_mms">Send secure MMS</string>
<string name="conversation_button_context__send_insecure_mms">Send unsecured MMS</string>
<!-- conversation_callable -->
<string name="conversation_callable__menu_call">Call</string>

View File

@ -48,7 +48,8 @@
<item name="conversation_send_button_sms_secure">@drawable/ic_send_sms_secure</item>
<item name="conversation_send_button_sms_insecure">@drawable/ic_send_sms_insecure</item>
<item name="conversation_delivery_delivered">@drawable/ic_delivery_delivered_dark</item>
<item name="conversation_transport_indicators">@array/transport_selection_icons_light</item>
<item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure_dark</item>
<item name="conversation_transport_push_indicator">@drawable/ic_send_push</item>
<item name="conversation_transport_popup_background">@color/white</item>
<item name="conversation_emoji_toggle">@drawable/ic_emoji_dark</item>
<item name="conversation_keyboard_toggle">@drawable/ic_ime_dark</item>
@ -173,7 +174,8 @@
<item name="conversation_send_button_sms_secure">@drawable/ic_send_sms_secure</item>
<item name="conversation_send_button_sms_insecure">@drawable/ic_send_sms_insecure_dark</item>
<item name="conversation_delivery_delivered">@drawable/ic_delivery_delivered_dark</item>
<item name="conversation_transport_indicators">@array/transport_selection_icons_dark</item>
<item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure_dark</item>
<item name="conversation_transport_push_indicator">@drawable/ic_send_push</item>
<item name="conversation_transport_popup_background">@color/black</item>
<item name="conversation_emoji_toggle">@drawable/ic_emoji_light</item>
<item name="conversation_keyboard_toggle">@drawable/ic_ime_light</item>

View File

@ -15,10 +15,6 @@
android:title="@string/preferences__make_default_sms_app"
android:summary="@string/preferences__make_textsecure_the_default_sms_mms_app" />
<org.thoughtcrime.securesms.components.OutgoingSmsPreference
android:key="pref_outgoing_sms"
android:title="@string/preferences__sms_fallback" />
<CheckBoxPreference android:defaultValue="false"
android:key="pref_delivery_report_sms"
android:summary="@string/preferences__request_a_delivery_report_for_each_sms_message_you_send"

View File

@ -1,120 +0,0 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
/**
* Activity which prompts the user to initiate a secure
* session. Initiated by whitespace tag detection from
* the remote endpoint.
*
* @author Moxie Marlinspike
*
*/
public class AutoInitiateActivity extends BaseActivity {
private long threadId;
private Recipient recipient;
private MasterSecret masterSecret;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.auto_initiate_activity);
initializeResources();
}
@Override
public void onDestroy() {
MemoryCleaner.clean(masterSecret);
super.onDestroy();
}
private void initializeResources() {
this.threadId = this.getIntent().getLongExtra("threadId", -1);
this.recipient = RecipientFactory.getRecipientForId(this, this.getIntent().getLongExtra("recipient", -1), true);
this.masterSecret = this.getIntent().getParcelableExtra("masterSecret");
((Button)findViewById(R.id.initiate_button)).setOnClickListener(new OkListener());
((Button)findViewById(R.id.cancel_button)).setOnClickListener(new CancelListener());
}
private void initiateKeyExchange() {
KeyExchangeInitiator.initiate(this, masterSecret, recipient, true);
finish();
}
private class OkListener implements View.OnClickListener {
@Override
public void onClick(View v) {
initiateKeyExchange();
}
}
private class CancelListener implements View.OnClickListener {
@Override
public void onClick(View v) {
Log.w("AutoInitiateActivity", "Exempting threadID: " + threadId);
exemptThread(AutoInitiateActivity.this, threadId);
AutoInitiateActivity.this.finish();
}
}
public static void exemptThread(Context context, long threadId) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
sp.edit().putBoolean("pref_thread_auto_init_exempt_" + threadId, true).apply();
}
public static boolean isValidAutoInitiateSituation(Context context, MasterSecret masterSecret,
Recipient recipient, String message, long threadId)
{
return
Tag.isTagged(message) &&
TextSecurePreferences.isPushRegistered(context) &&
isThreadQualified(context, threadId) &&
isExchangeQualified(context, masterSecret, recipient);
}
private static boolean isThreadQualified(Context context, long threadId) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return !sp.getBoolean("pref_thread_auto_init_exempt_" + threadId, false);
}
private static boolean isExchangeQualified(Context context,
MasterSecret masterSecret,
Recipient recipient)
{
return SessionUtil.hasSession(context, masterSecret, recipient);
}
}

View File

@ -45,23 +45,21 @@ import android.view.View.OnFocusChangeListener;
import android.view.View.OnKeyListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.EmojiDrawer;
import org.thoughtcrime.securesms.components.EmojiToggle;
import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
@ -80,7 +78,6 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
@ -108,6 +105,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
@ -142,7 +140,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private static final int GROUP_EDIT = 5;
private MasterSecret masterSecret;
private EditText composeText;
private ComposeText composeText;
private SendButton sendButton;
private TextView charactersLeft;
@ -158,10 +156,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private int distributionType;
private boolean isEncryptedConversation;
private boolean isMmsEnabled = true;
private boolean isCharactersLeftViewEnabled;
private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
@Override
protected void onCreate(Bundle state) {
@ -264,13 +261,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater();
menu.clear();
boolean pushRegistered = TextSecurePreferences.isPushRegistered(this);
if (isSingleConversation() && isEncryptedConversation) {
inflater.inflate(R.menu.conversation_secure_identity, menu);
inflater.inflate(R.menu.conversation_secure_sms, menu.findItem(R.id.menu_security).getSubMenu());
} else if (isSingleConversation()) {
if (!pushRegistered) inflater.inflate(R.menu.conversation_insecure_no_push, menu);
inflater.inflate(R.menu.conversation_insecure, menu);
}
@ -310,7 +304,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
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;
@ -362,33 +355,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
startActivity(verifyIdentityIntent);
}
private void handleStartSecureSession() {
if (getRecipients() == null) {
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
Toast.LENGTH_LONG).show();
return;
}
final Recipient recipient = getRecipients().getPrimaryRecipient();
String recipientName = (recipient.getName() == null ? recipient.getNumber() : recipient.getName());
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_initiate_secure_session_question);
builder.setIcon(Dialogs.resolveIcon(this, R.attr.dialog_info_icon));
builder.setCancelable(true);
builder.setMessage(String.format(getString(R.string.ConversationActivity_initiate_secure_session_with_s_question),
recipientName));
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
KeyExchangeInitiator.initiate(ConversationActivity.this, masterSecret,
recipient, true);
}
});
builder.setNegativeButton(R.string.no, null);
builder.show();
}
private void handleAbortSecureSession() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_abort_secure_session_confirmation);
@ -679,31 +645,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeSecurity() {
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
boolean isSecureSmsAllowed = (!isPushDestination || DirectoryHelper.isSmsFallbackAllowed(this, getRecipients()));
boolean isSecureSmsDestination = isSecureSmsAllowed &&
isSingleConversation() &&
SessionUtil.hasSession(this, masterSecret, primaryRecipient);
boolean isMediaMessage = !recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent();
this.isEncryptedConversation = DirectoryHelper.isPushDestination(this, getRecipients());
if (isPushDestination || isSecureSmsDestination) {
this.isEncryptedConversation = true;
} else {
this.isEncryptedConversation = false;
}
sendButton.resetAvailableTransports(isMediaMessage);
sendButton.initializeAvailableTransports(!recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent());
if (!isPushDestination ) sendButton.disableTransport("textsecure");
if (!isSecureSmsDestination ) sendButton.disableTransport("secure_sms");
if (recipients.isGroupRecipient()) sendButton.disableTransport("insecure_sms");
if (!isEncryptedConversation) sendButton.disableTransport(Type.TEXTSECURE);
if (recipients.isGroupRecipient()) sendButton.disableTransport(Type.SMS);
if (isPushDestination) {
sendButton.setDefaultTransport("textsecure");
} else if (isSecureSmsDestination) {
sendButton.setDefaultTransport("secure_sms");
} else {
sendButton.setDefaultTransport("insecure_sms");
}
if (isEncryptedConversation) sendButton.setDefaultTransport(Type.TEXTSECURE);
else sendButton.setDefaultTransport(Type.SMS);
calculateCharactersRemaining();
}
@ -731,9 +682,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeViews() {
sendButton = (SendButton) findViewById(R.id.send_button);
composeText = (EditText) findViewById(R.id.embedded_text_editor);
charactersLeft = (TextView) findViewById(R.id.space_left);
sendButton = (SendButton) findViewById(R.id.send_button);
composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
charactersLeft = (TextView) findViewById(R.id.space_left);
emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer);
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
@ -749,11 +700,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendButton.setOnClickListener(sendButtonListener);
sendButton.setEnabled(true);
sendButton.setComposeTextView(composeText);
sendButton.addOnTransportChangedListener(new OnTransportChangedListener() {
@Override
public void onChange(TransportOption newTransport) {
calculateCharactersRemaining();
composeText.setHint(newTransport.getComposeHint());
}
});
@ -784,10 +735,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
securityUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getLongExtra("thread_id", -1) == -1)
return;
long eventThreadId = intent.getLongExtra("thread_id", -1);
if (intent.getLongExtra("thread_id", -1) == threadId) {
if (eventThreadId == threadId || eventThreadId == -2) {
initializeSecurity();
initializeTitleBar();
calculateCharactersRemaining();
@ -959,23 +909,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
int charactersSpent = composeText.getText().toString().length();
TransportOption transportOption = sendButton.getSelectedTransport();
if (transportOption != null) {
CharacterState characterState = transportOption.calculateCharacters(charactersSpent);
CharacterState characterState = transportOption.calculateCharacters(charactersSpent);
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
charactersLeft.setText(characterState.charactersRemaining + "/" + characterState.maxMessageSize
+ " (" + characterState.messagesSpent + ")");
charactersLeft.setVisibility(View.VISIBLE);
} else {
charactersLeft.setVisibility(View.GONE);
}
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
charactersLeft.setText(characterState.charactersRemaining + "/" + characterState.maxMessageSize
+ " (" + characterState.messagesSpent + ")");
charactersLeft.setVisibility(View.VISIBLE);
} else {
charactersLeft.setVisibility(View.GONE);
}
}
private boolean isExistingConversation() {
return this.recipients != null && this.threadId != -1;
}
private boolean isSingleConversation() {
return getRecipients() != null && getRecipients().isSingleRecipient() && !getRecipients().isGroupRecipient();
}
@ -1013,9 +957,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation));
if (!isEncryptedConversation && Tag.isTaggable(rawText))
rawText = Tag.getTaggedMessage(rawText);
return rawText;
}
@ -1056,7 +997,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void sendMessage() {
try {
final Recipients recipients = getRecipients();
Recipients recipients = getRecipients();
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
Log.w(TAG, "isManual Selection: " + sendButton.isManualSelection());
Log.w(TAG, "forceSms: " + forceSms);
if (recipients == null) {
throw new RecipientFormattingException("Badly formatted");
@ -1065,9 +1010,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
handleManualMmsRequired();
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
sendMediaMessage(sendButton.getSelectedTransport().isPlaintext(), sendButton.getSelectedTransport().isSms());
sendMediaMessage(forceSms);
} else {
sendTextMessage(sendButton.getSelectedTransport().isPlaintext(), sendButton.getSelectedTransport().isSms());
sendTextMessage(forceSms);
}
} catch (RecipientFormattingException ex) {
Toast.makeText(ConversationActivity.this,
@ -1081,7 +1026,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void sendMediaMessage(boolean forcePlaintext, final boolean forceSms)
private void sendMediaMessage(final boolean forceSms)
throws InvalidMessageException
{
final Context context = getApplicationContext();
@ -1093,7 +1038,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(this, recipients, slideDeck,
getMessage(), distributionType);
if (isEncryptedConversation && !forcePlaintext) {
if (isEncryptedConversation && !forceSms) {
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);
}
@ -1113,13 +1058,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}.execute(outgoingMessage);
}
private void sendTextMessage(boolean forcePlaintext, final boolean forceSms)
private void sendTextMessage(final boolean forceSms)
throws InvalidMessageException
{
final Context context = getApplicationContext();
OutgoingTextMessage message;
if (isEncryptedConversation && !forcePlaintext) {
if (isEncryptedConversation && !forceSms) {
message = new OutgoingEncryptedMessage(recipients, getMessage());
} else {
message = new OutgoingTextMessage(recipients, getMessage());
@ -1183,16 +1128,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private class AddContactButtonListener implements OnClickListener {
@Override
public void onClick(View v) {
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipients.getPrimaryRecipient().getNumber());
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
startActivity(intent);
}
}
private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {

View File

@ -227,7 +227,7 @@ public class ConversationItem extends LinearLayout {
if ((messageRecord.isPending() || messageRecord.isFailed()) && pushDestination && !messageRecord.isForcedSms()) {
background = SENT_PUSH_PENDING;
triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
} else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingSmsFallback()) {
} else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) {
background = SENT_SMS_PENDING;
triangleBackground = SENT_SMS_PENDING_TRIANGLE;
} else if (messageRecord.isPush()) {
@ -291,11 +291,10 @@ public class ConversationItem extends LinearLayout {
mmsDownloadButton.setVisibility(View.GONE);
mmsDownloadingLabel.setVisibility(View.GONE);
if (messageRecord.isFailed()) setFailedStatusIcons();
else if (messageRecord.isPendingSmsFallback()) setFallbackStatusIcons();
else if (messageRecord.isPending()) dateText.setText(" ··· ");
else setSentStatusIcons();
if (messageRecord.isFailed()) setFailedStatusIcons();
else if (messageRecord.isPendingInsecureSmsFallback()) setFallbackStatusIcons();
else if (messageRecord.isPending()) dateText.setText(" ··· ");
else setSentStatusIcons();
}
private void setSentStatusIcons() {
@ -316,13 +315,7 @@ public class ConversationItem extends LinearLayout {
private void setFallbackStatusIcons() {
pendingIndicator.setVisibility(View.VISIBLE);
indicatorText.setVisibility(View.VISIBLE);
if (messageRecord.isPendingSecureSmsFallback()) {
if (messageRecord.isMms()) indicatorText.setText(R.string.ConversationItem_click_to_approve_mms);
else indicatorText.setText(R.string.ConversationItem_click_to_approve_sms);
} else {
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
}
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
}
private void setMinimumWidth() {
@ -335,20 +328,9 @@ public class ConversationItem extends LinearLayout {
}
private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isFailed()||
messageRecord.isPendingSmsFallback() ||
(messageRecord.isKeyExchange() &&
!messageRecord.isCorruptedKeyExchange() &&
!messageRecord.isOutgoing()));
if (!messageRecord.isOutgoing() &&
messageRecord.getRecipients().isSingleRecipient() &&
!messageRecord.isSecure())
{
checkForAutoInitiate(messageRecord.getIndividualRecipient(),
messageRecord.getBody().getBody(),
messageRecord.getThreadId());
}
setClickable(messageRecord.isFailed() ||
messageRecord.isPendingInsecureSmsFallback() ||
messageRecord.isBundleKeyExchange());
}
private void setGroupMessageStatus(MessageRecord messageRecord) {
@ -427,23 +409,6 @@ public class ConversationItem extends LinearLayout {
/// Helper Methods
private void checkForAutoInitiate(Recipient recipient, String body, long threadId) {
if (!groupThread &&
AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, recipient,
body, threadId))
{
AutoInitiateActivity.exemptThread(context, threadId);
Intent intent = new Intent();
intent.setClass(context, AutoInitiateActivity.class);
intent.putExtra("threadId", threadId);
intent.putExtra("masterSecret", masterSecret);
intent.putExtra("recipient", recipient.getRecipientId());
context.startActivity(intent);
}
}
private void setContactPhotoForRecipient(final Recipient recipient) {
if (contactPhoto == null) return;
@ -588,7 +553,7 @@ public class ConversationItem extends LinearLayout {
!messageRecord.isStaleKeyExchange())
{
handleKeyExchangeClicked();
} else if (messageRecord.isPendingSmsFallback()) {
} else if (messageRecord.isPendingInsecureSmsFallback()) {
handleMessageApproval();
}
}
@ -611,17 +576,10 @@ public class ConversationItem extends LinearLayout {
final int title;
final int message;
if (messageRecord.isPendingSecureSmsFallback()) {
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_mms_dialog_title;
else title = R.string.ConversationItem_click_to_approve_sms_dialog_title;
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_dialog_title;
else title = R.string.ConversationItem_click_to_approve_unencrypted_sms_dialog_title;
message = -1;
} else {
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_dialog_title;
else title = R.string.ConversationItem_click_to_approve_unencrypted_sms_dialog_title;
message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message;
}
message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title);
@ -633,9 +591,7 @@ public class ConversationItem extends LinearLayout {
public void onClick(DialogInterface dialogInterface, int i) {
if (messageRecord.isMms()) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
if (messageRecord.isPendingInsecureSmsFallback()) {
database.markAsInsecure(messageRecord.getId());
}
database.markAsInsecure(messageRecord.getId());
database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId());
@ -644,9 +600,7 @@ public class ConversationItem extends LinearLayout {
.add(new MmsSendJob(context, messageRecord.getId()));
} else {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
if (messageRecord.isPendingInsecureSmsFallback()) {
database.markAsInsecure(messageRecord.getId());
}
database.markAsInsecure(messageRecord.getId());
database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId());

View File

@ -27,10 +27,10 @@ import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.support.v4.widget.CursorAdapter;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -48,12 +48,12 @@ import org.thoughtcrime.securesms.components.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.ReminderView;
import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.Set;

View File

@ -34,15 +34,11 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.SmsDecryptJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.Base64;
@ -52,7 +48,6 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.util.guava.Optional;
@ -78,9 +73,9 @@ public class ReceiveKeyActivity extends BaseActivity {
private int recipientDeviceId;
private long messageId;
private MasterSecret masterSecret;
private IncomingKeyExchangeMessage message;
private IdentityKey identityKey;
private MasterSecret masterSecret;
private IncomingPreKeyBundleMessage message;
private IdentityKey identityKey;
@Override
protected void onCreate(Bundle state) {
@ -151,14 +146,7 @@ public class ReceiveKeyActivity extends BaseActivity {
getIntent().getStringExtra("body"),
Optional.<TextSecureGroup>absent());
if (getIntent().getBooleanExtra("is_bundle", false)) {
this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody());
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
this.message = new IncomingIdentityUpdateMessage(message, message.getMessageBody());
} else {
this.message = new IncomingKeyExchangeMessage(message, message.getMessageBody());
}
this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody());
this.identityKey = getIdentityKey(this.message);
}
@ -177,21 +165,12 @@ public class ReceiveKeyActivity extends BaseActivity {
this.cancelButton.setOnClickListener(new CancelListener());
}
private IdentityKey getIdentityKey(IncomingKeyExchangeMessage message)
private IdentityKey getIdentityKey(IncomingPreKeyBundleMessage message)
throws InvalidKeyException, InvalidVersionException,
InvalidMessageException, LegacyMessageException
{
try {
if (message.isIdentityUpdate()) {
return new IdentityKey(Base64.decodeWithoutPadding(message.getMessageBody()), 0);
} else if (message.isPreKeyBundle()) {
boolean isPush = getIntent().getBooleanExtra("is_push", false);
if (isPush) return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey();
else return new PreKeyWhisperMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey();
} else {
return new KeyExchangeMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey();
}
return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey();
} catch (IOException e) {
throw new AssertionError(e);
}
@ -213,37 +192,25 @@ public class ReceiveKeyActivity extends BaseActivity {
@Override
protected Void doInBackground(Void... params) {
Context context = ReceiveKeyActivity.this;
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
Context context = ReceiveKeyActivity.this;
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
identityDatabase.saveIdentity(masterSecret, recipient.getRecipientId(), identityKey);
try {
byte[] body = Base64.decode(message.getMessageBody());
TextSecureEnvelope envelope = new TextSecureEnvelope(3, message.getSender(),
message.getSenderDeviceId(), "",
message.getSentTimestampMillis(),
body);
if (message.isIdentityUpdate()) {
smsDatabase.markAsProcessedKeyExchange(messageId);
} else {
if (getIntent().getBooleanExtra("is_push", false)) {
try {
byte[] body = Base64.decode(message.getMessageBody());
TextSecureEnvelope envelope = new TextSecureEnvelope(3, message.getSender(),
message.getSenderDeviceId(), "",
message.getSentTimestampMillis(),
body);
long pushId = pushDatabase.insert(envelope);
long pushId = pushDatabase.insert(envelope);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new PushDecryptJob(context, pushId, messageId, message.getSender()));
} catch (IOException e) {
throw new AssertionError(e);
}
} else {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SmsDecryptJob(context, messageId));
}
ApplicationContext.getInstance(context)
.getJobManager()
.add(new PushDecryptJob(context, pushId, messageId, message.getSender()));
} catch (IOException e) {
throw new AssertionError(e);
}
return null;

View File

@ -88,7 +88,9 @@ public class RegistrationActivity extends BaseActionBarActivity {
this.skipButton.setOnClickListener(new CancelButtonListener());
if (getIntent().getBooleanExtra("cancel_button", false)) {
this.skipButton.setText(android.R.string.cancel);
this.skipButton.setVisibility(View.VISIBLE);
} else {
this.skipButton.setVisibility(View.INVISIBLE);
}
}

View File

@ -7,36 +7,60 @@ import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
public class TransportOption {
public int drawable;
public String text;
public String key;
public String composeHint;
public CharacterCalculator characterCalculator;
public TransportOption(String key, int drawable, String text, String composeHint) {
this.key = key;
this.drawable = drawable;
this.text = text;
this.composeHint = composeHint;
public enum Type {
SMS,
TEXTSECURE
}
if (isPlaintext() && isSms()) {
this.characterCalculator = new SmsCharacterCalculator();
} else if (isSms()) {
this.characterCalculator = new EncryptedSmsCharacterCalculator();
} else {
this.characterCalculator = new PushCharacterCalculator();
}
private int drawable;
private String text;
private Type type;
private String composeHint;
private CharacterCalculator characterCalculator;
public TransportOption(Type type,
int drawable,
String text,
String composeHint,
CharacterCalculator characterCalculator)
{
this.type = type;
this.drawable = drawable;
this.text = text;
this.composeHint = composeHint;
this.characterCalculator = characterCalculator;
}
public Type getType() {
return type;
}
public boolean isType(Type type) {
return this.type == type;
}
public boolean isPlaintext() {
return key.equals("insecure_sms");
return type == Type.SMS;
}
public boolean isSms() {
return key.equals("insecure_sms") || key.equals("secure_sms");
return type == Type.SMS;
}
public CharacterState calculateCharacters(int charactersSpent) {
return characterCalculator.calculateCharacters(charactersSpent);
}
public int getDrawable() {
return drawable;
}
public String getComposeHint() {
return composeHint;
}
public String getDescription() {
return text;
}
}

View File

@ -2,149 +2,138 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.BitmapDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
import org.thoughtcrime.securesms.util.PushCharacterCalculator;
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
import org.whispersystems.libaxolotl.util.guava.Optional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.thoughtcrime.securesms.TransportOption.Type;
public class TransportOptions {
private static final String TAG = TransportOptions.class.getSimpleName();
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
private final Context context;
private PopupWindow transportPopup;
private final List<String> enabledTransports = new ArrayList<>();
private final Map<String, TransportOption> transportMetadata = new HashMap<>();
private String selectedTransport;
private boolean transportOverride = false;
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
private final List<TransportOption> enabledTransports;
public TransportOptions(Context context) {
this.context = context;
private Type selectedType;
private boolean manuallySelected;
public TransportOptions(Context context, boolean media) {
this.context = context;
this.enabledTransports = initializeAvailableTransports(media);
setDefaultTransport(Type.SMS);
}
private void initializeTransportPopup() {
if (transportPopup == null) {
final View selectionMenu = LayoutInflater.from(context).inflate(R.layout.transport_selection, null);
final ListView list = (ListView) selectionMenu.findViewById(R.id.transport_selection_list);
public void reset(boolean media) {
List<TransportOption> transportOptions = initializeAvailableTransports(media);
this.enabledTransports.clear();
this.enabledTransports.addAll(transportOptions);
final TransportOptionsAdapter adapter = new TransportOptionsAdapter(context, enabledTransports, transportMetadata);
list.setAdapter(adapter);
transportPopup = new PopupWindow(selectionMenu);
transportPopup.setFocusable(true);
transportPopup.setBackgroundDrawable(new BitmapDrawable(context.getResources(), ""));
transportPopup.setOutsideTouchable(true);
transportPopup.setWindowLayoutMode(0, WindowManager.LayoutParams.WRAP_CONTENT);
transportPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
list.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
transportOverride = true;
setTransport((TransportOption) adapter.getItem(position));
transportPopup.dismiss();
}
});
if (!find(selectedType).isPresent()) {
this.manuallySelected = false;
setTransport(Type.SMS);
} else {
final ListView list = (ListView) transportPopup.getContentView().findViewById(R.id.transport_selection_list);
final TransportOptionsAdapter adapter = (TransportOptionsAdapter) list.getAdapter();
adapter.setEnabledTransports(enabledTransports);
adapter.notifyDataSetInvalidated();
notifyTransportChangeListeners();
}
}
public void initializeAvailableTransports(boolean isMediaMessage) {
String[] entryArray = (isMediaMessage)
? context.getResources().getStringArray(R.array.transport_selection_entries_media)
: context.getResources().getStringArray(R.array.transport_selection_entries_text);
String[] composeHintArray = (isMediaMessage)
? context.getResources().getStringArray(R.array.transport_selection_entries_compose_media)
: context.getResources().getStringArray(R.array.transport_selection_entries_compose_text);
final String[] valuesArray = context.getResources().getStringArray(R.array.transport_selection_values);
final int[] attrs = new int[]{R.attr.conversation_transport_indicators};
final TypedArray iconArray = context.obtainStyledAttributes(attrs);
final int iconArrayResource = iconArray.getResourceId(0, -1);
final TypedArray icons = context.getResources().obtainTypedArray(iconArrayResource);
enabledTransports.clear();
for (int i=0; i<valuesArray.length; i++) {
String key = valuesArray[i];
enabledTransports.add(key);
transportMetadata.put(key, new TransportOption(key, icons.getResourceId(i, -1), entryArray[i], composeHintArray[i]));
public void setDefaultTransport(Type type) {
if (!this.manuallySelected) {
setTransport(type);
}
iconArray.recycle();
icons.recycle();
updateViews();
}
public void setTransport(String transport) {
selectedTransport = transport;
updateViews();
public void setSelectedTransport(Type type) {
this.manuallySelected= true;
setTransport(type);
}
private void setTransport(TransportOption transport) {
setTransport(transport.key);
}
public void showPopup(final View parent) {
initializeTransportPopup();
final int xoff = context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff);
final int yoff = context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff);
transportPopup.showAsDropDown(parent,
xoff,
yoff);
parent.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
transportPopup.update(parent, xoff, yoff, -1, -1);
}
});
}
public void setDefaultTransport(String transportName) {
if (!transportOverride) {
setTransport(transportName);
}
public boolean isManualSelection() {
return manuallySelected;
}
public TransportOption getSelectedTransport() {
return transportMetadata.get(selectedTransport);
Optional<TransportOption> option = find(selectedType);
if (option.isPresent()) return option.get();
else throw new AssertionError("Selected type isn't present!");
}
public void disableTransport(String transportName) {
enabledTransports.remove(transportName);
}
public List<String> getEnabledTransports() {
return enabledTransports;
}
private void updateViews() {
if (selectedTransport == null) return;
for (OnTransportChangedListener listener : listeners) {
listener.onChange(getSelectedTransport());
public void disableTransport(Type type) {
Optional<TransportOption> option = find(type);
if (option.isPresent()) {
enabledTransports.remove(option.get());
}
}
public List<TransportOption> getEnabledTransports() {
return enabledTransports;
}
public void addOnTransportChangedListener(OnTransportChangedListener listener) {
this.listeners.add(listener);
}
private List<TransportOption> initializeAvailableTransports(boolean isMediaMessage) {
List<TransportOption> results = new LinkedList<>();
int[] attributes = new int[]{R.attr.conversation_transport_sms_indicator,
R.attr.conversation_transport_push_indicator};
TypedArray iconArray = context.obtainStyledAttributes(attributes);
int smsIconResource = iconArray.getResourceId(0, -1);
int pushIconResource = iconArray.getResourceId(1, -1);
if (isMediaMessage) {
results.add(new TransportOption(Type.SMS, smsIconResource,
context.getString(R.string.ConversationActivity_transport_insecure_mms),
context.getString(R.string.conversation_activity__type_message_mms_insecure),
new MmsCharacterCalculator()));
} else {
results.add(new TransportOption(Type.SMS, smsIconResource,
context.getString(R.string.ConversationActivity_transport_insecure_sms),
context.getString(R.string.conversation_activity__type_message_sms_insecure),
new SmsCharacterCalculator()));
}
results.add(new TransportOption(Type.TEXTSECURE, pushIconResource,
context.getString(R.string.ConversationActivity_transport_textsecure),
context.getString(R.string.conversation_activity__type_message_push),
new PushCharacterCalculator()));
iconArray.recycle();
return results;
}
private void setTransport(Type type) {
this.selectedType = type;
notifyTransportChangeListeners();
}
private void notifyTransportChangeListeners() {
for (OnTransportChangedListener listener : listeners) {
listener.onChange(getSelectedTransport());
}
}
private Optional<TransportOption> find(Type type) {
for (TransportOption option : enabledTransports) {
if (option.isType(type)) {
return Optional.of(option);
}
}
return Optional.absent();
}
public interface OnTransportChangedListener {
public void onChange(TransportOption newTransport);
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -9,41 +10,33 @@ import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
import java.util.Map;
public class TransportOptionsAdapter extends BaseAdapter {
private final Context context;
private final LayoutInflater inflater;
private List<String> enabledTransports;
private final Map<String, TransportOption> transportMetadata;
public TransportOptionsAdapter(final Context context,
final List<String> enabledTransports,
final Map<String, TransportOption> transportMetadata) {
private final LayoutInflater inflater;
private List<TransportOption> enabledTransports;
public TransportOptionsAdapter(@NonNull Context context,
@NonNull List<TransportOption> enabledTransports)
{
super();
this.context = context;
this.inflater = LayoutInflater.from(context);
this.enabledTransports = enabledTransports;
this.transportMetadata = transportMetadata;
}
public TransportOptionsAdapter(final Context context,
final Map<String, TransportOption> transportMetadata) {
this(context, null, transportMetadata);
}
public void setEnabledTransports(final List<String> enabledTransports) {
public void setEnabledTransports(List<TransportOption> enabledTransports) {
this.enabledTransports = enabledTransports;
}
@Override
public int getCount() {
return enabledTransports == null ? 0 : enabledTransports.size();
return enabledTransports.size();
}
@Override
public Object getItem(int position) {
return transportMetadata.get(enabledTransports.get(position));
return enabledTransports.get(position);
}
@Override
@ -53,19 +46,17 @@ public class TransportOptionsAdapter extends BaseAdapter {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view;
if (convertView == null) {
view = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
} else {
view = convertView;
convertView = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
}
TransportOption transport = (TransportOption) getItem(position);
final ImageView imageView = (ImageView)view.findViewById(R.id.icon);
final TextView textView = (TextView) view.findViewById(R.id.text);
ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
TextView textView = (TextView) convertView.findViewById(R.id.text);
imageView.setImageResource(transport.drawable);
textView.setText(transport.text);
return view;
imageView.setImageResource(transport.getDrawable());
textView.setText(transport.getDescription());
return convertView;
}
}

View File

@ -0,0 +1,73 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.PopupWindow;
import java.util.LinkedList;
import java.util.List;
public class TransportOptionsPopup implements ListView.OnItemClickListener {
private final TransportOptionsAdapter adapter;
private final PopupWindow popupWindow;
private final SelectedListener listener;
public TransportOptionsPopup(@NonNull Context context, @NonNull SelectedListener listener) {
this.listener = listener;
this.adapter = new TransportOptionsAdapter(context, new LinkedList<TransportOption>());
View selectionMenu = LayoutInflater.from(context).inflate(R.layout.transport_selection, null);
ListView listView = (ListView) selectionMenu.findViewById(R.id.transport_selection_list);
listView.setAdapter(adapter);
this.popupWindow = new PopupWindow(selectionMenu);
this.popupWindow.setFocusable(true);
this.popupWindow.setBackgroundDrawable(new BitmapDrawable(context.getResources(), ""));
this.popupWindow.setOutsideTouchable(true);
this.popupWindow.setWindowLayoutMode(0, WindowManager.LayoutParams.WRAP_CONTENT);
this.popupWindow.setWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
listView.setOnItemClickListener(this);
}
public void display(Context context, final View parent, List<TransportOption> enabledTransports) {
this.adapter.setEnabledTransports(enabledTransports);
this.adapter.notifyDataSetChanged();
final int xoff = context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff);
final int yoff = context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff);
popupWindow.showAsDropDown(parent, xoff, yoff);
parent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
popupWindow.update(parent, xoff, yoff, -1, -1);
}
});
}
public void dismiss() {
this.popupWindow.dismiss();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
listener.onSelected((TransportOption)adapter.getItem(position));
}
public interface SelectedListener {
public void onSelected(TransportOption option);
}
}

View File

@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.components;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.widget.EditText;
public class ComposeText extends EditText {
public ComposeText(Context context) {
super(context);
}
public ComposeText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ComposeText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ComposeText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public void setHint(@NonNull String hint) {
SpannableString span = new SpannableString(hint);
span.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
super.setHint(span);
}
}

View File

@ -1,65 +0,0 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class OutgoingSmsPreference extends DialogPreference {
private CheckBox dataUsers;
private CheckBox askForFallback;
private CheckBox neverFallbackMms;
private CheckBox nonDataUsers;
public OutgoingSmsPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setPersistent(false);
setDialogLayoutResource(R.layout.outgoing_sms_preference);
}
@Override
protected void onBindDialogView(final View view) {
super.onBindDialogView(view);
dataUsers = (CheckBox) view.findViewById(R.id.data_users);
askForFallback = (CheckBox) view.findViewById(R.id.ask_before_fallback_data);
neverFallbackMms = (CheckBox) view.findViewById(R.id.never_send_mms);
nonDataUsers = (CheckBox) view.findViewById(R.id.non_data_users);
dataUsers.setChecked(TextSecurePreferences.isFallbackSmsAllowed(getContext()));
askForFallback.setChecked(TextSecurePreferences.isFallbackSmsAskRequired(getContext()));
neverFallbackMms.setChecked(!TextSecurePreferences.isFallbackMmsEnabled(getContext()));
nonDataUsers.setChecked(TextSecurePreferences.isDirectSmsAllowed(getContext()));
dataUsers.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
updateEnabledViews();
}
});
updateEnabledViews();
}
private void updateEnabledViews() {
askForFallback.setEnabled(dataUsers.isChecked());
neverFallbackMms.setEnabled(dataUsers.isChecked());
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
TextSecurePreferences.setFallbackSmsAllowed(getContext(), dataUsers.isChecked());
TextSecurePreferences.setFallbackSmsAskRequired(getContext(), askForFallback.isChecked());
TextSecurePreferences.setDirectSmsAllowed(getContext(), nonDataUsers.isChecked());
TextSecurePreferences.setFallbackMmsEnabled(getContext(), !neverFallbackMms.isChecked());
if (getOnPreferenceChangeListener() != null) getOnPreferenceChangeListener().onPreferenceChange(this, null);
}
}
}

View File

@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.crypto.MasterSecret;
public class PushRegistrationReminder extends Reminder {
public static final long REMINDER_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000;
public PushRegistrationReminder(final Context context, final MasterSecret masterSecret) {
super(R.drawable.ic_push_registration_reminder,
@ -27,18 +26,16 @@ public class PushRegistrationReminder extends Reminder {
context.startActivity(intent);
}
};
final OnClickListener cancelListener = new OnClickListener() {
@Override
public void onClick(View v) {
TextSecurePreferences.setLastPushReminderTime(context, System.currentTimeMillis());
}
};
setOkListener(okListener);
setCancelListener(cancelListener);
}
@Override
public boolean isDismissable() {
return false;
}
public static boolean isEligible(Context context) {
return !TextSecurePreferences.isPushRegistered(context) &&
(TextSecurePreferences.getLastPushReminderTime(context) + REMINDER_INTERVAL_MS < System.currentTimeMillis());
return !TextSecurePreferences.isPushRegistered(context);
}
}

View File

@ -55,8 +55,9 @@ public class ReminderView extends LinearLayout {
title.setText(reminder.getTitleResId());
text.setText(reminder.getTextResId());
this.setOnClickListener(reminder.getOkListener());
if (reminder.isDismissable()) {
this.setOnClickListener(reminder.getOkListener());
cancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@ -64,10 +65,11 @@ public class ReminderView extends LinearLayout {
if (reminder.getCancelListener() != null) reminder.getCancelListener().onClick(v);
}
});
container.setVisibility(View.VISIBLE);
} else {
cancel.setVisibility(View.GONE);
}
container.setVisibility(View.VISIBLE);
}
public void hide() {

View File

@ -1,94 +1,101 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.TransportOptions;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.TransportOptionsPopup;
public class SendButton extends ImageButton {
private TransportOptions transportOptions;
private EditText composeText;
public class SendButton extends ImageButton
implements TransportOptions.OnTransportChangedListener,
TransportOptionsPopup.SelectedListener,
View.OnLongClickListener
{
private final TransportOptions transportOptions;
private final TransportOptionsPopup transportOptionsPopup;
@SuppressWarnings("unused")
public SendButton(Context context) {
super(context);
initialize();
this.transportOptions = initializeTransportOptions(false);
this.transportOptionsPopup = initializeTransportOptionsPopup();
}
@SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
this.transportOptions = initializeTransportOptions(false);
this.transportOptionsPopup = initializeTransportOptionsPopup();
}
@SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
this.transportOptions = initializeTransportOptions(false);
this.transportOptionsPopup = initializeTransportOptionsPopup();
}
private void initialize() {
transportOptions = new TransportOptions(getContext());
transportOptions.addOnTransportChangedListener(new OnTransportChangedListener() {
@Override
public void onChange(TransportOption newTransport) {
setImageResource(newTransport.drawable);
setContentDescription(newTransport.composeHint);
if (composeText != null) setComposeTextHint(newTransport.composeHint);
}
});
private TransportOptions initializeTransportOptions(boolean media) {
TransportOptions transportOptions = new TransportOptions(getContext(), media);
transportOptions.addOnTransportChangedListener(this);
setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
if (transportOptions.getEnabledTransports().size() > 1) {
transportOptions.showPopup(SendButton.this);
return true;
}
return false;
}
});
setOnLongClickListener(this);
return transportOptions;
}
private TransportOptionsPopup initializeTransportOptionsPopup() {
return new TransportOptionsPopup(getContext(), this);
}
public boolean isManualSelection() {
return transportOptions.isManualSelection();
}
public void addOnTransportChangedListener(OnTransportChangedListener listener) {
transportOptions.addOnTransportChangedListener(listener);
}
public void setComposeTextView(EditText composeText) {
this.composeText = composeText;
}
public TransportOption getSelectedTransport() {
return transportOptions.getSelectedTransport();
}
public void initializeAvailableTransports(boolean isMediaMessage) {
transportOptions.initializeAvailableTransports(isMediaMessage);
public void resetAvailableTransports(boolean isMediaMessage) {
transportOptions.reset(isMediaMessage);
}
public void disableTransport(String transport) {
transportOptions.disableTransport(transport);
public void disableTransport(TransportOption.Type type) {
transportOptions.disableTransport(type);
}
public void setDefaultTransport(String transport) {
transportOptions.setDefaultTransport(transport);
public void setDefaultTransport(TransportOption.Type type) {
transportOptions.setDefaultTransport(type);
}
private void setComposeTextHint(String hint) {
if (hint == null) {
this.composeText.setHint(null);
} else {
SpannableString span = new SpannableString(hint);
span.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
this.composeText.setHint(span);
@Override
public void onSelected(TransportOption option) {
transportOptions.setSelectedTransport(option.getType());
transportOptionsPopup.dismiss();
}
@Override
public void onChange(TransportOption newTransport) {
setImageResource(newTransport.getDrawable());
setContentDescription(newTransport.getDescription());
}
@Override
public boolean onLongClick(View v) {
if (transportOptions.getEnabledTransports().size() > 1) {
transportOptionsPopup.display(getContext(), SendButton.this, transportOptions.getEnabledTransports());
return true;
}
return false;
}
}

View File

@ -97,7 +97,11 @@ public class ContactsDatabase {
}
public Cursor query(String filter, boolean pushOnly) {
final boolean includeAndroidContacts = !pushOnly && TextSecurePreferences.isDirectSmsAllowed(context);
// FIXME: This doesn't make sense to me. You pass in pushOnly, but then
// conditionally check to see whether other contacts should be included
// in the query method itself? I don't think this method should have any
// understanding of that stuff.
final boolean includeAndroidContacts = !pushOnly && TextSecurePreferences.isSmsEnabled(context);
final Cursor localCursor = queryLocalDb(filter);
final Cursor androidCursor;
final MatrixCursor newNumberCursor;

View File

@ -1,89 +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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.crypto;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Dialogs;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.state.PreKeyStore;
import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.libaxolotl.state.SignedPreKeyStore;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
public class KeyExchangeInitiator {
public static void initiate(final Context context, final MasterSecret masterSecret, final Recipient recipient, boolean promptOnExisting) {
if (promptOnExisting && hasInitiatedSession(context, masterSecret, recipient)) {
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);
dialog.setIcon(Dialogs.resolveIcon(context, R.attr.dialog_alert_icon));
dialog.setCancelable(true);
dialog.setPositiveButton(R.string.KeyExchangeInitiator_send, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
initiateKeyExchange(context, masterSecret, recipient);
}
});
dialog.setNegativeButton(android.R.string.cancel, null);
dialog.show();
} else {
initiateKeyExchange(context, masterSecret, recipient);
}
}
private static void initiateKeyExchange(Context context, MasterSecret masterSecret, Recipient recipient) {
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
SessionBuilder sessionBuilder = new SessionBuilder(sessionStore, preKeyStore, signedPreKeyStore,
identityKeyStore, new AxolotlAddress(recipient.getNumber(),
TextSecureAddress.DEFAULT_DEVICE_ID));
KeyExchangeMessage keyExchangeMessage = sessionBuilder.process();
String serializedMessage = Base64.encodeBytesWithoutPadding(keyExchangeMessage.serialize());
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, serializedMessage);
MessageSender.send(context, masterSecret, textMessage, -1, false);
}
private static boolean hasInitiatedSession(Context context, MasterSecret masterSecret,
Recipient recipient)
{
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
SessionRecord sessionRecord = sessionStore.loadSession(new AxolotlAddress(recipient.getNumber(), TextSecureAddress.DEFAULT_DEVICE_ID));
return sessionRecord.getSessionState().hasPendingKeyExchange();
}
}

View File

@ -1,134 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import java.io.IOException;
import ws.com.google.android.mms.ContentType;
import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.PduBody;
import ws.com.google.android.mms.pdu.PduComposer;
import ws.com.google.android.mms.pdu.PduParser;
import ws.com.google.android.mms.pdu.PduPart;
import ws.com.google.android.mms.pdu.RetrieveConf;
import ws.com.google.android.mms.pdu.SendReq;
public class MmsCipher {
private static final String TAG = MmsCipher.class.getSimpleName();
private final TextTransport textTransport = new TextTransport();
private final AxolotlStore axolotlStore;
public MmsCipher(AxolotlStore axolotlStore) {
this.axolotlStore = axolotlStore;
}
public MultimediaMessagePdu decrypt(Context context, MultimediaMessagePdu pdu)
throws InvalidMessageException, LegacyMessageException, DuplicateMessageException,
NoSessionException
{
try {
SessionCipher sessionCipher = new SessionCipher(axolotlStore, new AxolotlAddress(pdu.getFrom().getString(), TextSecureAddress.DEFAULT_DEVICE_ID));
Optional<byte[]> ciphertext = getEncryptedData(pdu);
if (!ciphertext.isPresent()) {
throw new InvalidMessageException("No ciphertext present!");
}
byte[] decodedCiphertext = textTransport.getDecodedMessage(ciphertext.get());
byte[] plaintext;
if (decodedCiphertext == null) {
throw new InvalidMessageException("failed to decode ciphertext");
}
try {
plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext));
} catch (InvalidMessageException e) {
// NOTE - For some reason, Sprint seems to append a single character to the
// end of message text segments. I don't know why, so here we just try
// truncating the message by one if the MAC fails.
if (ciphertext.get().length > 2) {
Log.w(TAG, "Attempting truncated decrypt...");
byte[] truncated = Util.trim(ciphertext.get(), ciphertext.get().length - 1);
decodedCiphertext = textTransport.getDecodedMessage(truncated);
plaintext = sessionCipher.decrypt(new WhisperMessage(decodedCiphertext));
} else {
throw e;
}
}
MultimediaMessagePdu plaintextGenericPdu = (MultimediaMessagePdu) new PduParser(plaintext).parse();
return new RetrieveConf(plaintextGenericPdu.getPduHeaders(), plaintextGenericPdu.getBody());
} catch (IOException e) {
throw new InvalidMessageException(e);
}
}
public SendReq encrypt(Context context, SendReq message)
throws NoSessionException, RecipientFormattingException, UndeliverableMessageException
{
EncodedStringValue[] encodedRecipient = message.getTo();
String recipientString = encodedRecipient[0].getString();
byte[] pduBytes = new PduComposer(context, message).make();
if (pduBytes == null) {
throw new UndeliverableMessageException("PDU composition failed, null payload");
}
if (!axolotlStore.containsSession(new AxolotlAddress(recipientString, TextSecureAddress.DEFAULT_DEVICE_ID))) {
throw new NoSessionException("No session for: " + recipientString);
}
SessionCipher cipher = new SessionCipher(axolotlStore, new AxolotlAddress(recipientString, TextSecureAddress.DEFAULT_DEVICE_ID));
CiphertextMessage ciphertextMessage = cipher.encrypt(pduBytes);
byte[] encryptedPduBytes = textTransport.getEncodedMessage(ciphertextMessage.serialize());
PduBody body = new PduBody();
PduPart part = new PduPart();
SendReq encryptedPdu = new SendReq(message.getPduHeaders(), body);
part.setContentId((System.currentTimeMillis()+"").getBytes());
part.setContentType(ContentType.TEXT_PLAIN.getBytes());
part.setName((System.currentTimeMillis()+"").getBytes());
part.setData(encryptedPduBytes);
body.addPart(part);
encryptedPdu.setSubject(new EncodedStringValue(WirePrefix.calculateEncryptedMmsSubject()));
encryptedPdu.setBody(body);
return encryptedPdu;
}
private Optional<byte[]> getEncryptedData(MultimediaMessagePdu pdu) {
for (int i=0;i<pdu.getBody().getPartsNum();i++) {
if (new String(pdu.getBody().getPart(i).getContentType()).equals(ContentType.TEXT_PLAIN)) {
return Optional.of(pdu.getBody().getPart(i).getData());
}
}
return Optional.absent();
}
}

View File

@ -15,6 +15,10 @@ public class SecurityEvent {
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
public static void broadcastSecurityUpdateEvent(Context context) {
broadcastSecurityUpdateEvent(context, -2);
}
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", threadId);

View File

@ -1,128 +0,0 @@
package org.thoughtcrime.securesms.crypto;
import android.content.Context;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidKeyIdException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.SessionBuilder;
import org.whispersystems.libaxolotl.SessionCipher;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.AxolotlStore;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
import java.io.IOException;
public class SmsCipher {
private final SmsTransportDetails transportDetails = new SmsTransportDetails();
private final AxolotlStore axolotlStore;
public SmsCipher(AxolotlStore axolotlStore) {
this.axolotlStore = axolotlStore;
}
public IncomingTextMessage decrypt(Context context, IncomingTextMessage message)
throws LegacyMessageException, InvalidMessageException,
DuplicateMessageException, NoSessionException
{
try {
byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
WhisperMessage whisperMessage = new WhisperMessage(decoded);
SessionCipher sessionCipher = new SessionCipher(axolotlStore, new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID));
byte[] padded = sessionCipher.decrypt(whisperMessage);
byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded);
if (message.isEndSession() && "TERMINATE".equals(new String(plaintext))) {
axolotlStore.deleteSession(new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID));
}
return message.withMessageBody(new String(plaintext));
} catch (IOException e) {
throw new InvalidMessageException(e);
}
}
public IncomingEncryptedMessage decrypt(Context context, IncomingPreKeyBundleMessage message)
throws InvalidVersionException, InvalidMessageException, DuplicateMessageException,
UntrustedIdentityException, LegacyMessageException
{
try {
byte[] decoded = transportDetails.getDecodedMessage(message.getMessageBody().getBytes());
PreKeyWhisperMessage preKeyMessage = new PreKeyWhisperMessage(decoded);
SessionCipher sessionCipher = new SessionCipher(axolotlStore, new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID));
byte[] padded = sessionCipher.decrypt(preKeyMessage);
byte[] plaintext = transportDetails.getStrippedPaddingMessageBody(padded);
return new IncomingEncryptedMessage(message, new String(plaintext));
} catch (IOException | InvalidKeyException | InvalidKeyIdException e) {
throw new InvalidMessageException(e);
}
}
public OutgoingTextMessage encrypt(OutgoingTextMessage message) throws NoSessionException {
byte[] paddedBody = transportDetails.getPaddedMessageBody(message.getMessageBody().getBytes());
String recipientNumber = message.getRecipients().getPrimaryRecipient().getNumber();
if (!axolotlStore.containsSession(new AxolotlAddress(recipientNumber, TextSecureAddress.DEFAULT_DEVICE_ID))) {
throw new NoSessionException("No session for: " + recipientNumber);
}
SessionCipher cipher = new SessionCipher(axolotlStore, new AxolotlAddress(recipientNumber, TextSecureAddress.DEFAULT_DEVICE_ID));
CiphertextMessage ciphertextMessage = cipher.encrypt(paddedBody);
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) {
return new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
} else {
return message.withBody(encodedCiphertext);
}
}
public OutgoingKeyExchangeMessage process(Context context, IncomingKeyExchangeMessage message)
throws UntrustedIdentityException, StaleKeyExchangeException,
InvalidVersionException, LegacyMessageException, InvalidMessageException
{
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, message.getSender(), false).getPrimaryRecipient();
AxolotlAddress axolotlAddress = new AxolotlAddress(message.getSender(), TextSecureAddress.DEFAULT_DEVICE_ID);
KeyExchangeMessage exchangeMessage = new KeyExchangeMessage(transportDetails.getDecodedMessage(message.getMessageBody().getBytes()));
SessionBuilder sessionBuilder = new SessionBuilder(axolotlStore, axolotlAddress);
KeyExchangeMessage response = sessionBuilder.process(exchangeMessage);
if (response != null) {
byte[] serializedResponse = transportDetails.getEncodedMessage(response.serialize());
return new OutgoingKeyExchangeMessage(recipient, new String(serializedResponse));
} else {
return null;
}
} catch (IOException | InvalidKeyException e) {
throw new InvalidMessageException(e);
}
}
}

View File

@ -383,11 +383,6 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsPendingSecureSmsFallback(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_SECURE_SMS_FALLBACK);
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsPendingInsecureSmsFallback(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
notifyConversationListeners(getThreadIdForMessage(messageId));

View File

@ -35,10 +35,8 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.JsonUtils;
@ -183,18 +181,6 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT);
}
public void markAsStaleKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_STALE_BIT);
}
public void markAsProcessedKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_PROCESSED_BIT);
}
public void markAsCorruptKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT);
}
public void markAsInvalidVersionKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT);
}
@ -239,10 +225,6 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE);
}
public void markAsPendingSecureSmsFallback(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_SECURE_SMS_FALLBACK);
}
public void markAsPendingInsecureSmsFallback(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
}
@ -363,19 +345,10 @@ public class SmsDatabase extends MessagingDatabase {
}
protected Pair<Long, Long> insertMessageInbox(IncomingTextMessage message, long type) {
if (message.isKeyExchange()) {
type |= Types.KEY_EXCHANGE_BIT;
if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_BIT;
else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
else if (((IncomingKeyExchangeMessage)message).isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
else if (((IncomingKeyExchangeMessage)message).isLegacyVersion()) type |= Types.ENCRYPTION_REMOTE_LEGACY_BIT;
else if (((IncomingKeyExchangeMessage)message).isDuplicate()) type |= Types.ENCRYPTION_REMOTE_DUPLICATE_BIT;
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
if (message.isPreKeyBundle()) {
type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
// type |= Types.ENCRYPTION_REMOTE_BIT;
} else if (message.isGroup()) {
type |= Types.SECURE_MESSAGE_BIT;
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
@ -383,7 +356,6 @@ public class SmsDatabase extends MessagingDatabase {
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT;
// type |= Types.ENCRYPTION_REMOTE_BIT;
}
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
@ -406,7 +378,7 @@ public class SmsDatabase extends MessagingDatabase {
}
boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
message.isSecureMessage() || message.isKeyExchange();
message.isSecureMessage() || message.isPreKeyBundle();
long threadId;

View File

@ -143,7 +143,7 @@ public class TextSecureDirectory {
try {
for (ContactTokenDetails token : activeTokens) {
Log.w("Directory", "Adding active token: " + token);
Log.w("Directory", "Adding active token: " + token.getNumber() + ", " + token.getToken());
ContentValues values = new ContentValues();
values.put(NUMBER, token.getNumber());
values.put(REGISTERED, 1);

View File

@ -80,7 +80,8 @@ public abstract class MessageRecord extends DisplayRecord {
public boolean isFailed() {
return
MmsSmsColumns.Types.isFailedMessageType(type) ||
MmsSmsColumns.Types.isFailedMessageType(type) ||
MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) ||
getDeliveryStatus() == DELIVERY_STATUS_FAILED;
}
@ -149,22 +150,14 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isProcessedKeyExchange(type);
}
public boolean isPendingSmsFallback() {
return SmsDatabase.Types.isPendingSmsFallbackType(type);
public boolean isPendingInsecureSmsFallback() {
return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type);
}
public boolean isIdentityMismatchFailure() {
return mismatches != null && !mismatches.isEmpty();
}
public boolean isPendingSecureSmsFallback() {
return SmsDatabase.Types.isPendingSecureSmsFallbackType(type);
}
public boolean isPendingInsecureSmsFallback() {
return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type);
}
public boolean isBundleKeyExchange() {
return SmsDatabase.Types.isBundleKeyExchange(type);
}

View File

@ -23,9 +23,8 @@ import android.text.SpannableString;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
@ -91,8 +90,6 @@ public class SmsMessageRecord extends MessageRecord {
return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message));
} else if (SmsDatabase.Types.isEndSessionType(type)) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_ended));
} else if (isOutgoing() && Tag.isTagged(getBody().getBody())) {
return new SpannableString(Tag.stripTag(getBody().getBody()));
} else {
return super.getDisplayBody();
}

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.os.PowerManager;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -30,6 +31,7 @@ public class DirectoryRefreshJob extends ContextJob {
try {
wakeLock.acquire();
DirectoryHelper.refreshDirectory(context);
SecurityEvent.broadcastSecurityUpdateEvent(context);
} finally {
if (wakeLock.isHeld()) wakeLock.release();
}

View File

@ -8,8 +8,6 @@ import android.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MmsCipher;
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
@ -35,7 +33,6 @@ import java.io.IOException;
import ws.com.google.android.mms.InvalidHeaderValueException;
import ws.com.google.android.mms.MmsException;
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
import ws.com.google.android.mms.pdu.NotificationInd;
import ws.com.google.android.mms.pdu.NotifyRespInd;
import ws.com.google.android.mms.pdu.PduComposer;
@ -212,24 +209,14 @@ public class MmsDownloadJob extends MasterSecretJob {
Pair<Long, Long> messageAndThreadId;
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
MmsCipher mmsCipher = new MmsCipher(new TextSecureAxolotlStore(context, masterSecret));
MultimediaMessagePdu plaintextPdu = mmsCipher.decrypt(context, retrieved);
RetrieveConf plaintextRetrieved = new RetrieveConf(plaintextPdu.getPduHeaders(), plaintextPdu.getBody());
IncomingMediaMessage plaintextMessage = new IncomingMediaMessage(plaintextRetrieved);
messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, plaintextMessage,
threadId);
// if (masterSecret != null)
// DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
// messageAndThreadId.second, retrieved);
database.markAsLegacyVersion(messageId, threadId);
messageAndThreadId = new Pair<>(messageId, threadId);
} else {
messageAndThreadId = database.insertMessageInbox(masterSecret, message,
contentLocation, threadId);
database.delete(messageId);
}
database.delete(messageId);
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
}
@ -262,8 +249,6 @@ public class MmsDownloadJob extends MasterSecretJob {
db.markIncomingNotificationReceived(threadId);
MessageNotifier.updateNotification(context, masterSecret, threadId);
}
//
// toastHandler.makeToast(error);
}
private boolean isCdmaNetwork() {

View File

@ -5,8 +5,6 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MmsCipher;
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
@ -18,7 +16,6 @@ import org.thoughtcrime.securesms.mms.MmsRadioException;
import org.thoughtcrime.securesms.mms.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
@ -27,7 +24,6 @@ import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libaxolotl.NoSessionException;
import java.io.IOException;
import java.util.Arrays;
@ -69,10 +65,6 @@ public class MmsSendJob extends SendJob {
try {
MmsSendResult result = deliver(masterSecret, message);
if (result.isUpgradedSecure()) {
database.markAsSecure(messageId);
}
database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus());
} catch (UndeliverableMessageException e) {
Log.w(TAG, e);
@ -146,14 +138,12 @@ public class MmsSendJob extends SendJob {
boolean usingMmsRadio, boolean useProxy)
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
{
String number = TelephonyUtil.getManager(context).getLine1Number();
boolean upgradedSecure = false;
String number = TelephonyUtil.getManager(context).getLine1Number();
prepareMessageMedia(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true);
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
message = getEncryptedMessage(masterSecret, message);
upgradedSecure = true;
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
}
if (number != null && number.trim().length() != 0) {
@ -177,26 +167,13 @@ public class MmsSendJob extends SendJob {
} else if (isInconsistentResponse(message, conf)) {
throw new UndeliverableMessageException("Mismatched response!");
} else {
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus(), upgradedSecure, false);
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
}
} catch (ApnUnavailableException aue) {
throw new IOException("no APN was retrievable");
}
}
private SendReq getEncryptedMessage(MasterSecret masterSecret, SendReq pdu)
throws InsecureFallbackApprovalException, UndeliverableMessageException
{
try {
MmsCipher cipher = new MmsCipher(new TextSecureAxolotlStore(context, masterSecret));
return cipher.encrypt(context, pdu);
} catch (NoSessionException e) {
throw new InsecureFallbackApprovalException(e);
} catch (RecipientFormattingException e) {
throw new AssertionError(e);
}
}
private boolean isInconsistentResponse(SendReq message, SendConf response) {
Log.w(TAG, "Comparing: " + Hex.toString(message.getTransactionId()));
Log.w(TAG, "With: " + Hex.toString(response.getTransactionId()));

View File

@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
@ -29,7 +28,6 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;

View File

@ -5,19 +5,16 @@ import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.whispersystems.textsecure.api.TextSecureMessageSender;
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
@ -46,7 +43,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
private final long messageId;
public PushMediaSendJob(Context context, long messageId, String destination) {
super(context, constructParameters(context, destination, true));
super(context, constructParameters(context, destination));
this.messageId = messageId;
}
@ -66,19 +63,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
try {
if (deliver(masterSecret, message)) {
database.markAsPush(messageId);
database.markAsSecure(messageId);
database.markAsSent(messageId, "push".getBytes(), 0);
}
deliver(masterSecret, message);
database.markAsPush(messageId);
database.markAsSecure(messageId);
database.markAsSent(messageId, "push".getBytes(), 0);
} catch (InsecureFallbackApprovalException ifae) {
Log.w(TAG, ifae);
database.markAsPendingInsecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
} catch (SecureFallbackApprovalException sfae) {
Log.w(TAG, sfae);
database.markAsPendingSecureSmsFallback(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context));
} catch (UntrustedIdentityException uie) {
Log.w(TAG, uie);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false);
@ -103,54 +96,32 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
}
private boolean deliver(MasterSecret masterSecret, SendReq message)
throws RetryLaterException, SecureFallbackApprovalException,
InsecureFallbackApprovalException, UntrustedIdentityException,
private void deliver(MasterSecret masterSecret, SendReq message)
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
UndeliverableMessageException
{
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
String destination = message.getTo()[0].getString();
boolean isSmsFallbackSupported = isSmsFallbackSupported(context, destination, true);
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
String destination = message.getTo()[0].getString();
try {
prepareMessageMedia(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
TextSecureAddress address = getPushAddress(destination);
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
String body = PartParser.getMessageText(message.getBody());
TextSecureMessage mediaMessage = new TextSecureMessage(message.getSentTimestamp(), attachments, body);
TextSecureMessage mediaMessage = TextSecureMessage.newBuilder()
.withBody(body)
.withAttachments(attachments)
.withTimestamp(message.getSentTimestamp())
.build();
messageSender.sendMessage(address, mediaMessage);
return true;
} catch (InvalidNumberException | UnregisteredUserException e) {
Log.w(TAG, e);
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
else database.markAsSentFailed(messageId);
throw new InsecureFallbackApprovalException(e);
} catch (IOException e) {
Log.w(TAG, e);
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
else throw new RetryLaterException(e);
}
return false;
}
private void fallbackOrAskApproval(MasterSecret masterSecret, SendReq mediaMessage, String destination)
throws SecureFallbackApprovalException, InsecureFallbackApprovalException
{
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination, true);
if (!isSmsFallbackApprovalRequired) {
Log.w(TAG, "Falling back to MMS");
DatabaseFactory.getMmsDatabase(context).markAsForcedSms(mediaMessage.getDatabaseMessageId());
ApplicationContext.getInstance(context).getJobManager().add(new MmsSendJob(context, messageId));
} else if (!SessionUtil.hasSession(context, masterSecret, destination)) {
Log.w(TAG, "Marking message as pending insecure SMS fallback");
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
} else {
Log.w(TAG, "Marking message as pending secure SMS fallback");
throw new SecureFallbackApprovalException("Pending user approval for fallback secure to SMS");
throw new RetryLaterException(e);
}
}
}

View File

@ -38,54 +38,23 @@ public abstract class PushSendJob extends SendJob {
super(context, parameters);
}
protected static JobParameters constructParameters(Context context, String destination, boolean media) {
protected static JobParameters constructParameters(Context context, String destination) {
JobParameters.Builder builder = JobParameters.newBuilder();
builder.withPersistence();
builder.withGroupId(destination);
builder.withRequirement(new MasterSecretRequirement(context));
if (!isSmsFallbackSupported(context, destination, media)) {
builder.withRequirement(new NetworkRequirement(context));
builder.withRetryCount(5);
}
builder.withRequirement(new NetworkRequirement(context));
builder.withRetryCount(5);
return builder.create();
}
protected static boolean isSmsFallbackSupported(Context context, String destination, boolean media) {
try {
String e164number = Util.canonicalizeNumber(context, destination);
if (GroupUtil.isEncodedGroup(e164number)) {
return false;
}
if (!TextSecurePreferences.isFallbackSmsAllowed(context)) {
return false;
}
if (media && !TextSecurePreferences.isFallbackMmsEnabled(context)) {
return false;
}
TextSecureDirectory directory = TextSecureDirectory.getInstance(context);
return directory.isSmsFallbackSupported(e164number);
} catch (InvalidNumberException e) {
Log.w(TAG, e);
return false;
}
}
protected TextSecureAddress getPushAddress(String number) throws InvalidNumberException {
String e164number = Util.canonicalizeNumber(context, number);
String relay = TextSecureDirectory.getInstance(context).getRelay(e164number);
return new TextSecureAddress(e164number, Optional.fromNullable(relay));
}
protected boolean isSmsFallbackApprovalRequired(String destination, boolean media) {
return (isSmsFallbackSupported(context, destination, media) && TextSecurePreferences.isFallbackSmsAskRequired(context));
}
protected List<TextSecureAttachment> getAttachments(final MasterSecret masterSecret, final SendReq message) {
List<TextSecureAttachment> attachments = new LinkedList<>();

View File

@ -5,7 +5,6 @@ import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
@ -13,12 +12,10 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.whispersystems.textsecure.api.TextSecureMessageSender;
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
@ -41,7 +38,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
private final long messageId;
public PushTextSendJob(Context context, long messageId, String destination) {
super(context, constructParameters(context, destination, false));
super(context, constructParameters(context, destination));
this.messageId = messageId;
}
@ -54,26 +51,22 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
@Override
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
String destination = record.getIndividualRecipient().getNumber();
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
try {
Log.w(TAG, "Sending message: " + messageId);
if (deliver(masterSecret, record, destination)) {
database.markAsPush(messageId);
database.markAsSecure(messageId);
database.markAsSent(messageId);
}
deliver(masterSecret, record);
database.markAsPush(messageId);
database.markAsSecure(messageId);
database.markAsSent(messageId);
} catch (InsecureFallbackApprovalException e) {
Log.w(TAG, e);
database.markAsPendingInsecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (SecureFallbackApprovalException e) {
Log.w(TAG, e);
database.markAsPendingSecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context));
} catch (UntrustedIdentityException e) {
Log.w(TAG, e);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, e.getE164Number(), false);
@ -102,55 +95,26 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
}
private boolean deliver(MasterSecret masterSecret, SmsMessageRecord message, String destination)
throws UntrustedIdentityException, SecureFallbackApprovalException,
InsecureFallbackApprovalException, RetryLaterException
private void deliver(MasterSecret masterSecret, SmsMessageRecord message)
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException
{
boolean isSmsFallbackSupported = isSmsFallbackSupported(context, destination, false);
try {
TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber());
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber());
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
TextSecureMessage textSecureMessage = TextSecureMessage.newBuilder()
.withTimestamp(message.getDateSent())
.withBody(message.getBody().getBody())
.asEndSessionMessage(message.isEndSession())
.build();
if (message.isEndSession()) {
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null,
null, null, true, true));
} else {
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), message.getBody().getBody()));
}
return true;
messageSender.sendMessage(address, textSecureMessage);
} catch (InvalidNumberException | UnregisteredUserException e) {
Log.w(TAG, e);
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
else DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
throw new InsecureFallbackApprovalException(e);
} catch (IOException e) {
Log.w(TAG, e);
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
else throw new RetryLaterException(e);
}
return false;
}
private void fallbackOrAskApproval(MasterSecret masterSecret, SmsMessageRecord smsMessage, String destination)
throws SecureFallbackApprovalException, InsecureFallbackApprovalException
{
Recipient recipient = smsMessage.getIndividualRecipient();
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination, false);
if (!isSmsFallbackApprovalRequired) {
Log.w(TAG, "Falling back to SMS");
DatabaseFactory.getSmsDatabase(context).markAsForcedSms(smsMessage.getId());
ApplicationContext.getInstance(context).getJobManager().add(new SmsSendJob(context, messageId, destination));
} else if (!SessionUtil.hasSession(context, masterSecret, recipient)) {
Log.w(TAG, "Marking message as pending insecure fallback.");
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
} else {
Log.w(TAG, "Marking message as pending secure fallback.");
throw new SecureFallbackApprovalException("Pending user approval for fallback to secure SMS");
throw new RetryLaterException(e);
}
}
}

View File

@ -40,6 +40,7 @@ public abstract class SendJob extends MasterSecretJob {
protected abstract void onSend(MasterSecret masterSecret) throws Exception;
// FIXME: This should return a value rather than modifying one.
protected void prepareMessageMedia(MasterSecret masterSecret, SendReq message,
MediaConstraints constraints, boolean toMemory)
throws IOException, UndeliverableMessageException

View File

@ -7,32 +7,16 @@ import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.crypto.SmsCipher;
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.NoSessionException;
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
import org.whispersystems.libaxolotl.UntrustedIdentityException;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
@ -54,11 +38,7 @@ public class SmsDecryptJob extends MasterSecretJob {
}
@Override
public void onAdded() {
if (KeyCachingService.getMasterSecret(context) == null) {
MessageNotifier.updateNotification(context, null, -2);
}
}
public void onAdded() {}
@Override
public void onRun(MasterSecret masterSecret) throws NoSuchMessageException {
@ -68,27 +48,17 @@ public class SmsDecryptJob extends MasterSecretJob {
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
IncomingTextMessage message = createIncomingTextMessage(masterSecret, record);
long messageId = record.getId();
long threadId = record.getThreadId();
if (message.isSecureMessage()) handleSecureMessage(masterSecret, messageId, threadId, message);
else if (message.isPreKeyBundle()) handlePreKeyWhisperMessage(masterSecret, messageId, threadId, (IncomingPreKeyBundleMessage) message);
else if (message.isKeyExchange()) handleKeyExchangeMessage(masterSecret, messageId, threadId, (IncomingKeyExchangeMessage) message);
else if (message.isEndSession()) handleSecureMessage(masterSecret, messageId, threadId, message);
else database.updateMessageBody(masterSecret, messageId, message.getMessageBody());
if (message.isSecureMessage()) {
database.markAsLegacyVersion(messageId);
} else {
database.updateMessageBody(masterSecret, messageId, message.getMessageBody());
}
MessageNotifier.updateNotification(context, masterSecret);
} catch (LegacyMessageException e) {
Log.w(TAG, e);
database.markAsLegacyVersion(messageId);
} catch (InvalidMessageException e) {
Log.w(TAG, e);
database.markAsDecryptFailed(messageId);
} catch (DuplicateMessageException e) {
Log.w(TAG, e);
database.markAsDecryptDuplicate(messageId);
} catch (NoSessionException e) {
Log.w(TAG, e);
database.markAsNoSession(messageId);
}
}
@ -102,77 +72,6 @@ public class SmsDecryptJob extends MasterSecretJob {
// TODO
}
private void handleSecureMessage(MasterSecret masterSecret, long messageId, long threadId,
IncomingTextMessage message)
throws NoSessionException, DuplicateMessageException,
InvalidMessageException, LegacyMessageException
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
SmsCipher cipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret));
IncomingTextMessage plaintext = cipher.decrypt(context, message);
database.updateMessageBody(masterSecret, messageId, plaintext.getMessageBody());
if (message.isEndSession()) SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
}
private void handlePreKeyWhisperMessage(MasterSecret masterSecret, long messageId, long threadId,
IncomingPreKeyBundleMessage message)
throws NoSessionException, DuplicateMessageException,
InvalidMessageException, LegacyMessageException
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
try {
SmsCipher smsCipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret));
IncomingEncryptedMessage plaintext = smsCipher.decrypt(context, message);
database.updateBundleMessageBody(masterSecret, messageId, plaintext.getMessageBody());
SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
} catch (InvalidVersionException e) {
Log.w(TAG, e);
database.markAsInvalidVersionKeyExchange(messageId);
} catch (UntrustedIdentityException e) {
Log.w(TAG, e);
}
}
private void handleKeyExchangeMessage(MasterSecret masterSecret, long messageId, long threadId,
IncomingKeyExchangeMessage message)
{
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
SmsCipher cipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret));
OutgoingKeyExchangeMessage response = cipher.process(context, message);
database.markAsProcessedKeyExchange(messageId);
SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
if (response != null) {
MessageSender.send(context, masterSecret, response, threadId, true);
}
} catch (InvalidVersionException e) {
Log.w(TAG, e);
database.markAsInvalidVersionKeyExchange(messageId);
} catch (InvalidMessageException e) {
Log.w(TAG, e);
database.markAsCorruptKeyExchange(messageId);
} catch (LegacyMessageException e) {
Log.w(TAG, e);
database.markAsLegacyVersion(messageId);
} catch (StaleKeyExchangeException e) {
Log.w(TAG, e);
database.markAsStaleKeyExchange(messageId);
} catch (UntrustedIdentityException e) {
Log.w(TAG, e);
}
}
}
private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
throws InvalidMessageException
{
@ -189,28 +88,17 @@ public class SmsDecryptJob extends MasterSecretJob {
private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record)
throws InvalidMessageException
{
String plaintextBody = record.getBody().getBody();
if (record.isAsymmetricEncryption()) {
plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
}
IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(),
record.getRecipientDeviceId(),
record.getDateSent(),
plaintextBody,
record.getBody().getBody(),
Optional.<TextSecureGroup>absent());
if (record.isEndSession()) {
return new IncomingEndSessionMessage(message);
} else if (record.isBundleKeyExchange()) {
return new IncomingPreKeyBundleMessage(message, message.getMessageBody());
} else if (record.isKeyExchange()) {
return new IncomingKeyExchangeMessage(message, message.getMessageBody());
} else if (record.isSecure()) {
if (record.isAsymmetricEncryption()) {
String plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
return new IncomingTextMessage(message, plaintextBody);
} else {
return new IncomingEncryptedMessage(message, message.getMessageBody());
}
return message;
}
}

View File

@ -12,8 +12,8 @@ import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.util.guava.Optional;
@ -24,8 +24,6 @@ public class SmsReceiveJob extends ContextJob {
private static final String TAG = SmsReceiveJob.class.getSimpleName();
private static MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
private final Object[] pdus;
public SmsReceiveJob(Context context, Object[] pdus) {
@ -67,18 +65,15 @@ public class SmsReceiveJob extends ContextJob {
if (message.isSecureMessage()) {
messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message);
database.markAsLegacyVersion(messageAndThreadId.first);
} else if (masterSecret == null) {
messageAndThreadId = database.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message);
} else {
messageAndThreadId = database.insertMessageInbox(masterSecret, message);
}
if (masterSecret == null || message.isSecureMessage() || message.isKeyExchange() || message.isEndSession()) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SmsDecryptJob(context, messageAndThreadId.first));
} else {
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
messageAndThreadId = database.insertMessageInbox(masterSecret, message);
}
return messageAndThreadId;
@ -102,7 +97,7 @@ public class SmsReceiveJob extends ContextJob {
WirePrefix.isPreKeyBundle(message.getMessageBody()) ||
WirePrefix.isEndSession(message.getMessageBody()))
{
return Optional.fromNullable(multipartMessageHandler.processPotentialMultipartMessage(message));
return Optional.<IncomingTextMessage>of(new IncomingEncryptedMessage(message, message.getMessageBody()));
} else {
return Optional.of(message);
}

View File

@ -8,8 +8,6 @@ import android.telephony.SmsManager;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SmsCipher;
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
@ -20,14 +18,11 @@ import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirement;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.NoSessionException;
import java.util.ArrayList;
@ -55,15 +50,11 @@ public class SmsSendJob extends SendJob {
try {
Log.w(TAG, "Sending message: " + messageId);
deliver(masterSecret, record);
deliver(record);
} catch (UndeliverableMessageException ude) {
Log.w(TAG, ude);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (InsecureFallbackApprovalException ifae) {
Log.w(TAG, ifae);
DatabaseFactory.getSmsDatabase(context).markAsPendingInsecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
}
}
@ -82,61 +73,17 @@ public class SmsSendJob extends SendJob {
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
}
private void deliver(MasterSecret masterSecret, SmsMessageRecord record)
throws UndeliverableMessageException, InsecureFallbackApprovalException
{
if (!NumberUtil.isValidSmsOrEmail(record.getIndividualRecipient().getNumber())) {
throw new UndeliverableMessageException("Not a valid SMS destination! " + record.getIndividualRecipient().getNumber());
}
if (record.isSecure() || record.isKeyExchange() || record.isEndSession()) {
deliverSecureMessage(masterSecret, record);
} else {
deliverPlaintextMessage(record);
}
}
private void deliverSecureMessage(MasterSecret masterSecret, SmsMessageRecord message)
throws UndeliverableMessageException, InsecureFallbackApprovalException
{
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
if (message.isSecure() || message.isEndSession()) {
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
}
ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
for (int i=0;i<messages.size();i++) {
// 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 {
SmsManager.getDefault().sendTextMessage(message.getIndividualRecipient().getNumber(), null, messages.get(i),
sentIntents.get(i),
deliveredIntents == null ? null : deliveredIntents.get(i));
} catch (NullPointerException npe) {
Log.w(TAG, npe);
Log.w(TAG, "Recipient: " + message.getIndividualRecipient().getNumber());
Log.w(TAG, "Message Total Parts/Current: " + messages.size() + "/" + i);
Log.w(TAG, "Message Part Length: " + messages.get(i).getBytes().length);
throw new UndeliverableMessageException(npe);
} catch (IllegalArgumentException iae) {
Log.w(TAG, iae);
throw new UndeliverableMessageException(iae);
}
}
}
private void deliverPlaintextMessage(SmsMessageRecord message)
private void deliver(SmsMessageRecord message)
throws UndeliverableMessageException
{
if (!NumberUtil.isValidSmsOrEmail(message.getIndividualRecipient().getNumber())) {
throw new UndeliverableMessageException("Not a valid SMS destination! " + message.getIndividualRecipient().getNumber());
}
if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
throw new UndeliverableMessageException("Trying to send a secure SMS?");
}
ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
@ -166,17 +113,6 @@ public class SmsSendJob extends SendJob {
}
}
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
OutgoingTextMessage message)
throws InsecureFallbackApprovalException
{
try {
return new SmsCipher(new TextSecureAxolotlStore(context, masterSecret)).encrypt(message);
} catch (NoSessionException e) {
throw new InsecureFallbackApprovalException(e);
}
}
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type,
ArrayList<String> messages, boolean secure)
{

View File

@ -4,22 +4,10 @@ public class MmsSendResult {
private final byte[] messageId;
private final int responseStatus;
private final boolean upgradedSecure;
private final boolean push;
public MmsSendResult(byte[] messageId, int responseStatus, boolean upgradedSecure, boolean push) {
public MmsSendResult(byte[] messageId, int responseStatus) {
this.messageId = messageId;
this.responseStatus = responseStatus;
this.upgradedSecure = upgradedSecure;
this.push = push;
}
public boolean isPush() {
return push;
}
public boolean isUpgradedSecure() {
return upgradedSecure;
}
public int getResponseStatus() {

View File

@ -12,20 +12,14 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.preference.PreferenceFragment;
import android.text.TextUtils;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.OutgoingSmsPreference;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.util.LinkedList;
import java.util.List;
public class SmsMmsPreferenceFragment extends PreferenceFragment {
private static final String KITKAT_DEFAULT_PREF = "pref_set_default";
private static final String OUTGOING_SMS_PREF = "pref_outgoing_sms";
private static final String MMS_PREF = "pref_mms_preferences";
@Override
@ -33,12 +27,8 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
super.onCreate(paramBundle);
addPreferencesFromResource(R.xml.preferences_sms_mms);
this.findPreference(OUTGOING_SMS_PREF)
.setOnPreferenceChangeListener(new OutgoingSmsPreferenceListener());
this.findPreference(MMS_PREF)
.setOnPreferenceClickListener(new ApnPreferencesClickListener());
initializeOutgoingSmsSummary((OutgoingSmsPreference) findPreference(OUTGOING_SMS_PREF));
}
@Override
@ -75,50 +65,6 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
}
}
private void initializeOutgoingSmsSummary(OutgoingSmsPreference pref) {
pref.setSummary(buildOutgoingSmsDescription());
}
private class OutgoingSmsPreferenceListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(final Preference preference, Object newValue) {
preference.setSummary(buildOutgoingSmsDescription());
return false;
}
}
private String buildOutgoingSmsDescription() {
final StringBuilder builder = new StringBuilder();
final boolean dataFallback = TextSecurePreferences.isFallbackSmsAllowed(getActivity());
final boolean dataFallbackAsk = TextSecurePreferences.isFallbackSmsAskRequired(getActivity());
final boolean mmsFallback = TextSecurePreferences.isFallbackMmsEnabled(getActivity());
final boolean nonData = TextSecurePreferences.isDirectSmsAllowed(getActivity());
if (dataFallback) {
builder.append(getString(R.string.preferences__sms_outgoing_push_users));
List<String> fallbackOptions = new LinkedList<>();
if (dataFallbackAsk) fallbackOptions.add(getString(R.string.preferences__sms_fallback_push_users_ask));
if (!mmsFallback) fallbackOptions.add(getString(R.string.preferences__sms_fallback_push_users_no_mms));
if (fallbackOptions.size() > 0) {
builder.append(" (")
.append(TextUtils.join(", ", fallbackOptions))
.append(")");
}
}
if (nonData) {
if (dataFallback) builder.append(", ");
builder.append(getString(R.string.preferences__sms_fallback_non_push_users));
}
if (!dataFallback && !nonData) {
builder.append(getString(R.string.preferences__sms_fallback_nobody));
}
return builder.toString();
}
private class ApnPreferencesClickListener implements Preference.OnPreferenceClickListener {
@Override
@ -135,7 +81,7 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
}
public static CharSequence getSummary(Context context) {
return getIncomingSmsSummary(context) + ", " + getOutgoingSmsSummary(context);
return getIncomingSmsSummary(context);
}
private static CharSequence getIncomingSmsSummary(Context context) {
@ -160,21 +106,4 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
}
return context.getString(incomingSmsResId, context.getString(incomingSmsSummary));
}
private static CharSequence getOutgoingSmsSummary(Context context) {
final int onResId = R.string.ApplicationPreferencesActivity_on;
final int offResId = R.string.ApplicationPreferencesActivity_off;
final int partialResId = R.string.ApplicationPreferencesActivity_partial;
final int outgoingSmsResId = R.string.ApplicationPreferencesActivity_outgoing_sms_summary;
final int outgoingSmsSummary;
if (TextSecurePreferences.isFallbackSmsAllowed(context) && TextSecurePreferences.isDirectSmsAllowed(context)) {
outgoingSmsSummary = onResId;
} else if (TextSecurePreferences.isFallbackSmsAllowed(context) ^ TextSecurePreferences.isDirectSmsAllowed(context)) {
outgoingSmsSummary = partialResId;
} else {
outgoingSmsSummary = offResId;
}
return context.getString(outgoingSmsResId, context.getString(outgoingSmsSummary));
}
}

View File

@ -1,33 +0,0 @@
package org.thoughtcrime.securesms.protocol;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
public class Tag {
public static final String WHITESPACE_TAG = " ";
public static boolean isTaggable(String message) {
return message.matches(".*[^\\s].*") &&
message.replaceAll("\\s+$", "").length() + WHITESPACE_TAG.length() <= 158;
}
public static boolean isTagged(String message) {
return message != null && message.matches(".*[^\\s]" + WHITESPACE_TAG + "$");
}
public static String getTaggedMessage(String message) {
return message.replaceAll("\\s+$", "") + WHITESPACE_TAG;
}
public static String stripTag(String message) {
if (isTagged(message))
return message.substring(0, message.length() - WHITESPACE_TAG.length());
return message;
}
}

View File

@ -1,30 +0,0 @@
package org.thoughtcrime.securesms.sms;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.libaxolotl.IdentityKey;
public class IncomingIdentityUpdateMessage extends IncomingKeyExchangeMessage {
public IncomingIdentityUpdateMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
}
@Override
public IncomingIdentityUpdateMessage withMessageBody(String messageBody) {
return new IncomingIdentityUpdateMessage(this, messageBody);
}
@Override
public boolean isIdentityUpdate() {
return true;
}
public static IncomingIdentityUpdateMessage createFor(String sender, IdentityKey identityKey) {
return createFor(sender, identityKey, null);
}
public static IncomingIdentityUpdateMessage createFor(String sender, IdentityKey identityKey, String groupId) {
IncomingTextMessage base = new IncomingTextMessage(sender, groupId);
return new IncomingIdentityUpdateMessage(base, Base64.encodeBytesWithoutPadding(identityKey.serialize()));
}
}

View File

@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.sms;
public class IncomingKeyExchangeMessage extends IncomingTextMessage {
private boolean isStale;
private boolean isProcessed;
private boolean isCorrupted;
private boolean isInvalidVersion;
private boolean isLegacyVersion;
private boolean isDuplicate;
public IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
if (base instanceof IncomingKeyExchangeMessage) {
this.isStale = ((IncomingKeyExchangeMessage)base).isStale;
this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed;
this.isCorrupted = ((IncomingKeyExchangeMessage)base).isCorrupted;
this.isInvalidVersion = ((IncomingKeyExchangeMessage)base).isInvalidVersion;
this.isLegacyVersion = ((IncomingKeyExchangeMessage)base).isLegacyVersion;
}
}
@Override
public IncomingTextMessage withMessageBody(String messageBody) {
return new IncomingKeyExchangeMessage(this, messageBody);
}
public boolean isIdentityUpdate() {
return false;
}
public boolean isStale() {
return isStale;
}
public boolean isProcessed() {
return isProcessed;
}
public void setStale(boolean isStale) {
this.isStale = isStale;
}
public void setProcessed(boolean isProcessed) {
this.isProcessed = isProcessed;
}
public boolean isCorrupted() {
return isCorrupted;
}
public void setCorrupted(boolean isCorrupted) {
this.isCorrupted = isCorrupted;
}
public boolean isInvalidVersion() {
return isInvalidVersion;
}
public void setInvalidVersion(boolean isInvalidVersion) {
this.isInvalidVersion = isInvalidVersion;
}
public boolean isLegacyVersion() {
return isLegacyVersion;
}
public void setLegacyVersion(boolean isLegacyVersion) {
this.isLegacyVersion = isLegacyVersion;
}
public void setDuplicate(boolean isDuplicate) {
this.isDuplicate = isDuplicate;
}
public boolean isDuplicate() {
return isDuplicate;
}
@Override
public boolean isKeyExchange() {
return true;
}
}

View File

@ -1,6 +1,6 @@
package org.thoughtcrime.securesms.sms;
public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
public class IncomingPreKeyBundleMessage extends IncomingTextMessage {
public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
@ -15,4 +15,5 @@ public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
public boolean isPreKeyBundle() {
return true;
}
}

View File

@ -164,10 +164,6 @@ public class IncomingTextMessage implements Parcelable {
return replyPathPresent;
}
public boolean isKeyExchange() {
return false;
}
public boolean isSecureMessage() {
return false;
}

View File

@ -1,100 +0,0 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.sms;
import android.util.Log;
import org.thoughtcrime.securesms.util.Base64;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
public class MultipartSmsMessageHandler {
private static final String TAG = MultipartSmsMessageHandler.class.getSimpleName();
private final HashMap<String, MultipartSmsTransportMessageFragments> partialMessages = new HashMap<>();
private IncomingTextMessage processMultipartMessage(MultipartSmsTransportMessage message) {
Log.w(TAG, "Processing multipart message...");
Log.w(TAG, "Multipart Count: " + message.getMultipartCount());
Log.w(TAG, "Multipart ID: " + message.getIdentifier());
Log.w(TAG, "Multipart Key: " + message.getKey());
MultipartSmsTransportMessageFragments container = partialMessages.get(message.getKey());
Log.w(TAG, "Found multipart container: " + container);
if (container == null || container.getSize() != message.getMultipartCount() || container.isExpired()) {
Log.w(TAG, "Constructing new container...");
container = new MultipartSmsTransportMessageFragments(message.getMultipartCount());
partialMessages.put(message.getKey(), container);
}
container.add(message);
Log.w(TAG, "Filled buffer at index: " + message.getMultipartIndex());
if (!container.isComplete())
return null;
partialMessages.remove(message.getKey());
String strippedMessage = Base64.encodeBytesWithoutPadding(container.getJoined());
if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) {
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
} else {
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
}
}
private IncomingTextMessage processSinglePartMessage(MultipartSmsTransportMessage message) {
Log.w(TAG, "Processing single part message...");
String strippedMessage = Base64.encodeBytesWithoutPadding(message.getStrippedMessage());
if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) {
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_END_SESSION) {
return new IncomingEndSessionMessage(message.getBaseMessage(), strippedMessage);
} else {
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
}
}
public synchronized IncomingTextMessage processPotentialMultipartMessage(IncomingTextMessage message) {
try {
MultipartSmsTransportMessage transportMessage = new MultipartSmsTransportMessage(message);
if (transportMessage.isInvalid()) return message;
else if (transportMessage.isSinglePart()) return processSinglePartMessage(transportMessage);
else return processMultipartMessage(transportMessage);
} catch (IOException e) {
Log.w(TAG, e);
return message;
}
}
public synchronized ArrayList<String> divideMessage(OutgoingTextMessage message) {
String number = message.getRecipients().getPrimaryRecipient().getNumber();
byte identifier = MultipartSmsIdentifier.getInstance().getIdForRecipient(number);
return MultipartSmsTransportMessage.getEncoded(message, identifier);
}
}

View File

@ -4,7 +4,8 @@ public class InsecureFallbackApprovalException extends Exception {
public InsecureFallbackApprovalException(String detailMessage) {
super(detailMessage);
}
public InsecureFallbackApprovalException(Exception e) {
public InsecureFallbackApprovalException(Throwable e) {
super(e);
}
}

View File

@ -1,7 +0,0 @@
package org.thoughtcrime.securesms.transport;
public class SecureFallbackApprovalException extends Exception {
public SecureFallbackApprovalException(String detailMessage) {
super(detailMessage);
}
}

View File

@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.util;
public class MmsCharacterCalculator extends CharacterCalculator {
private static final int MAX_SIZE = 5000;
@Override
public CharacterState calculateCharacters(int charactersSpent) {
return new CharacterState(1, MAX_SIZE - charactersSpent, MAX_SIZE);
}
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
@ -40,7 +41,6 @@ public class TextSecurePreferences {
public static final String ALL_SMS_PREF = "pref_all_sms";
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval";
private static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
private static final String AUTO_KEY_EXCHANGE_PREF = "pref_auto_complete_key_exchange";
public static final String SCREEN_SECURITY_PREF = "pref_screen_security";
private static final String ENTER_SENDS_PREF = "pref_enter_sends";
private static final String ENTER_PRESENT_PREF = "pref_enter_key";
@ -57,10 +57,6 @@ public class TextSecurePreferences {
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
private static final String LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id";
private static final String FALLBACK_SMS_ALLOWED_PREF = "pref_allow_sms_traffic_out";
private static final String FALLBACK_SMS_ASK_REQUIRED_PREF = "pref_sms_fallback_ask";
private static final String DIRECT_SMS_ALLOWED_PREF = "pref_sms_non_data_out";
private static final String FALLBACK_MMS_ENABLED_PREF = "pref_mms_fallback_enabled";
private static final String SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered";
private static final String WIFI_SMS_PREF = "pref_wifi_sms";
@ -68,7 +64,6 @@ public class TextSecurePreferences {
private static final String GCM_REGISTRATION_ID_VERSION_PREF = "pref_gcm_registration_id_version";
private static final String WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered";
private static final String PUSH_REGISTRATION_REMINDER_PREF = "pref_push_registration_reminder";
public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
public static boolean isWebsocketRegistered(Context context) {
@ -119,36 +114,12 @@ public class TextSecurePreferences {
}
}
public static boolean isFallbackSmsAllowed(Context context) {
return getBooleanPreference(context, FALLBACK_SMS_ALLOWED_PREF, true);
}
public static void setFallbackSmsAllowed(Context context, boolean allowed) {
setBooleanPreference(context, FALLBACK_SMS_ALLOWED_PREF, allowed);
}
public static boolean isFallbackSmsAskRequired(Context context) {
return getBooleanPreference(context, FALLBACK_SMS_ASK_REQUIRED_PREF, false);
}
public static void setFallbackSmsAskRequired(Context context, boolean required) {
setBooleanPreference(context, FALLBACK_SMS_ASK_REQUIRED_PREF, required);
}
public static boolean isFallbackMmsEnabled(Context context) {
return getBooleanPreference(context, FALLBACK_MMS_ENABLED_PREF, true);
}
public static void setFallbackMmsEnabled(Context context, boolean enabled) {
setBooleanPreference(context, FALLBACK_MMS_ENABLED_PREF, enabled);
}
public static boolean isDirectSmsAllowed(Context context) {
return getBooleanPreference(context, DIRECT_SMS_ALLOWED_PREF, true);
}
public static void setDirectSmsAllowed(Context context, boolean allowed) {
setBooleanPreference(context, DIRECT_SMS_ALLOWED_PREF, allowed);
public static boolean isSmsEnabled(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return Util.isDefaultSmsProvider(context);
} else {
return isInterceptAllSmsEnabled(context);
}
}
public static int getLocalRegistrationId(Context context) {
@ -304,10 +275,6 @@ public class TextSecurePreferences {
setStringPreference(context, IDENTITY_PREF, identityUri);
}
public static boolean isAutoRespondKeyExchangeEnabled(Context context) {
return getBooleanPreference(context, AUTO_KEY_EXCHANGE_PREF, true);
}
public static boolean isScreenSecurityEnabled(Context context) {
return getBooleanPreference(context, SCREEN_SECURITY_PREF, true);
}
@ -431,14 +398,6 @@ public class TextSecurePreferences {
return Integer.parseInt(getStringPreference(context, THREAD_TRIM_LENGTH, "500"));
}
public static long getLastPushReminderTime(Context context) {
return getLongPreference(context, PUSH_REGISTRATION_REMINDER_PREF, 0L);
}
public static void setLastPushReminderTime(Context context, long time) {
setLongPreference(context, PUSH_REGISTRATION_REMINDER_PREF, time);
}
public static void setBooleanPreference(Context context, String key, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}