Support for per-recipient muting, blocking, and ringtones.
Fixes #757 Fixes #354 Fixes #222 Closes #1815 Closes #3378 // FREEBIE
@ -226,6 +226,14 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".RecipientPreferenceActivity"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".BlockedContactsActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
|
||||
|
||||
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
|
||||
|
BIN
res/drawable-hdpi/ic_block_grey600_18dp.png
Normal file
After Width: | Height: | Size: 502 B |
BIN
res/drawable-hdpi/ic_block_white_18dp.png
Normal file
After Width: | Height: | Size: 484 B |
BIN
res/drawable-hdpi/ic_volume_off_grey600_18dp.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
res/drawable-hdpi/ic_volume_off_white_18dp.png
Normal file
After Width: | Height: | Size: 461 B |
BIN
res/drawable-mdpi/ic_block_grey600_18dp.png
Normal file
After Width: | Height: | Size: 359 B |
BIN
res/drawable-mdpi/ic_block_white_18dp.png
Normal file
After Width: | Height: | Size: 355 B |
BIN
res/drawable-mdpi/ic_volume_off_grey600_18dp.png
Normal file
After Width: | Height: | Size: 347 B |
BIN
res/drawable-mdpi/ic_volume_off_white_18dp.png
Normal file
After Width: | Height: | Size: 342 B |
BIN
res/drawable-xhdpi/ic_block_grey600_18dp.png
Normal file
After Width: | Height: | Size: 622 B |
BIN
res/drawable-xhdpi/ic_block_white_18dp.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
res/drawable-xhdpi/ic_volume_off_grey600_18dp.png
Normal file
After Width: | Height: | Size: 543 B |
BIN
res/drawable-xhdpi/ic_volume_off_white_18dp.png
Normal file
After Width: | Height: | Size: 532 B |
BIN
res/drawable-xxhdpi/ic_block_grey600_18dp.png
Normal file
After Width: | Height: | Size: 855 B |
BIN
res/drawable-xxhdpi/ic_block_white_18dp.png
Normal file
After Width: | Height: | Size: 848 B |
BIN
res/drawable-xxhdpi/ic_volume_off_grey600_18dp.png
Normal file
After Width: | Height: | Size: 754 B |
BIN
res/drawable-xxhdpi/ic_volume_off_white_18dp.png
Normal file
After Width: | Height: | Size: 745 B |
BIN
res/drawable-xxxhdpi/ic_block_grey600_18dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-xxxhdpi/ic_block_white_18dp.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-xxxhdpi/ic_volume_off_grey600_18dp.png
Normal file
After Width: | Height: | Size: 921 B |
BIN
res/drawable-xxxhdpi/ic_volume_off_white_18dp.png
Normal file
After Width: | Height: | Size: 911 B |
32
res/layout/blocked_contact_list_item.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<org.thoughtcrime.securesms.preferences.BlockedContactListItem
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||
android:paddingRight="25dip">
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/contact_photo_image"
|
||||
android:layout_width="@dimen/contact_selection_photo_size"
|
||||
android:layout_height="@dimen/contact_selection_photo_size"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:cropToPadding="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
|
||||
|
||||
<TextView android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginLeft="14dip"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="marquee"
|
||||
android:layout_toRightOf="@id/contact_photo_image"
|
||||
android:gravity="center_vertical|left"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
</org.thoughtcrime.securesms.preferences.BlockedContactListItem>
|
23
res/layout/blocked_contacts_fragment.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingRight="16dip">
|
||||
|
||||
<ListView android:id="@id/android:list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:drawSelectorOnTop="false"/>
|
||||
|
||||
<TextView android:id="@id/android:empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center|center_vertical"
|
||||
android:gravity="center|center_vertical"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/blocked_contacts_fragment__no_blocked_contacts"/>
|
||||
|
||||
</LinearLayout>
|
@ -131,6 +131,13 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button android:id="@+id/unblock_button"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:text="Unblock"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView android:id="@+id/space_left"
|
||||
android:paddingLeft="5dip"
|
||||
android:layout_width="fill_parent"
|
||||
|
@ -17,7 +17,6 @@
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:cropToPadding="true"
|
||||
android:visibility="gone"
|
||||
tools:src="@drawable/ic_contact_picture"
|
||||
android:contentDescription="@string/conversation_list_item_view__contact_photo_image"
|
||||
android:layout_marginRight="10dp"
|
||||
@ -50,7 +49,8 @@
|
||||
android:textColor="?attr/conversation_list_item_contact_color"
|
||||
android:singleLine="true"
|
||||
tools:text="Jules Bonnot"
|
||||
android:ellipsize="marquee" />
|
||||
android:ellipsize="end"
|
||||
android:drawablePadding="5dp"/>
|
||||
|
||||
<ImageView android:id="@+id/error"
|
||||
android:layout_marginLeft="3dip"
|
||||
|
29
res/layout/conversation_title_view.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.ConversationTitleView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:drawablePadding="5dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_gravity="center_vertical"
|
||||
style="@style/TextSecure.TitleTextStyle"/>
|
||||
|
||||
<TextView android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
style="@style/TextSecure.SubtitleTextStyle"/>
|
||||
|
||||
</org.thoughtcrime.securesms.ConversationTitleView>
|
58
res/layout/recipient_preference_activity.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_height="130dp"
|
||||
android:layout_width="match_parent"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/TextSecure.LightActionBar">
|
||||
|
||||
<RelativeLayout android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/avatar"
|
||||
android:foreground="@drawable/contact_photo_background"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:cropToPadding="true"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_alignParentLeft="true"/>
|
||||
|
||||
<TextView android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
style="@style/TextSecure.TitleTextStyle"
|
||||
android:layout_toRightOf="@id/avatar"
|
||||
android:layout_marginLeft="10dip"/>
|
||||
|
||||
<TextView android:id="@+id/blocked_indicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/avatar"
|
||||
android:layout_below="@id/name"
|
||||
android:layout_alignLeft="@id/name"
|
||||
android:text="@string/recipient_preference_activity__blocked"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?recipient_preference_blocked"
|
||||
android:textAllCaps="true" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
||||
<FrameLayout android:id="@+id/preference_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
5
res/menu/conversation_muted.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:title="@string/conversation_muted__unmute"
|
||||
android:id="@+id/menu_unmute_notifications" />
|
||||
</menu>
|
7
res/menu/conversation_unmuted.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:title="@string/conversation_unmuted__mute_notifications"
|
||||
android:id="@+id/menu_mute_notifications" />
|
||||
|
||||
</menu>
|
@ -171,4 +171,23 @@
|
||||
<item>@string/arrays__use_custom</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mute_durations">
|
||||
<item>@string/arrays__mute_for_one_hour</item>
|
||||
<item>@string/arrays__mute_for_two_hours</item>
|
||||
<item>@string/arrays__mute_for_one_day</item>
|
||||
<item>@string/arrays__mute_for_seven_days</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="recipient_vibrate_entries">
|
||||
<item>@string/arrays__settings_default</item>
|
||||
<item>@string/arrays__enabled</item>
|
||||
<item>@string/arrays__disabled</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="recipient_vibrate_values">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
@ -116,4 +116,6 @@
|
||||
|
||||
<attr name="group_members_dialog_icon" format="reference"/>
|
||||
<attr name="lockscreen_watermark" format="reference" />
|
||||
|
||||
<attr name="recipient_preference_blocked" format="color"/>
|
||||
</resources>
|
||||
|
@ -56,6 +56,9 @@
|
||||
<string name="AttachmentTypeSelectorAdapter_audio">Audio</string>
|
||||
<string name="AttachmentTypeSelectorAdapter_contact">Contact info</string>
|
||||
|
||||
<!-- BlockedContactsActivity -->
|
||||
<string name="BlockedContactsActivity_blocked_contacts">Blocked contacts</string>
|
||||
|
||||
<!-- ConfirmIdentityDialog -->
|
||||
<string name="ConfirmIdentityDialog_the_signature_on_this_key_exchange_is_different">The
|
||||
identifying key material for %1$s has changed. This could either mean that someone is trying to
|
||||
@ -118,6 +121,9 @@
|
||||
<string name="ConversationActivity_mms_not_supported_title">MMS not supported</string>
|
||||
<string name="ConversationActivity_mms_not_supported_message">This message cannot be sent since your carrier doesn\'t support MMS.</string>
|
||||
<string name="ConversationActivity_specify_recipient">Please choose a contact</string>
|
||||
<string name="ConversationActivity_unblock_question">Unblock?</string>
|
||||
<string name="ConversationActivity_are_you_sure_you_want_to_unblock_this_contact">Are you sure you want to unblock this contact?</string>
|
||||
<string name="ConversationActivity_unblock">Unblock</string>
|
||||
|
||||
<!-- ConversationFragment -->
|
||||
<string name="ConversationFragment_message_details">Message details</string>
|
||||
@ -294,6 +300,14 @@
|
||||
<string name="RatingManager_no_thanks">No thanks</string>
|
||||
<string name="RatingManager_later">Later</string>
|
||||
|
||||
<!-- RecipientPreferencesActivity -->
|
||||
<string name="RecipientPreferenceActivity_block_this_contact_question">Block this contact?</string>
|
||||
<string name="RecipientPreferenceActivity_you_will_no_longer_see_messages_from_this_user">You will no longer see messages sent from this user.</string>
|
||||
<string name="RecipientPreferenceActivity_block">Block</string>
|
||||
<string name="RecipientPreferenceActivity_unblock_this_contact_question">Unblock this contact?</string>
|
||||
<string name="RecipientPreferenceActivity_are_you_sure_you_want_to_unblock_this_contact">Are you sure you want to unblock this contact?</string>
|
||||
<string name="RecipientPreferenceActivity_unblock">Unblock</string>
|
||||
|
||||
<!-- RegistrationActivity -->
|
||||
<string name="RegistrationActivity_connect_with_textsecure">Connect With TextSecure</string>
|
||||
<string name="RegistrationActivity_select_your_country">Select your country</string>
|
||||
@ -465,9 +479,15 @@
|
||||
<!-- ContactSelectionListFragment-->
|
||||
<string name="ContactSelectionlistFragment_select_for">Select for</string>
|
||||
|
||||
<!-- blocked_contacts_fragment -->
|
||||
<string name="blocked_contacts_fragment__no_blocked_contacts">No blocked contacts...</string>
|
||||
|
||||
<!-- contact_selection_recent_activity -->
|
||||
<string name="contact_selection_recent_activity__no_recent_calls">No recent calls.</string>
|
||||
|
||||
<!-- conversation_title_view -->
|
||||
<string name="conversation_title_view__conversation_muted">Conversation muted</string>
|
||||
|
||||
<!-- conversation_activity -->
|
||||
<string name="conversation_activity__type_message_push">Send TextSecure message</string>
|
||||
<string name="conversation_activity__type_message_sms_insecure">Send unsecured SMS</string>
|
||||
@ -563,6 +583,16 @@
|
||||
<string name="prompt_mms_activity__textsecure_requires_mms_settings_to_deliver_media_and_group_messages">TextSecure requires MMS settings to deliver media and group messages through your wireless carrier. Your device does not make this information available, which is occasionally true for locked devices and other restrictive configurations.</string>
|
||||
<string name="prompt_mms_activity__to_send_media_and_group_messages_click_ok">To send media and group messages, click \'OK\' and complete the requested settings. The MMS settings for your carrier can generally be located by searching for \'your carrier APN\'. You will only need to do this once.</string>
|
||||
|
||||
<!-- recipient_preferences_activity -->
|
||||
<string name="recipient_preference_activity__blocked">BLOCKED</string>
|
||||
|
||||
<!-- recipient_preferences -->
|
||||
<string name="recipient_preferences__mute_conversation">Mute conversation</string>
|
||||
<string name="recipient_preferences__disable_notifications_for_this_conversation">Disable notifications for this conversation</string>
|
||||
<string name="recipient_preferences__ringtone">Ringtone</string>
|
||||
<string name="recipient_preferences__vibrate">Vibrate</string>
|
||||
<string name="recipient_preferences__block">Block</string>
|
||||
|
||||
<!-- registration_activity -->
|
||||
<string name="registration_activity__textsecure_can_use_instant_messages_to_avoid_sms_charges_when_communicating_with_other_textsecure_users">
|
||||
Verify your phone number to connect with TextSecure.
|
||||
@ -684,6 +714,15 @@
|
||||
<string name="arrays__use_default">Use default</string>
|
||||
<string name="arrays__use_custom">Use custom</string>
|
||||
|
||||
<string name="arrays__mute_for_one_hour">Mute for 1 hour</string>
|
||||
<string name="arrays__mute_for_two_hours">Mute for 2 hours</string>
|
||||
<string name="arrays__mute_for_one_day">Mute for 1 day</string>
|
||||
<string name="arrays__mute_for_seven_days">Mute for 7 days</string>
|
||||
|
||||
<string name="arrays__settings_default">Settings default</string>
|
||||
<string name="arrays__enabled">Enabled</string>
|
||||
<string name="arrays__disabled">Disabled</string>
|
||||
|
||||
<!-- plurals.xml -->
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%d hour</item>
|
||||
@ -787,6 +826,8 @@
|
||||
<string name="preferences__submit_debug_log">Submit debug log</string>
|
||||
<string name="preferences__support_wifi_calling">\'WiFi Calling\' compatibility mode</string>
|
||||
<string name="preferences__enable_if_your_device_supports_sms_mms_delivery_over_wifi">Enable if your device uses SMS/MMS delivery over WiFi (only enable when \'WiFi Calling\' is enabled on your device)</string>
|
||||
<string name="preferences__privacy">Privacy</string>
|
||||
<string name="preferences_app_protection__blocked_contacts">Blocked contacts</string>
|
||||
|
||||
<!-- **************************************** -->
|
||||
<!-- menus -->
|
||||
@ -844,6 +885,12 @@
|
||||
<string name="conversation_secure_verified__menu_verify_identity">Verify identity</string>
|
||||
<string name="conversation_secure_verified__menu_abort_secure_session">End secure session</string>
|
||||
|
||||
<!-- conversation_muted -->
|
||||
<string name="conversation_muted__unmute">Unmute</string>
|
||||
|
||||
<!-- conversation_unmuted -->
|
||||
<string name="conversation_unmuted__mute_notifications">Mute notifications</string>
|
||||
|
||||
<!-- conversation -->
|
||||
<string name="conversation__menu_add_attachment">Add attachment</string>
|
||||
<string name="conversation__menu_update_group">Update group</string>
|
||||
@ -900,6 +947,8 @@
|
||||
|
||||
<!-- transport_selection_list_item -->
|
||||
<string name="transport_selection_list_item__transport_icon">Transport icon</string>
|
||||
<string name="MuteDialog_mute_notifications">Mute notifications</string>
|
||||
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="Theme.AppCompat.NoActionBar" parent="@style/Theme.AppCompat">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="NoAnimation.Theme.BlackScreen" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
@ -32,7 +28,8 @@
|
||||
<item name="logo">@drawable/actionbar_icon_holo_dark</item>
|
||||
<item name="android:popupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
|
||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
|
||||
|
||||
<item name="titleTextStyle">@style/TextSecure.TitleTextStyle</item>
|
||||
<item name="subtitleTextStyle">@style/TextSecure.SubtitleTextStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightActionBar"
|
||||
@ -42,6 +39,9 @@
|
||||
<item name="logo">@drawable/actionbar_icon_holo_dark</item>
|
||||
<item name="icon">@drawable/actionbar_icon_holo_dark</item>
|
||||
<item name="titleTextStyle">@style/TextSecure.TitleTextStyle</item>
|
||||
<item name="subtitleTextStyle">@style/TextSecure.SubtitleTextStyle</item>
|
||||
<item name="android:textColorPrimary">@color/white</item>
|
||||
<item name="android:textColorSecondary">#99ffffff</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkActionBar.TabBar"
|
||||
@ -58,10 +58,11 @@
|
||||
|
||||
<style name="TextSecure.TitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.SubtitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
|
||||
<item name="android:textColor">#ff555555</item>
|
||||
<item name="android:textColor">#99ffffff</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.IntroActionBar" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse">
|
||||
|
@ -1,5 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="TextSecure.LightNoActionBar" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="actionBarStyle">@style/TextSecure.LightActionBar</item>
|
||||
<item name="actionBarTabBarStyle">@style/TextSecure.LightActionBar.TabBar</item>
|
||||
<item name="colorPrimary">@color/textsecure_primary</item>
|
||||
<item name="colorPrimaryDark">@color/textsecure_primary_dark</item>
|
||||
<item name="colorAccent">@color/textsecure_primary_dark</item>
|
||||
<item name="recipient_preference_blocked">#8e0000</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.DarkNoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
|
||||
<item name="actionBarStyle">@style/TextSecure.DarkActionBar</item>
|
||||
<item name="actionBarTabBarStyle">@style/TextSecure.DarkActionBar.TabBar</item>
|
||||
<item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
|
||||
<item name="recipient_preference_blocked">#d00000</item>
|
||||
</style>
|
||||
|
||||
<style name="TextSecure.LightIntroTheme" parent="@style/Theme.AppCompat.Light">
|
||||
<!--<item name="colorPrimary">@android:color/transparent</item>-->
|
||||
<item name="actionBarStyle">@style/TextSecure.IntroActionBar</item>
|
||||
|
@ -10,7 +10,7 @@
|
||||
android:icon="?pref_ic_notifications"/>
|
||||
|
||||
<Preference android:key="preference_category_app_protection"
|
||||
android:title="@string/preferences__app_protection"
|
||||
android:title="@string/preferences__privacy"
|
||||
android:icon="?pref_ic_app_protection"/>
|
||||
|
||||
<Preference android:key="preference_category_appearance"
|
||||
@ -24,4 +24,5 @@
|
||||
<Preference android:key="preference_category_advanced"
|
||||
android:title="@string/preferences__advanced"
|
||||
android:icon="?pref_ic_advanced"/>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
@ -27,4 +27,7 @@
|
||||
android:key="pref_screen_security"
|
||||
android:title="@string/preferences__screen_security"
|
||||
android:summary="@string/preferences__disable_screen_security_to_allow_screen_shots" />
|
||||
|
||||
<Preference android:key="preference_category_blocked"
|
||||
android:title="@string/preferences_app_protection__blocked_contacts" />
|
||||
</PreferenceScreen>
|
||||
|
31
res/xml/recipient_preferences.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:key="pref_key_recipient_mute"
|
||||
android:title="@string/recipient_preferences__mute_conversation"
|
||||
android:summary="@string/recipient_preferences__disable_notifications_for_this_conversation"
|
||||
android:defaultValue="false"
|
||||
android:disableDependentsState="true"
|
||||
android:persistent="false" />
|
||||
|
||||
<RingtonePreference android:dependency="pref_key_recipient_mute"
|
||||
android:key="pref_key_recipient_ringtone"
|
||||
android:title="@string/recipient_preferences__ringtone"
|
||||
android:ringtoneType="notification"
|
||||
android:showSilent="false"
|
||||
android:showDefault="true"
|
||||
android:persistent="false"/>
|
||||
|
||||
<ListPreference android:dependency="pref_key_recipient_mute"
|
||||
android:key="pref_key_recipient_vibrate"
|
||||
android:title="@string/recipient_preferences__vibrate"
|
||||
android:entries="@array/recipient_vibrate_entries"
|
||||
android:entryValues="@array/recipient_vibrate_values"
|
||||
android:defaultValue="0"
|
||||
android:persistent="false"/>
|
||||
|
||||
<Preference android:key="pref_key_recipient_block"
|
||||
android:title="@string/recipient_preferences__block" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
@ -2,7 +2,7 @@ package org.thoughtcrime.securesms;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
@ -10,7 +10,7 @@ import android.view.ViewConfiguration;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
||||
public abstract class BaseActionBarActivity extends ActionBarActivity {
|
||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
@ -46,7 +46,9 @@ public abstract class BaseActionBarActivity extends ActionBarActivity {
|
||||
menuKeyField.setAccessible(true);
|
||||
menuKeyField.setBoolean(config, false);
|
||||
}
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
} catch (IllegalAccessException e) {
|
||||
Log.w(TAG, "Failed to force overflow menu.");
|
||||
} catch (NoSuchFieldException e) {
|
||||
Log.w(TAG, "Failed to force overflow menu.");
|
||||
}
|
||||
}
|
||||
|
136
src/org/thoughtcrime/securesms/BlockedContactsActivity.java
Normal file
@ -0,0 +1,136 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
|
||||
import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity {
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
@Override
|
||||
public void onPreCreate() {
|
||||
dynamicTheme.onCreate(this);
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.BlockedContactsActivity_blocked_contacts);
|
||||
initFragment(android.R.id.content, new BlockedContactsFragment(), masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: finish(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class BlockedContactsFragment
|
||||
extends ListFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>, ListView.OnItemClickListener
|
||||
{
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
return inflater.inflate(R.layout.blocked_contacts_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
setListAdapter(new BlockedContactAdapter(getActivity(), null));
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle bundle) {
|
||||
super.onActivityCreated(bundle);
|
||||
getListView().setOnItemClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new BlockedContactsLoader(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if (getListAdapter() != null) {
|
||||
((CursorAdapter) getListAdapter()).changeCursor(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
if (getListAdapter() != null) {
|
||||
((CursorAdapter) getListAdapter()).changeCursor(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Recipients recipients = ((BlockedContactListItem)view).getRecipients();
|
||||
Intent intent = new Intent(getActivity(), RecipientPreferenceActivity.class);
|
||||
intent.putExtra(RecipientPreferenceActivity.RECIPIENTS_EXTRA, recipients.getIds());
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private static class BlockedContactAdapter extends CursorAdapter {
|
||||
|
||||
public BlockedContactAdapter(Context context, Cursor c) {
|
||||
super(context, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return LayoutInflater.from(context)
|
||||
.inflate(R.layout.blocked_contact_list_item, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
String recipientIds = cursor.getString(1);
|
||||
Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientIds, true);
|
||||
|
||||
((BlockedContactListItem) view).set(recipients);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -27,13 +27,13 @@ import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.support.v7.app.ActionBar.LayoutParams;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@ -44,6 +44,7 @@ import android.view.View.OnKeyListener;
|
||||
import android.view.ViewStub;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@ -54,9 +55,9 @@ import com.google.protobuf.ByteString;
|
||||
import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
|
||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||
import org.thoughtcrime.securesms.components.ComposeText;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
|
||||
import org.thoughtcrime.securesms.components.SendButton;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
@ -83,6 +84,7 @@ 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.recipients.Recipients.RecipientsModifiedListener;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
||||
@ -107,7 +109,6 @@ import java.util.List;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import static org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
|
||||
import static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext;
|
||||
|
||||
/**
|
||||
@ -120,7 +121,7 @@ import static org.whispersystems.textsecure.internal.push.PushMessageProtos.Push
|
||||
public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
implements ConversationFragment.ConversationFragmentListener,
|
||||
AttachmentManager.AttachmentListener,
|
||||
RecipientModifiedListener
|
||||
RecipientsModifiedListener
|
||||
{
|
||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||
|
||||
@ -139,13 +140,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int GROUP_EDIT = 5;
|
||||
private static final int CAPTURE_PHOTO = 6;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private MasterSecret masterSecret;
|
||||
private ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
private ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private View composePanel;
|
||||
|
||||
private AttachmentTypeSelectorAdapter attachmentAdapter;
|
||||
private AttachmentManager attachmentManager;
|
||||
@ -175,10 +179,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
this.masterSecret = masterSecret;
|
||||
|
||||
setContentView(R.layout.conversation_activity);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
fragment = initFragment(R.id.fragment_content, new ConversationFragment(), masterSecret, dynamicLanguage.getCurrentLocale());
|
||||
|
||||
fragment = initFragment(R.id.fragment_content, new ConversationFragment(),
|
||||
masterSecret, dynamicLanguage.getCurrentLocale());
|
||||
|
||||
initializeReceivers();
|
||||
initializeActionBar();
|
||||
initializeViews();
|
||||
initializeResources();
|
||||
initializeDraft();
|
||||
@ -210,10 +216,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
dynamicLanguage.onResume(this);
|
||||
|
||||
initializeSecurity();
|
||||
initializeTitleBar();
|
||||
initializeEnabledCheck();
|
||||
initializeMmsEnabledCheck();
|
||||
initializeIme();
|
||||
|
||||
titleView.setTitle(recipients);
|
||||
setBlockedUserState(recipients);
|
||||
calculateCharactersRemaining();
|
||||
|
||||
MessageNotifier.setVisibleThread(threadId);
|
||||
@ -263,7 +271,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
break;
|
||||
case GROUP_EDIT:
|
||||
this.recipients = RecipientFactory.getRecipientsForIds(this, data.getLongArrayExtra(GroupCreateActivity.GROUP_RECIPIENT_EXTRA), true);
|
||||
initializeTitleBar();
|
||||
titleView.setTitle(recipients);
|
||||
setBlockedUserState(recipients);
|
||||
supportInvalidateOptionsMenu();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -299,6 +309,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
inflater.inflate(R.menu.conversation, menu);
|
||||
|
||||
if (recipients != null && recipients.isMuted()) inflater.inflate(R.menu.conversation_muted, menu);
|
||||
else inflater.inflate(R.menu.conversation_unmuted, menu);
|
||||
|
||||
if (isSingleConversation() && getRecipients().getPrimaryRecipient().getContactUri() == null) {
|
||||
inflater.inflate(R.menu.conversation_add_to_contacts, menu);
|
||||
}
|
||||
@ -324,6 +337,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
case R.id.menu_edit_group: handleEditPushGroup(); return true;
|
||||
case R.id.menu_leave: handleLeavePushGroup(); return true;
|
||||
case R.id.menu_invite: handleInviteLink(); return true;
|
||||
case R.id.menu_mute_notifications: handleMuteNotifications(); return true;
|
||||
case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true;
|
||||
case android.R.id.home: handleReturnToConversationList(); return true;
|
||||
}
|
||||
|
||||
@ -349,6 +364,61 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
finish();
|
||||
}
|
||||
|
||||
private void handleMuteNotifications() {
|
||||
MuteDialog.show(this, new MuteDialog.MuteSelectionListener() {
|
||||
@Override
|
||||
public void onMuted(final long until) {
|
||||
recipients.setMuted(until);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
|
||||
.setMuted(recipients, until);
|
||||
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleUnmuteNotifications() {
|
||||
recipients.setMuted(0);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
|
||||
.setMuted(recipients, 0);
|
||||
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void handleUnblock() {
|
||||
new AlertDialogWrapper.Builder(this)
|
||||
.setTitle(R.string.ConversationActivity_unblock_question)
|
||||
.setMessage(R.string.ConversationActivity_are_you_sure_you_want_to_unblock_this_contact)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.ConversationActivity_unblock, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
recipients.setBlocked(false);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(ConversationActivity.this)
|
||||
.setBlocked(recipients, false);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void handleInviteLink() {
|
||||
try {
|
||||
boolean a = SecureRandom.getInstance("SHA1PRNG").nextBoolean();
|
||||
@ -551,45 +621,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
///// Initializers
|
||||
|
||||
private void initializeTitleBar() {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final Recipient recipient = getRecipients().getPrimaryRecipient();
|
||||
|
||||
if (isSingleConversation()) {
|
||||
if (TextUtils.isEmpty(recipient.getName())) {
|
||||
title = recipient.getNumber();
|
||||
subtitle = null;
|
||||
} else {
|
||||
title = recipient.getName();
|
||||
subtitle = PhoneNumberUtils.formatNumber(recipient.getNumber());
|
||||
}
|
||||
} else if (isGroupConversation()) {
|
||||
if (isPushGroupConversation()) {
|
||||
final String groupName = recipient.getName();
|
||||
|
||||
title = (!TextUtils.isEmpty(groupName)) ? groupName : getString(R.string.ConversationActivity_unnamed_group);
|
||||
subtitle = null;
|
||||
} else {
|
||||
final int size = getRecipients().getRecipientsList().size();
|
||||
|
||||
title = getString(R.string.ConversationActivity_group_conversation);
|
||||
subtitle = (size == 1) ? getString(R.string.ConversationActivity_d_recipients_in_group_singular)
|
||||
: String.format(getString(R.string.ConversationActivity_d_recipients_in_group), size);
|
||||
}
|
||||
} else {
|
||||
title = getString(R.string.ConversationActivity_compose_message);
|
||||
subtitle = null;
|
||||
}
|
||||
|
||||
getSupportActionBar().setTitle(title);
|
||||
getSupportActionBar().setSubtitle(subtitle);
|
||||
|
||||
getWindow().getDecorView().setContentDescription(getString(R.string.conversation_activity__window_description, title));
|
||||
|
||||
this.supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void initializeDraft() {
|
||||
String draftText = getIntent().getStringExtra(DRAFT_TEXT_EXTRA);
|
||||
Uri draftImage = getIntent().getParcelableExtra(DRAFT_IMAGE_EXTRA);
|
||||
@ -659,6 +690,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
else sendButton.setDefaultTransport(Type.SMS);
|
||||
|
||||
calculateCharactersRemaining();
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void initializeMmsEnabledCheck() {
|
||||
@ -692,6 +724,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
composeText = (ComposeText) findViewById(R.id.embedded_text_editor);
|
||||
charactersLeft = (TextView) findViewById(R.id.space_left);
|
||||
emojiToggle = (EmojiToggle) findViewById(R.id.emoji_toggle);
|
||||
titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
|
||||
unblockButton = (Button) findViewById(R.id.unblock_button);
|
||||
composePanel = findViewById(R.id.bottom_panel);
|
||||
|
||||
attachmentAdapter = new AttachmentTypeSelectorAdapter(this);
|
||||
attachmentManager = new AttachmentManager(this, this);
|
||||
@ -710,6 +745,22 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
});
|
||||
|
||||
titleView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent intent = new Intent(ConversationActivity.this, RecipientPreferenceActivity.class);
|
||||
intent.putExtra(RecipientPreferenceActivity.RECIPIENTS_EXTRA, recipients.getIds());
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
unblockButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
handleUnblock();
|
||||
}
|
||||
});
|
||||
|
||||
composeText.setOnKeyListener(composeKeyPressedListener);
|
||||
composeText.addTextChangedListener(composeKeyPressedListener);
|
||||
composeText.setOnEditorActionListener(sendButtonListener);
|
||||
@ -718,6 +769,13 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
emojiToggle.setOnClickListener(new EmojiToggleListener());
|
||||
}
|
||||
|
||||
private void initializeActionBar() {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setCustomView(R.layout.conversation_title_view);
|
||||
getSupportActionBar().setDisplayShowCustomEnabled(true);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
}
|
||||
|
||||
private EmojiDrawer getEmojiDrawer() {
|
||||
if (emojiDrawer.isPresent()) return emojiDrawer.get();
|
||||
EmojiDrawer emojiDrawer = (EmojiDrawer)((ViewStub)findViewById(R.id.emoji_drawer_stub)).inflate();
|
||||
@ -739,8 +797,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
initializeTitleBar();
|
||||
public void onModified(Recipients recipients) {
|
||||
titleView.setTitle(recipients);
|
||||
setBlockedUserState(recipients);
|
||||
}
|
||||
|
||||
private void initializeReceivers() {
|
||||
@ -751,7 +810,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
if (eventThreadId == threadId || eventThreadId == -2) {
|
||||
initializeSecurity();
|
||||
initializeTitleBar();
|
||||
calculateCharactersRemaining();
|
||||
}
|
||||
}
|
||||
@ -765,7 +823,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
long[] ids = recipients.getIds();
|
||||
Log.w("ConversationActivity", "Looking up new recipients...");
|
||||
recipients = RecipientFactory.getRecipientsForIds(context, ids, false);
|
||||
initializeTitleBar();
|
||||
titleView.setTitle(recipients);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -917,6 +975,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}.execute(thisThreadId);
|
||||
}
|
||||
|
||||
private void setBlockedUserState(Recipients recipients) {
|
||||
if (recipients.isBlocked()) {
|
||||
unblockButton.setVisibility(View.VISIBLE);
|
||||
composePanel.setVisibility(View.GONE);
|
||||
} else {
|
||||
composePanel.setVisibility(View.VISIBLE);
|
||||
unblockButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateCharactersRemaining() {
|
||||
int charactersSpent = composeText.getText().toString().length();
|
||||
TransportOption transportOption = sendButton.getSelectedTransport();
|
||||
@ -994,7 +1062,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (refreshFragment) {
|
||||
fragment.reload(recipients, threadId);
|
||||
|
||||
initializeTitleBar();
|
||||
initializeSecurity();
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.text.ClipboardManager;
|
||||
import android.util.Log;
|
||||
@ -335,7 +335,7 @@ public class ConversationFragment extends ListFragment
|
||||
((ConversationAdapter) getListAdapter()).toggleBatchSelected(messageRecord);
|
||||
((ConversationAdapter) getListAdapter()).notifyDataSetChanged();
|
||||
|
||||
actionMode = ((ActionBarActivity)getActivity()).startSupportActionMode(actionModeCallback);
|
||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.view.ActionMode;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@ -248,7 +248,7 @@ public class ConversationListFragment extends Fragment
|
||||
|
||||
@Override
|
||||
public void onItemLongClick(ConversationListItem item) {
|
||||
actionMode = ((ActionBarActivity)getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||
actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(ConversationListFragment.this);
|
||||
|
||||
getListAdapter().initializeBatchMode(true);
|
||||
getListAdapter().toggleThreadInBatchSet(item.getThreadId());
|
||||
|
@ -27,7 +27,6 @@ import android.widget.TextView;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
|
||||
@ -44,7 +43,7 @@ import static org.thoughtcrime.securesms.util.SpanUtil.color;
|
||||
*/
|
||||
|
||||
public class ConversationListItem extends RelativeLayout
|
||||
implements Recipient.RecipientModifiedListener
|
||||
implements Recipients.RecipientsModifiedListener
|
||||
{
|
||||
private final static String TAG = ConversationListItem.class.getSimpleName();
|
||||
|
||||
@ -76,12 +75,11 @@ public class ConversationListItem extends RelativeLayout
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.subjectView = (TextView) findViewById(R.id.subject);
|
||||
this.fromView = (FromTextView) findViewById(R.id.from);
|
||||
this.dateView = (TextView) findViewById(R.id.date);
|
||||
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
|
||||
|
||||
initializeContactWidgetVisibility();
|
||||
}
|
||||
|
||||
public void set(ThreadRecord thread, Locale locale, Set<Long> selectedThreads, boolean batchMode) {
|
||||
@ -112,10 +110,6 @@ public class ConversationListItem extends RelativeLayout
|
||||
this.recipients.removeListener(this);
|
||||
}
|
||||
|
||||
private void initializeContactWidgetVisibility() {
|
||||
contactPhotoImage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setBatchState(boolean batch) {
|
||||
setSelected(batch && selectedThreads.contains(threadId));
|
||||
}
|
||||
@ -133,7 +127,7 @@ public class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
public void onModified(final Recipients recipients) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
94
src/org/thoughtcrime/securesms/ConversationTitleView.java
Normal file
@ -0,0 +1,94 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class ConversationTitleView extends LinearLayout {
|
||||
|
||||
private static final String TAG = ConversationTitleView.class.getSimpleName();
|
||||
|
||||
private TextView title;
|
||||
private TextView subtitle;
|
||||
|
||||
public ConversationTitleView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ConversationTitleView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
this.title = (TextView) findViewById(R.id.title);
|
||||
this.subtitle = (TextView) findViewById(R.id.subtitle);
|
||||
}
|
||||
|
||||
|
||||
public void setTitle(@Nullable Recipients recipients) {
|
||||
if (recipients == null) setComposeTitle();
|
||||
else if (recipients.isSingleRecipient()) setRecipientTitle(recipients.getPrimaryRecipient());
|
||||
else setRecipientsTitle(recipients);
|
||||
|
||||
if (recipients != null && recipients.isBlocked()) {
|
||||
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_block_white_18dp, 0, 0, 0);
|
||||
} else if (recipients != null && recipients.isMuted()) {
|
||||
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_white_18dp, 0, 0, 0);
|
||||
} else {
|
||||
title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void setComposeTitle() {
|
||||
this.title.setText(R.string.ConversationActivity_compose_message);
|
||||
this.subtitle.setText(null);
|
||||
this.subtitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setRecipientTitle(Recipient recipient) {
|
||||
if (!recipient.isGroupRecipient()) {
|
||||
if (TextUtils.isEmpty(recipient.getName())) {
|
||||
this.title.setText(recipient.getNumber());
|
||||
this.subtitle.setText(null);
|
||||
this.subtitle.setVisibility(View.GONE);
|
||||
} else {
|
||||
this.title.setText(recipient.getName());
|
||||
this.subtitle.setText(recipient.getNumber());
|
||||
this.subtitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
String groupName = (!TextUtils.isEmpty(recipient.getName())) ?
|
||||
recipient.getName() :
|
||||
getContext().getString(R.string.ConversationActivity_unnamed_group);
|
||||
|
||||
this.title.setText(groupName);
|
||||
this.subtitle.setText(null);
|
||||
this.subtitle.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setRecipientsTitle(Recipients recipients) {
|
||||
int size = recipients.getRecipientsList().size();
|
||||
|
||||
title.setText(getContext().getString(R.string.ConversationActivity_group_conversation));
|
||||
subtitle.setText((size == 1) ? getContext().getString(R.string.ConversationActivity_d_recipients_in_group_singular) :
|
||||
String.format(getContext().getString(R.string.ConversationActivity_d_recipients_in_group), size));
|
||||
subtitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -465,7 +465,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
|
||||
}
|
||||
|
||||
private long handleCreateMmsGroup(Set<Recipient> members) {
|
||||
Recipients recipients = new Recipients(new LinkedList<Recipient>(members));
|
||||
Recipients recipients = RecipientFactory.getRecipientsFor(this, new LinkedList<>(members), false);
|
||||
return DatabaseFactory.getThreadDatabase(this)
|
||||
.getThreadIdFor(recipients,
|
||||
ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||
@ -532,7 +532,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity {
|
||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
|
||||
|
||||
ArrayList<Recipient> selectedContactsList = setToArrayList(selectedContacts);
|
||||
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, new Recipients(selectedContactsList).getIds());
|
||||
intent.putExtra(ConversationActivity.RECIPIENTS_EXTRA, RecipientFactory.getRecipientsFor(GroupCreateActivity.this, selectedContactsList, true).getIds());
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} else {
|
||||
|
@ -12,6 +12,7 @@ import com.afollestad.materialdialogs.AlertDialogWrapper;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@ -45,7 +46,7 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, Recipients> {
|
||||
.getGroupMembers(GroupUtil.getDecodedId(groupId), true);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return new Recipients(new LinkedList<Recipient>());
|
||||
return RecipientFactory.getRecipientsFor(context, new LinkedList<Recipient>(), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||
@ -276,7 +277,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
.getGroupMembers(GroupUtil.getDecodedId(groupId), false);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
recipients = new Recipients(new LinkedList<Recipient>());
|
||||
recipients = RecipientFactory.getRecipientsFor(MessageDetailsActivity.this, new LinkedList<Recipient>(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
43
src/org/thoughtcrime/securesms/MuteDialog.java
Normal file
@ -0,0 +1,43 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.afollestad.materialdialogs.AlertDialogWrapper;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MuteDialog extends AlertDialogWrapper {
|
||||
|
||||
private MuteDialog() {}
|
||||
|
||||
public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
|
||||
AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(context);
|
||||
builder.setTitle(R.string.MuteDialog_mute_notifications);
|
||||
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, final int which) {
|
||||
final long muteUntil;
|
||||
|
||||
switch (which) {
|
||||
case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||
case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
|
||||
case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
|
||||
case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
|
||||
default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
|
||||
}
|
||||
|
||||
listener.onMuted(muteUntil);
|
||||
}
|
||||
});
|
||||
|
||||
builder.show();
|
||||
|
||||
}
|
||||
|
||||
public interface MuteSelectionListener {
|
||||
public void onMuted(long until);
|
||||
}
|
||||
|
||||
}
|
@ -80,14 +80,35 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||
else finish();
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@NonNull T fragment,
|
||||
@NonNull MasterSecret masterSecret)
|
||||
{
|
||||
return initFragment(target, fragment, masterSecret, null);
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@NonNull T fragment,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@Nullable Locale locale) {
|
||||
@Nullable Locale locale)
|
||||
{
|
||||
return initFragment(target, fragment, masterSecret, locale, null);
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@NonNull T fragment,
|
||||
@NonNull MasterSecret masterSecret,
|
||||
@Nullable Locale locale,
|
||||
@Nullable Bundle extras)
|
||||
{
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable("master_secret", masterSecret);
|
||||
args.putSerializable(LOCALE_EXTRA, locale);
|
||||
|
||||
if (extras != null) {
|
||||
args.putAll(extras);
|
||||
}
|
||||
|
||||
fragment.setArguments(args);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(target, fragment)
|
||||
@ -95,12 +116,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||
return fragment;
|
||||
}
|
||||
|
||||
protected <T extends Fragment> T initFragment(@IdRes int target,
|
||||
@NonNull T fragment,
|
||||
@NonNull MasterSecret masterSecret) {
|
||||
return initFragment(target, fragment, masterSecret, null);
|
||||
}
|
||||
|
||||
private void routeApplicationState(MasterSecret masterSecret) {
|
||||
Intent intent = getIntentForState(masterSecret, getApplicationState(masterSecret));
|
||||
if (intent != null) {
|
||||
|
348
src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java
Normal file
@ -0,0 +1,348 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.media.Ringtone;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.afollestad.materialdialogs.AlertDialogWrapper;
|
||||
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements Recipients.RecipientsModifiedListener
|
||||
{
|
||||
private static final String TAG = RecipientPreferenceActivity.class.getSimpleName();
|
||||
|
||||
public static final String RECIPIENTS_EXTRA = "recipient_ids";
|
||||
|
||||
private static final String PREFERENCE_MUTED = "pref_key_recipient_mute";
|
||||
private static final String PREFERENCE_TONE = "pref_key_recipient_ringtone";
|
||||
private static final String PREFERENCE_VIBRATE = "pref_key_recipient_vibrate";
|
||||
private static final String PREFERENCE_BLOCK = "pref_key_recipient_block";
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private AvatarImageView avatar;
|
||||
private TextView title;
|
||||
private TextView blockedIndicator;
|
||||
|
||||
@Override
|
||||
public void onPreCreate() {
|
||||
dynamicTheme.onCreate(this);
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle instanceState, @NonNull MasterSecret masterSecret) {
|
||||
setContentView(R.layout.recipient_preference_activity);
|
||||
|
||||
long[] recipientIds = getIntent().getLongArrayExtra(RECIPIENTS_EXTRA);
|
||||
Recipients recipients = RecipientFactory.getRecipientsForIds(this, recipientIds, true);
|
||||
|
||||
initializeToolbar();
|
||||
setHeader(recipients);
|
||||
recipients.addListener(this);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putLongArray(RECIPIENTS_EXTRA, recipientIds);
|
||||
initFragment(R.id.preference_fragment, new RecipientPreferenceFragment(), masterSecret, null, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.preference_fragment);
|
||||
fragment.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: finish(); return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initializeToolbar() {
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
this.avatar = (AvatarImageView) toolbar.findViewById(R.id.avatar);
|
||||
this.title = (TextView) toolbar.findViewById(R.id.name);
|
||||
this.blockedIndicator = (TextView) toolbar.findViewById(R.id.blocked_indicator);
|
||||
}
|
||||
|
||||
private void setHeader(Recipients recipients) {
|
||||
this.avatar.setAvatar(recipients.getPrimaryRecipient(), true);
|
||||
this.title.setText(recipients.toShortString());
|
||||
|
||||
if (recipients.isBlocked()) this.blockedIndicator.setVisibility(View.VISIBLE);
|
||||
else this.blockedIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(final Recipients recipients) {
|
||||
title.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setHeader(recipients);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class RecipientPreferenceFragment
|
||||
extends PreferenceFragment
|
||||
implements Recipients.RecipientsModifiedListener
|
||||
{
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
private Recipients recipients;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
addPreferencesFromResource(R.xml.recipient_preferences);
|
||||
|
||||
this.recipients = RecipientFactory.getRecipientsForIds(getActivity(),
|
||||
getArguments().getLongArray(RECIPIENTS_EXTRA),
|
||||
true);
|
||||
|
||||
this.recipients.addListener(this);
|
||||
this.findPreference(PREFERENCE_TONE)
|
||||
.setOnPreferenceChangeListener(new RingtoneChangeListener());
|
||||
this.findPreference(PREFERENCE_VIBRATE)
|
||||
.setOnPreferenceChangeListener(new VibrateChangeListener());
|
||||
this.findPreference(PREFERENCE_MUTED)
|
||||
.setOnPreferenceClickListener(new MuteClickedListener());
|
||||
this.findPreference(PREFERENCE_BLOCK)
|
||||
.setOnPreferenceClickListener(new BlockClickedListener());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setSummaries(recipients);
|
||||
}
|
||||
|
||||
private void setSummaries(Recipients recipients) {
|
||||
CheckBoxPreference mutePreference = (CheckBoxPreference) this.findPreference(PREFERENCE_MUTED);
|
||||
Preference ringtonePreference = this.findPreference(PREFERENCE_TONE);
|
||||
Preference vibratePreference = this.findPreference(PREFERENCE_VIBRATE);
|
||||
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
|
||||
|
||||
mutePreference.setChecked(recipients.isMuted());
|
||||
|
||||
if (recipients.getRingtone() != null) {
|
||||
Ringtone tone = RingtoneManager.getRingtone(getActivity(), recipients.getRingtone());
|
||||
|
||||
if (tone != null) {
|
||||
ringtonePreference.setSummary(tone.getTitle(getActivity()));
|
||||
}
|
||||
} else {
|
||||
ringtonePreference.setSummary(R.string.preferences__default);
|
||||
}
|
||||
|
||||
if (recipients.getVibrate() == VibrateState.DEFAULT) {
|
||||
vibratePreference.setSummary(R.string.preferences__default);
|
||||
} else if (recipients.getVibrate() == VibrateState.ENABLED) {
|
||||
vibratePreference.setSummary("Enabled");
|
||||
} else {
|
||||
vibratePreference.setSummary("Disabled");
|
||||
}
|
||||
|
||||
if (!recipients.isSingleRecipient() || recipients.isGroupRecipient()) {
|
||||
blockPreference.setEnabled(false);
|
||||
} else {
|
||||
blockPreference.setEnabled(true);
|
||||
if (recipients.isBlocked()) blockPreference.setTitle("Unblock");
|
||||
else blockPreference.setTitle("Block");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(final Recipients recipients) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSummaries(recipients);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String value = (String)newValue;
|
||||
|
||||
final Uri uri;
|
||||
|
||||
if (TextUtils.isEmpty(value) || Settings.System.DEFAULT_NOTIFICATION_URI.toString().equals(value)) {
|
||||
uri = null;
|
||||
} else {
|
||||
uri = Uri.parse(value);
|
||||
}
|
||||
|
||||
recipients.setRingtone(uri);
|
||||
|
||||
new AsyncTask<Uri, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Uri... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
|
||||
.setRingtone(recipients, params[0]);
|
||||
return null;
|
||||
}
|
||||
}.execute(uri);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class VibrateChangeListener implements Preference.OnPreferenceChangeListener {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
int value = Integer.parseInt((String) newValue);
|
||||
final VibrateState vibrateState = VibrateState.fromId(value);
|
||||
|
||||
recipients.setVibrate(vibrateState);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
|
||||
.setVibrate(recipients, vibrateState);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class MuteClickedListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (recipients.isMuted()) handleUnmute();
|
||||
else handleMute();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleMute() {
|
||||
MuteDialog.show(getActivity(), new MuteDialog.MuteSelectionListener() {
|
||||
@Override
|
||||
public void onMuted(long until) {
|
||||
setMuted(recipients, until);
|
||||
}
|
||||
});
|
||||
|
||||
setSummaries(recipients);
|
||||
}
|
||||
|
||||
private void handleUnmute() {
|
||||
setMuted(recipients, 0);
|
||||
}
|
||||
|
||||
private void setMuted(final Recipients recipients, final long until) {
|
||||
recipients.setMuted(until);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
|
||||
.setMuted(recipients, until);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private class BlockClickedListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (recipients.isBlocked()) handleUnblock();
|
||||
else handleBlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleBlock() {
|
||||
new AlertDialogWrapper.Builder(getActivity())
|
||||
.setTitle(R.string.RecipientPreferenceActivity_block_this_contact_question)
|
||||
.setMessage(R.string.RecipientPreferenceActivity_you_will_no_longer_see_messages_from_this_user)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.RecipientPreferenceActivity_block, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setBlocked(recipients, true);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void handleUnblock() {
|
||||
new AlertDialogWrapper.Builder(getActivity())
|
||||
.setTitle(R.string.RecipientPreferenceActivity_unblock_this_contact_question)
|
||||
.setMessage(R.string.RecipientPreferenceActivity_are_you_sure_you_want_to_unblock_this_contact)
|
||||
.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
setBlocked(recipients, false);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void setBlocked(final Recipients recipients, final boolean blocked) {
|
||||
recipients.setBlocked(blocked);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseFactory.getRecipientPreferenceDatabase(getActivity())
|
||||
.setBlocked(recipients, blocked);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,6 @@ import android.widget.RelativeLayout;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.components.FromTextView;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
/**
|
||||
@ -34,7 +33,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ShareListItem extends RelativeLayout
|
||||
implements Recipient.RecipientModifiedListener
|
||||
implements Recipients.RecipientsModifiedListener
|
||||
{
|
||||
private final static String TAG = ShareListItem.class.getSimpleName();
|
||||
|
||||
@ -102,7 +101,7 @@ public class ShareListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
public void onModified(final Recipients recipients) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -3,19 +3,24 @@ package org.thoughtcrime.securesms.components;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class FromTextView extends EmojiTextView {
|
||||
|
||||
private static final String TAG = FromTextView.class.getSimpleName();
|
||||
|
||||
public FromTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@ -25,7 +30,7 @@ public class FromTextView extends EmojiTextView {
|
||||
}
|
||||
|
||||
public void setText(Recipient recipient) {
|
||||
setText(new Recipients(recipient));
|
||||
setText(RecipientFactory.getRecipientsFor(getContext(), recipient, true));
|
||||
}
|
||||
|
||||
public void setText(Recipients recipients) {
|
||||
@ -63,6 +68,10 @@ public class FromTextView extends EmojiTextView {
|
||||
colors.recycle();
|
||||
|
||||
setText(builder);
|
||||
|
||||
if (recipients.isBlocked()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_block_grey600_18dp, 0, 0, 0);
|
||||
else if (recipients.isMuted()) setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_grey600_18dp, 0, 0, 0);
|
||||
else setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
|
@ -127,7 +127,7 @@ public class PushRecipientsPanel extends RelativeLayout {
|
||||
try {
|
||||
recipients = getRecipients();
|
||||
} catch (RecipientFormattingException e) {
|
||||
recipients = new Recipients( new LinkedList<Recipient>() );
|
||||
recipients = RecipientFactory.getRecipientsFor(getContext(), new LinkedList<Recipient>(), true);
|
||||
}
|
||||
|
||||
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));
|
||||
|
@ -130,7 +130,7 @@ public class SingleRecipientPanel extends RelativeLayout {
|
||||
try {
|
||||
recipients = getRecipients();
|
||||
} catch (RecipientFormattingException e) {
|
||||
recipients = new Recipients( new LinkedList<Recipient>() );
|
||||
recipients = RecipientFactory.getRecipientsFor(getContext(), new LinkedList<Recipient>(), true);
|
||||
}
|
||||
|
||||
recipientsText.setAdapter(new RecipientsAdapter(this.getContext()));
|
||||
|
@ -96,11 +96,6 @@ public class ContactPhotoFactory {
|
||||
localUserContactPhotoCache.clear();
|
||||
}
|
||||
|
||||
public static void clearCache(Recipient recipient) {
|
||||
if (localUserContactPhotoCache.containsKey(recipient.getContactUri()))
|
||||
localUserContactPhotoCache.remove(recipient.getContactUri());
|
||||
}
|
||||
|
||||
public static Drawable getContactPhoto(Context context, Uri uri, String name) {
|
||||
final InputStream inputStream = getContactPhotoStream(context, uri);
|
||||
final int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
|
||||
|
@ -61,7 +61,8 @@ public class DatabaseFactory {
|
||||
private static final int INTRODUCED_THUMBNAILS_VERSION = 15;
|
||||
private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16;
|
||||
private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17;
|
||||
private static final int DATABASE_VERSION = 17;
|
||||
private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18;
|
||||
private static final int DATABASE_VERSION = 18;
|
||||
|
||||
private static final String DATABASE_NAME = "messages.db";
|
||||
private static final Object lock = new Object();
|
||||
@ -82,6 +83,7 @@ public class DatabaseFactory {
|
||||
private final DraftDatabase draftDatabase;
|
||||
private final PushDatabase pushDatabase;
|
||||
private final GroupDatabase groupDatabase;
|
||||
private final RecipientPreferenceDatabase recipientPreferenceDatabase;
|
||||
|
||||
public static DatabaseFactory getInstance(Context context) {
|
||||
synchronized (lock) {
|
||||
@ -140,20 +142,25 @@ public class DatabaseFactory {
|
||||
return getInstance(context).groupDatabase;
|
||||
}
|
||||
|
||||
public static RecipientPreferenceDatabase getRecipientPreferenceDatabase(Context context) {
|
||||
return getInstance(context).recipientPreferenceDatabase;
|
||||
}
|
||||
|
||||
private DatabaseFactory(Context context) {
|
||||
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
this.sms = new SmsDatabase(context, databaseHelper);
|
||||
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
|
||||
this.mms = new MmsDatabase(context, databaseHelper);
|
||||
this.part = new PartDatabase(context, databaseHelper);
|
||||
this.thread = new ThreadDatabase(context, databaseHelper);
|
||||
this.address = CanonicalAddressDatabase.getInstance(context);
|
||||
this.mmsAddress = new MmsAddressDatabase(context, databaseHelper);
|
||||
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
|
||||
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
|
||||
this.draftDatabase = new DraftDatabase(context, databaseHelper);
|
||||
this.pushDatabase = new PushDatabase(context, databaseHelper);
|
||||
this.groupDatabase = new GroupDatabase(context, databaseHelper);
|
||||
this.databaseHelper = new DatabaseHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
this.sms = new SmsDatabase(context, databaseHelper);
|
||||
this.encryptingSms = new EncryptingSmsDatabase(context, databaseHelper);
|
||||
this.mms = new MmsDatabase(context, databaseHelper);
|
||||
this.part = new PartDatabase(context, databaseHelper);
|
||||
this.thread = new ThreadDatabase(context, databaseHelper);
|
||||
this.address = CanonicalAddressDatabase.getInstance(context);
|
||||
this.mmsAddress = new MmsAddressDatabase(context, databaseHelper);
|
||||
this.mmsSmsDatabase = new MmsSmsDatabase(context, databaseHelper);
|
||||
this.identityDatabase = new IdentityDatabase(context, databaseHelper);
|
||||
this.draftDatabase = new DraftDatabase(context, databaseHelper);
|
||||
this.pushDatabase = new PushDatabase(context, databaseHelper);
|
||||
this.groupDatabase = new GroupDatabase(context, databaseHelper);
|
||||
this.recipientPreferenceDatabase = new RecipientPreferenceDatabase(context, databaseHelper);
|
||||
}
|
||||
|
||||
public void reset(Context context) {
|
||||
@ -171,6 +178,7 @@ public class DatabaseFactory {
|
||||
this.draftDatabase.reset(databaseHelper);
|
||||
this.pushDatabase.reset(databaseHelper);
|
||||
this.groupDatabase.reset(databaseHelper);
|
||||
this.recipientPreferenceDatabase.reset(databaseHelper);
|
||||
old.close();
|
||||
|
||||
this.address.reset(context);
|
||||
@ -480,6 +488,7 @@ public class DatabaseFactory {
|
||||
db.execSQL(DraftDatabase.CREATE_TABLE);
|
||||
db.execSQL(PushDatabase.CREATE_TABLE);
|
||||
db.execSQL(GroupDatabase.CREATE_TABLE);
|
||||
db.execSQL(RecipientPreferenceDatabase.CREATE_TABLE);
|
||||
|
||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||
@ -722,6 +731,12 @@ public class DatabaseFactory {
|
||||
db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0");
|
||||
}
|
||||
|
||||
if (oldVersion < INTRODUCED_RECIPIENT_PREFS_DB) {
|
||||
db.execSQL("CREATE TABLE recipient_preferences " +
|
||||
"(_id INTEGER PRIMARY KEY, recipient_ids TEXT UNIQUE, block INTEGER DEFAULT 0, " +
|
||||
"notification TEXT DEFAULT NULL, vibrate INTEGER DEFAULT 0, mute_until INTEGER DEFAULT 0)");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public class GroupDatabase extends Database {
|
||||
public Recipients getGroupMembers(byte[] groupId, boolean includeSelf) {
|
||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
||||
List<String> members = getCurrentMembers(groupId);
|
||||
List<Recipient> recipients = new LinkedList<Recipient>();
|
||||
List<Recipient> recipients = new LinkedList<>();
|
||||
|
||||
for (String member : members) {
|
||||
if (!includeSelf && member.equals(localNumber))
|
||||
@ -100,7 +100,7 @@ public class GroupDatabase extends Database {
|
||||
.getRecipientsList());
|
||||
}
|
||||
|
||||
return new Recipients(recipients);
|
||||
return RecipientFactory.getRecipientsFor(context, recipients, false);
|
||||
}
|
||||
|
||||
public void create(byte[] groupId, String title, List<String> members,
|
||||
|
@ -145,7 +145,7 @@ public class MmsAddressDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
return new Recipients(results);
|
||||
return RecipientFactory.getRecipientsFor(context, results, false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -345,7 +345,7 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
? Util.toIsoString(notification.getFrom().getTextString())
|
||||
: "";
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, fromString, false);
|
||||
if (recipients.isEmpty()) recipients = new Recipients(Recipient.getUnknownRecipient(context));
|
||||
if (recipients.isEmpty()) recipients = RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
|
||||
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
}
|
||||
|
||||
@ -1054,13 +1054,13 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
|
||||
private Recipients getRecipientsFor(String address) {
|
||||
if (TextUtils.isEmpty(address) || address.equals("insert-address-token")) {
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
|
||||
}
|
||||
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
|
||||
|
||||
if (recipients == null || recipients.isEmpty()) {
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
|
||||
}
|
||||
|
||||
return recipients;
|
||||
|
@ -0,0 +1,172 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
public class RecipientPreferenceDatabase extends Database {
|
||||
|
||||
private static final String TAG = RecipientPreferenceDatabase.class.getSimpleName();
|
||||
|
||||
private static final String TABLE_NAME = "recipient_preferences";
|
||||
private static final String ID = "_id";
|
||||
private static final String RECIPIENT_IDS = "recipient_ids";
|
||||
private static final String BLOCK = "block";
|
||||
private static final String NOTIFICATION = "notification";
|
||||
private static final String VIBRATE = "vibrate";
|
||||
private static final String MUTE_UNTIL = "mute_until";
|
||||
|
||||
public enum VibrateState {
|
||||
DEFAULT(0), ENABLED(1), DISABLED(2);
|
||||
|
||||
private final int id;
|
||||
|
||||
VibrateState(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static VibrateState fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
||||
|
||||
public static final String CREATE_TABLE =
|
||||
"CREATE TABLE " + TABLE_NAME +
|
||||
" (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
RECIPIENT_IDS + " TEXT UNIQUE, " +
|
||||
BLOCK + " INTEGER DEFAULT 0," +
|
||||
NOTIFICATION + " TEXT DEFAULT NULL, " +
|
||||
VIBRATE + " INTEGER DEFAULT " + VibrateState.DEFAULT.getId() + ", " +
|
||||
MUTE_UNTIL + " INTEGER DEFAULT 0);";
|
||||
|
||||
public RecipientPreferenceDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getBlocked() {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
|
||||
return database.query(TABLE_NAME, new String[] {ID, RECIPIENT_IDS}, BLOCK + " = 1",
|
||||
null, null, null, null, null);
|
||||
}
|
||||
|
||||
public Optional<RecipientsPreferences> getRecipientsPreferences(@NonNull long[] recipients) {
|
||||
Arrays.sort(recipients);
|
||||
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = database.query(TABLE_NAME, null, RECIPIENT_IDS + " = ?",
|
||||
new String[] {Util.join(recipients, " ")},
|
||||
null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToNext()) {
|
||||
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCK)) == 1;
|
||||
String notification = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION));
|
||||
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE));
|
||||
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
|
||||
Uri notificationUri = notification == null ? null : Uri.parse(notification);
|
||||
|
||||
Log.w(TAG, "Muted until: " + muteUntil);
|
||||
|
||||
return Optional.of(new RecipientsPreferences(blocked, muteUntil,
|
||||
VibrateState.fromId(vibrateState),
|
||||
notificationUri));
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlocked(Recipients recipients, boolean blocked) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(BLOCK, blocked ? 1 : 0);
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
public void setRingtone(Recipients recipients, @Nullable Uri notification) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(NOTIFICATION, notification == null ? null : notification.toString());
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
public void setVibrate(Recipients recipients, @NonNull VibrateState enabled) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(VIBRATE, enabled.getId());
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
public void setMuted(Recipients recipients, long until) {
|
||||
Log.w(TAG, "Setting muted until: " + until);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MUTE_UNTIL, until);
|
||||
updateOrInsert(recipients, values);
|
||||
}
|
||||
|
||||
private void updateOrInsert(Recipients recipients, ContentValues contentValues) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
database.beginTransaction();
|
||||
|
||||
int updated = database.update(TABLE_NAME, contentValues, RECIPIENT_IDS + " = ?",
|
||||
new String[] {String.valueOf(recipients.getSortedIdsString())});
|
||||
|
||||
if (updated < 1) {
|
||||
contentValues.put(RECIPIENT_IDS, recipients.getSortedIdsString());
|
||||
database.insert(TABLE_NAME, null, contentValues);
|
||||
}
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
database.endTransaction();
|
||||
}
|
||||
|
||||
public static class RecipientsPreferences {
|
||||
private final boolean blocked;
|
||||
private final long muteUntil;
|
||||
private final VibrateState vibrateState;
|
||||
private final Uri notification;
|
||||
|
||||
public RecipientsPreferences(boolean blocked, long muteUntil, VibrateState vibrateState, Uri notification) {
|
||||
this.blocked = blocked;
|
||||
this.muteUntil = muteUntil;
|
||||
this.vibrateState = vibrateState;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public boolean isBlocked() {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public long getMuteUntil() {
|
||||
return muteUntil;
|
||||
}
|
||||
|
||||
public @NonNull VibrateState getVibrateState() {
|
||||
return vibrateState;
|
||||
}
|
||||
|
||||
public @Nullable Uri getRingtone() {
|
||||
return notification;
|
||||
}
|
||||
}
|
||||
}
|
@ -368,7 +368,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), true);
|
||||
} else {
|
||||
Log.w(TAG, "Sender is null, returning unknown recipient");
|
||||
recipients = new Recipients(Recipient.getUnknownRecipient(context));
|
||||
recipients = RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
|
||||
}
|
||||
|
||||
Recipients groupRecipients;
|
||||
@ -615,13 +615,13 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address, false);
|
||||
|
||||
if (recipients == null || recipients.isEmpty()) {
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
|
||||
}
|
||||
|
||||
return recipients;
|
||||
} else {
|
||||
Log.w(TAG, "getRecipientsFor() address is null");
|
||||
return new Recipients(Recipient.getUnknownRecipient(context));
|
||||
return RecipientFactory.getRecipientsFor(context, Recipient.getUnknownRecipient(context), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.thoughtcrime.securesms.database.loaders;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.util.AbstractCursorLoader;
|
||||
|
||||
public class BlockedContactsLoader extends AbstractCursorLoader {
|
||||
|
||||
public BlockedContactsLoader(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor getCursor() {
|
||||
return DatabaseFactory.getRecipientPreferenceDatabase(getContext())
|
||||
.getBlocked();
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
@ -11,19 +10,15 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.push.TextSecurePushTrustStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.api.crypto.AttachmentCipherInputStream;
|
||||
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.internal.util.StaticCredentialsProvider;
|
||||
|
||||
import java.io.File;
|
||||
@ -72,10 +67,6 @@ public class AvatarDownloadJob extends MasterSecretJob {
|
||||
Bitmap avatar = BitmapUtil.createScaledBitmap(measureInputStream, scaleInputStream, 500, 500);
|
||||
|
||||
database.updateAvatar(groupId, avatar);
|
||||
|
||||
Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true)
|
||||
.getPrimaryRecipient();
|
||||
groupRecipient.setContactPhoto(new BitmapDrawable(avatar));
|
||||
}
|
||||
} catch (InvalidMessageException | BitmapDecodingException | NonSuccessfulResponseCodeException e) {
|
||||
Log.w(TAG, e);
|
||||
|
@ -7,6 +7,9 @@ import android.util.Pair;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
|
||||
import ws.com.google.android.mms.pdu.GenericPdu;
|
||||
@ -49,7 +52,7 @@ public class MmsReceiveJob extends ContextJob {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
if (pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
|
||||
if (isNotification(pdu) && !isBlocked(pdu)) {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox((NotificationInd)pdu);
|
||||
|
||||
@ -61,6 +64,8 @@ public class MmsReceiveJob extends ContextJob {
|
||||
messageAndThreadId.first,
|
||||
messageAndThreadId.second,
|
||||
true));
|
||||
} else if (isNotification(pdu)) {
|
||||
Log.w(TAG, "*** Received blocked MMS, ignoring...");
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,4 +78,17 @@ public class MmsReceiveJob extends ContextJob {
|
||||
public boolean onShouldRetry(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBlocked(GenericPdu pdu) {
|
||||
if (pdu.getFrom() != null && pdu.getFrom().getTextString() != null) {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, Util.toIsoString(pdu.getFrom().getTextString()), false);
|
||||
return recipients.isBlocked();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isNotification(GenericPdu pdu) {
|
||||
return pdu != null && pdu.getMessageType() == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NotInDirectoryException;
|
||||
import org.thoughtcrime.securesms.database.TextSecureDirectory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.whispersystems.jobqueue.JobManager;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
||||
@ -34,16 +36,21 @@ public abstract class PushReceivedJob extends ContextJob {
|
||||
}
|
||||
|
||||
private void handleMessage(TextSecureEnvelope envelope, boolean sendExplicitReceipt) {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
||||
JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
|
||||
long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope);
|
||||
|
||||
if (!recipients.isBlocked()) {
|
||||
long messageId = DatabaseFactory.getPushDatabase(context).insert(envelope);
|
||||
jobManager.add(new PushDecryptJob(context, messageId, envelope.getSource()));
|
||||
} else {
|
||||
Log.w(TAG, "*** Received blocked push message, ignoring...");
|
||||
}
|
||||
|
||||
if (sendExplicitReceipt) {
|
||||
jobManager.add(new DeliveryReceiptJob(context, envelope.getSource(),
|
||||
envelope.getTimestamp(),
|
||||
envelope.getRelay()));
|
||||
}
|
||||
|
||||
jobManager.add(new PushDecryptJob(context, messageId, envelope.getSource()));
|
||||
}
|
||||
|
||||
private void handleReceipt(TextSecureEnvelope envelope) {
|
||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
@ -11,6 +12,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
@ -42,9 +45,11 @@ public class SmsReceiveJob extends ContextJob {
|
||||
public void onRun() {
|
||||
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus);
|
||||
|
||||
if (message.isPresent()) {
|
||||
if (message.isPresent() && !isBlocked(message.get())) {
|
||||
Pair<Long, Long> messageAndThreadId = storeMessage(message.get());
|
||||
MessageNotifier.updateNotification(context, KeyCachingService.getMasterSecret(context), messageAndThreadId.second);
|
||||
} else if (message.isPresent()) {
|
||||
Log.w(TAG, "*** Received blocked SMS, ignoring...");
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +63,15 @@ public class SmsReceiveJob extends ContextJob {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isBlocked(IncomingTextMessage message) {
|
||||
if (message.getSender() != null) {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, message.getSender(), false);
|
||||
return recipients.isBlocked();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Pair<Long, Long> storeMessage(IncomingTextMessage message) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
|
@ -31,6 +31,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.Action;
|
||||
import android.support.v4.app.NotificationCompat.BigTextStyle;
|
||||
@ -49,7 +50,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
@ -88,7 +91,7 @@ public class MessageNotifier {
|
||||
|
||||
public static void notifyMessageDeliveryFailed(Context context, Recipients recipients, long threadId) {
|
||||
if (visibleThread == threadId) {
|
||||
sendInThreadNotification(context);
|
||||
sendInThreadNotification(context, recipients);
|
||||
} else {
|
||||
Intent intent = new Intent(context, ConversationActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
@ -105,7 +108,7 @@ public class MessageNotifier {
|
||||
builder.setTicker(context.getString(R.string.MessageNotifier_error_delivering_message));
|
||||
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
|
||||
builder.setAutoCancel(true);
|
||||
setNotificationAlarms(context, builder, true);
|
||||
setNotificationAlarms(context, builder, true, null, VibrateState.DEFAULT);
|
||||
|
||||
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.notify((int)threadId, builder.build());
|
||||
@ -126,8 +129,9 @@ public class MessageNotifier {
|
||||
}
|
||||
|
||||
if (visibleThread == threadId) {
|
||||
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
|
||||
sendInThreadNotification(context);
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
threads.setRead(threadId);
|
||||
sendInThreadNotification(context, threads.getRecipientsForThreadId(threadId));
|
||||
} else {
|
||||
updateNotification(context, masterSecret, true, 0);
|
||||
}
|
||||
@ -225,7 +229,9 @@ public class MessageNotifier {
|
||||
|
||||
builder.setStyle(new BigTextStyle().bigText(content));
|
||||
|
||||
setNotificationAlarms(context, builder, signal);
|
||||
setNotificationAlarms(context, builder, signal,
|
||||
notificationState.getRingtone(),
|
||||
notificationState.getVibrate());
|
||||
|
||||
if (signal) {
|
||||
builder.setTicker(notifications.get(0).getTickerText());
|
||||
@ -283,7 +289,9 @@ public class MessageNotifier {
|
||||
|
||||
builder.setStyle(style);
|
||||
|
||||
setNotificationAlarms(context, builder, signal);
|
||||
setNotificationAlarms(context, builder, signal,
|
||||
notificationState.getRingtone(),
|
||||
notificationState.getVibrate());
|
||||
|
||||
if (signal) {
|
||||
builder.setTicker(notifications.get(0).getTickerText());
|
||||
@ -293,23 +301,27 @@ public class MessageNotifier {
|
||||
.notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
private static void sendInThreadNotification(Context context) {
|
||||
private static void sendInThreadNotification(Context context, Recipients recipients) {
|
||||
try {
|
||||
if (!TextSecurePreferences.isInThreadNotifications(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String ringtone = TextSecurePreferences.getNotificationRingtone(context);
|
||||
|
||||
if (ringtone == null) {
|
||||
Log.w(TAG, "ringtone preference was null.");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = Uri.parse(ringtone);
|
||||
Uri uri = recipients.getRingtone();
|
||||
|
||||
if (uri == null) {
|
||||
Log.w(TAG, "couldn't parse ringtone uri " + ringtone);
|
||||
String ringtone = TextSecurePreferences.getNotificationRingtone(context);
|
||||
|
||||
if (ringtone == null) {
|
||||
Log.w(TAG, "ringtone preference was null.");
|
||||
return;
|
||||
} else {
|
||||
uri = Uri.parse(ringtone);
|
||||
}
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
Log.w(TAG, "couldn't parse ringtone uri " + TextSecurePreferences.getNotificationRingtone(context));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -358,7 +370,9 @@ public class MessageNotifier {
|
||||
SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_encrypted_message));
|
||||
body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
notificationState.addNotification(new NotificationItem(recipient, recipients, null, threadId, body, null, 0));
|
||||
if (!recipients.isMuted()) {
|
||||
notificationState.addNotification(new NotificationItem(recipient, recipients, null, threadId, body, null, 0));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (reader != null)
|
||||
@ -403,7 +417,9 @@ public class MessageNotifier {
|
||||
body = SpanUtil.italic(message, italicLength);
|
||||
}
|
||||
|
||||
notificationState.addNotification(new NotificationItem(recipient, recipients, threadRecipients, threadId, body, image, timestamp));
|
||||
if (threadRecipients == null || !threadRecipients.isMuted()) {
|
||||
notificationState.addNotification(new NotificationItem(recipient, recipients, threadRecipients, threadId, body, image, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
reader.close();
|
||||
@ -412,18 +428,23 @@ public class MessageNotifier {
|
||||
|
||||
private static void setNotificationAlarms(Context context,
|
||||
NotificationCompat.Builder builder,
|
||||
boolean signal)
|
||||
boolean signal,
|
||||
@Nullable Uri ringtone,
|
||||
VibrateState vibrate)
|
||||
|
||||
{
|
||||
String ringtone = TextSecurePreferences.getNotificationRingtone(context);
|
||||
boolean vibrate = TextSecurePreferences.isNotificationVibrateEnabled(context);
|
||||
String defaultRingtoneName = TextSecurePreferences.getNotificationRingtone(context);
|
||||
boolean defaultVibrate = TextSecurePreferences.isNotificationVibrateEnabled(context);
|
||||
String ledColor = TextSecurePreferences.getNotificationLedColor(context);
|
||||
String ledBlinkPattern = TextSecurePreferences.getNotificationLedPattern(context);
|
||||
String ledBlinkPatternCustom = TextSecurePreferences.getNotificationLedPatternCustom(context);
|
||||
String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
|
||||
|
||||
builder.setSound(TextUtils.isEmpty(ringtone) || !signal ? null : Uri.parse(ringtone));
|
||||
if (signal && ringtone != null) builder.setSound(ringtone);
|
||||
else if (signal && !TextUtils.isEmpty(defaultRingtoneName)) builder.setSound(Uri.parse(defaultRingtoneName));
|
||||
else builder.setSound(null);
|
||||
|
||||
if (signal && vibrate) {
|
||||
if (signal && (vibrate == VibrateState.ENABLED || (vibrate == VibrateState.DEFAULT && defaultVibrate))) {
|
||||
builder.setDefaults(Notification.DEFAULT_VIBRATE);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationActivity;
|
||||
@ -34,6 +35,10 @@ public class NotificationItem {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public @Nullable Recipients getRecipients() {
|
||||
return threadRecipients;
|
||||
}
|
||||
|
||||
public Recipient getIndividualRecipient() {
|
||||
return individualRecipient;
|
||||
}
|
||||
|
@ -3,9 +3,14 @@ package org.thoughtcrime.securesms.notifications;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
@ -14,8 +19,8 @@ import java.util.Set;
|
||||
|
||||
public class NotificationState {
|
||||
|
||||
private final LinkedList<NotificationItem> notifications = new LinkedList<NotificationItem>();
|
||||
private final Set<Long> threads = new HashSet<Long>();
|
||||
private final LinkedList<NotificationItem> notifications = new LinkedList<>();
|
||||
private final Set<Long> threads = new HashSet<>();
|
||||
|
||||
private int notificationCount = 0;
|
||||
|
||||
@ -25,6 +30,30 @@ public class NotificationState {
|
||||
notificationCount++;
|
||||
}
|
||||
|
||||
public @Nullable Uri getRingtone() {
|
||||
if (!notifications.isEmpty()) {
|
||||
Recipients recipients = notifications.getFirst().getRecipients();
|
||||
|
||||
if (recipients != null) {
|
||||
return recipients.getRingtone();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public VibrateState getVibrate() {
|
||||
if (!notifications.isEmpty()) {
|
||||
Recipients recipients = notifications.getFirst().getRecipients();
|
||||
|
||||
if (recipients != null) {
|
||||
return recipients.getVibrate();
|
||||
}
|
||||
}
|
||||
|
||||
return VibrateState.DEFAULT;
|
||||
}
|
||||
|
||||
public boolean hasMultipleThreads() {
|
||||
return threads.size() > 1;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import com.doomonafireball.betterpickers.hmspicker.HmsPickerBuilder;
|
||||
import com.doomonafireball.betterpickers.hmspicker.HmsPickerDialogFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.BlockedContactsActivity;
|
||||
import org.thoughtcrime.securesms.PassphraseChangeActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
@ -27,6 +28,9 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AppProtectionPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
private static final String PREFERENCE_CATEGORY_BLOCKED = "preference_category_blocked";
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
private CheckBoxPreference disablePassphrase;
|
||||
|
||||
@ -42,6 +46,8 @@ public class AppProtectionPreferenceFragment extends PreferenceFragment {
|
||||
.setOnPreferenceClickListener(new ChangePassphraseClickListener());
|
||||
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
|
||||
.setOnPreferenceClickListener(new PassphraseIntervalClickListener());
|
||||
this.findPreference(PREFERENCE_CATEGORY_BLOCKED)
|
||||
.setOnPreferenceClickListener(new BlockedContactsClickListener());
|
||||
disablePassphrase
|
||||
.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
||||
}
|
||||
@ -73,6 +79,15 @@ public class AppProtectionPreferenceFragment extends PreferenceFragment {
|
||||
.setSummary(getString(R.string.AppProtectionPreferenceFragment_minutes, timeoutMinutes));
|
||||
}
|
||||
|
||||
private class BlockedContactsClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(getActivity(), BlockedContactsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
|
@ -0,0 +1,53 @@
|
||||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class BlockedContactListItem extends RelativeLayout implements Recipients.RecipientsModifiedListener {
|
||||
|
||||
private AvatarImageView contactPhotoImage;
|
||||
private TextView nameView;
|
||||
private Recipients recipients;
|
||||
|
||||
public BlockedContactListItem(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public BlockedContactListItem(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public BlockedContactListItem(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.contactPhotoImage = (AvatarImageView)findViewById(R.id.contact_photo_image);
|
||||
this.nameView = (TextView) findViewById(R.id.name);
|
||||
}
|
||||
|
||||
public void set(Recipients recipients) {
|
||||
this.recipients = recipients;
|
||||
|
||||
onModified(recipients);
|
||||
recipients.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipients recipients) {
|
||||
this.contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), false);
|
||||
this.nameView.setText(recipients.toShortString());
|
||||
}
|
||||
|
||||
public Recipients getRecipients() {
|
||||
return recipients;
|
||||
}
|
||||
}
|
@ -91,16 +91,6 @@ public class Recipient {
|
||||
return this.contactUri;
|
||||
}
|
||||
|
||||
public synchronized void setContactPhoto(Drawable bitmap) {
|
||||
this.contactPhoto = bitmap;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized void setName(String name) {
|
||||
this.name = name;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
@ -19,10 +19,11 @@ package org.thoughtcrime.securesms.recipients;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -34,59 +35,73 @@ public class RecipientFactory {
|
||||
|
||||
public static Recipients getRecipientsForIds(Context context, String recipientIds, boolean asynchronous) {
|
||||
if (TextUtils.isEmpty(recipientIds))
|
||||
return new Recipients(new LinkedList<Recipient>());
|
||||
return new Recipients();
|
||||
|
||||
List<Recipient> results = new LinkedList<>();
|
||||
StringTokenizer tokenizer = new StringTokenizer(recipientIds.trim(), " ");
|
||||
return getRecipientsForIds(context, Util.split(recipientIds, " "), asynchronous);
|
||||
}
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String recipientId = tokenizer.nextToken();
|
||||
Recipient recipient = getRecipientFromProviderId(context, Long.parseLong(recipientId), asynchronous);
|
||||
public static Recipients getRecipientsFor(Context context, List<Recipient> recipients, boolean asynchronous) {
|
||||
long[] ids = new long[recipients.size()];
|
||||
int i = 0;
|
||||
|
||||
results.add(recipient);
|
||||
for (Recipient recipient : recipients) {
|
||||
ids[i++] = recipient.getRecipientId();
|
||||
}
|
||||
|
||||
return new Recipients(results);
|
||||
return provider.getRecipients(context, ids, asynchronous);
|
||||
}
|
||||
|
||||
public static Recipients getRecipientsFor(Context context, Recipient recipient, boolean asynchronous) {
|
||||
long[] ids = new long[1];
|
||||
ids[0] = recipient.getRecipientId();
|
||||
|
||||
return provider.getRecipients(context, ids, asynchronous);
|
||||
}
|
||||
|
||||
public static Recipient getRecipientForId(Context context, long recipientId, boolean asynchronous) {
|
||||
return getRecipientFromProviderId(context, recipientId, asynchronous);
|
||||
}
|
||||
|
||||
public static Recipients getRecipientsForIds(Context context, long[] recipientIds, boolean asynchronous) {
|
||||
List<Recipient> results = new LinkedList<>();
|
||||
if (recipientIds == null) return new Recipients(results);
|
||||
for (long recipientId : recipientIds) {
|
||||
results.add(getRecipientFromProviderId(context, recipientId, asynchronous));
|
||||
}
|
||||
return new Recipients(results);
|
||||
}
|
||||
|
||||
private static Recipient getRecipientForNumber(Context context, String number, boolean asynchronous) {
|
||||
long recipientId = CanonicalAddressDatabase.getInstance(context).getCanonicalAddressId(number);
|
||||
return provider.getRecipient(context, recipientId, asynchronous);
|
||||
}
|
||||
|
||||
public static Recipients getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) {
|
||||
List<Recipient> results = new LinkedList<>();
|
||||
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
Recipient recipient = parseRecipient(context, tokenizer.nextToken(), asynchronous);
|
||||
if( recipient != null )
|
||||
results.add(recipient);
|
||||
}
|
||||
|
||||
return new Recipients(results);
|
||||
public static Recipients getRecipientsForIds(Context context, long[] recipientIds, boolean asynchronous) {
|
||||
return provider.getRecipients(context, recipientIds, asynchronous);
|
||||
}
|
||||
|
||||
private static Recipient getRecipientFromProviderId(Context context, long recipientId, boolean asynchronous) {
|
||||
try {
|
||||
return provider.getRecipient(context, recipientId, asynchronous);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w("RecipientFactory", e);
|
||||
return Recipient.getUnknownRecipient(context);
|
||||
public static Recipients getRecipientsFromString(Context context, @NonNull String rawText, boolean asynchronous) {
|
||||
StringTokenizer tokenizer = new StringTokenizer(rawText, ",");
|
||||
List<String> ids = new LinkedList<>();
|
||||
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
Optional<Long> id = getRecipientIdFromNumber(context, tokenizer.nextToken());
|
||||
|
||||
if (id.isPresent()) {
|
||||
ids.add(String.valueOf(id.get()));
|
||||
}
|
||||
}
|
||||
|
||||
return getRecipientsForIds(context, ids, asynchronous);
|
||||
}
|
||||
|
||||
private static Recipients getRecipientsForIds(Context context, List<String> idStrings, boolean asynchronous) {
|
||||
long[] ids = new long[idStrings.size()];
|
||||
int i = 0;
|
||||
|
||||
for (String id : idStrings) {
|
||||
ids[i++] = Long.parseLong(id);
|
||||
}
|
||||
|
||||
return provider.getRecipients(context, ids, asynchronous);
|
||||
}
|
||||
|
||||
private static Optional<Long> getRecipientIdFromNumber(Context context, String number) {
|
||||
number = number.trim();
|
||||
|
||||
if (number.isEmpty()) return Optional.absent();
|
||||
|
||||
if (hasBracketedNumber(number)) {
|
||||
number = parseBracketedNumber(number);
|
||||
}
|
||||
|
||||
return Optional.of(CanonicalAddressDatabase.getInstance(context).getCanonicalAddressId(number));
|
||||
}
|
||||
|
||||
private static boolean hasBracketedNumber(String recipient) {
|
||||
@ -104,26 +119,9 @@ public class RecipientFactory {
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Recipient parseRecipient(Context context, String recipient, boolean asynchronous) {
|
||||
recipient = recipient.trim();
|
||||
|
||||
if( recipient.length() == 0 )
|
||||
return null;
|
||||
|
||||
if (hasBracketedNumber(recipient))
|
||||
return getRecipientForNumber(context, parseBracketedNumber(recipient), asynchronous);
|
||||
|
||||
return getRecipientForNumber(context, recipient, asynchronous);
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
ContactPhotoFactory.clearCache();
|
||||
provider.clearCache();
|
||||
}
|
||||
|
||||
public static void clearCache(Recipient recipient) {
|
||||
ContactPhotoFactory.clearCache(recipient);
|
||||
provider.clearCache(recipient);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,27 +22,34 @@ import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract.Contacts;
|
||||
import android.provider.ContactsContract.PhoneLookup;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
|
||||
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class RecipientProvider {
|
||||
|
||||
private static final Map<Long,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<Long,Recipient>(1000));
|
||||
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
|
||||
private static final Map<Long,Recipient> recipientCache = Collections.synchronizedMap(new LRUCache<Long,Recipient>(1000));
|
||||
private static final Map<RecipientIds,Recipients> recipientsCache = Collections.synchronizedMap(new LRUCache<RecipientIds, Recipients>(1000));
|
||||
private static final ExecutorService asyncRecipientResolver = Util.newSingleThreadedLifoExecutor();
|
||||
|
||||
private static final String[] CALLER_ID_PROJECTION = new String[] {
|
||||
PhoneLookup.DISPLAY_NAME,
|
||||
@ -51,12 +58,29 @@ public class RecipientProvider {
|
||||
PhoneLookup.NUMBER
|
||||
};
|
||||
|
||||
public Recipient getRecipient(Context context, long recipientId, boolean asynchronous) {
|
||||
Recipient getRecipient(Context context, long recipientId, boolean asynchronous) {
|
||||
Recipient cachedRecipient = recipientCache.get(recipientId);
|
||||
|
||||
if (cachedRecipient != null) return cachedRecipient;
|
||||
else if (asynchronous) return getAsynchronousRecipient(context, recipientId);
|
||||
else return getSynchronousRecipient(context, recipientId);
|
||||
if (cachedRecipient != null) return cachedRecipient;
|
||||
else if (asynchronous) return getAsynchronousRecipient(context, recipientId);
|
||||
else return getSynchronousRecipient(context, recipientId);
|
||||
}
|
||||
|
||||
Recipients getRecipients(Context context, long[] recipientIds, boolean asynchronous) {
|
||||
Recipients cachedRecipients = recipientsCache.get(new RecipientIds(recipientIds));
|
||||
if (cachedRecipients != null) return cachedRecipients;
|
||||
|
||||
List<Recipient> recipientList = new LinkedList<>();
|
||||
|
||||
for (long recipientId : recipientIds) {
|
||||
recipientList.add(getRecipient(context, recipientId, false));
|
||||
}
|
||||
|
||||
if (asynchronous) cachedRecipients = new Recipients(recipientList, getRecipientsPreferencesAsync(context, recipientIds));
|
||||
else cachedRecipients = new Recipients(recipientList, getRecipientsPreferencesSync(context, recipientIds));
|
||||
|
||||
recipientsCache.put(new RecipientIds(recipientIds), cachedRecipients);
|
||||
return cachedRecipients;
|
||||
}
|
||||
|
||||
private Recipient getSynchronousRecipient(final Context context, final long recipientId) {
|
||||
@ -116,13 +140,9 @@ public class RecipientProvider {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
void clearCache() {
|
||||
recipientCache.clear();
|
||||
}
|
||||
|
||||
public void clearCache(Recipient recipient) {
|
||||
if (recipientCache.containsKey(recipient.getRecipientId()))
|
||||
recipientCache.remove(recipient.getRecipientId());
|
||||
recipientsCache.clear();
|
||||
}
|
||||
|
||||
private RecipientDetails getRecipientDetails(Context context, String number) {
|
||||
@ -164,6 +184,25 @@ public class RecipientProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable RecipientsPreferences getRecipientsPreferencesSync(Context context, long[] recipientIds) {
|
||||
return DatabaseFactory.getRecipientPreferenceDatabase(context)
|
||||
.getRecipientsPreferences(recipientIds)
|
||||
.orNull();
|
||||
}
|
||||
|
||||
private ListenableFutureTask<RecipientsPreferences> getRecipientsPreferencesAsync(final Context context, final long[] recipientIds) {
|
||||
ListenableFutureTask<RecipientsPreferences> task = new ListenableFutureTask<>(new Callable<RecipientsPreferences>() {
|
||||
@Override
|
||||
public RecipientsPreferences call() throws Exception {
|
||||
return getRecipientsPreferencesSync(context, recipientIds);
|
||||
}
|
||||
});
|
||||
|
||||
asyncRecipientResolver.execute(task);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
public static class RecipientDetails {
|
||||
public final String name;
|
||||
public final String number;
|
||||
@ -178,5 +217,23 @@ public class RecipientProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecipientIds {
|
||||
private final long[] ids;
|
||||
|
||||
private RecipientIds(long[] ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
public boolean equals(Object other) {
|
||||
if (other == null || !(other instanceof RecipientIds)) return false;
|
||||
return Arrays.equals(this.ids, ((RecipientIds) other).ids);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(ids);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (C) 2011 Whisper Systems
|
||||
* Copyright (C) 2015 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
|
||||
@ -16,52 +16,155 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.recipients;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.util.Patterns;
|
||||
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.NumberUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class Recipients implements Iterable<Recipient> {
|
||||
public class Recipients implements Iterable<Recipient>, RecipientModifiedListener {
|
||||
|
||||
private List<Recipient> recipients;
|
||||
private static final String TAG = Recipients.class.getSimpleName();
|
||||
|
||||
public Recipients(List<Recipient> recipients) {
|
||||
private final Set<RecipientsModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientsModifiedListener, Boolean>());
|
||||
private final List<Recipient> recipients;
|
||||
|
||||
private Uri ringtone = null;
|
||||
private long mutedUntil = 0;
|
||||
private boolean blocked = false;
|
||||
private VibrateState vibrate = VibrateState.DEFAULT;
|
||||
|
||||
Recipients() {
|
||||
this(new LinkedList<Recipient>(), (RecipientsPreferences)null);
|
||||
}
|
||||
|
||||
Recipients(List<Recipient> recipients, @Nullable RecipientsPreferences preferences) {
|
||||
this.recipients = recipients;
|
||||
}
|
||||
|
||||
public Recipients(final Recipient recipient) {
|
||||
this.recipients = new LinkedList<Recipient>() {{
|
||||
add(recipient);
|
||||
}};
|
||||
}
|
||||
|
||||
public void append(Recipients recipients) {
|
||||
this.recipients.addAll(recipients.getRecipientsList());
|
||||
}
|
||||
|
||||
// public Recipients truncateToSingleRecipient() {
|
||||
// assert(!this.recipients.isEmpty());
|
||||
// this.recipients = this.recipients.subList(0, 1);
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public void addListener(RecipientModifiedListener listener) {
|
||||
for (Recipient recipient : recipients) {
|
||||
recipient.addListener(listener);
|
||||
if (preferences != null) {
|
||||
ringtone = preferences.getRingtone();
|
||||
mutedUntil = preferences.getMuteUntil();
|
||||
vibrate = preferences.getVibrateState();
|
||||
blocked = preferences.isBlocked();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(RecipientModifiedListener listener) {
|
||||
for (Recipient recipient : recipients) {
|
||||
recipient.removeListener(listener);
|
||||
Recipients(List<Recipient> recipients, ListenableFutureTask<RecipientsPreferences> preferences) {
|
||||
this.recipients = recipients;
|
||||
|
||||
preferences.addListener(new FutureTaskListener<RecipientsPreferences>() {
|
||||
@Override
|
||||
public void onSuccess(RecipientsPreferences result) {
|
||||
if (result != null) {
|
||||
|
||||
Set<RecipientsModifiedListener> localListeners;
|
||||
|
||||
synchronized (Recipients.this) {
|
||||
ringtone = result.getRingtone();
|
||||
mutedUntil = result.getMuteUntil();
|
||||
vibrate = result.getVibrateState();
|
||||
blocked = result.isBlocked();
|
||||
|
||||
localListeners = new HashSet<>(listeners);
|
||||
}
|
||||
|
||||
for (RecipientsModifiedListener listener : localListeners) {
|
||||
listener.onModified(Recipients.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable error) {
|
||||
Log.w(TAG, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized @Nullable Uri getRingtone() {
|
||||
return ringtone;
|
||||
}
|
||||
|
||||
public void setRingtone(Uri ringtone) {
|
||||
synchronized (this) {
|
||||
this.ringtone = ringtone;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isMuted() {
|
||||
return System.currentTimeMillis() <= mutedUntil;
|
||||
}
|
||||
|
||||
public void setMuted(long mutedUntil) {
|
||||
synchronized (this) {
|
||||
this.mutedUntil = mutedUntil;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isBlocked() {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public void setBlocked(boolean blocked) {
|
||||
synchronized (this) {
|
||||
this.blocked = blocked;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized VibrateState getVibrate() {
|
||||
return vibrate;
|
||||
}
|
||||
|
||||
public void setVibrate(VibrateState vibrate) {
|
||||
synchronized (this) {
|
||||
this.vibrate = vibrate;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized void addListener(RecipientsModifiedListener listener) {
|
||||
if (listeners.isEmpty()) {
|
||||
for (Recipient recipient : recipients) {
|
||||
recipient.addListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void removeListener(RecipientsModifiedListener listener) {
|
||||
listeners.remove(listener);
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
for (Recipient recipient : recipients) {
|
||||
recipient.removeListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,30 +181,6 @@ public class Recipients implements Iterable<Recipient> {
|
||||
return isSingleRecipient() && GroupUtil.isEncodedGroup(recipients.get(0).getNumber());
|
||||
}
|
||||
|
||||
// public Recipients getSecureSessionRecipients(Context context) {
|
||||
// List<Recipient> secureRecipients = new LinkedList<Recipient>();
|
||||
//
|
||||
// for (Recipient recipient : recipients) {
|
||||
// if (KeyUtil.isSessionFor(context, recipient)) {
|
||||
// secureRecipients.add(recipient);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return new Recipients(secureRecipients);
|
||||
// }
|
||||
//
|
||||
// public Recipients getInsecureSessionRecipients(Context context) {
|
||||
// List<Recipient> insecureRecipients = new LinkedList<Recipient>();
|
||||
//
|
||||
// for (Recipient recipient : recipients) {
|
||||
// if (!KeyUtil.isSessionFor(context, recipient)) {
|
||||
// insecureRecipients.add(recipient);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return new Recipients(insecureRecipients);
|
||||
// }
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.recipients.isEmpty();
|
||||
}
|
||||
@ -129,6 +208,25 @@ public class Recipients implements Iterable<Recipient> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public String getSortedIdsString() {
|
||||
Set<Long> recipientSet = new HashSet<>();
|
||||
|
||||
for (Recipient recipient : this.recipients) {
|
||||
recipientSet.add(recipient.getRecipientId());
|
||||
}
|
||||
|
||||
long[] recipientArray = new long[recipientSet.size()];
|
||||
int i = 0;
|
||||
|
||||
for (Long recipientId : recipientSet) {
|
||||
recipientArray[i++] = recipientId;
|
||||
}
|
||||
|
||||
Arrays.sort(recipientArray);
|
||||
|
||||
return Util.join(recipientArray, " ");
|
||||
}
|
||||
|
||||
public String[] toNumberStringArray(boolean scrub) {
|
||||
String[] recipientsArray = new String[recipients.size()];
|
||||
Iterator<Recipient> iterator = recipients.iterator();
|
||||
@ -163,12 +261,31 @@ public class Recipients implements Iterable<Recipient> {
|
||||
return fromString;
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Recipient> iterator() {
|
||||
return recipients.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipient recipient) {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
private void notifyListeners() {
|
||||
Set<RecipientsModifiedListener> localListeners;
|
||||
|
||||
synchronized (this) {
|
||||
localListeners = new HashSet<>(listeners);
|
||||
}
|
||||
|
||||
for (RecipientsModifiedListener listener : localListeners) {
|
||||
listener.onModified(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface RecipientsModifiedListener {
|
||||
public void onModified(Recipients recipient);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,10 +9,6 @@ public class OutgoingEncryptedMessage extends OutgoingTextMessage {
|
||||
super(recipients, body);
|
||||
}
|
||||
|
||||
public OutgoingEncryptedMessage(Recipient recipient, String body) {
|
||||
super(recipient, body);
|
||||
}
|
||||
|
||||
private OutgoingEncryptedMessage(OutgoingEncryptedMessage base, String body) {
|
||||
super(base, body);
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class OutgoingKeyExchangeMessage extends OutgoingTextMessage {
|
||||
|
||||
public OutgoingKeyExchangeMessage(Recipient recipient, String message) {
|
||||
super(recipient, message);
|
||||
public OutgoingKeyExchangeMessage(Recipients recipients, String message) {
|
||||
super(recipients, message);
|
||||
}
|
||||
|
||||
private OutgoingKeyExchangeMessage(OutgoingKeyExchangeMessage base, String body) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class OutgoingTextMessage {
|
||||
@ -9,10 +8,6 @@ public class OutgoingTextMessage {
|
||||
private final Recipients recipients;
|
||||
private final String message;
|
||||
|
||||
public OutgoingTextMessage(Recipient recipient, String message) {
|
||||
this(new Recipients(recipient), message);
|
||||
}
|
||||
|
||||
public OutgoingTextMessage(Recipients recipients, String message) {
|
||||
this.recipients = recipients;
|
||||
this.message = message;
|
||||
@ -49,13 +44,13 @@ public class OutgoingTextMessage {
|
||||
|
||||
public static OutgoingTextMessage from(SmsMessageRecord record) {
|
||||
if (record.isSecure()) {
|
||||
return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
||||
return new OutgoingEncryptedMessage(record.getRecipients(), record.getBody().getBody());
|
||||
} else if (record.isKeyExchange()) {
|
||||
return new OutgoingKeyExchangeMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
||||
return new OutgoingKeyExchangeMessage(record.getRecipients(), record.getBody().getBody());
|
||||
} else if (record.isEndSession()) {
|
||||
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody()));
|
||||
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody()));
|
||||
} else {
|
||||
return new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
||||
return new OutgoingTextMessage(record.getRecipients(), record.getBody().getBody());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,16 @@
|
||||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class DynamicNoActionBarTheme extends DynamicTheme {
|
||||
@Override
|
||||
protected int getSelectedTheme(Activity activity) {
|
||||
String theme = TextSecurePreferences.getTheme(activity);
|
||||
|
||||
if (theme.equals("dark")) return R.style.TextSecure_DarkNoActionBar;
|
||||
|
||||
return R.style.TextSecure_LightNoActionBar;
|
||||
}
|
||||
}
|
@ -77,6 +77,17 @@ public class Util {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String join(long[] list, String delimeter) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int j=0;j<list.length;j++) {
|
||||
if (j != 0) sb.append(delimeter);
|
||||
sb.append(list[j]);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static ExecutorService newSingleThreadedLifoExecutor() {
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingLifoQueue<Runnable>());
|
||||
|
||||
|