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