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:contentDescription="@string/conversation_activity__emoji_toggle_description"
android:padding="10dp"/> android:padding="10dp"/>
<EditText <org.thoughtcrime.securesms.components.ComposeText
android:id="@+id/embedded_text_editor" android:id="@+id/embedded_text_editor"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -89,7 +89,7 @@
android:paddingRight="0dp" android:paddingRight="0dp"
android:imeOptions="actionSend|flagNoEnterAction|flagNoExtractUi" android:imeOptions="actionSend|flagNoEnterAction|flagNoExtractUi"
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine" android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
android:maxLength="1000" android:maxLength="2000"
android:maxLines="4" android:maxLines="4"
android:nextFocusForward="@+id/send_button" android:nextFocusForward="@+id/send_button"
android:nextFocusRight="@+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"> android:orientation="horizontal">
<Button android:id="@+id/skipButton" <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_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginRight="5dip" 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> <item>custom</item>
</string-array> </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"> <string-array name="pref_repeat_alerts_entries">
<item>@string/preferences__never</item> <item>@string/preferences__never</item>
<item>@string/preferences__one_time</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_secure" format="reference"/>
<attr name="conversation_send_button_sms_insecure" format="reference"/> <attr name="conversation_send_button_sms_insecure" format="reference"/>
<attr name="conversation_delivery_delivered" 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_transport_popup_background" format="reference"/>
<attr name="conversation_emoji_toggle" format="reference"/> <attr name="conversation_emoji_toggle" format="reference"/>
<attr name="conversation_keyboard_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_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_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_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_for_details">Tap for details</string>
<string name="ConversationItem_click_to_approve_unencrypted">Tap for unsecured fallback</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_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_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> <string name="ConversationItem_unable_to_open_media">Can\'t find an app able to open this media.</string>
<!-- ConversationActivity --> <!-- 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_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_sms">Insecure SMS</string>
<string name="ConversationActivity_transport_insecure_mms">Insecure MMS</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_transport_textsecure">TextSecure</string>
<string name="ConversationActivity_get_with_it">Get with it: %s</string> <string name="ConversationActivity_get_with_it">Get with it: %s</string>
<string name="ConversationActivity_install_textsecure">Install TextSecure: %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 <string name="ReceiveKeyActivity_you_may_wish_to_verify_this_contact">You may wish to verify
this contact. this contact.
</string> </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">Processing</string>
<string name="ReceiveKeyActivity_processing_key_exchange">Processing key exchange…</string> <string name="ReceiveKeyActivity_processing_key_exchange">Processing key exchange…</string>
@ -453,10 +443,6 @@
<!-- QuickResponseService --> <!-- QuickResponseService -->
<string name="QuickResponseService_sorry_quick_response_is_not_yet_supported_by_textsecure">Sorry, Quick Response is not yet supported by TextSecure!</string> <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 --> <!-- change_passphrase_activity -->
<string name="change_passphrase_activity__old_passphrase">OLD PASSPHRASE:</string> <string name="change_passphrase_activity__old_passphrase">OLD PASSPHRASE:</string>
<string name="change_passphrase_activity__new_passphrase">NEW PASSPHRASE:</string> <string name="change_passphrase_activity__new_passphrase">NEW PASSPHRASE:</string>
@ -581,7 +567,7 @@
<!-- registration_activity --> <!-- registration_activity -->
<string name="registration_activity__textsecure_can_use_instant_messages_to_avoid_sms_charges_when_communicating_with_other_textsecure_users"> <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>
<string name="registration_activity__your_country">YOUR COUNTRY</string> <string name="registration_activity__your_country">YOUR COUNTRY</string>
<string name="registration_activity__your_country_code_and_phone_number">YOUR COUNTRY CODE AND <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__phone_number">PHONE NUMBER</string>
<string name="registration_activity__register">Register</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__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 --> <!-- registration_problems -->
<string name="registration_problems__some_possible_problems_include">Some possible 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__public_identity_key">Public identity key</string>
<string name="AndroidManifest__change_passphrase">Change passphrase</string> <string name="AndroidManifest__change_passphrase">Change passphrase</string>
<string name="AndroidManifest__verify_identity">Verify identity</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__log_submit">Submit debug logs</string>
<string name="AndroidManifest__media_preview">Media Preview</string> <string name="AndroidManifest__media_preview">Media Preview</string>
<string name="AndroidManifest__media_overview">All images</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__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_passphrase">Change passphrase</string>
<string name="preferences__change_my_passphrase">Change my 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__enable_passphrase">Enable passphrase</string>
<string name="preferences__passphrase_summary">Passphrase %s</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__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">Screen security</string>
<string name="preferences__screen_security_summary">Screen security %s</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__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__forget_passphrase_from_memory_after_some_interval">Forget passphrase from memory after some interval</string>
<string name="preferences__timeout_passphrase">Timeout passphrase</string> <string name="preferences__timeout_passphrase">Timeout passphrase</string>
@ -840,13 +822,6 @@
<!-- refreshing push directory from menu --> <!-- refreshing push directory from menu -->
<string name="push_directory__menu_refresh">Refresh contact list</string> <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 --> <!-- conversation_callable -->
<string name="conversation_callable__menu_call">Call</string> <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_secure">@drawable/ic_send_sms_secure</item>
<item name="conversation_send_button_sms_insecure">@drawable/ic_send_sms_insecure</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_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_transport_popup_background">@color/white</item>
<item name="conversation_emoji_toggle">@drawable/ic_emoji_dark</item> <item name="conversation_emoji_toggle">@drawable/ic_emoji_dark</item>
<item name="conversation_keyboard_toggle">@drawable/ic_ime_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_secure">@drawable/ic_send_sms_secure</item>
<item name="conversation_send_button_sms_insecure">@drawable/ic_send_sms_insecure_dark</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_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_transport_popup_background">@color/black</item>
<item name="conversation_emoji_toggle">@drawable/ic_emoji_light</item> <item name="conversation_emoji_toggle">@drawable/ic_emoji_light</item>
<item name="conversation_keyboard_toggle">@drawable/ic_ime_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:title="@string/preferences__make_default_sms_app"
android:summary="@string/preferences__make_textsecure_the_default_sms_mms_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" <CheckBoxPreference android:defaultValue="false"
android:key="pref_delivery_report_sms" android:key="pref_delivery_report_sms"
android:summary="@string/preferences__request_a_delivery_report_for_each_sms_message_you_send" 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.View.OnKeyListener;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener; import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.EmojiDrawer; import org.thoughtcrime.securesms.components.EmojiDrawer;
import org.thoughtcrime.securesms.components.EmojiToggle; import org.thoughtcrime.securesms.components.EmojiToggle;
import org.thoughtcrime.securesms.components.SendButton; import org.thoughtcrime.securesms.components.SendButton;
import org.thoughtcrime.securesms.contacts.ContactAccessor; import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
import org.thoughtcrime.securesms.crypto.MasterCipher; import org.thoughtcrime.securesms.crypto.MasterCipher;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft; 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.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.Tag;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
@ -108,6 +105,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.List; import java.util.List;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener; import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext; 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 static final int GROUP_EDIT = 5;
private MasterSecret masterSecret; private MasterSecret masterSecret;
private EditText composeText; private ComposeText composeText;
private SendButton sendButton; private SendButton sendButton;
private TextView charactersLeft; private TextView charactersLeft;
@ -158,7 +156,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private int distributionType; private int distributionType;
private boolean isEncryptedConversation; private boolean isEncryptedConversation;
private boolean isMmsEnabled = true; private boolean isMmsEnabled = true;
private boolean isCharactersLeftViewEnabled;
private DynamicTheme dynamicTheme = new DynamicTheme(); private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage(); private DynamicLanguage dynamicLanguage = new DynamicLanguage();
@ -264,13 +261,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
MenuInflater inflater = this.getMenuInflater(); MenuInflater inflater = this.getMenuInflater();
menu.clear(); menu.clear();
boolean pushRegistered = TextSecurePreferences.isPushRegistered(this);
if (isSingleConversation() && isEncryptedConversation) { if (isSingleConversation() && isEncryptedConversation) {
inflater.inflate(R.menu.conversation_secure_identity, menu); inflater.inflate(R.menu.conversation_secure_identity, menu);
inflater.inflate(R.menu.conversation_secure_sms, menu.findItem(R.id.menu_security).getSubMenu()); inflater.inflate(R.menu.conversation_secure_sms, menu.findItem(R.id.menu_security).getSubMenu());
} else if (isSingleConversation()) { } else if (isSingleConversation()) {
if (!pushRegistered) inflater.inflate(R.menu.conversation_insecure_no_push, menu);
inflater.inflate(R.menu.conversation_insecure, 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_add_attachment: handleAddAttachment(); return true;
case R.id.menu_view_media: handleViewMedia(); 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_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_abort_session: handleAbortSecureSession(); return true;
case R.id.menu_verify_identity: handleVerifyIdentity(); return true; case R.id.menu_verify_identity: handleVerifyIdentity(); return true;
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
@ -362,33 +355,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
startActivity(verifyIdentityIntent); 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() { private void handleAbortSecureSession() {
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.ConversationActivity_abort_secure_session_confirmation); builder.setTitle(R.string.ConversationActivity_abort_secure_session_confirmation);
@ -679,31 +645,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} }
private void initializeSecurity() { private void initializeSecurity() {
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient(); boolean isMediaMessage = !recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent();
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients()); this.isEncryptedConversation = DirectoryHelper.isPushDestination(this, getRecipients());
boolean isSecureSmsAllowed = (!isPushDestination || DirectoryHelper.isSmsFallbackAllowed(this, getRecipients()));
boolean isSecureSmsDestination = isSecureSmsAllowed &&
isSingleConversation() &&
SessionUtil.hasSession(this, masterSecret, primaryRecipient);
if (isPushDestination || isSecureSmsDestination) { sendButton.resetAvailableTransports(isMediaMessage);
this.isEncryptedConversation = true;
} else {
this.isEncryptedConversation = false;
}
sendButton.initializeAvailableTransports(!recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent()); if (!isEncryptedConversation) sendButton.disableTransport(Type.TEXTSECURE);
if (!isPushDestination ) sendButton.disableTransport("textsecure"); if (recipients.isGroupRecipient()) sendButton.disableTransport(Type.SMS);
if (!isSecureSmsDestination ) sendButton.disableTransport("secure_sms");
if (recipients.isGroupRecipient()) sendButton.disableTransport("insecure_sms");
if (isPushDestination) { if (isEncryptedConversation) sendButton.setDefaultTransport(Type.TEXTSECURE);
sendButton.setDefaultTransport("textsecure"); else sendButton.setDefaultTransport(Type.SMS);
} else if (isSecureSmsDestination) {
sendButton.setDefaultTransport("secure_sms");
} else {
sendButton.setDefaultTransport("insecure_sms");
}
calculateCharactersRemaining(); calculateCharactersRemaining();
} }
@ -732,7 +683,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void initializeViews() { private void initializeViews() {
sendButton = (SendButton) findViewById(R.id.send_button); sendButton = (SendButton) findViewById(R.id.send_button);
composeText = (EditText) findViewById(R.id.embedded_text_editor); composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
charactersLeft = (TextView) findViewById(R.id.space_left); charactersLeft = (TextView) findViewById(R.id.space_left);
emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer); emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer);
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle); emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
@ -749,11 +700,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
sendButton.setOnClickListener(sendButtonListener); sendButton.setOnClickListener(sendButtonListener);
sendButton.setEnabled(true); sendButton.setEnabled(true);
sendButton.setComposeTextView(composeText);
sendButton.addOnTransportChangedListener(new OnTransportChangedListener() { sendButton.addOnTransportChangedListener(new OnTransportChangedListener() {
@Override @Override
public void onChange(TransportOption newTransport) { public void onChange(TransportOption newTransport) {
calculateCharactersRemaining(); calculateCharactersRemaining();
composeText.setHint(newTransport.getComposeHint());
} }
}); });
@ -784,10 +735,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
securityUpdateReceiver = new BroadcastReceiver() { securityUpdateReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent.getLongExtra("thread_id", -1) == -1) long eventThreadId = intent.getLongExtra("thread_id", -1);
return;
if (intent.getLongExtra("thread_id", -1) == threadId) { if (eventThreadId == threadId || eventThreadId == -2) {
initializeSecurity(); initializeSecurity();
initializeTitleBar(); initializeTitleBar();
calculateCharactersRemaining(); calculateCharactersRemaining();
@ -959,7 +909,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
int charactersSpent = composeText.getText().toString().length(); int charactersSpent = composeText.getText().toString().length();
TransportOption transportOption = sendButton.getSelectedTransport(); TransportOption transportOption = sendButton.getSelectedTransport();
if (transportOption != null) {
CharacterState characterState = transportOption.calculateCharacters(charactersSpent); CharacterState characterState = transportOption.calculateCharacters(charactersSpent);
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) { if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
@ -970,11 +919,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
charactersLeft.setVisibility(View.GONE); charactersLeft.setVisibility(View.GONE);
} }
} }
}
private boolean isExistingConversation() {
return this.recipients != null && this.threadId != -1;
}
private boolean isSingleConversation() { private boolean isSingleConversation() {
return getRecipients() != null && getRecipients().isSingleRecipient() && !getRecipients().isGroupRecipient(); return getRecipients() != null && getRecipients().isSingleRecipient() && !getRecipients().isGroupRecipient();
@ -1013,9 +957,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent()) if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation)); throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation));
if (!isEncryptedConversation && Tag.isTaggable(rawText))
rawText = Tag.getTaggedMessage(rawText);
return rawText; return rawText;
} }
@ -1056,7 +997,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void sendMessage() { private void sendMessage() {
try { 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) { if (recipients == null) {
throw new RecipientFormattingException("Badly formatted"); throw new RecipientFormattingException("Badly formatted");
@ -1065,9 +1010,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) { if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
handleManualMmsRequired(); handleManualMmsRequired();
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) { } else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
sendMediaMessage(sendButton.getSelectedTransport().isPlaintext(), sendButton.getSelectedTransport().isSms()); sendMediaMessage(forceSms);
} else { } else {
sendTextMessage(sendButton.getSelectedTransport().isPlaintext(), sendButton.getSelectedTransport().isSms()); sendTextMessage(forceSms);
} }
} catch (RecipientFormattingException ex) { } catch (RecipientFormattingException ex) {
Toast.makeText(ConversationActivity.this, 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 throws InvalidMessageException
{ {
final Context context = getApplicationContext(); final Context context = getApplicationContext();
@ -1093,7 +1038,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(this, recipients, slideDeck, OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(this, recipients, slideDeck,
getMessage(), distributionType); getMessage(), distributionType);
if (isEncryptedConversation && !forcePlaintext) { if (isEncryptedConversation && !forceSms) {
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage); outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);
} }
@ -1113,13 +1058,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}.execute(outgoingMessage); }.execute(outgoingMessage);
} }
private void sendTextMessage(boolean forcePlaintext, final boolean forceSms) private void sendTextMessage(final boolean forceSms)
throws InvalidMessageException throws InvalidMessageException
{ {
final Context context = getApplicationContext(); final Context context = getApplicationContext();
OutgoingTextMessage message; OutgoingTextMessage message;
if (isEncryptedConversation && !forcePlaintext) { if (isEncryptedConversation && !forceSms) {
message = new OutgoingEncryptedMessage(recipients, getMessage()); message = new OutgoingEncryptedMessage(recipients, getMessage());
} else { } else {
message = new OutgoingTextMessage(recipients, getMessage()); 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 { private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener {
@Override @Override
public boolean onKey(View v, int keyCode, KeyEvent event) { 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()) { if ((messageRecord.isPending() || messageRecord.isFailed()) && pushDestination && !messageRecord.isForcedSms()) {
background = SENT_PUSH_PENDING; background = SENT_PUSH_PENDING;
triangleBackground = SENT_PUSH_PENDING_TRIANGLE; 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; background = SENT_SMS_PENDING;
triangleBackground = SENT_SMS_PENDING_TRIANGLE; triangleBackground = SENT_SMS_PENDING_TRIANGLE;
} else if (messageRecord.isPush()) { } else if (messageRecord.isPush()) {
@ -292,10 +292,9 @@ public class ConversationItem extends LinearLayout {
mmsDownloadingLabel.setVisibility(View.GONE); mmsDownloadingLabel.setVisibility(View.GONE);
if (messageRecord.isFailed()) setFailedStatusIcons(); if (messageRecord.isFailed()) setFailedStatusIcons();
else if (messageRecord.isPendingSmsFallback()) setFallbackStatusIcons(); else if (messageRecord.isPendingInsecureSmsFallback()) setFallbackStatusIcons();
else if (messageRecord.isPending()) dateText.setText(" ··· "); else if (messageRecord.isPending()) dateText.setText(" ··· ");
else setSentStatusIcons(); else setSentStatusIcons();
} }
private void setSentStatusIcons() { private void setSentStatusIcons() {
@ -316,14 +315,8 @@ public class ConversationItem extends LinearLayout {
private void setFallbackStatusIcons() { private void setFallbackStatusIcons() {
pendingIndicator.setVisibility(View.VISIBLE); pendingIndicator.setVisibility(View.VISIBLE);
indicatorText.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() { private void setMinimumWidth() {
if (indicatorText != null && indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) { if (indicatorText != null && indicatorText.getVisibility() == View.VISIBLE && indicatorText.getText() != null) {
@ -335,20 +328,9 @@ public class ConversationItem extends LinearLayout {
} }
private void setEvents(MessageRecord messageRecord) { private void setEvents(MessageRecord messageRecord) {
setClickable(messageRecord.isFailed()|| setClickable(messageRecord.isFailed() ||
messageRecord.isPendingSmsFallback() || messageRecord.isPendingInsecureSmsFallback() ||
(messageRecord.isKeyExchange() && messageRecord.isBundleKeyExchange());
!messageRecord.isCorruptedKeyExchange() &&
!messageRecord.isOutgoing()));
if (!messageRecord.isOutgoing() &&
messageRecord.getRecipients().isSingleRecipient() &&
!messageRecord.isSecure())
{
checkForAutoInitiate(messageRecord.getIndividualRecipient(),
messageRecord.getBody().getBody(),
messageRecord.getThreadId());
}
} }
private void setGroupMessageStatus(MessageRecord messageRecord) { private void setGroupMessageStatus(MessageRecord messageRecord) {
@ -427,23 +409,6 @@ public class ConversationItem extends LinearLayout {
/// Helper Methods /// 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) { private void setContactPhotoForRecipient(final Recipient recipient) {
if (contactPhoto == null) return; if (contactPhoto == null) return;
@ -588,7 +553,7 @@ public class ConversationItem extends LinearLayout {
!messageRecord.isStaleKeyExchange()) !messageRecord.isStaleKeyExchange())
{ {
handleKeyExchangeClicked(); handleKeyExchangeClicked();
} else if (messageRecord.isPendingSmsFallback()) { } else if (messageRecord.isPendingInsecureSmsFallback()) {
handleMessageApproval(); handleMessageApproval();
} }
} }
@ -611,17 +576,10 @@ public class ConversationItem extends LinearLayout {
final int title; final int title;
final int message; 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;
message = -1;
} else {
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_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; 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); AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(title); builder.setTitle(title);
@ -633,9 +591,7 @@ public class ConversationItem extends LinearLayout {
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
if (messageRecord.isMms()) { if (messageRecord.isMms()) {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
if (messageRecord.isPendingInsecureSmsFallback()) {
database.markAsInsecure(messageRecord.getId()); database.markAsInsecure(messageRecord.getId());
}
database.markAsOutbox(messageRecord.getId()); database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(messageRecord.getId()); database.markAsForcedSms(messageRecord.getId());
@ -644,9 +600,7 @@ public class ConversationItem extends LinearLayout {
.add(new MmsSendJob(context, messageRecord.getId())); .add(new MmsSendJob(context, messageRecord.getId()));
} else { } else {
SmsDatabase database = DatabaseFactory.getSmsDatabase(context); SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
if (messageRecord.isPendingInsecureSmsFallback()) {
database.markAsInsecure(messageRecord.getId()); database.markAsInsecure(messageRecord.getId());
}
database.markAsOutbox(messageRecord.getId()); database.markAsOutbox(messageRecord.getId());
database.markAsForcedSms(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.ListFragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader; 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.app.ActionBarActivity;
import android.support.v7.view.ActionMode; import android.support.v7.view.ActionMode;
import android.support.v4.widget.CursorAdapter; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; 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.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.ReminderView; import org.thoughtcrime.securesms.components.ReminderView;
import org.thoughtcrime.securesms.components.SystemSmsImportReminder; import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader; import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.Dialogs; import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import java.util.Set; 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.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.jobs.PushDecryptJob; import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.SmsDecryptJob;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; 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.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
@ -52,7 +48,6 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.libaxolotl.InvalidVersionException;
import org.whispersystems.libaxolotl.LegacyMessageException; import org.whispersystems.libaxolotl.LegacyMessageException;
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
import org.whispersystems.libaxolotl.state.IdentityKeyStore; import org.whispersystems.libaxolotl.state.IdentityKeyStore;
import org.whispersystems.libaxolotl.util.guava.Optional; import org.whispersystems.libaxolotl.util.guava.Optional;
@ -79,7 +74,7 @@ public class ReceiveKeyActivity extends BaseActivity {
private long messageId; private long messageId;
private MasterSecret masterSecret; private MasterSecret masterSecret;
private IncomingKeyExchangeMessage message; private IncomingPreKeyBundleMessage message;
private IdentityKey identityKey; private IdentityKey identityKey;
@Override @Override
@ -151,14 +146,7 @@ public class ReceiveKeyActivity extends BaseActivity {
getIntent().getStringExtra("body"), getIntent().getStringExtra("body"),
Optional.<TextSecureGroup>absent()); Optional.<TextSecureGroup>absent());
if (getIntent().getBooleanExtra("is_bundle", false)) {
this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody()); 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.identityKey = getIdentityKey(this.message); this.identityKey = getIdentityKey(this.message);
} }
@ -177,21 +165,12 @@ public class ReceiveKeyActivity extends BaseActivity {
this.cancelButton.setOnClickListener(new CancelListener()); this.cancelButton.setOnClickListener(new CancelListener());
} }
private IdentityKey getIdentityKey(IncomingKeyExchangeMessage message) private IdentityKey getIdentityKey(IncomingPreKeyBundleMessage message)
throws InvalidKeyException, InvalidVersionException, throws InvalidKeyException, InvalidVersionException,
InvalidMessageException, LegacyMessageException InvalidMessageException, LegacyMessageException
{ {
try { try {
if (message.isIdentityUpdate()) { return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey();
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();
}
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
@ -215,15 +194,9 @@ public class ReceiveKeyActivity extends BaseActivity {
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
Context context = ReceiveKeyActivity.this; Context context = ReceiveKeyActivity.this;
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context); PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
identityDatabase.saveIdentity(masterSecret, recipient.getRecipientId(), identityKey); identityDatabase.saveIdentity(masterSecret, recipient.getRecipientId(), identityKey);
if (message.isIdentityUpdate()) {
smsDatabase.markAsProcessedKeyExchange(messageId);
} else {
if (getIntent().getBooleanExtra("is_push", false)) {
try { try {
byte[] body = Base64.decode(message.getMessageBody()); byte[] body = Base64.decode(message.getMessageBody());
TextSecureEnvelope envelope = new TextSecureEnvelope(3, message.getSender(), TextSecureEnvelope envelope = new TextSecureEnvelope(3, message.getSender(),
@ -239,12 +212,6 @@ public class ReceiveKeyActivity extends BaseActivity {
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} else {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new SmsDecryptJob(context, messageId));
}
}
return null; return null;
} }

View File

@ -88,7 +88,9 @@ public class RegistrationActivity extends BaseActionBarActivity {
this.skipButton.setOnClickListener(new CancelButtonListener()); this.skipButton.setOnClickListener(new CancelButtonListener());
if (getIntent().getBooleanExtra("cancel_button", false)) { 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; import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
public class TransportOption { 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) { public enum Type {
this.key = key; SMS,
TEXTSECURE
}
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.drawable = drawable;
this.text = text; this.text = text;
this.composeHint = composeHint; this.composeHint = composeHint;
this.characterCalculator = characterCalculator;
if (isPlaintext() && isSms()) {
this.characterCalculator = new SmsCharacterCalculator();
} else if (isSms()) {
this.characterCalculator = new EncryptedSmsCharacterCalculator();
} else {
this.characterCalculator = new PushCharacterCalculator();
} }
public Type getType() {
return type;
}
public boolean isType(Type type) {
return this.type == type;
} }
public boolean isPlaintext() { public boolean isPlaintext() {
return key.equals("insecure_sms"); return type == Type.SMS;
} }
public boolean isSms() { public boolean isSms() {
return key.equals("insecure_sms") || key.equals("secure_sms"); return type == Type.SMS;
} }
public CharacterState calculateCharacters(int charactersSpent) { public CharacterState calculateCharacters(int charactersSpent) {
return characterCalculator.calculateCharacters(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.Context;
import android.content.res.TypedArray; 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.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import static org.thoughtcrime.securesms.TransportOption.Type;
public class TransportOptions { public class TransportOptions {
private static final String TAG = TransportOptions.class.getSimpleName(); private static final String TAG = TransportOptions.class.getSimpleName();
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<OnTransportChangedListener> listeners = new LinkedList<>();
private final Context context;
private final List<TransportOption> enabledTransports;
public TransportOptions(Context context) { private Type selectedType;
private boolean manuallySelected;
public TransportOptions(Context context, boolean media) {
this.context = context; this.context = context;
this.enabledTransports = initializeAvailableTransports(media);
setDefaultTransport(Type.SMS);
} }
private void initializeTransportPopup() { public void reset(boolean media) {
if (transportPopup == null) { List<TransportOption> transportOptions = initializeAvailableTransports(media);
final View selectionMenu = LayoutInflater.from(context).inflate(R.layout.transport_selection, null); this.enabledTransports.clear();
final ListView list = (ListView) selectionMenu.findViewById(R.id.transport_selection_list); this.enabledTransports.addAll(transportOptions);
final TransportOptionsAdapter adapter = new TransportOptionsAdapter(context, enabledTransports, transportMetadata); if (!find(selectedType).isPresent()) {
this.manuallySelected = false;
list.setAdapter(adapter); setTransport(Type.SMS);
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();
}
});
} else { } else {
final ListView list = (ListView) transportPopup.getContentView().findViewById(R.id.transport_selection_list); notifyTransportChangeListeners();
final TransportOptionsAdapter adapter = (TransportOptionsAdapter) list.getAdapter();
adapter.setEnabledTransports(enabledTransports);
adapter.notifyDataSetInvalidated();
} }
} }
public void initializeAvailableTransports(boolean isMediaMessage) { public void setDefaultTransport(Type type) {
String[] entryArray = (isMediaMessage) if (!this.manuallySelected) {
? context.getResources().getStringArray(R.array.transport_selection_entries_media) setTransport(type);
: 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]));
} }
iconArray.recycle();
icons.recycle();
updateViews();
} }
public void setTransport(String transport) { public void setSelectedTransport(Type type) {
selectedTransport = transport; this.manuallySelected= true;
updateViews(); setTransport(type);
} }
private void setTransport(TransportOption transport) { public boolean isManualSelection() {
setTransport(transport.key); return manuallySelected;
}
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 TransportOption getSelectedTransport() { 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) { public void disableTransport(Type type) {
enabledTransports.remove(transportName); Optional<TransportOption> option = find(type);
if (option.isPresent()) {
enabledTransports.remove(option.get());
}
} }
public List<String> getEnabledTransports() { public List<TransportOption> getEnabledTransports() {
return enabledTransports; return enabledTransports;
} }
private void updateViews() {
if (selectedTransport == null) return;
for (OnTransportChangedListener listener : listeners) {
listener.onChange(getSelectedTransport());
}
}
public void addOnTransportChangedListener(OnTransportChangedListener listener) { public void addOnTransportChangedListener(OnTransportChangedListener listener) {
this.listeners.add(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 interface OnTransportChangedListener {
public void onChange(TransportOption newTransport); public void onChange(TransportOption newTransport);
} }

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -9,41 +10,33 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import java.util.List; import java.util.List;
import java.util.Map;
public class TransportOptionsAdapter extends BaseAdapter { 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, private final LayoutInflater inflater;
final List<String> enabledTransports,
final Map<String, TransportOption> transportMetadata) { private List<TransportOption> enabledTransports;
public TransportOptionsAdapter(@NonNull Context context,
@NonNull List<TransportOption> enabledTransports)
{
super(); super();
this.context = context;
this.inflater = LayoutInflater.from(context); this.inflater = LayoutInflater.from(context);
this.enabledTransports = enabledTransports; this.enabledTransports = enabledTransports;
this.transportMetadata = transportMetadata;
} }
public TransportOptionsAdapter(final Context context, public void setEnabledTransports(List<TransportOption> enabledTransports) {
final Map<String, TransportOption> transportMetadata) {
this(context, null, transportMetadata);
}
public void setEnabledTransports(final List<String> enabledTransports) {
this.enabledTransports = enabledTransports; this.enabledTransports = enabledTransports;
} }
@Override @Override
public int getCount() { public int getCount() {
return enabledTransports == null ? 0 : enabledTransports.size(); return enabledTransports.size();
} }
@Override @Override
public Object getItem(int position) { public Object getItem(int position) {
return transportMetadata.get(enabledTransports.get(position)); return enabledTransports.get(position);
} }
@Override @Override
@ -53,19 +46,17 @@ public class TransportOptionsAdapter extends BaseAdapter {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
final View view;
if (convertView == null) { if (convertView == null) {
view = inflater.inflate(R.layout.transport_selection_list_item, parent, false); convertView = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
} else {
view = convertView;
} }
TransportOption transport = (TransportOption) getItem(position); TransportOption transport = (TransportOption) getItem(position);
final ImageView imageView = (ImageView)view.findViewById(R.id.icon); ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
final TextView textView = (TextView) view.findViewById(R.id.text); TextView textView = (TextView) convertView.findViewById(R.id.text);
imageView.setImageResource(transport.drawable); imageView.setImageResource(transport.getDrawable());
textView.setText(transport.text); textView.setText(transport.getDescription());
return view;
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; import org.thoughtcrime.securesms.crypto.MasterSecret;
public class PushRegistrationReminder extends Reminder { 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) { public PushRegistrationReminder(final Context context, final MasterSecret masterSecret) {
super(R.drawable.ic_push_registration_reminder, super(R.drawable.ic_push_registration_reminder,
@ -27,18 +26,16 @@ public class PushRegistrationReminder extends Reminder {
context.startActivity(intent); context.startActivity(intent);
} }
}; };
final OnClickListener cancelListener = new OnClickListener() {
@Override
public void onClick(View v) {
TextSecurePreferences.setLastPushReminderTime(context, System.currentTimeMillis());
}
};
setOkListener(okListener); setOkListener(okListener);
setCancelListener(cancelListener); }
@Override
public boolean isDismissable() {
return false;
} }
public static boolean isEligible(Context context) { public static boolean isEligible(Context context) {
return !TextSecurePreferences.isPushRegistered(context) && return !TextSecurePreferences.isPushRegistered(context);
(TextSecurePreferences.getLastPushReminderTime(context) + REMINDER_INTERVAL_MS < System.currentTimeMillis());
} }
} }

View File

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

View File

@ -1,94 +1,101 @@
package org.thoughtcrime.securesms.components; package org.thoughtcrime.securesms.components;
import android.content.Context; import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import org.thoughtcrime.securesms.TransportOption; import org.thoughtcrime.securesms.TransportOption;
import org.thoughtcrime.securesms.TransportOptions; import org.thoughtcrime.securesms.TransportOptions;
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener; import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.TransportOptionsPopup;
public class SendButton extends ImageButton { public class SendButton extends ImageButton
private TransportOptions transportOptions; implements TransportOptions.OnTransportChangedListener,
private EditText composeText; TransportOptionsPopup.SelectedListener,
View.OnLongClickListener
{
private final TransportOptions transportOptions;
private final TransportOptionsPopup transportOptionsPopup;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public SendButton(Context context) { public SendButton(Context context) {
super(context); super(context);
initialize(); this.transportOptions = initializeTransportOptions(false);
this.transportOptionsPopup = initializeTransportOptionsPopup();
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs) { public SendButton(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
initialize(); this.transportOptions = initializeTransportOptions(false);
this.transportOptionsPopup = initializeTransportOptionsPopup();
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
public SendButton(Context context, AttributeSet attrs, int defStyle) { public SendButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
initialize(); this.transportOptions = initializeTransportOptions(false);
this.transportOptionsPopup = initializeTransportOptionsPopup();
} }
private void initialize() { private TransportOptions initializeTransportOptions(boolean media) {
transportOptions = new TransportOptions(getContext()); TransportOptions transportOptions = new TransportOptions(getContext(), media);
transportOptions.addOnTransportChangedListener(new OnTransportChangedListener() { transportOptions.addOnTransportChangedListener(this);
@Override
public void onChange(TransportOption newTransport) {
setImageResource(newTransport.drawable);
setContentDescription(newTransport.composeHint);
if (composeText != null) setComposeTextHint(newTransport.composeHint);
}
});
setOnLongClickListener(new OnLongClickListener() { setOnLongClickListener(this);
@Override
public boolean onLongClick(View view) { return transportOptions;
if (transportOptions.getEnabledTransports().size() > 1) {
transportOptions.showPopup(SendButton.this);
return true;
} }
return false;
private TransportOptionsPopup initializeTransportOptionsPopup() {
return new TransportOptionsPopup(getContext(), this);
} }
});
public boolean isManualSelection() {
return transportOptions.isManualSelection();
} }
public void addOnTransportChangedListener(OnTransportChangedListener listener) { public void addOnTransportChangedListener(OnTransportChangedListener listener) {
transportOptions.addOnTransportChangedListener(listener); transportOptions.addOnTransportChangedListener(listener);
} }
public void setComposeTextView(EditText composeText) {
this.composeText = composeText;
}
public TransportOption getSelectedTransport() { public TransportOption getSelectedTransport() {
return transportOptions.getSelectedTransport(); return transportOptions.getSelectedTransport();
} }
public void initializeAvailableTransports(boolean isMediaMessage) { public void resetAvailableTransports(boolean isMediaMessage) {
transportOptions.initializeAvailableTransports(isMediaMessage); transportOptions.reset(isMediaMessage);
} }
public void disableTransport(String transport) { public void disableTransport(TransportOption.Type type) {
transportOptions.disableTransport(transport); transportOptions.disableTransport(type);
} }
public void setDefaultTransport(String transport) { public void setDefaultTransport(TransportOption.Type type) {
transportOptions.setDefaultTransport(transport); transportOptions.setDefaultTransport(type);
} }
private void setComposeTextHint(String hint) { @Override
if (hint == null) { public void onSelected(TransportOption option) {
this.composeText.setHint(null); transportOptions.setSelectedTransport(option.getType());
} else { transportOptionsPopup.dismiss();
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 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) { 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 localCursor = queryLocalDb(filter);
final Cursor androidCursor; final Cursor androidCursor;
final MatrixCursor newNumberCursor; 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 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) { public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
Intent intent = new Intent(SECURITY_UPDATE_EVENT); Intent intent = new Intent(SECURITY_UPDATE_EVENT);
intent.putExtra("thread_id", threadId); 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)); 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) { public void markAsPendingInsecureSmsFallback(long messageId) {
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK); updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
notifyConversationListeners(getThreadIdForMessage(messageId)); 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.jobs.TrimThreadJob;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
@ -183,18 +181,6 @@ public class SmsDatabase extends MessagingDatabase {
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT); 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) { public void markAsInvalidVersionKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT); 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); 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) { public void markAsPendingInsecureSmsFallback(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK); 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) { protected Pair<Long, Long> insertMessageInbox(IncomingTextMessage message, long type) {
if (message.isKeyExchange()) { if (message.isPreKeyBundle()) {
type |= Types.KEY_EXCHANGE_BIT; type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_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;
} else if (message.isSecureMessage()) { } else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
// type |= Types.ENCRYPTION_REMOTE_BIT;
} else if (message.isGroup()) { } else if (message.isGroup()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT; if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
@ -383,7 +356,6 @@ public class SmsDatabase extends MessagingDatabase {
} else if (message.isEndSession()) { } else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT; type |= Types.END_SESSION_BIT;
// type |= Types.ENCRYPTION_REMOTE_BIT;
} }
if (message.isPush()) type |= Types.PUSH_MESSAGE_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) || boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
message.isSecureMessage() || message.isKeyExchange(); message.isSecureMessage() || message.isPreKeyBundle();
long threadId; long threadId;

View File

@ -143,7 +143,7 @@ public class TextSecureDirectory {
try { try {
for (ContactTokenDetails token : activeTokens) { 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(); ContentValues values = new ContentValues();
values.put(NUMBER, token.getNumber()); values.put(NUMBER, token.getNumber());
values.put(REGISTERED, 1); values.put(REGISTERED, 1);

View File

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

View File

@ -23,9 +23,8 @@ import android.text.SpannableString;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsSmsColumns; import org.thoughtcrime.securesms.database.MmsSmsColumns;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; 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.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
@ -91,8 +90,6 @@ public class SmsMessageRecord extends MessageRecord {
return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message)); return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message));
} else if (SmsDatabase.Types.isEndSessionType(type)) { } else if (SmsDatabase.Types.isEndSessionType(type)) {
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_ended)); 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 { } else {
return super.getDisplayBody(); return super.getDisplayBody();
} }

View File

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

View File

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

View File

@ -5,8 +5,6 @@ import android.telephony.TelephonyManager;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; 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.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; 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.MmsSendResult;
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection; import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
@ -27,7 +24,6 @@ import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TelephonyUtil; import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import org.whispersystems.libaxolotl.NoSessionException;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -69,10 +65,6 @@ public class MmsSendJob extends SendJob {
try { try {
MmsSendResult result = deliver(masterSecret, message); MmsSendResult result = deliver(masterSecret, message);
if (result.isUpgradedSecure()) {
database.markAsSecure(messageId);
}
database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus()); database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus());
} catch (UndeliverableMessageException e) { } catch (UndeliverableMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
@ -147,13 +139,11 @@ public class MmsSendJob extends SendJob {
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
{ {
String number = TelephonyUtil.getManager(context).getLine1Number(); String number = TelephonyUtil.getManager(context).getLine1Number();
boolean upgradedSecure = false;
prepareMessageMedia(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true); prepareMessageMedia(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true);
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) { if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
message = getEncryptedMessage(masterSecret, message); throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
upgradedSecure = true;
} }
if (number != null && number.trim().length() != 0) { if (number != null && number.trim().length() != 0) {
@ -177,26 +167,13 @@ public class MmsSendJob extends SendJob {
} else if (isInconsistentResponse(message, conf)) { } else if (isInconsistentResponse(message, conf)) {
throw new UndeliverableMessageException("Mismatched response!"); throw new UndeliverableMessageException("Mismatched response!");
} else { } else {
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus(), upgradedSecure, false); return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
} }
} catch (ApnUnavailableException aue) { } catch (ApnUnavailableException aue) {
throw new IOException("no APN was retrievable"); 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) { private boolean isInconsistentResponse(SendReq message, SendConf response) {
Log.w(TAG, "Comparing: " + Hex.toString(message.getTransactionId())); Log.w(TAG, "Comparing: " + Hex.toString(message.getTransactionId()));
Log.w(TAG, "With: " + Hex.toString(response.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.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; 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.Base64;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyException;

View File

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

View File

@ -38,54 +38,23 @@ public abstract class PushSendJob extends SendJob {
super(context, parameters); 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(); JobParameters.Builder builder = JobParameters.newBuilder();
builder.withPersistence(); builder.withPersistence();
builder.withGroupId(destination); builder.withGroupId(destination);
builder.withRequirement(new MasterSecretRequirement(context)); builder.withRequirement(new MasterSecretRequirement(context));
if (!isSmsFallbackSupported(context, destination, media)) {
builder.withRequirement(new NetworkRequirement(context)); builder.withRequirement(new NetworkRequirement(context));
builder.withRetryCount(5); builder.withRetryCount(5);
}
return builder.create(); 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 { protected TextSecureAddress getPushAddress(String number) throws InvalidNumberException {
String e164number = Util.canonicalizeNumber(context, number); String e164number = Util.canonicalizeNumber(context, number);
String relay = TextSecureDirectory.getInstance(context).getRelay(e164number); String relay = TextSecureDirectory.getInstance(context).getRelay(e164number);
return new TextSecureAddress(e164number, Optional.fromNullable(relay)); 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) { protected List<TextSecureAttachment> getAttachments(final MasterSecret masterSecret, final SendReq message) {
List<TextSecureAttachment> attachments = new LinkedList<>(); List<TextSecureAttachment> attachments = new LinkedList<>();

View File

@ -5,7 +5,6 @@ import android.util.Log;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; 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.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
import org.whispersystems.textsecure.api.TextSecureMessageSender; import org.whispersystems.textsecure.api.TextSecureMessageSender;
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException; import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
import org.whispersystems.textsecure.api.messages.TextSecureMessage; import org.whispersystems.textsecure.api.messages.TextSecureMessage;
@ -41,7 +38,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
private final long messageId; private final long messageId;
public PushTextSendJob(Context context, long messageId, String destination) { public PushTextSendJob(Context context, long messageId, String destination) {
super(context, constructParameters(context, destination, false)); super(context, constructParameters(context, destination));
this.messageId = messageId; this.messageId = messageId;
} }
@ -56,24 +53,20 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException { public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context); EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
SmsMessageRecord record = database.getMessage(masterSecret, messageId); SmsMessageRecord record = database.getMessage(masterSecret, messageId);
String destination = record.getIndividualRecipient().getNumber();
try { try {
Log.w(TAG, "Sending message: " + messageId); Log.w(TAG, "Sending message: " + messageId);
if (deliver(masterSecret, record, destination)) { deliver(masterSecret, record);
database.markAsPush(messageId); database.markAsPush(messageId);
database.markAsSecure(messageId); database.markAsSecure(messageId);
database.markAsSent(messageId); database.markAsSent(messageId);
}
} catch (InsecureFallbackApprovalException e) { } catch (InsecureFallbackApprovalException e) {
Log.w(TAG, e); Log.w(TAG, e);
database.markAsPendingInsecureSmsFallback(record.getId()); database.markAsPendingInsecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId()); MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (SecureFallbackApprovalException e) { ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context));
Log.w(TAG, e);
database.markAsPendingSecureSmsFallback(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
} catch (UntrustedIdentityException e) { } catch (UntrustedIdentityException e) {
Log.w(TAG, e); Log.w(TAG, e);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, e.getE164Number(), false); Recipients recipients = RecipientFactory.getRecipientsFromString(context, e.getE164Number(), false);
@ -102,55 +95,26 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId); MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
} }
private boolean deliver(MasterSecret masterSecret, SmsMessageRecord message, String destination) private void deliver(MasterSecret masterSecret, SmsMessageRecord message)
throws UntrustedIdentityException, SecureFallbackApprovalException, throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException
InsecureFallbackApprovalException, RetryLaterException
{ {
boolean isSmsFallbackSupported = isSmsFallbackSupported(context, destination, false);
try { try {
TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber()); TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber());
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret); 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) { } catch (InvalidNumberException | UnregisteredUserException e) {
Log.w(TAG, e); Log.w(TAG, e);
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination); throw new InsecureFallbackApprovalException(e);
else DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination); throw new RetryLaterException(e);
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");
} }
} }
} }

View File

@ -40,6 +40,7 @@ public abstract class SendJob extends MasterSecretJob {
protected abstract void onSend(MasterSecret masterSecret) throws Exception; 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, protected void prepareMessageMedia(MasterSecret masterSecret, SendReq message,
MediaConstraints constraints, boolean toMemory) MediaConstraints constraints, boolean toMemory)
throws IOException, UndeliverableMessageException 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.AsymmetricMasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil; 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.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; 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.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.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.InvalidMessageException; 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.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.messages.TextSecureGroup; import org.whispersystems.textsecure.api.messages.TextSecureGroup;
@ -54,11 +38,7 @@ public class SmsDecryptJob extends MasterSecretJob {
} }
@Override @Override
public void onAdded() { public void onAdded() {}
if (KeyCachingService.getMasterSecret(context) == null) {
MessageNotifier.updateNotification(context, null, -2);
}
}
@Override @Override
public void onRun(MasterSecret masterSecret) throws NoSuchMessageException { public void onRun(MasterSecret masterSecret) throws NoSuchMessageException {
@ -68,27 +48,17 @@ public class SmsDecryptJob extends MasterSecretJob {
SmsMessageRecord record = database.getMessage(masterSecret, messageId); SmsMessageRecord record = database.getMessage(masterSecret, messageId);
IncomingTextMessage message = createIncomingTextMessage(masterSecret, record); IncomingTextMessage message = createIncomingTextMessage(masterSecret, record);
long messageId = record.getId(); long messageId = record.getId();
long threadId = record.getThreadId();
if (message.isSecureMessage()) handleSecureMessage(masterSecret, messageId, threadId, message); if (message.isSecureMessage()) {
else if (message.isPreKeyBundle()) handlePreKeyWhisperMessage(masterSecret, messageId, threadId, (IncomingPreKeyBundleMessage) message); database.markAsLegacyVersion(messageId);
else if (message.isKeyExchange()) handleKeyExchangeMessage(masterSecret, messageId, threadId, (IncomingKeyExchangeMessage) message); } else {
else if (message.isEndSession()) handleSecureMessage(masterSecret, messageId, threadId, message); database.updateMessageBody(masterSecret, messageId, message.getMessageBody());
else database.updateMessageBody(masterSecret, messageId, message.getMessageBody()); }
MessageNotifier.updateNotification(context, masterSecret); MessageNotifier.updateNotification(context, masterSecret);
} catch (LegacyMessageException e) {
Log.w(TAG, e);
database.markAsLegacyVersion(messageId);
} catch (InvalidMessageException e) { } catch (InvalidMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
database.markAsDecryptFailed(messageId); 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 // 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) private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
throws InvalidMessageException throws InvalidMessageException
{ {
@ -189,28 +88,17 @@ public class SmsDecryptJob extends MasterSecretJob {
private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record) private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record)
throws InvalidMessageException throws InvalidMessageException
{ {
String plaintextBody = record.getBody().getBody();
if (record.isAsymmetricEncryption()) {
plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
}
IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(), IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(),
record.getRecipientDeviceId(), record.getRecipientDeviceId(),
record.getDateSent(), record.getDateSent(),
plaintextBody, record.getBody().getBody(),
Optional.<TextSecureGroup>absent()); Optional.<TextSecureGroup>absent());
if (record.isEndSession()) { if (record.isAsymmetricEncryption()) {
return new IncomingEndSessionMessage(message); String plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
} else if (record.isBundleKeyExchange()) { return new IncomingTextMessage(message, plaintextBody);
return new IncomingPreKeyBundleMessage(message, message.getMessageBody()); } else {
} else if (record.isKeyExchange()) {
return new IncomingKeyExchangeMessage(message, message.getMessageBody());
} else if (record.isSecure()) {
return new IncomingEncryptedMessage(message, message.getMessageBody()); 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.notifications.MessageNotifier;
import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.util.guava.Optional; 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 final String TAG = SmsReceiveJob.class.getSimpleName();
private static MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
private final Object[] pdus; private final Object[] pdus;
public SmsReceiveJob(Context context, Object[] pdus) { public SmsReceiveJob(Context context, Object[] pdus) {
@ -67,18 +65,15 @@ public class SmsReceiveJob extends ContextJob {
if (message.isSecureMessage()) { if (message.isSecureMessage()) {
messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message); messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message);
database.markAsLegacyVersion(messageAndThreadId.first);
} else if (masterSecret == null) { } else if (masterSecret == null) {
messageAndThreadId = database.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message); 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) ApplicationContext.getInstance(context)
.getJobManager() .getJobManager()
.add(new SmsDecryptJob(context, messageAndThreadId.first)); .add(new SmsDecryptJob(context, messageAndThreadId.first));
} else { } else {
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second); messageAndThreadId = database.insertMessageInbox(masterSecret, message);
} }
return messageAndThreadId; return messageAndThreadId;
@ -102,7 +97,7 @@ public class SmsReceiveJob extends ContextJob {
WirePrefix.isPreKeyBundle(message.getMessageBody()) || WirePrefix.isPreKeyBundle(message.getMessageBody()) ||
WirePrefix.isEndSession(message.getMessageBody())) WirePrefix.isEndSession(message.getMessageBody()))
{ {
return Optional.fromNullable(multipartMessageHandler.processPotentialMultipartMessage(message)); return Optional.<IncomingTextMessage>of(new IncomingEncryptedMessage(message, message.getMessageBody()));
} else { } else {
return Optional.of(message); return Optional.of(message);
} }

View File

@ -8,8 +8,6 @@ import android.telephony.SmsManager;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.MasterSecret; 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.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase; import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException; 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.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.SmsDeliveryListener; 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.InsecureFallbackApprovalException;
import org.thoughtcrime.securesms.transport.UndeliverableMessageException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
import org.thoughtcrime.securesms.util.NumberUtil; import org.thoughtcrime.securesms.util.NumberUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.libaxolotl.NoSessionException;
import java.util.ArrayList; import java.util.ArrayList;
@ -55,15 +50,11 @@ public class SmsSendJob extends SendJob {
try { try {
Log.w(TAG, "Sending message: " + messageId); Log.w(TAG, "Sending message: " + messageId);
deliver(masterSecret, record); deliver(record);
} catch (UndeliverableMessageException ude) { } catch (UndeliverableMessageException ude) {
Log.w(TAG, ude); Log.w(TAG, ude);
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId()); DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId()); 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); MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
} }
private void deliver(MasterSecret masterSecret, SmsMessageRecord record) private void deliver(SmsMessageRecord message)
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)
throws UndeliverableMessageException 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<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false); ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages); 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, private ArrayList<PendingIntent> constructSentIntents(long messageId, long type,
ArrayList<String> messages, boolean secure) ArrayList<String> messages, boolean secure)
{ {

View File

@ -4,22 +4,10 @@ public class MmsSendResult {
private final byte[] messageId; private final byte[] messageId;
private final int responseStatus; 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.messageId = messageId;
this.responseStatus = responseStatus; this.responseStatus = responseStatus;
this.upgradedSecure = upgradedSecure;
this.push = push;
}
public boolean isPush() {
return push;
}
public boolean isUpgradedSecure() {
return upgradedSecure;
} }
public int getResponseStatus() { 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.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v4.preference.PreferenceFragment; import android.support.v4.preference.PreferenceFragment;
import android.text.TextUtils;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.OutgoingSmsPreference;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import java.util.LinkedList;
import java.util.List;
public class SmsMmsPreferenceFragment extends PreferenceFragment { public class SmsMmsPreferenceFragment extends PreferenceFragment {
private static final String KITKAT_DEFAULT_PREF = "pref_set_default"; 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"; private static final String MMS_PREF = "pref_mms_preferences";
@Override @Override
@ -33,12 +27,8 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
super.onCreate(paramBundle); super.onCreate(paramBundle);
addPreferencesFromResource(R.xml.preferences_sms_mms); addPreferencesFromResource(R.xml.preferences_sms_mms);
this.findPreference(OUTGOING_SMS_PREF)
.setOnPreferenceChangeListener(new OutgoingSmsPreferenceListener());
this.findPreference(MMS_PREF) this.findPreference(MMS_PREF)
.setOnPreferenceClickListener(new ApnPreferencesClickListener()); .setOnPreferenceClickListener(new ApnPreferencesClickListener());
initializeOutgoingSmsSummary((OutgoingSmsPreference) findPreference(OUTGOING_SMS_PREF));
} }
@Override @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 { private class ApnPreferencesClickListener implements Preference.OnPreferenceClickListener {
@Override @Override
@ -135,7 +81,7 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
} }
public static CharSequence getSummary(Context context) { public static CharSequence getSummary(Context context) {
return getIncomingSmsSummary(context) + ", " + getOutgoingSmsSummary(context); return getIncomingSmsSummary(context);
} }
private static CharSequence getIncomingSmsSummary(Context context) { private static CharSequence getIncomingSmsSummary(Context context) {
@ -160,21 +106,4 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
} }
return context.getString(incomingSmsResId, context.getString(incomingSmsSummary)); 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; package org.thoughtcrime.securesms.sms;
public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage { public class IncomingPreKeyBundleMessage extends IncomingTextMessage {
public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) { public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) {
super(base, newBody); super(base, newBody);
@ -15,4 +15,5 @@ public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
public boolean isPreKeyBundle() { public boolean isPreKeyBundle() {
return true; return true;
} }
} }

View File

@ -164,10 +164,6 @@ public class IncomingTextMessage implements Parcelable {
return replyPathPresent; return replyPathPresent;
} }
public boolean isKeyExchange() {
return false;
}
public boolean isSecureMessage() { public boolean isSecureMessage() {
return false; 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) { public InsecureFallbackApprovalException(String detailMessage) {
super(detailMessage); super(detailMessage);
} }
public InsecureFallbackApprovalException(Exception e) {
public InsecureFallbackApprovalException(Throwable e) {
super(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; package org.thoughtcrime.securesms.util;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; 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 ALL_SMS_PREF = "pref_all_sms";
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval"; 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 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"; 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_SENDS_PREF = "pref_enter_sends";
private static final String ENTER_PRESENT_PREF = "pref_enter_key"; 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 IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
private static final String LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id"; 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 SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered";
private static final String WIFI_SMS_PREF = "pref_wifi_sms"; 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 GCM_REGISTRATION_ID_VERSION_PREF = "pref_gcm_registration_id_version";
private static final String WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered"; 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 final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
public static boolean isWebsocketRegistered(Context context) { public static boolean isWebsocketRegistered(Context context) {
@ -119,36 +114,12 @@ public class TextSecurePreferences {
} }
} }
public static boolean isFallbackSmsAllowed(Context context) { public static boolean isSmsEnabled(Context context) {
return getBooleanPreference(context, FALLBACK_SMS_ALLOWED_PREF, true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return Util.isDefaultSmsProvider(context);
} else {
return isInterceptAllSmsEnabled(context);
} }
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 int getLocalRegistrationId(Context context) { public static int getLocalRegistrationId(Context context) {
@ -304,10 +275,6 @@ public class TextSecurePreferences {
setStringPreference(context, IDENTITY_PREF, identityUri); 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) { public static boolean isScreenSecurityEnabled(Context context) {
return getBooleanPreference(context, SCREEN_SECURITY_PREF, true); return getBooleanPreference(context, SCREEN_SECURITY_PREF, true);
} }
@ -431,14 +398,6 @@ public class TextSecurePreferences {
return Integer.parseInt(getStringPreference(context, THREAD_TRIM_LENGTH, "500")); 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) { public static void setBooleanPreference(Context context, String key, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply(); PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
} }