mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 19:03:39 +00:00
Remove encrypted SMS transport, simplify transport options.
Closes #2647 // FREEBIE
This commit is contained in:
parent
2011391e65
commit
a4e18c515c
@ -78,7 +78,7 @@
|
||||
android:contentDescription="@string/conversation_activity__emoji_toggle_description"
|
||||
android:padding="10dp"/>
|
||||
|
||||
<EditText
|
||||
<org.thoughtcrime.securesms.components.ComposeText
|
||||
android:id="@+id/embedded_text_editor"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@ -89,7 +89,7 @@
|
||||
android:paddingRight="0dp"
|
||||
android:imeOptions="actionSend|flagNoEnterAction|flagNoExtractUi"
|
||||
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
|
||||
android:maxLength="1000"
|
||||
android:maxLength="2000"
|
||||
android:maxLines="4"
|
||||
android:nextFocusForward="@+id/send_button"
|
||||
android:nextFocusRight="@+id/send_button"
|
||||
|
@ -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>
|
@ -77,7 +77,8 @@
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button android:id="@+id/skipButton"
|
||||
android:text="@string/registration_activity__skip"
|
||||
android:visibility="gone"
|
||||
android:text="@android:string/cancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="5dip"
|
||||
|
@ -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>
|
@ -143,48 +143,6 @@
|
||||
<item>custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_entries_text">
|
||||
<item>@string/ConversationActivity_transport_insecure_sms</item>
|
||||
<item>@string/ConversationActivity_transport_secure_sms</item>
|
||||
<item>@string/ConversationActivity_transport_textsecure</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_entries_media">
|
||||
<item>@string/ConversationActivity_transport_insecure_mms</item>
|
||||
<item>@string/ConversationActivity_transport_secure_mms</item>
|
||||
<item>@string/ConversationActivity_transport_textsecure</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_entries_compose_text">
|
||||
<item>@string/conversation_activity__type_message_sms_insecure</item>
|
||||
<item>@string/conversation_activity__type_message_sms_secure</item>
|
||||
<item>@string/conversation_activity__type_message_push</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_entries_compose_media">
|
||||
<item>@string/conversation_activity__type_message_mms_insecure</item>
|
||||
<item>@string/conversation_activity__type_message_mms_secure</item>
|
||||
<item>@string/conversation_activity__type_message_push</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_values">
|
||||
<item>insecure_sms</item>
|
||||
<item>secure_sms</item>
|
||||
<item>textsecure</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_icons_light">
|
||||
<item>@drawable/ic_send_sms_insecure</item>
|
||||
<item>@drawable/ic_send_sms_secure</item>
|
||||
<item>@drawable/ic_send_push</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="transport_selection_icons_dark">
|
||||
<item>@drawable/ic_send_sms_insecure_dark</item>
|
||||
<item>@drawable/ic_send_sms_secure</item>
|
||||
<item>@drawable/ic_send_push</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_repeat_alerts_entries">
|
||||
<item>@string/preferences__never</item>
|
||||
<item>@string/preferences__one_time</item>
|
||||
|
@ -31,7 +31,8 @@
|
||||
<attr name="conversation_send_button_sms_secure" format="reference"/>
|
||||
<attr name="conversation_send_button_sms_insecure" format="reference"/>
|
||||
<attr name="conversation_delivery_delivered" format="reference"/>
|
||||
<attr name="conversation_transport_indicators" format="reference"/>
|
||||
<attr name="conversation_transport_sms_indicator" format="reference"/>
|
||||
<attr name="conversation_transport_push_indicator" format="reference"/>
|
||||
<attr name="conversation_transport_popup_background" format="reference"/>
|
||||
<attr name="conversation_emoji_toggle" format="reference"/>
|
||||
<attr name="conversation_keyboard_toggle" format="reference"/>
|
||||
|
@ -78,15 +78,11 @@
|
||||
<string name="ConversationItem_error_received_stale_key_exchange_message">Error, received stale key exchange message.</string>
|
||||
<string name="ConversationItem_received_key_exchange_message_click_to_process">Received key exchange message, click to process.</string>
|
||||
<string name="ConversationItem_group_action_left">%1$s has left the group.</string>
|
||||
<string name="ConversationItem_click_to_approve_sms">Tap for SMS fallback</string>
|
||||
<string name="ConversationItem_click_to_approve_mms">Tap for MMS fallback</string>
|
||||
<string name="ConversationItem_click_for_details">Tap for details</string>
|
||||
<string name="ConversationItem_click_to_approve_unencrypted">Tap for unsecured fallback</string>
|
||||
<string name="ConversationItem_click_to_approve_sms_dialog_title">Fallback to SMS?</string>
|
||||
<string name="ConversationItem_click_to_approve_mms_dialog_title">Fallback to MMS?</string>
|
||||
<string name="ConversationItem_click_to_approve_unencrypted_sms_dialog_title">Fallback to unencrypted SMS?</string>
|
||||
<string name="ConversationItem_click_to_approve_unencrypted_mms_dialog_title">Fallback to unencrypted MMS?</string>
|
||||
<string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because a secure session could not be established.\n\nSend unsecured message?</string>
|
||||
<string name="ConversationItem_click_to_approve_unencrypted_dialog_message">This message will <b>not</b> be encrypted because the recipient is no longer a TextSecure user.\n\nSend unsecured message?</string>
|
||||
<string name="ConversationItem_unable_to_open_media">Can\'t find an app able to open this media.</string>
|
||||
|
||||
<!-- ConversationActivity -->
|
||||
@ -117,8 +113,6 @@
|
||||
<string name="ConversationActivity_are_you_sure_you_want_to_leave_this_group">Are you sure you want to leave this group?</string>
|
||||
<string name="ConversationActivity_transport_insecure_sms">Insecure SMS</string>
|
||||
<string name="ConversationActivity_transport_insecure_mms">Insecure MMS</string>
|
||||
<string name="ConversationActivity_transport_secure_sms">Secure SMS</string>
|
||||
<string name="ConversationActivity_transport_secure_mms">Secure MMS</string>
|
||||
<string name="ConversationActivity_transport_textsecure">TextSecure</string>
|
||||
<string name="ConversationActivity_get_with_it">Get with it: %s</string>
|
||||
<string name="ConversationActivity_install_textsecure">Install TextSecure: %s</string>
|
||||
@ -294,10 +288,6 @@
|
||||
<string name="ReceiveKeyActivity_you_may_wish_to_verify_this_contact">You may wish to verify
|
||||
this contact.
|
||||
</string>
|
||||
<string name="ReceiveKeyActivity_the_signature_on_this_key_exchange_is_trusted_but">The
|
||||
signature on this key exchange is trusted, but you have the \'automatically complete key
|
||||
exchanges\' setting disabled.
|
||||
</string>
|
||||
<string name="ReceiveKeyActivity_processing">Processing</string>
|
||||
<string name="ReceiveKeyActivity_processing_key_exchange">Processing key exchange…</string>
|
||||
|
||||
@ -453,10 +443,6 @@
|
||||
<!-- QuickResponseService -->
|
||||
<string name="QuickResponseService_sorry_quick_response_is_not_yet_supported_by_textsecure">Sorry, Quick Response is not yet supported by TextSecure!</string>
|
||||
|
||||
<!-- auto_initiate_activity -->
|
||||
<string name="auto_initiate_activity__you_have_received_a_message_from_someone_who_supports_textsecure_encrypted_sessions_would_you_like_to_initiate_a_secure_session">You have received a message from someone who supports TextSecure encrypted sessions. Would you like to initiate a secure session?</string>
|
||||
<string name="auto_initiate_activity__initiate_exchange">Initiate exchange</string>
|
||||
|
||||
<!-- change_passphrase_activity -->
|
||||
<string name="change_passphrase_activity__old_passphrase">OLD PASSPHRASE:</string>
|
||||
<string name="change_passphrase_activity__new_passphrase">NEW PASSPHRASE:</string>
|
||||
@ -581,7 +567,7 @@
|
||||
|
||||
<!-- registration_activity -->
|
||||
<string name="registration_activity__textsecure_can_use_instant_messages_to_avoid_sms_charges_when_communicating_with_other_textsecure_users">
|
||||
Registering is free and increases privacy when messaging other TextSecure users. Please verify your phone number.
|
||||
Verify your phone number to connect with TextSecure.
|
||||
</string>
|
||||
<string name="registration_activity__your_country">YOUR COUNTRY</string>
|
||||
<string name="registration_activity__your_country_code_and_phone_number">YOUR COUNTRY CODE AND
|
||||
@ -590,7 +576,6 @@
|
||||
<string name="registration_activity__phone_number">PHONE NUMBER</string>
|
||||
<string name="registration_activity__register">Register</string>
|
||||
<string name="registration_activity__registration_will_transmit_some_contact_information_to_the_server_temporariliy">Registration transmits some contact information to the server. It is not stored.</string>
|
||||
<string name="registration_activity__skip">Skip</string>
|
||||
|
||||
<!-- registration_problems -->
|
||||
<string name="registration_problems__some_possible_problems_include">Some possible problems
|
||||
@ -689,7 +674,6 @@
|
||||
<string name="AndroidManifest__public_identity_key">Public identity key</string>
|
||||
<string name="AndroidManifest__change_passphrase">Change passphrase</string>
|
||||
<string name="AndroidManifest__verify_identity">Verify identity</string>
|
||||
<string name="AndroidManifest__complete_key_exchange">Complete key exchange</string>
|
||||
<string name="AndroidManifest__log_submit">Submit debug logs</string>
|
||||
<string name="AndroidManifest__media_preview">Media Preview</string>
|
||||
<string name="AndroidManifest__media_overview">All images</string>
|
||||
@ -730,13 +714,11 @@
|
||||
<string name="preferences__choose_your_contact_entry_from_the_contacts_list">Choose your contact entry from the contacts list.</string>
|
||||
<string name="preferences__change_passphrase">Change passphrase</string>
|
||||
<string name="preferences__change_my_passphrase">Change my passphrase</string>
|
||||
<string name="preferences__complete_key_exchanges">Complete key exchanges</string>
|
||||
<string name="preferences__enable_passphrase">Enable passphrase</string>
|
||||
<string name="preferences__passphrase_summary">Passphrase %s</string>
|
||||
<string name="preferences__enable_local_encryption_of_messages_and_keys">Enable local encryption of messages and keys</string>
|
||||
<string name="preferences__screen_security">Screen security</string>
|
||||
<string name="preferences__screen_security_summary">Screen security %s</string>
|
||||
<string name="preferences__automatically_complete_key_exchanges_for_new_sessions_or_for_existing_sessions_with_the_same_identity_key">Automatically complete key exchanges for new sessions or for existing sessions with the same identity key</string>
|
||||
<string name="preferences__disable_screen_security_to_allow_screen_shots">Block screenshots in the recents list and inside the app</string>
|
||||
<string name="preferences__forget_passphrase_from_memory_after_some_interval">Forget passphrase from memory after some interval</string>
|
||||
<string name="preferences__timeout_passphrase">Timeout passphrase</string>
|
||||
@ -840,13 +822,6 @@
|
||||
<!-- refreshing push directory from menu -->
|
||||
<string name="push_directory__menu_refresh">Refresh contact list</string>
|
||||
|
||||
<!-- conversation_button_context -->
|
||||
<string name="conversation_button_context__send_textsecure_message">Send TextSecure message</string>
|
||||
<string name="conversation_button_context__send_secure_sms">Send secure SMS</string>
|
||||
<string name="conversation_button_context__send_insecure_sms">Send unsecured SMS</string>
|
||||
<string name="conversation_button_context__send_secure_mms">Send secure MMS</string>
|
||||
<string name="conversation_button_context__send_insecure_mms">Send unsecured MMS</string>
|
||||
|
||||
<!-- conversation_callable -->
|
||||
<string name="conversation_callable__menu_call">Call</string>
|
||||
|
||||
|
@ -48,7 +48,8 @@
|
||||
<item name="conversation_send_button_sms_secure">@drawable/ic_send_sms_secure</item>
|
||||
<item name="conversation_send_button_sms_insecure">@drawable/ic_send_sms_insecure</item>
|
||||
<item name="conversation_delivery_delivered">@drawable/ic_delivery_delivered_dark</item>
|
||||
<item name="conversation_transport_indicators">@array/transport_selection_icons_light</item>
|
||||
<item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure_dark</item>
|
||||
<item name="conversation_transport_push_indicator">@drawable/ic_send_push</item>
|
||||
<item name="conversation_transport_popup_background">@color/white</item>
|
||||
<item name="conversation_emoji_toggle">@drawable/ic_emoji_dark</item>
|
||||
<item name="conversation_keyboard_toggle">@drawable/ic_ime_dark</item>
|
||||
@ -173,7 +174,8 @@
|
||||
<item name="conversation_send_button_sms_secure">@drawable/ic_send_sms_secure</item>
|
||||
<item name="conversation_send_button_sms_insecure">@drawable/ic_send_sms_insecure_dark</item>
|
||||
<item name="conversation_delivery_delivered">@drawable/ic_delivery_delivered_dark</item>
|
||||
<item name="conversation_transport_indicators">@array/transport_selection_icons_dark</item>
|
||||
<item name="conversation_transport_sms_indicator">@drawable/ic_send_sms_insecure_dark</item>
|
||||
<item name="conversation_transport_push_indicator">@drawable/ic_send_push</item>
|
||||
<item name="conversation_transport_popup_background">@color/black</item>
|
||||
<item name="conversation_emoji_toggle">@drawable/ic_emoji_light</item>
|
||||
<item name="conversation_keyboard_toggle">@drawable/ic_ime_light</item>
|
||||
|
@ -15,10 +15,6 @@
|
||||
android:title="@string/preferences__make_default_sms_app"
|
||||
android:summary="@string/preferences__make_textsecure_the_default_sms_mms_app" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.OutgoingSmsPreference
|
||||
android:key="pref_outgoing_sms"
|
||||
android:title="@string/preferences__sms_fallback" />
|
||||
|
||||
<CheckBoxPreference android:defaultValue="false"
|
||||
android:key="pref_delivery_report_sms"
|
||||
android:summary="@string/preferences__request_a_delivery_report_for_each_sms_message_you_send"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -45,23 +45,21 @@ import android.view.View.OnFocusChangeListener;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
import org.thoughtcrime.securesms.components.EmojiDrawer;
|
||||
import org.thoughtcrime.securesms.components.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeInitiator;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
|
||||
@ -80,7 +78,6 @@ import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.Tag;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
@ -108,6 +105,7 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.List;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
|
||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
@ -142,7 +140,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int GROUP_EDIT = 5;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private EditText composeText;
|
||||
private ComposeText composeText;
|
||||
private SendButton sendButton;
|
||||
private TextView charactersLeft;
|
||||
|
||||
@ -158,10 +156,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private int distributionType;
|
||||
private boolean isEncryptedConversation;
|
||||
private boolean isMmsEnabled = true;
|
||||
private boolean isCharactersLeftViewEnabled;
|
||||
|
||||
private DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
private DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
@ -264,13 +261,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
MenuInflater inflater = this.getMenuInflater();
|
||||
menu.clear();
|
||||
|
||||
boolean pushRegistered = TextSecurePreferences.isPushRegistered(this);
|
||||
|
||||
if (isSingleConversation() && isEncryptedConversation) {
|
||||
inflater.inflate(R.menu.conversation_secure_identity, menu);
|
||||
inflater.inflate(R.menu.conversation_secure_sms, menu.findItem(R.id.menu_security).getSubMenu());
|
||||
} else if (isSingleConversation()) {
|
||||
if (!pushRegistered) inflater.inflate(R.menu.conversation_insecure_no_push, menu);
|
||||
inflater.inflate(R.menu.conversation_insecure, menu);
|
||||
}
|
||||
|
||||
@ -310,7 +304,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case R.id.menu_add_attachment: handleAddAttachment(); return true;
|
||||
case R.id.menu_view_media: handleViewMedia(); return true;
|
||||
case R.id.menu_add_to_contacts: handleAddToContacts(); return true;
|
||||
case R.id.menu_start_secure_session: handleStartSecureSession(); return true;
|
||||
case R.id.menu_abort_session: handleAbortSecureSession(); return true;
|
||||
case R.id.menu_verify_identity: handleVerifyIdentity(); return true;
|
||||
case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true;
|
||||
@ -362,33 +355,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
startActivity(verifyIdentityIntent);
|
||||
}
|
||||
|
||||
private void handleStartSecureSession() {
|
||||
if (getRecipients() == null) {
|
||||
Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient),
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
final Recipient recipient = getRecipients().getPrimaryRecipient();
|
||||
String recipientName = (recipient.getName() == null ? recipient.getNumber() : recipient.getName());
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.ConversationActivity_initiate_secure_session_question);
|
||||
builder.setIcon(Dialogs.resolveIcon(this, R.attr.dialog_info_icon));
|
||||
builder.setCancelable(true);
|
||||
builder.setMessage(String.format(getString(R.string.ConversationActivity_initiate_secure_session_with_s_question),
|
||||
recipientName));
|
||||
builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
KeyExchangeInitiator.initiate(ConversationActivity.this, masterSecret,
|
||||
recipient, true);
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.no, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void handleAbortSecureSession() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.ConversationActivity_abort_secure_session_confirmation);
|
||||
@ -679,31 +645,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeSecurity() {
|
||||
Recipient primaryRecipient = getRecipients() == null ? null : getRecipients().getPrimaryRecipient();
|
||||
boolean isPushDestination = DirectoryHelper.isPushDestination(this, getRecipients());
|
||||
boolean isSecureSmsAllowed = (!isPushDestination || DirectoryHelper.isSmsFallbackAllowed(this, getRecipients()));
|
||||
boolean isSecureSmsDestination = isSecureSmsAllowed &&
|
||||
isSingleConversation() &&
|
||||
SessionUtil.hasSession(this, masterSecret, primaryRecipient);
|
||||
boolean isMediaMessage = !recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent();
|
||||
this.isEncryptedConversation = DirectoryHelper.isPushDestination(this, getRecipients());
|
||||
|
||||
if (isPushDestination || isSecureSmsDestination) {
|
||||
this.isEncryptedConversation = true;
|
||||
} else {
|
||||
this.isEncryptedConversation = false;
|
||||
}
|
||||
sendButton.resetAvailableTransports(isMediaMessage);
|
||||
|
||||
sendButton.initializeAvailableTransports(!recipients.isSingleRecipient() || attachmentManager.isAttachmentPresent());
|
||||
if (!isPushDestination ) sendButton.disableTransport("textsecure");
|
||||
if (!isSecureSmsDestination ) sendButton.disableTransport("secure_sms");
|
||||
if (recipients.isGroupRecipient()) sendButton.disableTransport("insecure_sms");
|
||||
if (!isEncryptedConversation) sendButton.disableTransport(Type.TEXTSECURE);
|
||||
if (recipients.isGroupRecipient()) sendButton.disableTransport(Type.SMS);
|
||||
|
||||
if (isPushDestination) {
|
||||
sendButton.setDefaultTransport("textsecure");
|
||||
} else if (isSecureSmsDestination) {
|
||||
sendButton.setDefaultTransport("secure_sms");
|
||||
} else {
|
||||
sendButton.setDefaultTransport("insecure_sms");
|
||||
}
|
||||
if (isEncryptedConversation) sendButton.setDefaultTransport(Type.TEXTSECURE);
|
||||
else sendButton.setDefaultTransport(Type.SMS);
|
||||
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
@ -731,9 +682,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
sendButton = (SendButton) findViewById(R.id.send_button);
|
||||
composeText = (EditText) findViewById(R.id.embedded_text_editor);
|
||||
charactersLeft = (TextView) findViewById(R.id.space_left);
|
||||
sendButton = (SendButton) findViewById(R.id.send_button);
|
||||
composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
|
||||
charactersLeft = (TextView) findViewById(R.id.space_left);
|
||||
emojiDrawer = (EmojiDrawer) findViewById(R.id.emoji_drawer);
|
||||
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
|
||||
|
||||
@ -749,11 +700,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
sendButton.setOnClickListener(sendButtonListener);
|
||||
sendButton.setEnabled(true);
|
||||
sendButton.setComposeTextView(composeText);
|
||||
sendButton.addOnTransportChangedListener(new OnTransportChangedListener() {
|
||||
@Override
|
||||
public void onChange(TransportOption newTransport) {
|
||||
calculateCharactersRemaining();
|
||||
composeText.setHint(newTransport.getComposeHint());
|
||||
}
|
||||
});
|
||||
|
||||
@ -784,10 +735,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
securityUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getLongExtra("thread_id", -1) == -1)
|
||||
return;
|
||||
long eventThreadId = intent.getLongExtra("thread_id", -1);
|
||||
|
||||
if (intent.getLongExtra("thread_id", -1) == threadId) {
|
||||
if (eventThreadId == threadId || eventThreadId == -2) {
|
||||
initializeSecurity();
|
||||
initializeTitleBar();
|
||||
calculateCharactersRemaining();
|
||||
@ -959,23 +909,17 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
int charactersSpent = composeText.getText().toString().length();
|
||||
TransportOption transportOption = sendButton.getSelectedTransport();
|
||||
|
||||
if (transportOption != null) {
|
||||
CharacterState characterState = transportOption.calculateCharacters(charactersSpent);
|
||||
CharacterState characterState = transportOption.calculateCharacters(charactersSpent);
|
||||
|
||||
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
|
||||
charactersLeft.setText(characterState.charactersRemaining + "/" + characterState.maxMessageSize
|
||||
+ " (" + characterState.messagesSpent + ")");
|
||||
charactersLeft.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
charactersLeft.setVisibility(View.GONE);
|
||||
}
|
||||
if (characterState.charactersRemaining <= 15 || characterState.messagesSpent > 1) {
|
||||
charactersLeft.setText(characterState.charactersRemaining + "/" + characterState.maxMessageSize
|
||||
+ " (" + characterState.messagesSpent + ")");
|
||||
charactersLeft.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
charactersLeft.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExistingConversation() {
|
||||
return this.recipients != null && this.threadId != -1;
|
||||
}
|
||||
|
||||
private boolean isSingleConversation() {
|
||||
return getRecipients() != null && getRecipients().isSingleRecipient() && !getRecipients().isGroupRecipient();
|
||||
}
|
||||
@ -1013,9 +957,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
|
||||
throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation));
|
||||
|
||||
if (!isEncryptedConversation && Tag.isTaggable(rawText))
|
||||
rawText = Tag.getTaggedMessage(rawText);
|
||||
|
||||
return rawText;
|
||||
}
|
||||
|
||||
@ -1056,7 +997,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void sendMessage() {
|
||||
try {
|
||||
final Recipients recipients = getRecipients();
|
||||
Recipients recipients = getRecipients();
|
||||
boolean forceSms = sendButton.isManualSelection() && sendButton.getSelectedTransport().isSms();
|
||||
|
||||
Log.w(TAG, "isManual Selection: " + sendButton.isManualSelection());
|
||||
Log.w(TAG, "forceSms: " + forceSms);
|
||||
|
||||
if (recipients == null) {
|
||||
throw new RecipientFormattingException("Badly formatted");
|
||||
@ -1065,9 +1010,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
|
||||
handleManualMmsRequired();
|
||||
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
|
||||
sendMediaMessage(sendButton.getSelectedTransport().isPlaintext(), sendButton.getSelectedTransport().isSms());
|
||||
sendMediaMessage(forceSms);
|
||||
} else {
|
||||
sendTextMessage(sendButton.getSelectedTransport().isPlaintext(), sendButton.getSelectedTransport().isSms());
|
||||
sendTextMessage(forceSms);
|
||||
}
|
||||
} catch (RecipientFormattingException ex) {
|
||||
Toast.makeText(ConversationActivity.this,
|
||||
@ -1081,7 +1026,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMediaMessage(boolean forcePlaintext, final boolean forceSms)
|
||||
private void sendMediaMessage(final boolean forceSms)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
final Context context = getApplicationContext();
|
||||
@ -1093,7 +1038,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
OutgoingMediaMessage outgoingMessage = new OutgoingMediaMessage(this, recipients, slideDeck,
|
||||
getMessage(), distributionType);
|
||||
|
||||
if (isEncryptedConversation && !forcePlaintext) {
|
||||
if (isEncryptedConversation && !forceSms) {
|
||||
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessage);
|
||||
}
|
||||
|
||||
@ -1113,13 +1058,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}.execute(outgoingMessage);
|
||||
}
|
||||
|
||||
private void sendTextMessage(boolean forcePlaintext, final boolean forceSms)
|
||||
private void sendTextMessage(final boolean forceSms)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
final Context context = getApplicationContext();
|
||||
OutgoingTextMessage message;
|
||||
|
||||
if (isEncryptedConversation && !forcePlaintext) {
|
||||
if (isEncryptedConversation && !forceSms) {
|
||||
message = new OutgoingEncryptedMessage(recipients, getMessage());
|
||||
} else {
|
||||
message = new OutgoingTextMessage(recipients, getMessage());
|
||||
@ -1183,16 +1128,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private class AddContactButtonListener implements OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
|
||||
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipients.getPrimaryRecipient().getNumber());
|
||||
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
|
@ -227,7 +227,7 @@ public class ConversationItem extends LinearLayout {
|
||||
if ((messageRecord.isPending() || messageRecord.isFailed()) && pushDestination && !messageRecord.isForcedSms()) {
|
||||
background = SENT_PUSH_PENDING;
|
||||
triangleBackground = SENT_PUSH_PENDING_TRIANGLE;
|
||||
} else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingSmsFallback()) {
|
||||
} else if (messageRecord.isPending() || messageRecord.isFailed() || messageRecord.isPendingInsecureSmsFallback()) {
|
||||
background = SENT_SMS_PENDING;
|
||||
triangleBackground = SENT_SMS_PENDING_TRIANGLE;
|
||||
} else if (messageRecord.isPush()) {
|
||||
@ -291,11 +291,10 @@ public class ConversationItem extends LinearLayout {
|
||||
mmsDownloadButton.setVisibility(View.GONE);
|
||||
mmsDownloadingLabel.setVisibility(View.GONE);
|
||||
|
||||
if (messageRecord.isFailed()) setFailedStatusIcons();
|
||||
else if (messageRecord.isPendingSmsFallback()) setFallbackStatusIcons();
|
||||
else if (messageRecord.isPending()) dateText.setText(" ··· ");
|
||||
else setSentStatusIcons();
|
||||
|
||||
if (messageRecord.isFailed()) setFailedStatusIcons();
|
||||
else if (messageRecord.isPendingInsecureSmsFallback()) setFallbackStatusIcons();
|
||||
else if (messageRecord.isPending()) dateText.setText(" ··· ");
|
||||
else setSentStatusIcons();
|
||||
}
|
||||
|
||||
private void setSentStatusIcons() {
|
||||
@ -316,13 +315,7 @@ public class ConversationItem extends LinearLayout {
|
||||
private void setFallbackStatusIcons() {
|
||||
pendingIndicator.setVisibility(View.VISIBLE);
|
||||
indicatorText.setVisibility(View.VISIBLE);
|
||||
|
||||
if (messageRecord.isPendingSecureSmsFallback()) {
|
||||
if (messageRecord.isMms()) indicatorText.setText(R.string.ConversationItem_click_to_approve_mms);
|
||||
else indicatorText.setText(R.string.ConversationItem_click_to_approve_sms);
|
||||
} else {
|
||||
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
||||
}
|
||||
indicatorText.setText(R.string.ConversationItem_click_to_approve_unencrypted);
|
||||
}
|
||||
|
||||
private void setMinimumWidth() {
|
||||
@ -335,20 +328,9 @@ public class ConversationItem extends LinearLayout {
|
||||
}
|
||||
|
||||
private void setEvents(MessageRecord messageRecord) {
|
||||
setClickable(messageRecord.isFailed()||
|
||||
messageRecord.isPendingSmsFallback() ||
|
||||
(messageRecord.isKeyExchange() &&
|
||||
!messageRecord.isCorruptedKeyExchange() &&
|
||||
!messageRecord.isOutgoing()));
|
||||
|
||||
if (!messageRecord.isOutgoing() &&
|
||||
messageRecord.getRecipients().isSingleRecipient() &&
|
||||
!messageRecord.isSecure())
|
||||
{
|
||||
checkForAutoInitiate(messageRecord.getIndividualRecipient(),
|
||||
messageRecord.getBody().getBody(),
|
||||
messageRecord.getThreadId());
|
||||
}
|
||||
setClickable(messageRecord.isFailed() ||
|
||||
messageRecord.isPendingInsecureSmsFallback() ||
|
||||
messageRecord.isBundleKeyExchange());
|
||||
}
|
||||
|
||||
private void setGroupMessageStatus(MessageRecord messageRecord) {
|
||||
@ -427,23 +409,6 @@ public class ConversationItem extends LinearLayout {
|
||||
|
||||
/// Helper Methods
|
||||
|
||||
private void checkForAutoInitiate(Recipient recipient, String body, long threadId) {
|
||||
if (!groupThread &&
|
||||
AutoInitiateActivity.isValidAutoInitiateSituation(context, masterSecret, recipient,
|
||||
body, threadId))
|
||||
{
|
||||
AutoInitiateActivity.exemptThread(context, threadId);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(context, AutoInitiateActivity.class);
|
||||
intent.putExtra("threadId", threadId);
|
||||
intent.putExtra("masterSecret", masterSecret);
|
||||
intent.putExtra("recipient", recipient.getRecipientId());
|
||||
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void setContactPhotoForRecipient(final Recipient recipient) {
|
||||
if (contactPhoto == null) return;
|
||||
|
||||
@ -588,7 +553,7 @@ public class ConversationItem extends LinearLayout {
|
||||
!messageRecord.isStaleKeyExchange())
|
||||
{
|
||||
handleKeyExchangeClicked();
|
||||
} else if (messageRecord.isPendingSmsFallback()) {
|
||||
} else if (messageRecord.isPendingInsecureSmsFallback()) {
|
||||
handleMessageApproval();
|
||||
}
|
||||
}
|
||||
@ -611,17 +576,10 @@ public class ConversationItem extends LinearLayout {
|
||||
final int title;
|
||||
final int message;
|
||||
|
||||
if (messageRecord.isPendingSecureSmsFallback()) {
|
||||
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_mms_dialog_title;
|
||||
else title = R.string.ConversationItem_click_to_approve_sms_dialog_title;
|
||||
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_dialog_title;
|
||||
else title = R.string.ConversationItem_click_to_approve_unencrypted_sms_dialog_title;
|
||||
|
||||
message = -1;
|
||||
} else {
|
||||
if (messageRecord.isMms()) title = R.string.ConversationItem_click_to_approve_unencrypted_mms_dialog_title;
|
||||
else title = R.string.ConversationItem_click_to_approve_unencrypted_sms_dialog_title;
|
||||
|
||||
message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message;
|
||||
}
|
||||
message = R.string.ConversationItem_click_to_approve_unencrypted_dialog_message;
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(title);
|
||||
@ -633,9 +591,7 @@ public class ConversationItem extends LinearLayout {
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
if (messageRecord.isMms()) {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
if (messageRecord.isPendingInsecureSmsFallback()) {
|
||||
database.markAsInsecure(messageRecord.getId());
|
||||
}
|
||||
database.markAsInsecure(messageRecord.getId());
|
||||
database.markAsOutbox(messageRecord.getId());
|
||||
database.markAsForcedSms(messageRecord.getId());
|
||||
|
||||
@ -644,9 +600,7 @@ public class ConversationItem extends LinearLayout {
|
||||
.add(new MmsSendJob(context, messageRecord.getId()));
|
||||
} else {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
if (messageRecord.isPendingInsecureSmsFallback()) {
|
||||
database.markAsInsecure(messageRecord.getId());
|
||||
}
|
||||
database.markAsInsecure(messageRecord.getId());
|
||||
database.markAsOutbox(messageRecord.getId());
|
||||
database.markAsForcedSms(messageRecord.getId());
|
||||
|
||||
|
@ -27,10 +27,10 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.text.TextUtils;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@ -48,12 +48,12 @@ import org.thoughtcrime.securesms.components.ExpiredBuildReminder;
|
||||
import org.thoughtcrime.securesms.components.PushRegistrationReminder;
|
||||
import org.thoughtcrime.securesms.components.ReminderView;
|
||||
import org.thoughtcrime.securesms.components.SystemSmsImportReminder;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Dialogs;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -34,15 +34,11 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.SmsDecryptJob;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
@ -52,7 +48,6 @@ import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.protocol.KeyExchangeMessage;
|
||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||
import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
@ -78,9 +73,9 @@ public class ReceiveKeyActivity extends BaseActivity {
|
||||
private int recipientDeviceId;
|
||||
private long messageId;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private IncomingKeyExchangeMessage message;
|
||||
private IdentityKey identityKey;
|
||||
private MasterSecret masterSecret;
|
||||
private IncomingPreKeyBundleMessage message;
|
||||
private IdentityKey identityKey;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state) {
|
||||
@ -151,14 +146,7 @@ public class ReceiveKeyActivity extends BaseActivity {
|
||||
getIntent().getStringExtra("body"),
|
||||
Optional.<TextSecureGroup>absent());
|
||||
|
||||
if (getIntent().getBooleanExtra("is_bundle", false)) {
|
||||
this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody());
|
||||
} else if (getIntent().getBooleanExtra("is_identity_update", false)) {
|
||||
this.message = new IncomingIdentityUpdateMessage(message, message.getMessageBody());
|
||||
} else {
|
||||
this.message = new IncomingKeyExchangeMessage(message, message.getMessageBody());
|
||||
}
|
||||
|
||||
this.message = new IncomingPreKeyBundleMessage(message, message.getMessageBody());
|
||||
this.identityKey = getIdentityKey(this.message);
|
||||
}
|
||||
|
||||
@ -177,21 +165,12 @@ public class ReceiveKeyActivity extends BaseActivity {
|
||||
this.cancelButton.setOnClickListener(new CancelListener());
|
||||
}
|
||||
|
||||
private IdentityKey getIdentityKey(IncomingKeyExchangeMessage message)
|
||||
private IdentityKey getIdentityKey(IncomingPreKeyBundleMessage message)
|
||||
throws InvalidKeyException, InvalidVersionException,
|
||||
InvalidMessageException, LegacyMessageException
|
||||
{
|
||||
try {
|
||||
if (message.isIdentityUpdate()) {
|
||||
return new IdentityKey(Base64.decodeWithoutPadding(message.getMessageBody()), 0);
|
||||
} else if (message.isPreKeyBundle()) {
|
||||
boolean isPush = getIntent().getBooleanExtra("is_push", false);
|
||||
|
||||
if (isPush) return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey();
|
||||
else return new PreKeyWhisperMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey();
|
||||
} else {
|
||||
return new KeyExchangeMessage(Base64.decodeWithoutPadding(message.getMessageBody())).getIdentityKey();
|
||||
}
|
||||
return new PreKeyWhisperMessage(Base64.decode(message.getMessageBody())).getIdentityKey();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
@ -213,37 +192,25 @@ public class ReceiveKeyActivity extends BaseActivity {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
Context context = ReceiveKeyActivity.this;
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
|
||||
Context context = ReceiveKeyActivity.this;
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
|
||||
|
||||
identityDatabase.saveIdentity(masterSecret, recipient.getRecipientId(), identityKey);
|
||||
try {
|
||||
byte[] body = Base64.decode(message.getMessageBody());
|
||||
TextSecureEnvelope envelope = new TextSecureEnvelope(3, message.getSender(),
|
||||
message.getSenderDeviceId(), "",
|
||||
message.getSentTimestampMillis(),
|
||||
body);
|
||||
|
||||
if (message.isIdentityUpdate()) {
|
||||
smsDatabase.markAsProcessedKeyExchange(messageId);
|
||||
} else {
|
||||
if (getIntent().getBooleanExtra("is_push", false)) {
|
||||
try {
|
||||
byte[] body = Base64.decode(message.getMessageBody());
|
||||
TextSecureEnvelope envelope = new TextSecureEnvelope(3, message.getSender(),
|
||||
message.getSenderDeviceId(), "",
|
||||
message.getSentTimestampMillis(),
|
||||
body);
|
||||
long pushId = pushDatabase.insert(envelope);
|
||||
|
||||
long pushId = pushDatabase.insert(envelope);
|
||||
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new PushDecryptJob(context, pushId, messageId, message.getSender()));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
} else {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new SmsDecryptJob(context, messageId));
|
||||
}
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new PushDecryptJob(context, pushId, messageId, message.getSender()));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -88,7 +88,9 @@ public class RegistrationActivity extends BaseActionBarActivity {
|
||||
this.skipButton.setOnClickListener(new CancelButtonListener());
|
||||
|
||||
if (getIntent().getBooleanExtra("cancel_button", false)) {
|
||||
this.skipButton.setText(android.R.string.cancel);
|
||||
this.skipButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
this.skipButton.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,36 +7,60 @@ import org.thoughtcrime.securesms.util.PushCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
|
||||
|
||||
public class TransportOption {
|
||||
public int drawable;
|
||||
public String text;
|
||||
public String key;
|
||||
public String composeHint;
|
||||
public CharacterCalculator characterCalculator;
|
||||
|
||||
public TransportOption(String key, int drawable, String text, String composeHint) {
|
||||
this.key = key;
|
||||
this.drawable = drawable;
|
||||
this.text = text;
|
||||
this.composeHint = composeHint;
|
||||
public enum Type {
|
||||
SMS,
|
||||
TEXTSECURE
|
||||
}
|
||||
|
||||
if (isPlaintext() && isSms()) {
|
||||
this.characterCalculator = new SmsCharacterCalculator();
|
||||
} else if (isSms()) {
|
||||
this.characterCalculator = new EncryptedSmsCharacterCalculator();
|
||||
} else {
|
||||
this.characterCalculator = new PushCharacterCalculator();
|
||||
}
|
||||
private int drawable;
|
||||
private String text;
|
||||
private Type type;
|
||||
private String composeHint;
|
||||
private CharacterCalculator characterCalculator;
|
||||
|
||||
public TransportOption(Type type,
|
||||
int drawable,
|
||||
String text,
|
||||
String composeHint,
|
||||
CharacterCalculator characterCalculator)
|
||||
{
|
||||
this.type = type;
|
||||
this.drawable = drawable;
|
||||
this.text = text;
|
||||
this.composeHint = composeHint;
|
||||
this.characterCalculator = characterCalculator;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isType(Type type) {
|
||||
return this.type == type;
|
||||
}
|
||||
|
||||
public boolean isPlaintext() {
|
||||
return key.equals("insecure_sms");
|
||||
return type == Type.SMS;
|
||||
}
|
||||
|
||||
public boolean isSms() {
|
||||
return key.equals("insecure_sms") || key.equals("secure_sms");
|
||||
return type == Type.SMS;
|
||||
}
|
||||
|
||||
public CharacterState calculateCharacters(int charactersSpent) {
|
||||
return characterCalculator.calculateCharacters(charactersSpent);
|
||||
}
|
||||
|
||||
public int getDrawable() {
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public String getComposeHint() {
|
||||
return composeHint;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
@ -2,149 +2,138 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupWindow;
|
||||
|
||||
import org.thoughtcrime.securesms.util.MmsCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.PushCharacterCalculator;
|
||||
import org.thoughtcrime.securesms.util.SmsCharacterCalculator;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
|
||||
public class TransportOptions {
|
||||
|
||||
private static final String TAG = TransportOptions.class.getSimpleName();
|
||||
|
||||
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
|
||||
private final Context context;
|
||||
private PopupWindow transportPopup;
|
||||
private final List<String> enabledTransports = new ArrayList<>();
|
||||
private final Map<String, TransportOption> transportMetadata = new HashMap<>();
|
||||
private String selectedTransport;
|
||||
private boolean transportOverride = false;
|
||||
private final List<OnTransportChangedListener> listeners = new LinkedList<>();
|
||||
private final List<TransportOption> enabledTransports;
|
||||
|
||||
public TransportOptions(Context context) {
|
||||
this.context = context;
|
||||
private Type selectedType;
|
||||
private boolean manuallySelected;
|
||||
|
||||
public TransportOptions(Context context, boolean media) {
|
||||
this.context = context;
|
||||
this.enabledTransports = initializeAvailableTransports(media);
|
||||
|
||||
setDefaultTransport(Type.SMS);
|
||||
}
|
||||
|
||||
private void initializeTransportPopup() {
|
||||
if (transportPopup == null) {
|
||||
final View selectionMenu = LayoutInflater.from(context).inflate(R.layout.transport_selection, null);
|
||||
final ListView list = (ListView) selectionMenu.findViewById(R.id.transport_selection_list);
|
||||
public void reset(boolean media) {
|
||||
List<TransportOption> transportOptions = initializeAvailableTransports(media);
|
||||
this.enabledTransports.clear();
|
||||
this.enabledTransports.addAll(transportOptions);
|
||||
|
||||
final TransportOptionsAdapter adapter = new TransportOptionsAdapter(context, enabledTransports, transportMetadata);
|
||||
|
||||
list.setAdapter(adapter);
|
||||
transportPopup = new PopupWindow(selectionMenu);
|
||||
transportPopup.setFocusable(true);
|
||||
transportPopup.setBackgroundDrawable(new BitmapDrawable(context.getResources(), ""));
|
||||
transportPopup.setOutsideTouchable(true);
|
||||
transportPopup.setWindowLayoutMode(0, WindowManager.LayoutParams.WRAP_CONTENT);
|
||||
transportPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.transport_selection_popup_width));
|
||||
list.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
transportOverride = true;
|
||||
setTransport((TransportOption) adapter.getItem(position));
|
||||
transportPopup.dismiss();
|
||||
}
|
||||
});
|
||||
if (!find(selectedType).isPresent()) {
|
||||
this.manuallySelected = false;
|
||||
setTransport(Type.SMS);
|
||||
} else {
|
||||
final ListView list = (ListView) transportPopup.getContentView().findViewById(R.id.transport_selection_list);
|
||||
final TransportOptionsAdapter adapter = (TransportOptionsAdapter) list.getAdapter();
|
||||
adapter.setEnabledTransports(enabledTransports);
|
||||
adapter.notifyDataSetInvalidated();
|
||||
notifyTransportChangeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeAvailableTransports(boolean isMediaMessage) {
|
||||
String[] entryArray = (isMediaMessage)
|
||||
? context.getResources().getStringArray(R.array.transport_selection_entries_media)
|
||||
: context.getResources().getStringArray(R.array.transport_selection_entries_text);
|
||||
|
||||
String[] composeHintArray = (isMediaMessage)
|
||||
? context.getResources().getStringArray(R.array.transport_selection_entries_compose_media)
|
||||
: context.getResources().getStringArray(R.array.transport_selection_entries_compose_text);
|
||||
|
||||
final String[] valuesArray = context.getResources().getStringArray(R.array.transport_selection_values);
|
||||
|
||||
final int[] attrs = new int[]{R.attr.conversation_transport_indicators};
|
||||
final TypedArray iconArray = context.obtainStyledAttributes(attrs);
|
||||
final int iconArrayResource = iconArray.getResourceId(0, -1);
|
||||
final TypedArray icons = context.getResources().obtainTypedArray(iconArrayResource);
|
||||
|
||||
enabledTransports.clear();
|
||||
for (int i=0; i<valuesArray.length; i++) {
|
||||
String key = valuesArray[i];
|
||||
enabledTransports.add(key);
|
||||
transportMetadata.put(key, new TransportOption(key, icons.getResourceId(i, -1), entryArray[i], composeHintArray[i]));
|
||||
public void setDefaultTransport(Type type) {
|
||||
if (!this.manuallySelected) {
|
||||
setTransport(type);
|
||||
}
|
||||
iconArray.recycle();
|
||||
icons.recycle();
|
||||
updateViews();
|
||||
}
|
||||
|
||||
public void setTransport(String transport) {
|
||||
selectedTransport = transport;
|
||||
updateViews();
|
||||
public void setSelectedTransport(Type type) {
|
||||
this.manuallySelected= true;
|
||||
setTransport(type);
|
||||
}
|
||||
|
||||
private void setTransport(TransportOption transport) {
|
||||
setTransport(transport.key);
|
||||
}
|
||||
|
||||
public void showPopup(final View parent) {
|
||||
initializeTransportPopup();
|
||||
final int xoff = context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_xoff);
|
||||
final int yoff = context.getResources().getDimensionPixelOffset(R.dimen.transport_selection_popup_yoff);
|
||||
transportPopup.showAsDropDown(parent,
|
||||
xoff,
|
||||
yoff);
|
||||
parent.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
transportPopup.update(parent, xoff, yoff, -1, -1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setDefaultTransport(String transportName) {
|
||||
if (!transportOverride) {
|
||||
setTransport(transportName);
|
||||
}
|
||||
public boolean isManualSelection() {
|
||||
return manuallySelected;
|
||||
}
|
||||
|
||||
public TransportOption getSelectedTransport() {
|
||||
return transportMetadata.get(selectedTransport);
|
||||
Optional<TransportOption> option = find(selectedType);
|
||||
|
||||
if (option.isPresent()) return option.get();
|
||||
else throw new AssertionError("Selected type isn't present!");
|
||||
}
|
||||
|
||||
public void disableTransport(String transportName) {
|
||||
enabledTransports.remove(transportName);
|
||||
}
|
||||
|
||||
public List<String> getEnabledTransports() {
|
||||
return enabledTransports;
|
||||
}
|
||||
|
||||
private void updateViews() {
|
||||
if (selectedTransport == null) return;
|
||||
|
||||
for (OnTransportChangedListener listener : listeners) {
|
||||
listener.onChange(getSelectedTransport());
|
||||
public void disableTransport(Type type) {
|
||||
Optional<TransportOption> option = find(type);
|
||||
if (option.isPresent()) {
|
||||
enabledTransports.remove(option.get());
|
||||
}
|
||||
}
|
||||
|
||||
public List<TransportOption> getEnabledTransports() {
|
||||
return enabledTransports;
|
||||
}
|
||||
|
||||
public void addOnTransportChangedListener(OnTransportChangedListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
private List<TransportOption> initializeAvailableTransports(boolean isMediaMessage) {
|
||||
List<TransportOption> results = new LinkedList<>();
|
||||
int[] attributes = new int[]{R.attr.conversation_transport_sms_indicator,
|
||||
R.attr.conversation_transport_push_indicator};
|
||||
TypedArray iconArray = context.obtainStyledAttributes(attributes);
|
||||
int smsIconResource = iconArray.getResourceId(0, -1);
|
||||
int pushIconResource = iconArray.getResourceId(1, -1);
|
||||
|
||||
if (isMediaMessage) {
|
||||
results.add(new TransportOption(Type.SMS, smsIconResource,
|
||||
context.getString(R.string.ConversationActivity_transport_insecure_mms),
|
||||
context.getString(R.string.conversation_activity__type_message_mms_insecure),
|
||||
new MmsCharacterCalculator()));
|
||||
} else {
|
||||
results.add(new TransportOption(Type.SMS, smsIconResource,
|
||||
context.getString(R.string.ConversationActivity_transport_insecure_sms),
|
||||
context.getString(R.string.conversation_activity__type_message_sms_insecure),
|
||||
new SmsCharacterCalculator()));
|
||||
}
|
||||
|
||||
results.add(new TransportOption(Type.TEXTSECURE, pushIconResource,
|
||||
context.getString(R.string.ConversationActivity_transport_textsecure),
|
||||
context.getString(R.string.conversation_activity__type_message_push),
|
||||
new PushCharacterCalculator()));
|
||||
|
||||
iconArray.recycle();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
private void setTransport(Type type) {
|
||||
this.selectedType = type;
|
||||
|
||||
notifyTransportChangeListeners();
|
||||
}
|
||||
|
||||
private void notifyTransportChangeListeners() {
|
||||
for (OnTransportChangedListener listener : listeners) {
|
||||
listener.onChange(getSelectedTransport());
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<TransportOption> find(Type type) {
|
||||
for (TransportOption option : enabledTransports) {
|
||||
if (option.isType(type)) {
|
||||
return Optional.of(option);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public interface OnTransportChangedListener {
|
||||
public void onChange(TransportOption newTransport);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -9,41 +10,33 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TransportOptionsAdapter extends BaseAdapter {
|
||||
private final Context context;
|
||||
private final LayoutInflater inflater;
|
||||
private List<String> enabledTransports;
|
||||
private final Map<String, TransportOption> transportMetadata;
|
||||
|
||||
public TransportOptionsAdapter(final Context context,
|
||||
final List<String> enabledTransports,
|
||||
final Map<String, TransportOption> transportMetadata) {
|
||||
private final LayoutInflater inflater;
|
||||
|
||||
private List<TransportOption> enabledTransports;
|
||||
|
||||
public TransportOptionsAdapter(@NonNull Context context,
|
||||
@NonNull List<TransportOption> enabledTransports)
|
||||
{
|
||||
super();
|
||||
this.context = context;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.enabledTransports = enabledTransports;
|
||||
this.transportMetadata = transportMetadata;
|
||||
}
|
||||
|
||||
public TransportOptionsAdapter(final Context context,
|
||||
final Map<String, TransportOption> transportMetadata) {
|
||||
this(context, null, transportMetadata);
|
||||
}
|
||||
|
||||
public void setEnabledTransports(final List<String> enabledTransports) {
|
||||
public void setEnabledTransports(List<TransportOption> enabledTransports) {
|
||||
this.enabledTransports = enabledTransports;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return enabledTransports == null ? 0 : enabledTransports.size();
|
||||
return enabledTransports.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return transportMetadata.get(enabledTransports.get(position));
|
||||
return enabledTransports.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -53,19 +46,17 @@ public class TransportOptionsAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
final View view;
|
||||
if (convertView == null) {
|
||||
view = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
|
||||
} else {
|
||||
view = convertView;
|
||||
convertView = inflater.inflate(R.layout.transport_selection_list_item, parent, false);
|
||||
}
|
||||
|
||||
TransportOption transport = (TransportOption) getItem(position);
|
||||
final ImageView imageView = (ImageView)view.findViewById(R.id.icon);
|
||||
final TextView textView = (TextView) view.findViewById(R.id.text);
|
||||
ImageView imageView = (ImageView) convertView.findViewById(R.id.icon);
|
||||
TextView textView = (TextView) convertView.findViewById(R.id.text);
|
||||
|
||||
imageView.setImageResource(transport.drawable);
|
||||
textView.setText(transport.text);
|
||||
return view;
|
||||
imageView.setImageResource(transport.getDrawable());
|
||||
textView.setText(transport.getDescription());
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
|
73
src/org/thoughtcrime/securesms/TransportOptionsPopup.java
Normal file
73
src/org/thoughtcrime/securesms/TransportOptionsPopup.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
36
src/org/thoughtcrime/securesms/components/ComposeText.java
Normal file
36
src/org/thoughtcrime/securesms/components/ComposeText.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
||||
public class PushRegistrationReminder extends Reminder {
|
||||
public static final long REMINDER_INTERVAL_MS = 3 * 24 * 60 * 60 * 1000;
|
||||
|
||||
public PushRegistrationReminder(final Context context, final MasterSecret masterSecret) {
|
||||
super(R.drawable.ic_push_registration_reminder,
|
||||
@ -27,18 +26,16 @@ public class PushRegistrationReminder extends Reminder {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
};
|
||||
final OnClickListener cancelListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TextSecurePreferences.setLastPushReminderTime(context, System.currentTimeMillis());
|
||||
}
|
||||
};
|
||||
|
||||
setOkListener(okListener);
|
||||
setCancelListener(cancelListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDismissable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isEligible(Context context) {
|
||||
return !TextSecurePreferences.isPushRegistered(context) &&
|
||||
(TextSecurePreferences.getLastPushReminderTime(context) + REMINDER_INTERVAL_MS < System.currentTimeMillis());
|
||||
return !TextSecurePreferences.isPushRegistered(context);
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +55,9 @@ public class ReminderView extends LinearLayout {
|
||||
title.setText(reminder.getTitleResId());
|
||||
text.setText(reminder.getTextResId());
|
||||
|
||||
this.setOnClickListener(reminder.getOkListener());
|
||||
|
||||
if (reminder.isDismissable()) {
|
||||
this.setOnClickListener(reminder.getOkListener());
|
||||
cancel.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -64,10 +65,11 @@ public class ReminderView extends LinearLayout {
|
||||
if (reminder.getCancelListener() != null) reminder.getCancelListener().onClick(v);
|
||||
}
|
||||
});
|
||||
container.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
cancel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
container.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
|
@ -1,94 +1,101 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.TransportOptions;
|
||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||
import org.thoughtcrime.securesms.TransportOptionsPopup;
|
||||
|
||||
public class SendButton extends ImageButton {
|
||||
private TransportOptions transportOptions;
|
||||
private EditText composeText;
|
||||
public class SendButton extends ImageButton
|
||||
implements TransportOptions.OnTransportChangedListener,
|
||||
TransportOptionsPopup.SelectedListener,
|
||||
View.OnLongClickListener
|
||||
{
|
||||
|
||||
private final TransportOptions transportOptions;
|
||||
private final TransportOptionsPopup transportOptionsPopup;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SendButton(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
this.transportOptions = initializeTransportOptions(false);
|
||||
this.transportOptionsPopup = initializeTransportOptionsPopup();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SendButton(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
this.transportOptions = initializeTransportOptions(false);
|
||||
this.transportOptionsPopup = initializeTransportOptionsPopup();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public SendButton(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialize();
|
||||
this.transportOptions = initializeTransportOptions(false);
|
||||
this.transportOptionsPopup = initializeTransportOptionsPopup();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
transportOptions = new TransportOptions(getContext());
|
||||
transportOptions.addOnTransportChangedListener(new OnTransportChangedListener() {
|
||||
@Override
|
||||
public void onChange(TransportOption newTransport) {
|
||||
setImageResource(newTransport.drawable);
|
||||
setContentDescription(newTransport.composeHint);
|
||||
if (composeText != null) setComposeTextHint(newTransport.composeHint);
|
||||
}
|
||||
});
|
||||
private TransportOptions initializeTransportOptions(boolean media) {
|
||||
TransportOptions transportOptions = new TransportOptions(getContext(), media);
|
||||
transportOptions.addOnTransportChangedListener(this);
|
||||
|
||||
setOnLongClickListener(new OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View view) {
|
||||
if (transportOptions.getEnabledTransports().size() > 1) {
|
||||
transportOptions.showPopup(SendButton.this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
setOnLongClickListener(this);
|
||||
|
||||
return transportOptions;
|
||||
}
|
||||
|
||||
private TransportOptionsPopup initializeTransportOptionsPopup() {
|
||||
return new TransportOptionsPopup(getContext(), this);
|
||||
}
|
||||
|
||||
public boolean isManualSelection() {
|
||||
return transportOptions.isManualSelection();
|
||||
}
|
||||
|
||||
public void addOnTransportChangedListener(OnTransportChangedListener listener) {
|
||||
transportOptions.addOnTransportChangedListener(listener);
|
||||
}
|
||||
|
||||
public void setComposeTextView(EditText composeText) {
|
||||
this.composeText = composeText;
|
||||
}
|
||||
|
||||
public TransportOption getSelectedTransport() {
|
||||
return transportOptions.getSelectedTransport();
|
||||
}
|
||||
|
||||
public void initializeAvailableTransports(boolean isMediaMessage) {
|
||||
transportOptions.initializeAvailableTransports(isMediaMessage);
|
||||
public void resetAvailableTransports(boolean isMediaMessage) {
|
||||
transportOptions.reset(isMediaMessage);
|
||||
}
|
||||
|
||||
public void disableTransport(String transport) {
|
||||
transportOptions.disableTransport(transport);
|
||||
public void disableTransport(TransportOption.Type type) {
|
||||
transportOptions.disableTransport(type);
|
||||
}
|
||||
|
||||
public void setDefaultTransport(String transport) {
|
||||
transportOptions.setDefaultTransport(transport);
|
||||
public void setDefaultTransport(TransportOption.Type type) {
|
||||
transportOptions.setDefaultTransport(type);
|
||||
}
|
||||
|
||||
private void setComposeTextHint(String hint) {
|
||||
if (hint == null) {
|
||||
this.composeText.setHint(null);
|
||||
} else {
|
||||
SpannableString span = new SpannableString(hint);
|
||||
span.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
this.composeText.setHint(span);
|
||||
@Override
|
||||
public void onSelected(TransportOption option) {
|
||||
transportOptions.setSelectedTransport(option.getType());
|
||||
transportOptionsPopup.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(TransportOption newTransport) {
|
||||
setImageResource(newTransport.getDrawable());
|
||||
setContentDescription(newTransport.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
if (transportOptions.getEnabledTransports().size() > 1) {
|
||||
transportOptionsPopup.display(getContext(), SendButton.this, transportOptions.getEnabledTransports());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,11 @@ public class ContactsDatabase {
|
||||
}
|
||||
|
||||
public Cursor query(String filter, boolean pushOnly) {
|
||||
final boolean includeAndroidContacts = !pushOnly && TextSecurePreferences.isDirectSmsAllowed(context);
|
||||
// FIXME: This doesn't make sense to me. You pass in pushOnly, but then
|
||||
// conditionally check to see whether other contacts should be included
|
||||
// in the query method itself? I don't think this method should have any
|
||||
// understanding of that stuff.
|
||||
final boolean includeAndroidContacts = !pushOnly && TextSecurePreferences.isSmsEnabled(context);
|
||||
final Cursor localCursor = queryLocalDb(filter);
|
||||
final Cursor androidCursor;
|
||||
final MatrixCursor newNumberCursor;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -15,6 +15,10 @@ public class SecurityEvent {
|
||||
|
||||
public static final String SECURITY_UPDATE_EVENT = "org.thoughtcrime.securesms.KEY_EXCHANGE_UPDATE";
|
||||
|
||||
public static void broadcastSecurityUpdateEvent(Context context) {
|
||||
broadcastSecurityUpdateEvent(context, -2);
|
||||
}
|
||||
|
||||
public static void broadcastSecurityUpdateEvent(Context context, long threadId) {
|
||||
Intent intent = new Intent(SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", threadId);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -383,11 +383,6 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markAsPendingSecureSmsFallback(long messageId) {
|
||||
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_SECURE_SMS_FALLBACK);
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markAsPendingInsecureSmsFallback(long messageId) {
|
||||
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
|
@ -35,10 +35,8 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.TrimThreadJob;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
@ -183,18 +181,6 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT);
|
||||
}
|
||||
|
||||
public void markAsStaleKeyExchange(long id) {
|
||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_STALE_BIT);
|
||||
}
|
||||
|
||||
public void markAsProcessedKeyExchange(long id) {
|
||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_PROCESSED_BIT);
|
||||
}
|
||||
|
||||
public void markAsCorruptKeyExchange(long id) {
|
||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_CORRUPTED_BIT);
|
||||
}
|
||||
|
||||
public void markAsInvalidVersionKeyExchange(long id) {
|
||||
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT);
|
||||
}
|
||||
@ -239,10 +225,6 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE);
|
||||
}
|
||||
|
||||
public void markAsPendingSecureSmsFallback(long id) {
|
||||
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_SECURE_SMS_FALLBACK);
|
||||
}
|
||||
|
||||
public void markAsPendingInsecureSmsFallback(long id) {
|
||||
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
|
||||
}
|
||||
@ -363,19 +345,10 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
protected Pair<Long, Long> insertMessageInbox(IncomingTextMessage message, long type) {
|
||||
if (message.isKeyExchange()) {
|
||||
type |= Types.KEY_EXCHANGE_BIT;
|
||||
if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isLegacyVersion()) type |= Types.ENCRYPTION_REMOTE_LEGACY_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isDuplicate()) type |= Types.ENCRYPTION_REMOTE_DUPLICATE_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isPreKeyBundle()) type |= Types.KEY_EXCHANGE_BUNDLE_BIT;
|
||||
if (message.isPreKeyBundle()) {
|
||||
type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT;
|
||||
} else if (message.isSecureMessage()) {
|
||||
type |= Types.SECURE_MESSAGE_BIT;
|
||||
// type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
} else if (message.isGroup()) {
|
||||
type |= Types.SECURE_MESSAGE_BIT;
|
||||
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
|
||||
@ -383,7 +356,6 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
} else if (message.isEndSession()) {
|
||||
type |= Types.SECURE_MESSAGE_BIT;
|
||||
type |= Types.END_SESSION_BIT;
|
||||
// type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
}
|
||||
|
||||
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
|
||||
@ -406,7 +378,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
boolean unread = org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
|
||||
message.isSecureMessage() || message.isKeyExchange();
|
||||
message.isSecureMessage() || message.isPreKeyBundle();
|
||||
|
||||
long threadId;
|
||||
|
||||
|
@ -143,7 +143,7 @@ public class TextSecureDirectory {
|
||||
|
||||
try {
|
||||
for (ContactTokenDetails token : activeTokens) {
|
||||
Log.w("Directory", "Adding active token: " + token);
|
||||
Log.w("Directory", "Adding active token: " + token.getNumber() + ", " + token.getToken());
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(NUMBER, token.getNumber());
|
||||
values.put(REGISTERED, 1);
|
||||
|
@ -80,7 +80,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
|
||||
public boolean isFailed() {
|
||||
return
|
||||
MmsSmsColumns.Types.isFailedMessageType(type) ||
|
||||
MmsSmsColumns.Types.isFailedMessageType(type) ||
|
||||
MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) ||
|
||||
getDeliveryStatus() == DELIVERY_STATUS_FAILED;
|
||||
}
|
||||
|
||||
@ -149,22 +150,14 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return SmsDatabase.Types.isProcessedKeyExchange(type);
|
||||
}
|
||||
|
||||
public boolean isPendingSmsFallback() {
|
||||
return SmsDatabase.Types.isPendingSmsFallbackType(type);
|
||||
public boolean isPendingInsecureSmsFallback() {
|
||||
return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type);
|
||||
}
|
||||
|
||||
public boolean isIdentityMismatchFailure() {
|
||||
return mismatches != null && !mismatches.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isPendingSecureSmsFallback() {
|
||||
return SmsDatabase.Types.isPendingSecureSmsFallbackType(type);
|
||||
}
|
||||
|
||||
public boolean isPendingInsecureSmsFallback() {
|
||||
return SmsDatabase.Types.isPendingInsecureSmsFallbackType(type);
|
||||
}
|
||||
|
||||
public boolean isBundleKeyExchange() {
|
||||
return SmsDatabase.Types.isBundleKeyExchange(type);
|
||||
}
|
||||
|
@ -23,9 +23,8 @@ import android.text.SpannableString;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.protocol.Tag;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
@ -91,8 +90,6 @@ public class SmsMessageRecord extends MessageRecord {
|
||||
return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message));
|
||||
} else if (SmsDatabase.Types.isEndSessionType(type)) {
|
||||
return emphasisAdded(context.getString(R.string.SmsMessageRecord_secure_session_ended));
|
||||
} else if (isOutgoing() && Tag.isTagged(getBody().getBody())) {
|
||||
return new SpannableString(Tag.stripTag(getBody().getBody()));
|
||||
} else {
|
||||
return super.getDisplayBody();
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import android.content.Context;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
@ -30,6 +31,7 @@ public class DirectoryRefreshJob extends ContextJob {
|
||||
try {
|
||||
wakeLock.acquire();
|
||||
DirectoryHelper.refreshDirectory(context);
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context);
|
||||
} finally {
|
||||
if (wakeLock.isHeld()) wakeLock.release();
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MmsCipher;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
@ -35,7 +33,6 @@ import java.io.IOException;
|
||||
|
||||
import ws.com.google.android.mms.InvalidHeaderValueException;
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
import ws.com.google.android.mms.pdu.MultimediaMessagePdu;
|
||||
import ws.com.google.android.mms.pdu.NotificationInd;
|
||||
import ws.com.google.android.mms.pdu.NotifyRespInd;
|
||||
import ws.com.google.android.mms.pdu.PduComposer;
|
||||
@ -212,24 +209,14 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
Pair<Long, Long> messageAndThreadId;
|
||||
|
||||
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
|
||||
MmsCipher mmsCipher = new MmsCipher(new TextSecureAxolotlStore(context, masterSecret));
|
||||
MultimediaMessagePdu plaintextPdu = mmsCipher.decrypt(context, retrieved);
|
||||
RetrieveConf plaintextRetrieved = new RetrieveConf(plaintextPdu.getPduHeaders(), plaintextPdu.getBody());
|
||||
IncomingMediaMessage plaintextMessage = new IncomingMediaMessage(plaintextRetrieved);
|
||||
|
||||
messageAndThreadId = database.insertSecureDecryptedMessageInbox(masterSecret, plaintextMessage,
|
||||
threadId);
|
||||
|
||||
// if (masterSecret != null)
|
||||
// DecryptingQueue.scheduleDecryption(context, masterSecret, messageAndThreadId.first,
|
||||
// messageAndThreadId.second, retrieved);
|
||||
|
||||
database.markAsLegacyVersion(messageId, threadId);
|
||||
messageAndThreadId = new Pair<>(messageId, threadId);
|
||||
} else {
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, message,
|
||||
contentLocation, threadId);
|
||||
database.delete(messageId);
|
||||
}
|
||||
|
||||
database.delete(messageId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
}
|
||||
|
||||
@ -262,8 +249,6 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
db.markIncomingNotificationReceived(threadId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
||||
}
|
||||
//
|
||||
// toastHandler.makeToast(error);
|
||||
}
|
||||
|
||||
private boolean isCdmaNetwork() {
|
||||
|
@ -5,8 +5,6 @@ import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MmsCipher;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
@ -18,7 +16,6 @@ import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.mms.MmsSendResult;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMmsConnection;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
@ -27,7 +24,6 @@ import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@ -69,10 +65,6 @@ public class MmsSendJob extends SendJob {
|
||||
try {
|
||||
MmsSendResult result = deliver(masterSecret, message);
|
||||
|
||||
if (result.isUpgradedSecure()) {
|
||||
database.markAsSecure(messageId);
|
||||
}
|
||||
|
||||
database.markAsSent(messageId, result.getMessageId(), result.getResponseStatus());
|
||||
} catch (UndeliverableMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
@ -146,14 +138,12 @@ public class MmsSendJob extends SendJob {
|
||||
boolean usingMmsRadio, boolean useProxy)
|
||||
throws IOException, UndeliverableMessageException, InsecureFallbackApprovalException
|
||||
{
|
||||
String number = TelephonyUtil.getManager(context).getLine1Number();
|
||||
boolean upgradedSecure = false;
|
||||
String number = TelephonyUtil.getManager(context).getLine1Number();
|
||||
|
||||
prepareMessageMedia(masterSecret, message, MediaConstraints.MMS_CONSTRAINTS, true);
|
||||
|
||||
if (MmsDatabase.Types.isSecureType(message.getDatabaseMessageBox())) {
|
||||
message = getEncryptedMessage(masterSecret, message);
|
||||
upgradedSecure = true;
|
||||
throw new UndeliverableMessageException("Attempt to send encrypted MMS?");
|
||||
}
|
||||
|
||||
if (number != null && number.trim().length() != 0) {
|
||||
@ -177,26 +167,13 @@ public class MmsSendJob extends SendJob {
|
||||
} else if (isInconsistentResponse(message, conf)) {
|
||||
throw new UndeliverableMessageException("Mismatched response!");
|
||||
} else {
|
||||
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus(), upgradedSecure, false);
|
||||
return new MmsSendResult(conf.getMessageId(), conf.getResponseStatus());
|
||||
}
|
||||
} catch (ApnUnavailableException aue) {
|
||||
throw new IOException("no APN was retrievable");
|
||||
}
|
||||
}
|
||||
|
||||
private SendReq getEncryptedMessage(MasterSecret masterSecret, SendReq pdu)
|
||||
throws InsecureFallbackApprovalException, UndeliverableMessageException
|
||||
{
|
||||
try {
|
||||
MmsCipher cipher = new MmsCipher(new TextSecureAxolotlStore(context, masterSecret));
|
||||
return cipher.encrypt(context, pdu);
|
||||
} catch (NoSessionException e) {
|
||||
throw new InsecureFallbackApprovalException(e);
|
||||
} catch (RecipientFormattingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInconsistentResponse(SendReq message, SendConf response) {
|
||||
Log.w(TAG, "Comparing: " + Hex.toString(message.getTransactionId()));
|
||||
Log.w(TAG, "With: " + Hex.toString(response.getTransactionId()));
|
||||
|
@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
@ -29,7 +28,6 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
|
@ -5,19 +5,16 @@ import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.whispersystems.textsecure.api.TextSecureMessageSender;
|
||||
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
|
||||
@ -46,7 +43,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
private final long messageId;
|
||||
|
||||
public PushMediaSendJob(Context context, long messageId, String destination) {
|
||||
super(context, constructParameters(context, destination, true));
|
||||
super(context, constructParameters(context, destination));
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@ -66,19 +63,15 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
SendReq message = database.getOutgoingMessage(masterSecret, messageId);
|
||||
|
||||
try {
|
||||
if (deliver(masterSecret, message)) {
|
||||
database.markAsPush(messageId);
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId, "push".getBytes(), 0);
|
||||
}
|
||||
deliver(masterSecret, message);
|
||||
database.markAsPush(messageId);
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId, "push".getBytes(), 0);
|
||||
} catch (InsecureFallbackApprovalException ifae) {
|
||||
Log.w(TAG, ifae);
|
||||
database.markAsPendingInsecureSmsFallback(messageId);
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
} catch (SecureFallbackApprovalException sfae) {
|
||||
Log.w(TAG, sfae);
|
||||
database.markAsPendingSecureSmsFallback(messageId);
|
||||
notifyMediaMessageDeliveryFailed(context, messageId);
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context));
|
||||
} catch (UntrustedIdentityException uie) {
|
||||
Log.w(TAG, uie);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, uie.getE164Number(), false);
|
||||
@ -103,54 +96,32 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
}
|
||||
|
||||
|
||||
private boolean deliver(MasterSecret masterSecret, SendReq message)
|
||||
throws RetryLaterException, SecureFallbackApprovalException,
|
||||
InsecureFallbackApprovalException, UntrustedIdentityException,
|
||||
private void deliver(MasterSecret masterSecret, SendReq message)
|
||||
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
|
||||
UndeliverableMessageException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
String destination = message.getTo()[0].getString();
|
||||
boolean isSmsFallbackSupported = isSmsFallbackSupported(context, destination, true);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
String destination = message.getTo()[0].getString();
|
||||
|
||||
try {
|
||||
prepareMessageMedia(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
|
||||
|
||||
TextSecureAddress address = getPushAddress(destination);
|
||||
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
||||
String body = PartParser.getMessageText(message.getBody());
|
||||
TextSecureMessage mediaMessage = new TextSecureMessage(message.getSentTimestamp(), attachments, body);
|
||||
TextSecureMessage mediaMessage = TextSecureMessage.newBuilder()
|
||||
.withBody(body)
|
||||
.withAttachments(attachments)
|
||||
.withTimestamp(message.getSentTimestamp())
|
||||
.build();
|
||||
|
||||
messageSender.sendMessage(address, mediaMessage);
|
||||
return true;
|
||||
} catch (InvalidNumberException | UnregisteredUserException e) {
|
||||
Log.w(TAG, e);
|
||||
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
|
||||
else database.markAsSentFailed(messageId);
|
||||
throw new InsecureFallbackApprovalException(e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
|
||||
else throw new RetryLaterException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void fallbackOrAskApproval(MasterSecret masterSecret, SendReq mediaMessage, String destination)
|
||||
throws SecureFallbackApprovalException, InsecureFallbackApprovalException
|
||||
{
|
||||
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination, true);
|
||||
|
||||
if (!isSmsFallbackApprovalRequired) {
|
||||
Log.w(TAG, "Falling back to MMS");
|
||||
DatabaseFactory.getMmsDatabase(context).markAsForcedSms(mediaMessage.getDatabaseMessageId());
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new MmsSendJob(context, messageId));
|
||||
} else if (!SessionUtil.hasSession(context, masterSecret, destination)) {
|
||||
Log.w(TAG, "Marking message as pending insecure SMS fallback");
|
||||
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
|
||||
} else {
|
||||
Log.w(TAG, "Marking message as pending secure SMS fallback");
|
||||
throw new SecureFallbackApprovalException("Pending user approval for fallback secure to SMS");
|
||||
throw new RetryLaterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -38,54 +38,23 @@ public abstract class PushSendJob extends SendJob {
|
||||
super(context, parameters);
|
||||
}
|
||||
|
||||
protected static JobParameters constructParameters(Context context, String destination, boolean media) {
|
||||
protected static JobParameters constructParameters(Context context, String destination) {
|
||||
JobParameters.Builder builder = JobParameters.newBuilder();
|
||||
builder.withPersistence();
|
||||
builder.withGroupId(destination);
|
||||
builder.withRequirement(new MasterSecretRequirement(context));
|
||||
|
||||
if (!isSmsFallbackSupported(context, destination, media)) {
|
||||
builder.withRequirement(new NetworkRequirement(context));
|
||||
builder.withRetryCount(5);
|
||||
}
|
||||
builder.withRequirement(new NetworkRequirement(context));
|
||||
builder.withRetryCount(5);
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
protected static boolean isSmsFallbackSupported(Context context, String destination, boolean media) {
|
||||
try {
|
||||
String e164number = Util.canonicalizeNumber(context, destination);
|
||||
|
||||
if (GroupUtil.isEncodedGroup(e164number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isFallbackSmsAllowed(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (media && !TextSecurePreferences.isFallbackMmsEnabled(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TextSecureDirectory directory = TextSecureDirectory.getInstance(context);
|
||||
return directory.isSmsFallbackSupported(e164number);
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w(TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected TextSecureAddress getPushAddress(String number) throws InvalidNumberException {
|
||||
String e164number = Util.canonicalizeNumber(context, number);
|
||||
String relay = TextSecureDirectory.getInstance(context).getRelay(e164number);
|
||||
return new TextSecureAddress(e164number, Optional.fromNullable(relay));
|
||||
}
|
||||
|
||||
protected boolean isSmsFallbackApprovalRequired(String destination, boolean media) {
|
||||
return (isSmsFallbackSupported(context, destination, media) && TextSecurePreferences.isFallbackSmsAskRequired(context));
|
||||
}
|
||||
|
||||
protected List<TextSecureAttachment> getAttachments(final MasterSecret masterSecret, final SendReq message) {
|
||||
List<TextSecureAttachment> attachments = new LinkedList<>();
|
||||
|
||||
|
@ -5,7 +5,6 @@ import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
@ -13,12 +12,10 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.transport.SecureFallbackApprovalException;
|
||||
import org.whispersystems.textsecure.api.TextSecureMessageSender;
|
||||
import org.whispersystems.textsecure.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
|
||||
@ -41,7 +38,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
private final long messageId;
|
||||
|
||||
public PushTextSendJob(Context context, long messageId, String destination) {
|
||||
super(context, constructParameters(context, destination, false));
|
||||
super(context, constructParameters(context, destination));
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@ -54,26 +51,22 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
|
||||
@Override
|
||||
public void onSend(MasterSecret masterSecret) throws NoSuchMessageException, RetryLaterException {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
String destination = record.getIndividualRecipient().getNumber();
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
|
||||
if (deliver(masterSecret, record, destination)) {
|
||||
database.markAsPush(messageId);
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId);
|
||||
}
|
||||
deliver(masterSecret, record);
|
||||
database.markAsPush(messageId);
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId);
|
||||
|
||||
} catch (InsecureFallbackApprovalException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsPendingInsecureSmsFallback(record.getId());
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
|
||||
} catch (SecureFallbackApprovalException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsPendingSecureSmsFallback(record.getId());
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new DirectoryRefreshJob(context));
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, e.getE164Number(), false);
|
||||
@ -102,55 +95,26 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
}
|
||||
|
||||
private boolean deliver(MasterSecret masterSecret, SmsMessageRecord message, String destination)
|
||||
throws UntrustedIdentityException, SecureFallbackApprovalException,
|
||||
InsecureFallbackApprovalException, RetryLaterException
|
||||
private void deliver(MasterSecret masterSecret, SmsMessageRecord message)
|
||||
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException
|
||||
{
|
||||
boolean isSmsFallbackSupported = isSmsFallbackSupported(context, destination, false);
|
||||
|
||||
try {
|
||||
TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber());
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber());
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureMessage textSecureMessage = TextSecureMessage.newBuilder()
|
||||
.withTimestamp(message.getDateSent())
|
||||
.withBody(message.getBody().getBody())
|
||||
.asEndSessionMessage(message.isEndSession())
|
||||
.build();
|
||||
|
||||
if (message.isEndSession()) {
|
||||
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), null,
|
||||
null, null, true, true));
|
||||
} else {
|
||||
messageSender.sendMessage(address, new TextSecureMessage(message.getDateSent(), message.getBody().getBody()));
|
||||
}
|
||||
|
||||
return true;
|
||||
messageSender.sendMessage(address, textSecureMessage);
|
||||
} catch (InvalidNumberException | UnregisteredUserException e) {
|
||||
Log.w(TAG, e);
|
||||
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
|
||||
else DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
|
||||
throw new InsecureFallbackApprovalException(e);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
if (isSmsFallbackSupported) fallbackOrAskApproval(masterSecret, message, destination);
|
||||
else throw new RetryLaterException(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void fallbackOrAskApproval(MasterSecret masterSecret, SmsMessageRecord smsMessage, String destination)
|
||||
throws SecureFallbackApprovalException, InsecureFallbackApprovalException
|
||||
{
|
||||
Recipient recipient = smsMessage.getIndividualRecipient();
|
||||
boolean isSmsFallbackApprovalRequired = isSmsFallbackApprovalRequired(destination, false);
|
||||
|
||||
if (!isSmsFallbackApprovalRequired) {
|
||||
Log.w(TAG, "Falling back to SMS");
|
||||
DatabaseFactory.getSmsDatabase(context).markAsForcedSms(smsMessage.getId());
|
||||
ApplicationContext.getInstance(context).getJobManager().add(new SmsSendJob(context, messageId, destination));
|
||||
} else if (!SessionUtil.hasSession(context, masterSecret, recipient)) {
|
||||
Log.w(TAG, "Marking message as pending insecure fallback.");
|
||||
throw new InsecureFallbackApprovalException("Pending user approval for fallback to insecure SMS");
|
||||
} else {
|
||||
Log.w(TAG, "Marking message as pending secure fallback.");
|
||||
throw new SecureFallbackApprovalException("Pending user approval for fallback to secure SMS");
|
||||
throw new RetryLaterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public abstract class SendJob extends MasterSecretJob {
|
||||
|
||||
protected abstract void onSend(MasterSecret masterSecret) throws Exception;
|
||||
|
||||
// FIXME: This should return a value rather than modifying one.
|
||||
protected void prepareMessageMedia(MasterSecret masterSecret, SendReq message,
|
||||
MediaConstraints constraints, boolean toMemory)
|
||||
throws IOException, UndeliverableMessageException
|
||||
|
@ -7,32 +7,16 @@ import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.crypto.SmsCipher;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
import org.whispersystems.libaxolotl.StaleKeyExchangeException;
|
||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||
|
||||
@ -54,11 +38,7 @@ public class SmsDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
if (KeyCachingService.getMasterSecret(context) == null) {
|
||||
MessageNotifier.updateNotification(context, null, -2);
|
||||
}
|
||||
}
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws NoSuchMessageException {
|
||||
@ -68,27 +48,17 @@ public class SmsDecryptJob extends MasterSecretJob {
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
IncomingTextMessage message = createIncomingTextMessage(masterSecret, record);
|
||||
long messageId = record.getId();
|
||||
long threadId = record.getThreadId();
|
||||
|
||||
if (message.isSecureMessage()) handleSecureMessage(masterSecret, messageId, threadId, message);
|
||||
else if (message.isPreKeyBundle()) handlePreKeyWhisperMessage(masterSecret, messageId, threadId, (IncomingPreKeyBundleMessage) message);
|
||||
else if (message.isKeyExchange()) handleKeyExchangeMessage(masterSecret, messageId, threadId, (IncomingKeyExchangeMessage) message);
|
||||
else if (message.isEndSession()) handleSecureMessage(masterSecret, messageId, threadId, message);
|
||||
else database.updateMessageBody(masterSecret, messageId, message.getMessageBody());
|
||||
if (message.isSecureMessage()) {
|
||||
database.markAsLegacyVersion(messageId);
|
||||
} else {
|
||||
database.updateMessageBody(masterSecret, messageId, message.getMessageBody());
|
||||
}
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
} catch (LegacyMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsLegacyVersion(messageId);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
} catch (DuplicateMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsDecryptDuplicate(messageId);
|
||||
} catch (NoSessionException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsNoSession(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,77 +72,6 @@ public class SmsDecryptJob extends MasterSecretJob {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private void handleSecureMessage(MasterSecret masterSecret, long messageId, long threadId,
|
||||
IncomingTextMessage message)
|
||||
throws NoSessionException, DuplicateMessageException,
|
||||
InvalidMessageException, LegacyMessageException
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsCipher cipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret));
|
||||
IncomingTextMessage plaintext = cipher.decrypt(context, message);
|
||||
|
||||
database.updateMessageBody(masterSecret, messageId, plaintext.getMessageBody());
|
||||
|
||||
if (message.isEndSession()) SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
|
||||
}
|
||||
|
||||
private void handlePreKeyWhisperMessage(MasterSecret masterSecret, long messageId, long threadId,
|
||||
IncomingPreKeyBundleMessage message)
|
||||
throws NoSessionException, DuplicateMessageException,
|
||||
InvalidMessageException, LegacyMessageException
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
try {
|
||||
SmsCipher smsCipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret));
|
||||
IncomingEncryptedMessage plaintext = smsCipher.decrypt(context, message);
|
||||
|
||||
database.updateBundleMessageBody(masterSecret, messageId, plaintext.getMessageBody());
|
||||
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsInvalidVersionKeyExchange(messageId);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleKeyExchangeMessage(MasterSecret masterSecret, long messageId, long threadId,
|
||||
IncomingKeyExchangeMessage message)
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
|
||||
try {
|
||||
SmsCipher cipher = new SmsCipher(new TextSecureAxolotlStore(context, masterSecret));
|
||||
OutgoingKeyExchangeMessage response = cipher.process(context, message);
|
||||
|
||||
database.markAsProcessedKeyExchange(messageId);
|
||||
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
|
||||
|
||||
if (response != null) {
|
||||
MessageSender.send(context, masterSecret, response, threadId, true);
|
||||
}
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsInvalidVersionKeyExchange(messageId);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsCorruptKeyExchange(messageId);
|
||||
} catch (LegacyMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsLegacyVersion(messageId);
|
||||
} catch (StaleKeyExchangeException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsStaleKeyExchange(messageId);
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
@ -189,28 +88,17 @@ public class SmsDecryptJob extends MasterSecretJob {
|
||||
private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
String plaintextBody = record.getBody().getBody();
|
||||
|
||||
if (record.isAsymmetricEncryption()) {
|
||||
plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
|
||||
}
|
||||
|
||||
IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(),
|
||||
record.getRecipientDeviceId(),
|
||||
record.getDateSent(),
|
||||
plaintextBody,
|
||||
record.getBody().getBody(),
|
||||
Optional.<TextSecureGroup>absent());
|
||||
|
||||
if (record.isEndSession()) {
|
||||
return new IncomingEndSessionMessage(message);
|
||||
} else if (record.isBundleKeyExchange()) {
|
||||
return new IncomingPreKeyBundleMessage(message, message.getMessageBody());
|
||||
} else if (record.isKeyExchange()) {
|
||||
return new IncomingKeyExchangeMessage(message, message.getMessageBody());
|
||||
} else if (record.isSecure()) {
|
||||
if (record.isAsymmetricEncryption()) {
|
||||
String plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
|
||||
return new IncomingTextMessage(message, plaintextBody);
|
||||
} else {
|
||||
return new IncomingEncryptedMessage(message, message.getMessageBody());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
@ -24,8 +24,6 @@ public class SmsReceiveJob extends ContextJob {
|
||||
|
||||
private static final String TAG = SmsReceiveJob.class.getSimpleName();
|
||||
|
||||
private static MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
|
||||
|
||||
private final Object[] pdus;
|
||||
|
||||
public SmsReceiveJob(Context context, Object[] pdus) {
|
||||
@ -67,18 +65,15 @@ public class SmsReceiveJob extends ContextJob {
|
||||
|
||||
if (message.isSecureMessage()) {
|
||||
messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message);
|
||||
database.markAsLegacyVersion(messageAndThreadId.first);
|
||||
} else if (masterSecret == null) {
|
||||
messageAndThreadId = database.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message);
|
||||
} else {
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, message);
|
||||
}
|
||||
|
||||
if (masterSecret == null || message.isSecureMessage() || message.isKeyExchange() || message.isEndSession()) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new SmsDecryptJob(context, messageAndThreadId.first));
|
||||
} else {
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, message);
|
||||
}
|
||||
|
||||
return messageAndThreadId;
|
||||
@ -102,7 +97,7 @@ public class SmsReceiveJob extends ContextJob {
|
||||
WirePrefix.isPreKeyBundle(message.getMessageBody()) ||
|
||||
WirePrefix.isEndSession(message.getMessageBody()))
|
||||
{
|
||||
return Optional.fromNullable(multipartMessageHandler.processPotentialMultipartMessage(message));
|
||||
return Optional.<IncomingTextMessage>of(new IncomingEncryptedMessage(message, message.getMessageBody()));
|
||||
} else {
|
||||
return Optional.of(message);
|
||||
}
|
||||
|
@ -8,8 +8,6 @@ import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SmsCipher;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
@ -20,14 +18,11 @@ import org.thoughtcrime.securesms.jobs.requirements.ServiceRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
|
||||
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.NoSessionException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -55,15 +50,11 @@ public class SmsSendJob extends SendJob {
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
|
||||
deliver(masterSecret, record);
|
||||
deliver(record);
|
||||
} catch (UndeliverableMessageException ude) {
|
||||
Log.w(TAG, ude);
|
||||
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(record.getId());
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
|
||||
} catch (InsecureFallbackApprovalException ifae) {
|
||||
Log.w(TAG, ifae);
|
||||
DatabaseFactory.getSmsDatabase(context).markAsPendingInsecureSmsFallback(record.getId());
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, record.getRecipients(), record.getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,61 +73,17 @@ public class SmsSendJob extends SendJob {
|
||||
MessageNotifier.notifyMessageDeliveryFailed(context, recipients, threadId);
|
||||
}
|
||||
|
||||
private void deliver(MasterSecret masterSecret, SmsMessageRecord record)
|
||||
throws UndeliverableMessageException, InsecureFallbackApprovalException
|
||||
{
|
||||
if (!NumberUtil.isValidSmsOrEmail(record.getIndividualRecipient().getNumber())) {
|
||||
throw new UndeliverableMessageException("Not a valid SMS destination! " + record.getIndividualRecipient().getNumber());
|
||||
}
|
||||
|
||||
if (record.isSecure() || record.isKeyExchange() || record.isEndSession()) {
|
||||
deliverSecureMessage(masterSecret, record);
|
||||
} else {
|
||||
deliverPlaintextMessage(record);
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverSecureMessage(MasterSecret masterSecret, SmsMessageRecord message)
|
||||
throws UndeliverableMessageException, InsecureFallbackApprovalException
|
||||
{
|
||||
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
|
||||
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
|
||||
|
||||
if (message.isSecure() || message.isEndSession()) {
|
||||
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
|
||||
}
|
||||
|
||||
ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
|
||||
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
|
||||
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
|
||||
|
||||
Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
|
||||
|
||||
for (int i=0;i<messages.size();i++) {
|
||||
// NOTE 11/04/14 -- There's apparently a bug where for some unknown recipients
|
||||
// and messages, this will throw an NPE. We have no idea why, so we're just
|
||||
// catching it and marking the message as a failure. That way at least it
|
||||
// doesn't repeatedly crash every time you start the app.
|
||||
try {
|
||||
SmsManager.getDefault().sendTextMessage(message.getIndividualRecipient().getNumber(), null, messages.get(i),
|
||||
sentIntents.get(i),
|
||||
deliveredIntents == null ? null : deliveredIntents.get(i));
|
||||
} catch (NullPointerException npe) {
|
||||
Log.w(TAG, npe);
|
||||
Log.w(TAG, "Recipient: " + message.getIndividualRecipient().getNumber());
|
||||
Log.w(TAG, "Message Total Parts/Current: " + messages.size() + "/" + i);
|
||||
Log.w(TAG, "Message Part Length: " + messages.get(i).getBytes().length);
|
||||
throw new UndeliverableMessageException(npe);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
Log.w(TAG, iae);
|
||||
throw new UndeliverableMessageException(iae);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deliverPlaintextMessage(SmsMessageRecord message)
|
||||
private void deliver(SmsMessageRecord message)
|
||||
throws UndeliverableMessageException
|
||||
{
|
||||
if (!NumberUtil.isValidSmsOrEmail(message.getIndividualRecipient().getNumber())) {
|
||||
throw new UndeliverableMessageException("Not a valid SMS destination! " + message.getIndividualRecipient().getNumber());
|
||||
}
|
||||
|
||||
if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
|
||||
throw new UndeliverableMessageException("Trying to send a secure SMS?");
|
||||
}
|
||||
|
||||
ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
|
||||
ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
|
||||
ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
|
||||
@ -166,17 +113,6 @@ public class SmsSendJob extends SendJob {
|
||||
}
|
||||
}
|
||||
|
||||
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
|
||||
OutgoingTextMessage message)
|
||||
throws InsecureFallbackApprovalException
|
||||
{
|
||||
try {
|
||||
return new SmsCipher(new TextSecureAxolotlStore(context, masterSecret)).encrypt(message);
|
||||
} catch (NoSessionException e) {
|
||||
throw new InsecureFallbackApprovalException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<PendingIntent> constructSentIntents(long messageId, long type,
|
||||
ArrayList<String> messages, boolean secure)
|
||||
{
|
||||
|
@ -4,22 +4,10 @@ public class MmsSendResult {
|
||||
|
||||
private final byte[] messageId;
|
||||
private final int responseStatus;
|
||||
private final boolean upgradedSecure;
|
||||
private final boolean push;
|
||||
|
||||
public MmsSendResult(byte[] messageId, int responseStatus, boolean upgradedSecure, boolean push) {
|
||||
public MmsSendResult(byte[] messageId, int responseStatus) {
|
||||
this.messageId = messageId;
|
||||
this.responseStatus = responseStatus;
|
||||
this.upgradedSecure = upgradedSecure;
|
||||
this.push = push;
|
||||
}
|
||||
|
||||
public boolean isPush() {
|
||||
return push;
|
||||
}
|
||||
|
||||
public boolean isUpgradedSecure() {
|
||||
return upgradedSecure;
|
||||
}
|
||||
|
||||
public int getResponseStatus() {
|
||||
|
@ -12,20 +12,14 @@ import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.OutgoingSmsPreference;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
||||
private static final String KITKAT_DEFAULT_PREF = "pref_set_default";
|
||||
private static final String OUTGOING_SMS_PREF = "pref_outgoing_sms";
|
||||
private static final String MMS_PREF = "pref_mms_preferences";
|
||||
|
||||
@Override
|
||||
@ -33,12 +27,8 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
||||
super.onCreate(paramBundle);
|
||||
addPreferencesFromResource(R.xml.preferences_sms_mms);
|
||||
|
||||
this.findPreference(OUTGOING_SMS_PREF)
|
||||
.setOnPreferenceChangeListener(new OutgoingSmsPreferenceListener());
|
||||
this.findPreference(MMS_PREF)
|
||||
.setOnPreferenceClickListener(new ApnPreferencesClickListener());
|
||||
|
||||
initializeOutgoingSmsSummary((OutgoingSmsPreference) findPreference(OUTGOING_SMS_PREF));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -75,50 +65,6 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeOutgoingSmsSummary(OutgoingSmsPreference pref) {
|
||||
pref.setSummary(buildOutgoingSmsDescription());
|
||||
}
|
||||
|
||||
private class OutgoingSmsPreferenceListener implements Preference.OnPreferenceChangeListener {
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(final Preference preference, Object newValue) {
|
||||
|
||||
preference.setSummary(buildOutgoingSmsDescription());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildOutgoingSmsDescription() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final boolean dataFallback = TextSecurePreferences.isFallbackSmsAllowed(getActivity());
|
||||
final boolean dataFallbackAsk = TextSecurePreferences.isFallbackSmsAskRequired(getActivity());
|
||||
final boolean mmsFallback = TextSecurePreferences.isFallbackMmsEnabled(getActivity());
|
||||
final boolean nonData = TextSecurePreferences.isDirectSmsAllowed(getActivity());
|
||||
|
||||
if (dataFallback) {
|
||||
builder.append(getString(R.string.preferences__sms_outgoing_push_users));
|
||||
|
||||
List<String> fallbackOptions = new LinkedList<>();
|
||||
if (dataFallbackAsk) fallbackOptions.add(getString(R.string.preferences__sms_fallback_push_users_ask));
|
||||
if (!mmsFallback) fallbackOptions.add(getString(R.string.preferences__sms_fallback_push_users_no_mms));
|
||||
|
||||
if (fallbackOptions.size() > 0) {
|
||||
builder.append(" (")
|
||||
.append(TextUtils.join(", ", fallbackOptions))
|
||||
.append(")");
|
||||
}
|
||||
}
|
||||
if (nonData) {
|
||||
if (dataFallback) builder.append(", ");
|
||||
builder.append(getString(R.string.preferences__sms_fallback_non_push_users));
|
||||
}
|
||||
if (!dataFallback && !nonData) {
|
||||
builder.append(getString(R.string.preferences__sms_fallback_nobody));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private class ApnPreferencesClickListener implements Preference.OnPreferenceClickListener {
|
||||
|
||||
@Override
|
||||
@ -135,7 +81,7 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
|
||||
public static CharSequence getSummary(Context context) {
|
||||
return getIncomingSmsSummary(context) + ", " + getOutgoingSmsSummary(context);
|
||||
return getIncomingSmsSummary(context);
|
||||
}
|
||||
|
||||
private static CharSequence getIncomingSmsSummary(Context context) {
|
||||
@ -160,21 +106,4 @@ public class SmsMmsPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
return context.getString(incomingSmsResId, context.getString(incomingSmsSummary));
|
||||
}
|
||||
|
||||
private static CharSequence getOutgoingSmsSummary(Context context) {
|
||||
final int onResId = R.string.ApplicationPreferencesActivity_on;
|
||||
final int offResId = R.string.ApplicationPreferencesActivity_off;
|
||||
final int partialResId = R.string.ApplicationPreferencesActivity_partial;
|
||||
final int outgoingSmsResId = R.string.ApplicationPreferencesActivity_outgoing_sms_summary;
|
||||
|
||||
final int outgoingSmsSummary;
|
||||
if (TextSecurePreferences.isFallbackSmsAllowed(context) && TextSecurePreferences.isDirectSmsAllowed(context)) {
|
||||
outgoingSmsSummary = onResId;
|
||||
} else if (TextSecurePreferences.isFallbackSmsAllowed(context) ^ TextSecurePreferences.isDirectSmsAllowed(context)) {
|
||||
outgoingSmsSummary = partialResId;
|
||||
} else {
|
||||
outgoingSmsSummary = offResId;
|
||||
}
|
||||
return context.getString(outgoingSmsResId, context.getString(outgoingSmsSummary));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
|
||||
public class IncomingPreKeyBundleMessage extends IncomingTextMessage {
|
||||
|
||||
public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) {
|
||||
super(base, newBody);
|
||||
@ -15,4 +15,5 @@ public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
|
||||
public boolean isPreKeyBundle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -164,10 +164,6 @@ public class IncomingTextMessage implements Parcelable {
|
||||
return replyPathPresent;
|
||||
}
|
||||
|
||||
public boolean isKeyExchange() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSecureMessage() {
|
||||
return false;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -4,7 +4,8 @@ public class InsecureFallbackApprovalException extends Exception {
|
||||
public InsecureFallbackApprovalException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
public InsecureFallbackApprovalException(Exception e) {
|
||||
|
||||
public InsecureFallbackApprovalException(Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
package org.thoughtcrime.securesms.transport;
|
||||
|
||||
public class SecureFallbackApprovalException extends Exception {
|
||||
public SecureFallbackApprovalException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
@ -40,7 +41,6 @@ public class TextSecurePreferences {
|
||||
public static final String ALL_SMS_PREF = "pref_all_sms";
|
||||
public static final String PASSPHRASE_TIMEOUT_INTERVAL_PREF = "pref_timeout_interval";
|
||||
private static final String PASSPHRASE_TIMEOUT_PREF = "pref_timeout_passphrase";
|
||||
private static final String AUTO_KEY_EXCHANGE_PREF = "pref_auto_complete_key_exchange";
|
||||
public static final String SCREEN_SECURITY_PREF = "pref_screen_security";
|
||||
private static final String ENTER_SENDS_PREF = "pref_enter_sends";
|
||||
private static final String ENTER_PRESENT_PREF = "pref_enter_key";
|
||||
@ -57,10 +57,6 @@ public class TextSecurePreferences {
|
||||
private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications";
|
||||
|
||||
private static final String LOCAL_REGISTRATION_ID_PREF = "pref_local_registration_id";
|
||||
private static final String FALLBACK_SMS_ALLOWED_PREF = "pref_allow_sms_traffic_out";
|
||||
private static final String FALLBACK_SMS_ASK_REQUIRED_PREF = "pref_sms_fallback_ask";
|
||||
private static final String DIRECT_SMS_ALLOWED_PREF = "pref_sms_non_data_out";
|
||||
private static final String FALLBACK_MMS_ENABLED_PREF = "pref_mms_fallback_enabled";
|
||||
private static final String SIGNED_PREKEY_REGISTERED_PREF = "pref_signed_prekey_registered";
|
||||
private static final String WIFI_SMS_PREF = "pref_wifi_sms";
|
||||
|
||||
@ -68,7 +64,6 @@ public class TextSecurePreferences {
|
||||
private static final String GCM_REGISTRATION_ID_VERSION_PREF = "pref_gcm_registration_id_version";
|
||||
private static final String WEBSOCKET_REGISTERED_PREF = "pref_websocket_registered";
|
||||
|
||||
private static final String PUSH_REGISTRATION_REMINDER_PREF = "pref_push_registration_reminder";
|
||||
public static final String REPEAT_ALERTS_PREF = "pref_repeat_alerts";
|
||||
|
||||
public static boolean isWebsocketRegistered(Context context) {
|
||||
@ -119,36 +114,12 @@ public class TextSecurePreferences {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFallbackSmsAllowed(Context context) {
|
||||
return getBooleanPreference(context, FALLBACK_SMS_ALLOWED_PREF, true);
|
||||
}
|
||||
|
||||
public static void setFallbackSmsAllowed(Context context, boolean allowed) {
|
||||
setBooleanPreference(context, FALLBACK_SMS_ALLOWED_PREF, allowed);
|
||||
}
|
||||
|
||||
public static boolean isFallbackSmsAskRequired(Context context) {
|
||||
return getBooleanPreference(context, FALLBACK_SMS_ASK_REQUIRED_PREF, false);
|
||||
}
|
||||
|
||||
public static void setFallbackSmsAskRequired(Context context, boolean required) {
|
||||
setBooleanPreference(context, FALLBACK_SMS_ASK_REQUIRED_PREF, required);
|
||||
}
|
||||
|
||||
public static boolean isFallbackMmsEnabled(Context context) {
|
||||
return getBooleanPreference(context, FALLBACK_MMS_ENABLED_PREF, true);
|
||||
}
|
||||
|
||||
public static void setFallbackMmsEnabled(Context context, boolean enabled) {
|
||||
setBooleanPreference(context, FALLBACK_MMS_ENABLED_PREF, enabled);
|
||||
}
|
||||
|
||||
public static boolean isDirectSmsAllowed(Context context) {
|
||||
return getBooleanPreference(context, DIRECT_SMS_ALLOWED_PREF, true);
|
||||
}
|
||||
|
||||
public static void setDirectSmsAllowed(Context context, boolean allowed) {
|
||||
setBooleanPreference(context, DIRECT_SMS_ALLOWED_PREF, allowed);
|
||||
public static boolean isSmsEnabled(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
return Util.isDefaultSmsProvider(context);
|
||||
} else {
|
||||
return isInterceptAllSmsEnabled(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getLocalRegistrationId(Context context) {
|
||||
@ -304,10 +275,6 @@ public class TextSecurePreferences {
|
||||
setStringPreference(context, IDENTITY_PREF, identityUri);
|
||||
}
|
||||
|
||||
public static boolean isAutoRespondKeyExchangeEnabled(Context context) {
|
||||
return getBooleanPreference(context, AUTO_KEY_EXCHANGE_PREF, true);
|
||||
}
|
||||
|
||||
public static boolean isScreenSecurityEnabled(Context context) {
|
||||
return getBooleanPreference(context, SCREEN_SECURITY_PREF, true);
|
||||
}
|
||||
@ -431,14 +398,6 @@ public class TextSecurePreferences {
|
||||
return Integer.parseInt(getStringPreference(context, THREAD_TRIM_LENGTH, "500"));
|
||||
}
|
||||
|
||||
public static long getLastPushReminderTime(Context context) {
|
||||
return getLongPreference(context, PUSH_REGISTRATION_REMINDER_PREF, 0L);
|
||||
}
|
||||
|
||||
public static void setLastPushReminderTime(Context context, long time) {
|
||||
setLongPreference(context, PUSH_REGISTRATION_REMINDER_PREF, time);
|
||||
}
|
||||
|
||||
public static void setBooleanPreference(Context context, String key, boolean value) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user