aboutsummaryrefslogtreecommitdiffstats
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/AndroidManifest.xml27
-rw-r--r--src/main/java/de/pixart/messenger/Config.java4
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java5
-rw-r--r--src/main/java/de/pixart/messenger/entities/Account.java11
-rw-r--r--src/main/java/de/pixart/messenger/entities/Contact.java12
-rw-r--r--src/main/java/de/pixart/messenger/entities/Conversation.java1
-rw-r--r--src/main/java/de/pixart/messenger/entities/MucOptions.java9
-rw-r--r--src/main/java/de/pixart/messenger/generator/AbstractGenerator.java5
-rw-r--r--src/main/java/de/pixart/messenger/generator/IqGenerator.java10
-rw-r--r--src/main/java/de/pixart/messenger/parser/MessageParser.java7
-rw-r--r--src/main/java/de/pixart/messenger/parser/PresenceParser.java12
-rw-r--r--src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java24
-rw-r--r--src/main/java/de/pixart/messenger/persistance/FileBackend.java137
-rw-r--r--src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java120
-rw-r--r--src/main/java/de/pixart/messenger/services/AudioPlayer.java155
-rw-r--r--src/main/java/de/pixart/messenger/services/BarcodeProvider.java1
-rw-r--r--src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java7
-rw-r--r--src/main/java/de/pixart/messenger/services/EventReceiver.java19
-rw-r--r--src/main/java/de/pixart/messenger/services/ExportLogsService.java82
-rw-r--r--src/main/java/de/pixart/messenger/services/MediaPlayer.java15
-rw-r--r--src/main/java/de/pixart/messenger/services/NotificationService.java389
-rw-r--r--src/main/java/de/pixart/messenger/services/UpdateService.java15
-rw-r--r--src/main/java/de/pixart/messenger/services/XmppConnectionService.java393
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java148
-rw-r--r--src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java41
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConversationFragment.java530
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConversationsActivity.java55
-rw-r--r--src/main/java/de/pixart/messenger/ui/EditAccountActivity.java12
-rw-r--r--src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java77
-rw-r--r--src/main/java/de/pixart/messenger/ui/RecordingActivity.java18
-rw-r--r--src/main/java/de/pixart/messenger/ui/SearchActivity.java9
-rw-r--r--src/main/java/de/pixart/messenger/ui/SettingsActivity.java91
-rw-r--r--src/main/java/de/pixart/messenger/ui/SettingsFragment.java2
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShareWithActivity.java297
-rw-r--r--src/main/java/de/pixart/messenger/ui/StartConversationActivity.java44
-rw-r--r--src/main/java/de/pixart/messenger/ui/UpdaterActivity.java13
-rw-r--r--src/main/java/de/pixart/messenger/ui/WelcomeActivity.java22
-rw-r--r--src/main/java/de/pixart/messenger/ui/XmppActivity.java12
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java8
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java10
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java184
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java8
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java226
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java188
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java69
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java8
-rw-r--r--src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java10
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/Attachment.java166
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java60
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java5
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/Drawable.java43
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/GridManager.java76
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java163
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/PendingItem.java6
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java17
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java30
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/StyledAttributes.java (renamed from src/main/java/de/pixart/messenger/ui/util/Color.java)18
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/ViewUtil.java72
-rw-r--r--src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java25
-rw-r--r--src/main/java/de/pixart/messenger/utils/Compatibility.java76
-rw-r--r--src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java11
-rw-r--r--src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java4
-rw-r--r--src/main/java/de/pixart/messenger/utils/Namespace.java2
-rw-r--r--src/main/java/de/pixart/messenger/utils/Resolver.java248
-rw-r--r--src/main/java/de/pixart/messenger/utils/UIHelper.java3
-rw-r--r--src/main/java/de/pixart/messenger/utils/XEP0392Helper.java30
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/InvalidJid.java5
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/XmppConnection.java16
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/pep/PublishOptions.java7
-rw-r--r--src/main/res/drawable-hdpi/ic_android_black_48dp.pngbin0 -> 710 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_android_white_48dp.pngbin0 -> 733 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_archive_black_48dp.pngbin0 -> 560 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_archive_white_48dp.pngbin0 -> 557 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_attach_videocam.pngbin0 -> 222 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_contact_white_24dp.pngbin0 -> 324 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_description_black_48dp.pngbin0 -> 405 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_description_white_48dp.pngbin0 -> 421 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_event_black_48dp.pngbin0 -> 344 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_event_white_48dp.pngbin0 -> 342 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_headset_black_48dp.pngbin0 -> 857 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_headset_white_48dp.pngbin0 -> 865 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_help_black_48dp.pngbin0 -> 1176 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_help_white_48dp.pngbin0 -> 1177 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_mic_black_48dp.pngbin0 -> 866 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_mic_white_48dp.pngbin0 -> 870 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_person_black_48dp.pngbin0 -> 622 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_person_white_48dp.pngbin0 -> 623 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_room_black_48dp.pngbin0 -> 948 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_room_white_48dp.pngbin0 -> 966 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_send_videocam_away.pngbin0 -> 342 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_send_videocam_dnd.pngbin0 -> 397 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_send_videocam_online.pngbin0 -> 389 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_android_black_48dp.pngbin0 -> 410 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_android_white_48dp.pngbin0 -> 416 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_archive_black_48dp.pngbin0 -> 309 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_archive_white_48dp.pngbin0 -> 306 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_attach_videocam.pngbin0 -> 137 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_contact_white_24dp.pngbin0 -> 201 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_description_black_48dp.pngbin0 -> 247 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_description_white_48dp.pngbin0 -> 257 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_event_black_48dp.pngbin0 -> 228 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_event_white_48dp.pngbin0 -> 221 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_headset_black_48dp.pngbin0 -> 466 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_headset_white_48dp.pngbin0 -> 473 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_help_black_48dp.pngbin0 -> 656 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_help_white_48dp.pngbin0 -> 659 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_mic_black_48dp.pngbin0 -> 487 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_mic_white_48dp.pngbin0 -> 489 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_person_black_48dp.pngbin0 -> 359 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_person_white_48dp.pngbin0 -> 351 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_room_black_48dp.pngbin0 -> 524 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_room_white_48dp.pngbin0 -> 537 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_send_videocam_away.pngbin0 -> 217 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_send_videocam_dnd.pngbin0 -> 242 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_send_videocam_online.pngbin0 -> 245 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_android_black_48dp.pngbin0 -> 838 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_android_white_48dp.pngbin0 -> 852 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_archive_black_48dp.pngbin0 -> 607 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_archive_white_48dp.pngbin0 -> 620 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_attach_videocam.pngbin0 -> 202 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_contact_white_24dp.pngbin0 -> 370 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_description_black_48dp.pngbin0 -> 460 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_description_white_48dp.pngbin0 -> 485 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_event_black_48dp.pngbin0 -> 435 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_event_white_48dp.pngbin0 -> 439 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_headset_black_48dp.pngbin0 -> 1006 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_headset_white_48dp.pngbin0 -> 1017 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_help_black_48dp.pngbin0 -> 1406 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_help_white_48dp.pngbin0 -> 1426 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_mic_black_48dp.pngbin0 -> 1002 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_mic_white_48dp.pngbin0 -> 1008 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_person_black_48dp.pngbin0 -> 751 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_person_white_48dp.pngbin0 -> 738 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_room_black_48dp.pngbin0 -> 1093 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_room_white_48dp.pngbin0 -> 1121 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_send_videocam_away.pngbin0 -> 307 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_send_videocam_dnd.pngbin0 -> 325 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_send_videocam_online.pngbin0 -> 318 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_android_black_48dp.pngbin0 -> 1548 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_android_white_48dp.pngbin0 -> 1602 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_archive_black_48dp.pngbin0 -> 1073 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_archive_white_48dp.pngbin0 -> 1084 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_attach_videocam.pngbin0 -> 294 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_contact_white_24dp.pngbin0 -> 647 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_description_black_48dp.pngbin0 -> 824 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_description_white_48dp.pngbin0 -> 861 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_event_black_48dp.pngbin0 -> 787 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_event_white_48dp.pngbin0 -> 800 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_headset_black_48dp.pngbin0 -> 1867 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_headset_white_48dp.pngbin0 -> 1896 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_help_black_48dp.pngbin0 -> 2613 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_help_white_48dp.pngbin0 -> 2702 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_mic_black_48dp.pngbin0 -> 1846 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_mic_white_48dp.pngbin0 -> 1866 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_person_black_48dp.pngbin0 -> 1351 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_person_white_48dp.pngbin0 -> 1360 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_room_black_48dp.pngbin0 -> 2010 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_room_white_48dp.pngbin0 -> 2071 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_send_videocam_away.pngbin0 -> 609 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_send_videocam_dnd.pngbin0 -> 667 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_send_videocam_online.pngbin0 -> 654 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_android_black_48dp.pngbin0 -> 1868 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_android_white_48dp.pngbin0 -> 1951 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.pngbin0 -> 1219 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_archive_white_48dp.pngbin0 -> 1243 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_attach_videocam.pngbin0 -> 405 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_contact_white_24dp.pngbin0 -> 756 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_description_black_48dp.pngbin0 -> 1050 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_description_white_48dp.pngbin0 -> 1087 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_event_black_48dp.pngbin0 -> 1061 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_event_white_48dp.pngbin0 -> 1053 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_headset_black_48dp.pngbin0 -> 2162 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_headset_white_48dp.pngbin0 -> 2214 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_help_black_48dp.pngbin0 -> 2945 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_help_white_48dp.pngbin0 -> 3067 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_mic_black_48dp.pngbin0 -> 2108 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_mic_white_48dp.pngbin0 -> 2141 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_person_black_48dp.pngbin0 -> 1552 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_person_white_48dp.pngbin0 -> 1552 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_room_black_48dp.pngbin0 -> 2283 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_room_white_48dp.pngbin0 -> 2439 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_send_videocam_away.pngbin0 -> 630 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_send_videocam_dnd.pngbin0 -> 663 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_send_videocam_online.pngbin0 -> 643 bytes
-rw-r--r--src/main/res/drawable/date_bubble.xml6
-rw-r--r--src/main/res/drawable/date_bubble_dark.xml6
-rw-r--r--src/main/res/drawable/message_bubble_received_light.xml48
-rw-r--r--src/main/res/drawable/message_bubble_received_light_dark.xml48
-rw-r--r--src/main/res/drawable/message_bubble_received_warning.xml48
-rw-r--r--src/main/res/drawable/message_bubble_received_warning_dark.xml48
-rw-r--r--src/main/res/drawable/message_bubble_sent_blue.xml49
-rw-r--r--src/main/res/drawable/message_bubble_sent_blue_dark.xml48
-rw-r--r--src/main/res/layout/account_row.xml2
-rw-r--r--src/main/res/layout/activity_choose_contact.xml2
-rw-r--r--src/main/res/layout/activity_contact_details.xml54
-rw-r--r--src/main/res/layout/activity_edit_account.xml4
-rw-r--r--src/main/res/layout/activity_manage_accounts.xml2
-rw-r--r--src/main/res/layout/activity_media_browser.xml24
-rw-r--r--src/main/res/layout/activity_muc_details.xml50
-rw-r--r--src/main/res/layout/activity_publish_profile_picture.xml2
-rw-r--r--src/main/res/layout/activity_recording.xml1
-rw-r--r--src/main/res/layout/activity_start_conversation.xml2
-rw-r--r--src/main/res/layout/contact.xml2
-rw-r--r--src/main/res/layout/conversation_list_row.xml6
-rw-r--r--src/main/res/layout/fragment_conversation.xml37
-rw-r--r--src/main/res/layout/media.xml16
-rw-r--r--src/main/res/layout/media_preview.xml28
-rw-r--r--src/main/res/layout/message_content.xml2
-rw-r--r--src/main/res/layout/message_received.xml4
-rw-r--r--src/main/res/layout/message_sent.xml32
-rw-r--r--src/main/res/layout/message_status.xml2
-rw-r--r--src/main/res/menu/choose_attachment.xml7
-rw-r--r--src/main/res/menu/fragment_conversation.xml44
-rw-r--r--src/main/res/values-h360dp/dimens.xml4
-rw-r--r--src/main/res/values-h500dp/dimens.xml4
-rw-r--r--src/main/res/values-w384dp/dimens.xml4
-rw-r--r--src/main/res/values-w585dp/dimens.xml3
-rw-r--r--src/main/res/values/arrays.xml6
-rw-r--r--src/main/res/values/attrs.xml12
-rw-r--r--src/main/res/values/defaults.xml4
-rw-r--r--src/main/res/values/dimens.xml6
-rw-r--r--src/main/res/values/strings.xml28
-rw-r--r--src/main/res/values/themes.xml31
-rw-r--r--src/main/res/xml/preferences.xml34
224 files changed, 3520 insertions, 1865 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index c64f89ef8..bfdb2ec3d 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -18,6 +18,7 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.CAMERA" />
@@ -161,7 +162,12 @@
android:launchMode="singleTask" />
<activity
android:name=".ui.SettingsActivity"
- android:label="@string/title_activity_settings" />
+ android:label="@string/title_activity_settings">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
+ </intent-filter>
+ </activity>
<activity
android:name=".ui.ChooseContactActivity"
android:label="@string/title_activity_choose_contact" />
@@ -208,18 +214,10 @@
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEND" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="text/plain" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.SEND" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="*/*" />
- </intent-filter>
- <intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
+
<category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="image/*" />
+ <data android:mimeType="*/*" />
</intent-filter>
<meta-data
@@ -235,8 +233,7 @@
android:label="@string/share_location" />
<activity
android:name=".ui.ShowLocationActivity"
- android:label="@string/show_location">
- </activity>
+ android:label="@string/show_location"></activity>
<activity
android:name=".ui.SearchActivity"
android:label="@string/search_messages" />
@@ -278,6 +275,10 @@
android:theme="@style/Base.Theme.AppCompat" />
<activity android:name=".ui.MemorizingActivity" />
+ <activity
+ android:name=".ui.MediaBrowserActivity"
+ android:label="@string/media_browser" />
+
<service android:name=".services.ExportLogsService" />
<service
android:name=".services.ContactChooserTargetService"
diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java
index ac99b1709..e2914f9d3 100644
--- a/src/main/java/de/pixart/messenger/Config.java
+++ b/src/main/java/de/pixart/messenger/Config.java
@@ -77,6 +77,8 @@ public final class Config {
public static final int CONNECT_DISCO_TIMEOUT = 30;
public static final int MINI_GRACE_PERIOD = 750;
+ public static final boolean XEP_0392 = false; //enables a variant of XEP-0392 that is based on HSLUV
+
public static final int FILE_SIZE = 1048576; // 1 MiB
public static final int AVATAR_SIZE = 480;
@@ -86,8 +88,6 @@ public final class Config {
public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
public static final int IMAGE_QUALITY = 75;
- public static final boolean QUICK_SHARE_ATTACHMENT_CHOICE = true; // set to true to use attachment choice instead of quick share for send button
-
public static final int DEFAULT_ZOOM = 15; //for locations
public final static long LOCATION_FIX_TIME_DELTA = 1000 * 10; // ms
public final static float LOCATION_FIX_SPACE_DELTA = 10; // m
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
index 0adbbe51f..f9b2539c3 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
@@ -21,6 +21,7 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import de.pixart.messenger.Config;
+import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.xml.Element;
import rocks.xmpp.addr.Jid;
@@ -177,7 +178,7 @@ public class XmppAxolotlMessage {
try {
SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
- Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
+ Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes());
if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) {
@@ -293,7 +294,7 @@ public class XmppAxolotlMessage {
key = newKey;
}
- Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
+ Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
diff --git a/src/main/java/de/pixart/messenger/entities/Account.java b/src/main/java/de/pixart/messenger/entities/Account.java
index a8c72cdf9..6fd4c1c05 100644
--- a/src/main/java/de/pixart/messenger/entities/Account.java
+++ b/src/main/java/de/pixart/messenger/entities/Account.java
@@ -139,8 +139,7 @@ public class Account extends AbstractEntity {
STREAM_ERROR,
POLICY_VIOLATION,
PAYMENT_REQUIRED,
- MISSING_INTERNET_PERMISSION(false),
- NETWORK_IS_UNREACHABLE(false);
+ MISSING_INTERNET_PERMISSION(false);
private final boolean isError;
private final boolean attemptReconnect;
@@ -218,8 +217,6 @@ public class Account extends AbstractEntity {
return R.string.payment_required;
case MISSING_INTERNET_PERMISSION:
return R.string.missing_internet_permission;
- case NETWORK_IS_UNREACHABLE:
- return R.string.network_is_unreachable;
default:
return R.string.account_status_unknown;
}
@@ -638,8 +635,10 @@ public class Account extends AbstractEntity {
return this.avatar;
}
- public void activateGracePeriod(long duration) {
- this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
+ public void activateGracePeriod(final long duration) {
+ if (duration > 0) {
+ this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
+ }
}
public void deactivateGracePeriod() {
diff --git a/src/main/java/de/pixart/messenger/entities/Contact.java b/src/main/java/de/pixart/messenger/entities/Contact.java
index 40bd69976..6edb14ada 100644
--- a/src/main/java/de/pixart/messenger/entities/Contact.java
+++ b/src/main/java/de/pixart/messenger/entities/Contact.java
@@ -159,6 +159,9 @@ public class Contact implements ListItem, Blockable {
if (isBlocked()) {
tags.add(new Tag(context.getString(R.string.blocked), 0xff2e2f3b, 0));
}
+ if (showInPhoneBook()) {
+ tags.add(new Tag(context.getString(R.string.phone_book), 0xFF1E88E5, 0));
+ }
return tags;
}
@@ -357,13 +360,16 @@ public class Contact implements ListItem, Blockable {
}
}
- public void setPgpKeyId(long keyId) {
+ public boolean setPgpKeyId(long keyId) {
+ final long previousKeyId = getPgpKeyId();
synchronized (this.keys) {
try {
this.keys.put("pgp_keyid", keyId);
+ return previousKeyId != keyId;
} catch (final JSONException ignored) {
}
}
+ return false;
}
public void setOption(int option) {
@@ -384,6 +390,10 @@ public class Contact implements ListItem, Blockable {
|| (this.getOption(Contact.Options.DIRTY_PUSH));
}
+ public boolean showInPhoneBook() {
+ return systemAccount != null && !systemAccount.trim().isEmpty();
+ }
+
public void parseSubscriptionFromElement(Element item) {
String ask = item.getAttribute("ask");
String subscription = item.getAttribute("subscription");
diff --git a/src/main/java/de/pixart/messenger/entities/Conversation.java b/src/main/java/de/pixart/messenger/entities/Conversation.java
index cc995a30b..1e5224489 100644
--- a/src/main/java/de/pixart/messenger/entities/Conversation.java
+++ b/src/main/java/de/pixart/messenger/entities/Conversation.java
@@ -1135,6 +1135,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
&& !contact.isOwnServer()
&& !contact.showInRoster()
&& !contact.isSelf()
+ && !contact.showInPhoneBook()
&& sentMessagesCount() == 0;
}
diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java
index 774620707..258ded842 100644
--- a/src/main/java/de/pixart/messenger/entities/MucOptions.java
+++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java
@@ -559,6 +559,15 @@ public class MucOptions {
return null;
}
+ public User findOrCreateUserByRealJid(Jid jid) {
+ User user = findUserByRealJid(jid);
+ if (user == null) {
+ user = new User(this, null);
+ user.setRealJid(jid);
+ }
+ return user;
+ }
+
public User findUser(ReadByMarker readByMarker) {
if (readByMarker.getRealJid() != null) {
User user = findUserByRealJid(readByMarker.getRealJid().asBareJid());
diff --git a/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java b/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java
index 37a098300..507c0c39a 100644
--- a/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java
+++ b/src/main/java/de/pixart/messenger/generator/AbstractGenerator.java
@@ -36,6 +36,7 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify",
"http://jabber.org/protocol/nick+notify",
+ Namespace.BOOKMARKS + "+notify",
"urn:xmpp:ping",
"jabber:iq:version",
"http://jabber.org/protocol/chatstates"
@@ -59,11 +60,11 @@ public abstract class AbstractGenerator {
protected XmppConnectionService mXmppConnectionService;
- protected AbstractGenerator(XmppConnectionService service) {
+ AbstractGenerator(XmppConnectionService service) {
this.mXmppConnectionService = service;
}
- protected String getIdentityVersion() {
+ String getIdentityVersion() {
if (mVersion == null) {
this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService);
}
diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java
index 918c95e6f..86f58af69 100644
--- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java
+++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java
@@ -138,6 +138,13 @@ public class IqGenerator extends AbstractGenerator {
return publish("urn:xmpp:avatar:data", item);
}
+ public IqPacket publishElement(final String namespace, final Element element, final Bundle options) {
+ final Element item = new Element("item");
+ item.setAttribute("id", "current");
+ item.addChild(element);
+ return publish(namespace, item, options);
+ }
+
public IqPacket publishAvatarMetadata(final Avatar avatar) {
final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum);
@@ -197,6 +204,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
final Element item = new Element("item");
+ item.setAttribute("id", "current");
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
for (Integer id : ids) {
final Element device = new Element("device");
@@ -209,6 +217,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
final Element item = new Element("item");
+ item.setAttribute("id", "current");
final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
@@ -231,6 +240,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
final Element item = new Element("item");
+ item.setAttribute("id", "current");
final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
final Element chain = verification.addChild("chain");
for (int i = 0; i < certificates.length; ++i) {
diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java
index 7cfd69e8b..df85f2f25 100644
--- a/src/main/java/de/pixart/messenger/parser/MessageParser.java
+++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java
@@ -304,6 +304,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
AxolotlService axolotlService = account.getAxolotlService();
axolotlService.registerDevices(from, deviceIds);
mXmppConnectionService.updateAccountUi();
+ } else if (Namespace.BOOKMARKS.equals(node)) {
+ Log.d(Config.LOGTAG, "received bookmarks from " + from);
+ if (account.getJid().asBareJid().equals(from)) {
+ final Element i = items.findChild("item");
+ final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
+ mXmppConnectionService.processBookmarks(account, storage);
+ }
}
}
diff --git a/src/main/java/de/pixart/messenger/parser/PresenceParser.java b/src/main/java/de/pixart/messenger/parser/PresenceParser.java
index 6602e8aef..39a3cfeba 100644
--- a/src/main/java/de/pixart/messenger/parser/PresenceParser.java
+++ b/src/main/java/de/pixart/messenger/parser/PresenceParser.java
@@ -236,7 +236,7 @@ public class PresenceParser extends AbstractParser implements
return codes;
}
- public void parseContactPresence(final PresencePacket packet, final Account account) {
+ private void parseContactPresence(final PresencePacket packet, final Account account) {
final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
final Jid from = packet.getFrom();
if (from == null || from.equals(account.getJid())) {
@@ -269,6 +269,10 @@ public class PresenceParser extends AbstractParser implements
mXmppConnectionService.fetchAvatar(account, avatar);
}
}
+
+ if (mXmppConnectionService.isMuc(account, from)) {
+ return;
+ }
int sizeBefore = contact.getPresences().size();
final String show = packet.findChildContent("show");
@@ -302,7 +306,9 @@ public class PresenceParser extends AbstractParser implements
if (pgp != null && x != null) {
Element status = packet.findChild("status");
String msg = status != null ? status.getContent() : "";
- contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent()));
+ if (contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent()))) {
+ mXmppConnectionService.syncRoster(account);
+ }
}
boolean online = sizeBefore < contact.getPresences().size();
mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online);
@@ -349,6 +355,8 @@ public class PresenceParser extends AbstractParser implements
this.parseConferencePresence(packet, account);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
this.parseConferencePresence(packet, account);
+ } else if ("error".equals(packet.getAttribute("type")) && mXmppConnectionService.isMuc(account, packet.getFrom())) {
+ this.parseConferencePresence(packet, account);
} else {
this.parseContactPresence(packet, account);
}
diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
index 94710b86e..365f8fa87 100644
--- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
+++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
@@ -35,6 +35,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import de.pixart.messenger.Config;
@@ -787,6 +788,29 @@ public class DatabaseBackend extends SQLiteOpenHelper {
};
}
+ public List<FilePath> getRelativeFilePaths(String account, Jid jid, int limit) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ final String SQL = "select uuid,relativeFilePath from messages where type in (1,2) and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc";
+ final String[] args = {account, jid.toEscapedString(), jid.toEscapedString() + "/%"};
+ Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + String.valueOf(limit) : ""), args);
+ List<FilePath> filesPaths = new ArrayList<>();
+ while (cursor.moveToNext()) {
+ filesPaths.add(new FilePath(cursor.getString(0), cursor.getString(1)));
+ }
+ cursor.close();
+ return filesPaths;
+ }
+
+ public static class FilePath {
+ public final UUID uuid;
+ public final String path;
+
+ private FilePath(String uuid, String path) {
+ this.uuid = UUID.fromString(uuid);
+ this.path = path;
+ }
+ }
+
public Conversation findConversation(final Account account, final Jid contactJid) {
SQLiteDatabase db = this.getReadableDatabase();
String[] selectionArgs = {account.getUuid(),
diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java
index 3bb1e5c4e..822fa6318 100644
--- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java
+++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java
@@ -46,6 +46,7 @@ import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -56,6 +57,7 @@ import de.pixart.messenger.R;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.util.Attachment;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.ExifHelper;
import de.pixart.messenger.utils.FileUtils;
@@ -80,8 +82,8 @@ public class FileBackend {
}
private void createNoMedia() {
- final File nomedia_files = new File(getConversationsDirectory("Files", true) + ".nomedia");
- final File nomedia_audios = new File(getConversationsDirectory("Audios", true) + ".nomedia");
+ final File nomedia_files = new File(getConversationsDirectory("Files") + ".nomedia");
+ final File nomedia_audios = new File(getConversationsDirectory("Audios") + ".nomedia");
if (!nomedia_files.exists()) {
try {
nomedia_files.createNewFile();
@@ -99,8 +101,8 @@ public class FileBackend {
}
public void updateMediaScanner(File file) {
- if (file.getAbsolutePath().startsWith(getConversationsDirectory("Images", true))
- || file.getAbsolutePath().startsWith(getConversationsDirectory("Videos", true))) {
+ if (file.getAbsolutePath().startsWith(getConversationsDirectory("Images"))
+ || file.getAbsolutePath().startsWith(getConversationsDirectory("Videos"))) {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
mXmppConnectionService.sendBroadcast(intent);
@@ -129,13 +131,13 @@ public class FileBackend {
file = new DownloadableFile(path);
} else {
if (mime != null && mime.startsWith("image")) {
- file = new DownloadableFile(getConversationsDirectory("Images", true) + path);
+ file = new DownloadableFile(getConversationsDirectory("Images") + path);
} else if (mime != null && mime.startsWith("video")) {
- file = new DownloadableFile(getConversationsDirectory("Videos", true) + path);
+ file = new DownloadableFile(getConversationsDirectory("Videos") + path);
} else if (mime != null && mime.startsWith("audio")) {
- file = new DownloadableFile(getConversationsDirectory("Audios", true) + path);
+ file = new DownloadableFile(getConversationsDirectory("Audios") + path);
} else {
- file = new DownloadableFile(getConversationsDirectory("Files", true) + path);
+ file = new DownloadableFile(getConversationsDirectory("Files") + path);
}
}
return file;
@@ -151,7 +153,7 @@ public class FileBackend {
}
final DownloadableFile file = getFileForPath(path, message.getMimeType());
if (encrypted) {
- return new DownloadableFile(getConversationsDirectory("Files", true) + file.getName() + ".pgp");
+ return new DownloadableFile(getConversationsDirectory("Files") + file.getName() + ".pgp");
} else {
return file;
}
@@ -172,16 +174,19 @@ public class FileBackend {
}
}
- public static boolean allFilesUnderSize(Context context, List<Uri> uris, long max) {
+ public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, long max) {
if (max <= 0) {
Log.d(Config.LOGTAG, "server did not report max file size for http upload");
return true; //exception to be compatible with HTTP Upload < v0.2
}
- for (Uri uri : uris) {
- String mime = context.getContentResolver().getType(uri);
+ for (Attachment attachment : attachments) {
+ if (attachment.getType() != Attachment.Type.FILE) {
+ continue;
+ }
+ String mime = attachment.getMime();
if (mime != null && mime.startsWith("video/")) {
try {
- Dimensions dimensions = FileBackend.getVideoDimensions(context, uri);
+ Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri());
if (dimensions.getMin() >= 720) {
Log.d(Config.LOGTAG, "do not consider video file with min width larger or equal than 720 for size check");
continue;
@@ -190,7 +195,7 @@ public class FileBackend {
//ignore and fall through
}
}
- if (FileBackend.getFileSize(context, uri) > max) {
+ if (FileBackend.getFileSize(context, attachment.getUri()) > max) {
Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle");
return false;
}
@@ -198,31 +203,43 @@ public class FileBackend {
return true;
}
- public static String getDirectoryName(final String type, final boolean isMedia) {
- String media = "";
- if (isMedia) {
- media = "Media/Pix-Art Messenger ";
+ public List<Attachment> convertToAttachments(List<DatabaseBackend.FilePath> relativeFilePaths) {
+ List<Attachment> attachments = new ArrayList<>();
+ for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
+ final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path));
+ Log.d(Config.LOGTAG, "mime=" + mime);
+ File file = getFileForPath(relativeFilePath.path, mime);
+ if (file.exists()) {
+ attachments.add(Attachment.of(relativeFilePath.uuid, file, mime));
+ } else {
+ Log.d(Config.LOGTAG, "file " + file.getAbsolutePath() + " doesn't exist");
+ }
}
- if (type == "null" || type == null) {
- return "/Pix-Art Messenger/";
+ return attachments;
+ }
+
+ public static String getConversationsDirectory(final String type) {
+ if (type.equalsIgnoreCase("null") || type == null) {
+ return getAppMediaDirectory() + "Pix-Art Messenger" + "/";
} else {
- return "/Pix-Art Messenger" + "/" + media + type + "/";
+ return getAppMediaDirectory() + "Pix-Art Messenger" + " " + type + "/";
}
}
- public static String getConversationsDirectory(final String type, final boolean isMedia) {
- String DirName = null;
- if (type != "null" || type != null) {
- DirName = type;
- }
- String path = Environment.getExternalStorageDirectory().getAbsolutePath() + getDirectoryName(DirName, isMedia);
- File createFolders = new File(path);
- if (!createFolders.exists()) {
- Log.d(Config.LOGTAG, "creating directory " + createFolders);
- createFolders.mkdirs();
- }
- return path;
+ public static String getAppMediaDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Media/";
+ }
+
+ public static String getBackupDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Database/";
+ }
+ public static String getAppLogsDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Chats/";
+ }
+
+ public static String getAppUpdateDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Update/";
}
private Bitmap resize(final Bitmap originalBitmap, int size) throws IOException {
@@ -294,7 +311,7 @@ public class FileBackend {
Log.d(Config.LOGTAG, "File path = null");
return false;
}
- if (path.contains(getDirectoryName("null", true))) {
+ if (path.contains(getConversationsDirectory("null"))) {
Log.d(Config.LOGTAG, "File " + path + " is in our directory");
return true;
}
@@ -367,7 +384,12 @@ public class FileBackend {
private String getExtensionFromUri(Uri uri) {
String[] projection = {MediaStore.MediaColumns.DATA};
String filename = null;
- Cursor cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null);
+ Cursor cursor;
+ try {
+ cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null);
+ } catch (IllegalArgumentException e) {
+ cursor = null;
+ }
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
@@ -549,6 +571,21 @@ public class FileBackend {
return paint;
}
+ private Bitmap cropCenterSquareVideo(Uri uri, int size) {
+ MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
+ Bitmap frame;
+ try {
+ metadataRetriever.setDataSource(mXmppConnectionService, uri);
+ frame = metadataRetriever.getFrameAtTime(0);
+ metadataRetriever.release();
+ return cropCenterSquare(frame, size);
+ } catch (Exception e) {
+ frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ frame.eraseColor(0xff000000);
+ return frame;
+ }
+ }
+
private Bitmap getVideoPreview(File file, int size) throws IOException {
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
Bitmap frame;
@@ -641,11 +678,12 @@ public class FileBackend {
bitmap = BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri));
return getPepAvatar(bitmap, Bitmap.CompressFormat.PNG, 100);
} catch (Exception e) {
+ return null;
+ } finally {
if (bitmap != null) {
bitmap.recycle();
}
}
- return null;
}
private Avatar getPepAvatar(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
@@ -679,6 +717,9 @@ public class FileBackend {
avatar.width = bitmap.getWidth();
avatar.height = bitmap.getHeight();
return avatar;
+ } catch (OutOfMemoryError e) {
+ Log.d(Config.LOGTAG, "unable to convert avatar to base64 due to low memory");
+ return null;
} catch (Exception e) {
return null;
}
@@ -1032,6 +1073,30 @@ public class FileBackend {
return getVideoDimensions(metadataRetriever);
}
+ public Bitmap getPreviewForUri(Attachment attachment, int size, boolean cacheOnly) {
+ final String key = "attachment_" + attachment.getUuid().toString() + "_" + String.valueOf(size);
+ final LruCache<String, Bitmap> cache = mXmppConnectionService.getBitmapCache();
+ Bitmap bitmap = cache.get(key);
+ if (bitmap != null || cacheOnly) {
+ return bitmap;
+ }
+ if (attachment.getMime() != null && attachment.getMime().startsWith("video/")) {
+ bitmap = cropCenterSquareVideo(attachment.getUri(), size);
+ drawOverlay(bitmap, R.drawable.play_video, 0.75f);
+ } else {
+ bitmap = cropCenterSquare(attachment.getUri(), size);
+ if ("image/gif".equals(attachment.getMime())) {
+ Bitmap withGifOverlay = bitmap.copy(Bitmap.Config.ARGB_8888, true);
+ drawOverlay(withGifOverlay, R.drawable.play_gif, 1.0f);
+ bitmap.recycle();
+ bitmap = withGifOverlay;
+ }
+ }
+ if (bitmap != null) {
+ cache.put(key, bitmap);
+ }
+ return bitmap;
+ }
private static Dimensions getVideoDimensions(Context context, Uri uri) throws NotAVideoFile {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
diff --git a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java
index 1c4c6ca19..65711ef42 100644
--- a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java
+++ b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java
@@ -1,85 +1,39 @@
package de.pixart.messenger.services;
-import android.Manifest;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.util.Log;
import android.util.Pair;
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.modes.AEADBlockCipher;
-import org.bouncycastle.crypto.modes.GCMBlockCipher;
-import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicLong;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
-import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
-import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.DownloadableFile;
+import de.pixart.messenger.utils.Compatibility;
public class AbstractConnectionManager {
- protected XmppConnectionService mXmppConnectionService;
+ private static final String KEYTYPE = "AES";
+ private static final String CIPHERMODE = "AES/GCM/NoPadding";
+ private static final String PROVIDER = "BC";
private static final int UI_REFRESH_THRESHOLD = 250;
private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
+ protected XmppConnectionService mXmppConnectionService;
public AbstractConnectionManager(XmppConnectionService service) {
this.mXmppConnectionService = service;
}
- public XmppConnectionService getXmppConnectionService() {
- return this.mXmppConnectionService;
- }
-
- public long getAutoAcceptFileSize() {
- long defaultValue_wifi = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_wifi);
- long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile);
- long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming);
-
- String config = "0";
- if (mXmppConnectionService.isWIFI()) {
- config = this.mXmppConnectionService.getPreferences().getString(
- "auto_accept_file_size_wifi", String.valueOf(defaultValue_wifi));
- } else if (mXmppConnectionService.isMobile()) {
- config = this.mXmppConnectionService.getPreferences().getString(
- "auto_accept_file_size_mobile", String.valueOf(defaultValue_mobile));
- } else if (mXmppConnectionService.isMobileRoaming()) {
- config = this.mXmppConnectionService.getPreferences().getString(
- "auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming));
- }
- try {
- return Long.parseLong(config);
- } catch (NumberFormatException e) {
- return defaultValue_mobile;
- }
- }
-
- public boolean hasStoragePermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
- } else {
- return true;
- }
- }
-
public static Pair<InputStream, Integer> createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException {
FileInputStream is;
int size;
@@ -90,15 +44,15 @@ public class AbstractConnectionManager {
}
try {
if (gcm) {
- AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
- cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
- InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher);
- return new Pair<>(cis, cipher.getOutputSize(size));
+ Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(file.getIv());
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
+ return new Pair<>(new CipherInputStream(is, cipher), cipher.getOutputSize(size));
} else {
IvParameterSpec ips = new IvParameterSpec(file.getIv());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
- Log.d(Config.LOGTAG, "opening encrypted input stream");
+ cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips);
return new Pair<>(new CipherInputStream(is, cipher), (size / 16 + 1) * 16);
}
} catch (Exception e) {
@@ -126,27 +80,53 @@ public class AbstractConnectionManager {
}
try {
if (gcm) {
- AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
- cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
- return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher);
+ Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(file.getIv());
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+ return new CipherOutputStream(os, cipher);
} else {
IvParameterSpec ips = new IvParameterSpec(file.getIv());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
- Log.d(Config.LOGTAG, "opening encrypted output stream");
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips);
return new CipherOutputStream(os, cipher);
}
- } catch (InvalidKeyException e) {
- return null;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } catch (NoSuchPaddingException e) {
- return null;
- } catch (InvalidAlgorithmParameterException e) {
- return null;
+ } catch (Exception e) {
+ throw new AssertionError(e);
}
}
+ public XmppConnectionService getXmppConnectionService() {
+ return this.mXmppConnectionService;
+ }
+
+ public long getAutoAcceptFileSize() {
+ long defaultValue_wifi = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_wifi);
+ long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile);
+ long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming);
+
+ String config = "0";
+ if (mXmppConnectionService.isWIFI()) {
+ config = this.mXmppConnectionService.getPreferences().getString(
+ "auto_accept_file_size_wifi", String.valueOf(defaultValue_wifi));
+ } else if (mXmppConnectionService.isMobile()) {
+ config = this.mXmppConnectionService.getPreferences().getString(
+ "auto_accept_file_size_mobile", String.valueOf(defaultValue_mobile));
+ } else if (mXmppConnectionService.isMobileRoaming()) {
+ config = this.mXmppConnectionService.getPreferences().getString(
+ "auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming));
+ }
+ try {
+ return Long.parseLong(config);
+ } catch (NumberFormatException e) {
+ return defaultValue_mobile;
+ }
+ }
+
+ public boolean hasStoragePermission() {
+ return Compatibility.hasStoragePermission(mXmppConnectionService);
+ }
+
public void updateConversationUi(boolean force) {
synchronized (LAST_UI_UPDATE_CALL) {
if (force || SystemClock.elapsedRealtime() - LAST_UI_UPDATE_CALL.get() >= UI_REFRESH_THRESHOLD) {
diff --git a/src/main/java/de/pixart/messenger/services/AudioPlayer.java b/src/main/java/de/pixart/messenger/services/AudioPlayer.java
index e1f97ef1a..e3e17fdb0 100644
--- a/src/main/java/de/pixart/messenger/services/AudioPlayer.java
+++ b/src/main/java/de/pixart/messenger/services/AudioPlayer.java
@@ -1,13 +1,21 @@
package de.pixart.messenger.services;
import android.Manifest;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.Handler;
+import android.os.PowerManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
+import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
@@ -17,6 +25,7 @@ import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.Locale;
+import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.ui.ConversationsActivity;
@@ -24,24 +33,44 @@ import de.pixart.messenger.ui.adapter.MessageAdapter;
import de.pixart.messenger.ui.util.PendingItem;
import de.pixart.messenger.utils.WeakReferenceSet;
-public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable {
+public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable, SensorEventListener {
private static final int REFRESH_INTERVAL = 250;
private static final Object LOCK = new Object();
- public static MediaPlayer player = null;
+ private static MediaPlayerWrapper player = null;
private static Message currentlyPlayingMessage = null;
private final MessageAdapter messageAdapter;
private final WeakReferenceSet<RelativeLayout> audioPlayerLayouts = new WeakReferenceSet<>();
+ private final SensorManager sensorManager;
+ private final Sensor proximitySensor;
+ private static PowerManager.WakeLock wakeLock;
private final PendingItem<WeakReference<ImageButton>> pendingOnClickView = new PendingItem<>();
private final Handler handler = new Handler();
public AudioPlayer(MessageAdapter adapter) {
+ final Context context = adapter.getContext();
this.messageAdapter = adapter;
+ this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ this.proximitySensor = this.sensorManager == null ? null : this.sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ if (Build.VERSION.SDK_INT >= 21) {
+ synchronized (AudioPlayer.LOCK) {
+ if (AudioPlayer.wakeLock == null) {
+ final PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ AudioPlayer.wakeLock = powerManager == null ? null : powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, AudioPlayer.class.getSimpleName());
+ AudioPlayer.wakeLock.setReferenceCounted(false);
+ }
+ }
+ } else {
+ AudioPlayer.wakeLock = null;
+ }
synchronized (AudioPlayer.LOCK) {
if (AudioPlayer.player != null) {
AudioPlayer.player.setOnCompletionListener(this);
+ if (AudioPlayer.player.isPlaying() && sensorManager != null) {
+ sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
}
}
}
@@ -91,6 +120,8 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
viewHolder.progress.setProgress(0);
viewHolder.progress.setEnabled(false);
messageAdapter.flagScreenOff();
+ releaseProximityWakeLock();
+ messageAdapter.flagEnableInputs();
return false;
}
}
@@ -126,32 +157,52 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
viewHolder.progress.setEnabled(false);
player.pause();
messageAdapter.flagScreenOff();
+ releaseProximityWakeLock();
+ messageAdapter.flagEnableInputs();
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp);
} else {
viewHolder.progress.setEnabled(true);
player.start();
messageAdapter.flagScreenOn();
+ acquireProximityWakeLock();
this.stopRefresher(true);
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
}
return false;
}
- private boolean play(ViewHolder viewHolder, Message message) {
- AudioPlayer.player = new MediaPlayer();
+ private void play(ViewHolder viewHolder, Message message, boolean earpiece, double progress) {
+ if (play(viewHolder, message, earpiece)) {
+ AudioPlayer.player.seekTo((int) (AudioPlayer.player.getDuration() * progress));
+ }
+ }
+
+ private boolean play(ViewHolder viewHolder, Message message, boolean earpiece) {
+ AudioPlayer.player = new MediaPlayerWrapper();
try {
AudioPlayer.currentlyPlayingMessage = message;
+ AudioPlayer.player.setAudioStreamType(earpiece ? AudioManager.STREAM_VOICE_CALL : AudioManager.STREAM_MUSIC);
AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath());
AudioPlayer.player.setOnCompletionListener(this);
AudioPlayer.player.prepare();
AudioPlayer.player.start();
messageAdapter.flagScreenOn();
+ acquireProximityWakeLock();
+ if (earpiece) {
+ messageAdapter.flagDisableInputs();
+ } else {
+ messageAdapter.flagEnableInputs();
+ }
viewHolder.progress.setEnabled(true);
viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp);
+ sensorManager.registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
return true;
} catch (Exception e) {
messageAdapter.flagScreenOff();
+ releaseProximityWakeLock();
+ messageAdapter.flagEnableInputs();
AudioPlayer.currentlyPlayingMessage = null;
+ sensorManager.unregisterListener(this);
return false;
}
}
@@ -173,7 +224,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
if (AudioPlayer.player != null) {
stopCurrent();
}
- return play(viewHolder, message);
+ return play(viewHolder, message, false);
}
private void stopCurrent() {
@@ -182,8 +233,11 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
}
AudioPlayer.player.release();
messageAdapter.flagScreenOff();
+ releaseProximityWakeLock();
+ messageAdapter.flagEnableInputs();
AudioPlayer.player = null;
resetPlayerUi();
+ sensorManager.unregisterListener(this);
}
private void resetPlayerUi() {
@@ -207,7 +261,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
}
@Override
- public void onCompletion(MediaPlayer mediaPlayer) {
+ public void onCompletion(android.media.MediaPlayer mediaPlayer) {
synchronized (AudioPlayer.LOCK) {
this.stopRefresher(false);
if (AudioPlayer.player == mediaPlayer) {
@@ -216,7 +270,10 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
}
mediaPlayer.release();
messageAdapter.flagScreenOff();
+ releaseProximityWakeLock();
+ messageAdapter.flagEnableInputs();
resetPlayerUi();
+ sensorManager.unregisterListener(this);
}
}
@@ -251,6 +308,11 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
stopCurrent();
}
AudioPlayer.currentlyPlayingMessage = null;
+ sensorManager.unregisterListener(this);
+ if (wakeLock != null && wakeLock.isHeld()) {
+ wakeLock.release();
+ }
+ wakeLock = null;
}
}
@@ -261,6 +323,12 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
}
}
+ public void unregisterListener() {
+ if (sensorManager != null) {
+ sensorManager.unregisterListener(this);
+ }
+ }
+
@Override
public void run() {
synchronized (AudioPlayer.LOCK) {
@@ -288,6 +356,67 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
return true;
}
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.sensor.getType() != Sensor.TYPE_PROXIMITY) {
+ return;
+ }
+ if (AudioPlayer.player == null || !AudioPlayer.player.isPlaying()) {
+ return;
+ }
+ int streamType;
+ if (event.values[0] < 5f && event.values[0] != proximitySensor.getMaximumRange()) {
+ streamType = AudioManager.STREAM_VOICE_CALL;
+ } else {
+ streamType = AudioManager.STREAM_MUSIC;
+ }
+ double position = AudioPlayer.player.getCurrentPosition();
+ double duration = AudioPlayer.player.getDuration();
+ double progress = position / duration;
+ if (AudioPlayer.player.getAudioStreamType() != streamType) {
+ synchronized (AudioPlayer.LOCK) {
+ AudioPlayer.player.stop();
+ AudioPlayer.player.release();
+ AudioPlayer.player = null;
+ try {
+ ViewHolder currentViewHolder = getCurrentViewHolder();
+ if (currentViewHolder != null) {
+ play(currentViewHolder, currentlyPlayingMessage, streamType == AudioManager.STREAM_VOICE_CALL, progress);
+ }
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "AudioPlayer Exception: " + e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int i) {
+ }
+ private void acquireProximityWakeLock() {
+ synchronized (AudioPlayer.LOCK) {
+ if (wakeLock != null) {
+ wakeLock.acquire();
+ }
+ }
+ }
+ private void releaseProximityWakeLock() {
+ synchronized (AudioPlayer.LOCK) {
+ if (wakeLock != null && wakeLock.isHeld()) {
+ wakeLock.release();
+ }
+ }
+ }
+ private ViewHolder getCurrentViewHolder() {
+ for (WeakReference<RelativeLayout> audioPlayer : audioPlayerLayouts) {
+ final Message message = (Message) audioPlayer.get().getTag();
+ if (message == currentlyPlayingMessage) {
+ return ViewHolder.get(audioPlayer.get());
+ }
+ }
+ return null;
+ }
+
public static class ViewHolder {
private TextView runtime;
private SeekBar progress;
@@ -310,4 +439,18 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
this.darkBackground = darkBackground;
}
}
+
+ private static class MediaPlayerWrapper extends MediaPlayer {
+ private int streamType;
+
+ private int getAudioStreamType() {
+ return streamType;
+ }
+
+ @Override
+ public void setAudioStreamType(int streamType) {
+ this.streamType = streamType;
+ super.setAudioStreamType(streamType);
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/BarcodeProvider.java b/src/main/java/de/pixart/messenger/services/BarcodeProvider.java
index 67dc74cc6..5ac2ee016 100644
--- a/src/main/java/de/pixart/messenger/services/BarcodeProvider.java
+++ b/src/main/java/de/pixart/messenger/services/BarcodeProvider.java
@@ -162,7 +162,6 @@ public class BarcodeProvider extends ContentProvider implements ServiceConnectio
synchronized (this) {
if (mXmppConnectionService == null && !mBindingInProcess) {
Log.d(Config.LOGTAG, "calling to bind service");
- context.startService(intent);
context.bindService(intent, this, Context.BIND_AUTO_CREATE);
this.mBindingInProcess = true;
}
diff --git a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java
index 5d6469bae..4c0aec3ca 100644
--- a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java
+++ b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java
@@ -17,7 +17,7 @@ import java.util.ArrayList;
import java.util.List;
import de.pixart.messenger.entities.Conversation;
-import de.pixart.messenger.ui.ShareWithActivity;
+import de.pixart.messenger.ui.ConversationsActivity;
@TargetApi(Build.VERSION_CODES.M)
public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection {
@@ -32,7 +32,6 @@ public class ContactChooserTargetService extends ChooserTargetService implements
public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
Intent intent = new Intent(this, XmppConnectionService.class);
intent.setAction("contact_chooser");
- startService(intent);
bindService(intent, this, Context.BIND_AUTO_CREATE);
ArrayList<ChooserTarget> chooserTargets = new ArrayList<>();
try {
@@ -42,7 +41,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
return chooserTargets;
}
mXmppConnectionService.populateWithOrderedConversations(conversations, false);
- final ComponentName componentName = new ComponentName(this, ShareWithActivity.class);
+ final ComponentName componentName = new ComponentName(this, ConversationsActivity.class);
final int pixel = (int) (48 * getResources().getDisplayMetrics().density);
for (Conversation conversation : conversations) {
if (conversation.sentMessagesCount() == 0) {
@@ -52,7 +51,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel));
final float score = 1 - (1.0f / MAX_TARGETS) * chooserTargets.size();
final Bundle extras = new Bundle();
- extras.putString("uuid", conversation.getUuid());
+ extras.putString(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras));
if (chooserTargets.size() >= MAX_TARGETS) {
break;
diff --git a/src/main/java/de/pixart/messenger/services/EventReceiver.java b/src/main/java/de/pixart/messenger/services/EventReceiver.java
index 6200a40ce..529741ead 100644
--- a/src/main/java/de/pixart/messenger/services/EventReceiver.java
+++ b/src/main/java/de/pixart/messenger/services/EventReceiver.java
@@ -4,6 +4,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.preference.PreferenceManager;
+import android.support.v4.content.ContextCompat;
import android.util.Log;
import de.pixart.messenger.Config;
@@ -13,26 +14,26 @@ public class EventReceiver extends BroadcastReceiver {
public static final String SETTING_ENABLED_ACCOUNTS = "enabled_accounts";
@Override
- public void onReceive(Context context, Intent intent) {
- Intent mIntentForService = new Intent(context, XmppConnectionService.class);
- if (intent.getAction() != null) {
- mIntentForService.setAction(intent.getAction());
+ public void onReceive(final Context context, final Intent originalIntent) {
+ final Intent intentForService = new Intent(context, XmppConnectionService.class);
+ if (originalIntent.getAction() != null) {
+ intentForService.setAction(originalIntent.getAction());
} else {
- mIntentForService.setAction("other");
+ intentForService.setAction("other");
}
- final String action = intent.getAction();
+ final String action = originalIntent.getAction();
if (action.equals("ui") || hasEnabledAccounts(context)) {
try {
- context.startService(mIntentForService);
+ ContextCompat.startForegroundService(context, intentForService);
} catch (RuntimeException e) {
Log.d(Config.LOGTAG, "EventReceiver was unable to start service");
}
} else {
- Log.d(Config.LOGTAG, "EventReceiver ignored action " + mIntentForService.getAction());
+ Log.d(Config.LOGTAG, "EventReceiver ignored action " + intentForService.getAction());
}
}
- public static boolean hasEnabledAccounts(Context context) {
+ public static boolean hasEnabledAccounts(final Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTING_ENABLED_ACCOUNTS, true);
}
diff --git a/src/main/java/de/pixart/messenger/services/ExportLogsService.java b/src/main/java/de/pixart/messenger/services/ExportLogsService.java
index 33725e211..e66873acc 100644
--- a/src/main/java/de/pixart/messenger/services/ExportLogsService.java
+++ b/src/main/java/de/pixart/messenger/services/ExportLogsService.java
@@ -1,7 +1,5 @@
package de.pixart.messenger.services;
-import android.app.NotificationManager;
-import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -10,7 +8,6 @@ import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.support.annotation.BoolRes;
-import android.support.v4.app.NotificationCompat;
import android.util.Log;
import java.io.BufferedWriter;
@@ -41,19 +38,17 @@ import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.ui.SettingsActivity.USE_MULTI_ACCOUNTS;
-public class ExportLogsService extends Service {
+public class ExportLogsService extends XmppConnectionService {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
- private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsDirectory("Chats", false) + "%s";
+ private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s";
private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
- private static final int NOTIFICATION_ID = 1;
private static AtomicBoolean running = new AtomicBoolean(false);
+ boolean ReadableLogsEnabled = false;
private DatabaseBackend mDatabaseBackend;
private List<Account> mAccounts;
- boolean ReadableLogsEnabled = false;
private WakeLock wakeLock;
private PowerManager pm;
- XmppConnectionService mXmppConnectionService;
@Override
public void onCreate() {
@@ -63,20 +58,19 @@ public class ExportLogsService extends Service {
ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", getResources().getBoolean(R.bool.plain_text_logs));
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ExportLogsService");
+ this.startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, getNotificationService().exportLogsNotification());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (running.compareAndSet(false, true)) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- export();
- stopForeground(true);
- WakeLockHelper.release(wakeLock);
- running.set(false);
- stopSelf();
- }
+ new Thread(() -> {
+ startForcingForegroundNotification();
+ export();
+ stopForcingForegroundNotification();
+ WakeLockHelper.release(wakeLock);
+ running.set(false);
+ stopSelf();
}).start();
}
return START_NOT_STICKY;
@@ -84,27 +78,22 @@ public class ExportLogsService extends Service {
private void export() {
wakeLock.acquire();
+ getNotificationService().exportLogsServiceNotification(getNotificationService().exportLogsNotification());
List<Conversation> conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE);
conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED));
- NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext());
- mBuilder.setContentTitle(getString(R.string.app_name))
- .setContentText(getString(R.string.notification_export_logs_title))
- .setSmallIcon(R.drawable.ic_import_export_white_24dp)
- .setProgress(0, 0, true);
- startForeground(NOTIFICATION_ID, mBuilder.build());
- mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build());
- if (ReadableLogsEnabled) {
- for (Conversation conversation : conversations) {
- writeToFile(conversation);
+ if (mAccounts.size() >= 1) {
+ if (ReadableLogsEnabled) {
+ for (Conversation conversation : conversations) {
+ writeToFile(conversation);
+ }
}
- }
- if (mAccounts.size() == 1) {
try {
ExportDatabase();
} catch (IOException e) {
e.printStackTrace();
}
+ } else {
+ Log.d(Config.LOGTAG, "ExportLogsService: no accounts, aborting export");
}
}
@@ -176,21 +165,24 @@ public class ExportLogsService extends Service {
}
public void ExportDatabase() throws IOException {
+ Log.d(Config.LOGTAG, "ExportLogsService: start creating backup");
Account mAccount = mAccounts.get(0);
String EncryptionKey = null;
// Get hold of the db:
FileInputStream InputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
// Set the output folder on the SDcard
- File directory = new File(FileBackend.getConversationsDirectory("Database", false));
+ File directory = new File(FileBackend.getBackupDirectory());
// Create the folder if it doesn't exist:
if (!directory.exists()) {
- directory.mkdirs();
+ boolean directory_created = directory.mkdirs();
+ Log.d(Config.LOGTAG, "ExportLogsService: backup directory created " + directory_created);
}
//Delete old database export file
File temp_db_file = new File(directory + "/database.bak");
if (temp_db_file.exists()) {
- Log.d(Config.LOGTAG, "Delete temp database backup file from " + temp_db_file.toString());
- temp_db_file.delete();
+ Log.d(Config.LOGTAG, "ExportLogsService: Delete temp database backup file from " + temp_db_file.toString());
+ boolean temp_db_file_deleted = temp_db_file.delete();
+ Log.d(Config.LOGTAG, "ExportLogsService: old backup file deleted " + temp_db_file_deleted);
}
// Set the output file stream up:
FileOutputStream OutputFile = new FileOutputStream(directory.getPath() + "/database.db.crypt");
@@ -199,26 +191,28 @@ public class ExportLogsService extends Service {
EncryptionKey = mAccount.getPassword(); //get account password
} else {
SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE);
- String password = multiaccount_prefs.getString("BackupPW", null);
- if (password == null) {
- Log.d(Config.LOGTAG, "Database exporter: failed to write encryted backup to sdcard because of missing password");
- return;
- }
- EncryptionKey = password; //get previously set backup password
+ EncryptionKey = multiaccount_prefs.getString("BackupPW", null);
+ }
+ if (EncryptionKey == null) {
+ Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: failed to write encryted backup to sdcard because of missing password");
+ return;
}
// encrypt database from the input file to the output file
try {
EncryptDecryptFile.encrypt(InputFile, OutputFile, EncryptionKey);
+ Log.d(Config.LOGTAG, "ExportLogsService: starting encrypted output to " + OutputFile.toString());
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "Database exporter: encryption failed with " + e);
+ Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed with " + e);
e.printStackTrace();
} catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "Database exporter: encryption failed (invalid key) with " + e);
+ Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed (invalid key) with " + e);
e.printStackTrace();
} catch (IOException e) {
- Log.d(Config.LOGTAG, "Database exporter: encryption failed (IO) with " + e);
+ Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed (IO) with " + e);
e.printStackTrace();
+ } finally {
+ Log.d(Config.LOGTAG, "ExportLogsService: backup job finished");
}
}
@@ -236,6 +230,6 @@ public class ExportLogsService extends Service {
}
public boolean multipleAccounts() {
- return getBooleanPreference("enable_multi_accounts", R.bool.confirm_messages);
+ return getBooleanPreference("enable_multi_accounts", R.bool.enable_multi_accounts);
}
}
diff --git a/src/main/java/de/pixart/messenger/services/MediaPlayer.java b/src/main/java/de/pixart/messenger/services/MediaPlayer.java
new file mode 100644
index 000000000..59e8bfecb
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/services/MediaPlayer.java
@@ -0,0 +1,15 @@
+package de.pixart.messenger.services;
+
+public class MediaPlayer extends android.media.MediaPlayer {
+ private int streamType;
+
+ public int getAudioStreamType() {
+ return streamType;
+ }
+
+ @Override
+ public void setAudioStreamType(int streamType) {
+ this.streamType = streamType;
+ super.setAudioStreamType(streamType);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java
index 7fa091478..8f82207ac 100644
--- a/src/main/java/de/pixart/messenger/services/NotificationService.java
+++ b/src/main/java/de/pixart/messenger/services/NotificationService.java
@@ -1,16 +1,23 @@
package de.pixart.messenger.services;
import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Typeface;
+import android.media.AudioAttributes;
+import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.preference.PreferenceManager;
+import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.BigPictureStyle;
import android.support.v4.app.NotificationCompat.Builder;
@@ -28,6 +35,7 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -46,9 +54,9 @@ import de.pixart.messenger.entities.Conversational;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.ui.ConversationsActivity;
-import de.pixart.messenger.ui.EditAccountActivity;
import de.pixart.messenger.ui.ManageAccountActivity;
import de.pixart.messenger.ui.TimePreference;
+import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.GeoHelper;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xmpp.XmppConnection;
@@ -56,47 +64,135 @@ import de.pixart.messenger.xmpp.XmppConnection;
public class NotificationService {
public static final Object CATCHUP_LOCK = new Object();
+
private static final String CONVERSATIONS_GROUP = "de.pixart.messenger";
- private final XmppConnectionService mXmppConnectionService;
- private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024;
public static final int NOTIFICATION_ID = 2 * NOTIFICATION_ID_MULTIPLIER;
public static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4;
public static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6;
- public static final int UPDATE_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 8;
+ public static final String MESSAGES_CHANNEL_ID = "messages";
+ public static final String FOREGROUND_CHANNEL_ID = "foreground";
+ public static final String BACKUP_CHANNEL_ID = "backup";
+ public static final String UPDATE_CHANNEL_ID = "appupdate";
+ public static final String VIDEOCOMPRESSION_CHANNEL_ID = "compression";
+ public static final String ERROR_CHANNEL_ID = "error";
+
+ private final XmppConnectionService mXmppConnectionService;
+ private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
+ private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
private Conversation mOpenConversation;
private boolean mIsInForeground;
private long mLastNotification;
- private final HashMap<Conversation, AtomicInteger> mBacklogMessageCounter = new HashMap<>();
-
- public NotificationService(final XmppConnectionService service) {
+ NotificationService(final XmppConnectionService service) {
this.mXmppConnectionService = service;
}
+ private static boolean displaySnoozeAction(List<Message> messages) {
+ int numberOfMessagesWithoutReply = 0;
+ for (Message message : messages) {
+ if (message.getStatus() == Message.STATUS_RECEIVED) {
+ ++numberOfMessagesWithoutReply;
+ } else {
+ return false;
+ }
+ }
+ return numberOfMessagesWithoutReply >= 3;
+ }
+
public static Pattern generateNickHighlightPattern(final String nick) {
return Pattern.compile("(?<=(^|\\s))" + Pattern.quote(nick) + "\\b");
}
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public void initializeChannels() {
+ final Context c = mXmppConnectionService;
+ NotificationManager notificationManager = c.getSystemService(NotificationManager.class);
+ if (notificationManager == null) {
+ return;
+ }
+ notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("status", c.getString(R.string.notification_group_status_information)));
+ notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("chats", c.getString(R.string.notification_group_messages)));
+
+ final NotificationChannel foregroundServiceChannel = new NotificationChannel(FOREGROUND_CHANNEL_ID,
+ c.getString(R.string.foreground_service_channel_name),
+ NotificationManager.IMPORTANCE_MIN);
+ foregroundServiceChannel.setDescription(c.getString(R.string.foreground_service_channel_description));
+ foregroundServiceChannel.setShowBadge(false);
+ foregroundServiceChannel.setGroup("status");
+ notificationManager.createNotificationChannel(foregroundServiceChannel);
+
+ final NotificationChannel backupChannel = new NotificationChannel(BACKUP_CHANNEL_ID,
+ c.getString(R.string.backup_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
+ backupChannel.setShowBadge(false);
+ backupChannel.setGroup("status");
+ notificationManager.createNotificationChannel(backupChannel);
+
+ final NotificationChannel videoCompressionChannel = new NotificationChannel(VIDEOCOMPRESSION_CHANNEL_ID,
+ c.getString(R.string.video_compression_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
+ videoCompressionChannel.setShowBadge(false);
+ videoCompressionChannel.setGroup("status");
+ notificationManager.createNotificationChannel(videoCompressionChannel);
+
+ final NotificationChannel AppUpdateChannel = new NotificationChannel(UPDATE_CHANNEL_ID,
+ c.getString(R.string.app_update_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
+ AppUpdateChannel.setShowBadge(false);
+ AppUpdateChannel.setGroup("status");
+ notificationManager.createNotificationChannel(AppUpdateChannel);
+
+ final NotificationChannel errorChannel = new NotificationChannel(ERROR_CHANNEL_ID,
+ c.getString(R.string.error_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
+ errorChannel.setDescription(c.getString(R.string.error_channel_description));
+ errorChannel.setShowBadge(false);
+ errorChannel.setGroup("status");
+ notificationManager.createNotificationChannel(errorChannel);
+
+ final NotificationChannel messagesChannel = new NotificationChannel(MESSAGES_CHANNEL_ID,
+ c.getString(R.string.messages_channel_name),
+ NotificationManager.IMPORTANCE_HIGH);
+ messagesChannel.setShowBadge(true);
+ messagesChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build());
+ messagesChannel.setLightColor(0xff0080FF);
+ final int dat = 70;
+ final long[] pattern = {0, 3 * dat, dat, dat};
+ messagesChannel.setVibrationPattern(pattern);
+ messagesChannel.enableVibration(true);
+ messagesChannel.enableLights(true);
+ messagesChannel.setGroup("chats");
+ notificationManager.createNotificationChannel(messagesChannel);
+
+ final NotificationChannel silentMessagesChannel = new NotificationChannel("silent_messages",
+ c.getString(R.string.silent_messages_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
+ silentMessagesChannel.setDescription(c.getString(R.string.silent_messages_channel_description));
+ silentMessagesChannel.setShowBadge(true);
+ silentMessagesChannel.setLightColor(0xff0080FF);
+ silentMessagesChannel.enableLights(true);
+ silentMessagesChannel.setGroup("chats");
+ notificationManager.createNotificationChannel(silentMessagesChannel);
+ }
+
public boolean notify(final Message message) {
final Conversation conversation = (Conversation) message.getConversation();
return message.getStatus() == Message.STATUS_RECEIVED
- && notificationsEnabled()
&& !conversation.isMuted()
&& (conversation.alwaysNotify() || wasHighlightedOrPrivate(message))
- && (!conversation.isWithStranger() || notificationsFromStrangers()
- );
- }
-
- public boolean notificationsEnabled() {
- return mXmppConnectionService.getBooleanPreference("show_notification", R.bool.show_notification);
+ && (!conversation.isWithStranger() || notificationsFromStrangers())
+ ;
}
private boolean notificationsFromStrangers() {
return mXmppConnectionService.getBooleanPreference("notifications_from_strangers", R.bool.notifications_from_strangers);
}
- public boolean isQuietHours() {
+ private boolean isQuietHours() {
if (!mXmppConnectionService.getBooleanPreference("enable_quiet_hours", R.bool.enable_quiet_hours)) {
return false;
}
@@ -104,7 +200,6 @@ public class NotificationService {
final long startTime = preferences.getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
final long endTime = preferences.getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY;
-
if (endTime < startTime) {
return nowTime > startTime || nowTime < endTime;
} else {
@@ -143,23 +238,37 @@ public class NotificationService {
if (account == null || !notify) {
updateNotification(notify);
} else {
- updateNotification(getBacklogMessageCount(account) > 0);
+ final int count;
+ final List<String> conversations;
+ synchronized (this.mBacklogMessageCounter) {
+ conversations = getBacklogConversations(account);
+ count = getBacklogMessageCount(account);
+ }
+ updateNotification(count > 0, conversations);
+ }
+ }
+ }
+ private List<String> getBacklogConversations(Account account) {
+ final List<String> conversations = new ArrayList<>();
+ for (Iterator<Map.Entry<Conversation, AtomicInteger>> it = mBacklogMessageCounter.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry<Conversation, AtomicInteger> entry = it.next();
+ if (entry.getKey().getAccount() == account) {
+ conversations.add(entry.getKey().getUuid());
}
}
+ return conversations;
}
private int getBacklogMessageCount(Account account) {
int count = 0;
- synchronized (this.mBacklogMessageCounter) {
- for (Iterator<Map.Entry<Conversation, AtomicInteger>> it = mBacklogMessageCounter.entrySet().iterator(); it.hasNext(); ) {
- Map.Entry<Conversation, AtomicInteger> entry = it.next();
- if (entry.getKey().getAccount() == account) {
- count += entry.getValue().get();
- it.remove();
- }
+ for (Iterator<Map.Entry<Conversation, AtomicInteger>> it = mBacklogMessageCounter.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry<Conversation, AtomicInteger> entry = it.next();
+ if (entry.getKey().getAccount() == account) {
+ count += entry.getValue().get();
+ it.remove();
}
}
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": backlog message count="+count);
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": backlog message count=" + count);
return count;
}
@@ -206,11 +315,12 @@ public class NotificationService {
}
synchronized (notifications) {
pushToStack(message);
- final Account account = message.getConversation().getAccount();
+ final Conversational conversation = message.getConversation();
+ final Account account = conversation.getAccount();
final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
&& !account.inGracePeriod()
&& !this.inMiniGracePeriod(account);
- updateNotification(doNotify);
+ updateNotification(doNotify, Collections.singletonList(conversation.getUuid()));
}
}
@@ -232,7 +342,7 @@ public class NotificationService {
markAsReadIfHasDirectReply(conversation);
if (notifications.remove(conversation.getUuid()) != null) {
cancel(conversation.getUuid(), NOTIFICATION_ID);
- updateNotification(false, true);
+ updateNotification(false, null, true);
}
}
}
@@ -257,12 +367,16 @@ public class NotificationService {
}
public void updateNotification(final boolean notify) {
- updateNotification(notify, false);
+ updateNotification(notify, null, false);
}
- public void updateNotification(final boolean notify, boolean summaryOnly) {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
+ public void updateNotification(final boolean notify, final List<String> conversations) {
+ updateNotification(notify, conversations, false);
+ }
+ private void updateNotification(final boolean notify, final List<String> conversations, final boolean summaryOnly) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
+ final boolean notifyOnlyOneChild = notify && conversations != null && conversations.size() == 1; //if this check is changed to > 0 catchup messages will create one notification per conversation
if (notifications.size() == 0) {
cancel(NOTIFICATION_ID);
} else {
@@ -271,15 +385,22 @@ public class NotificationService {
}
final Builder mBuilder;
if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
- mBuilder = buildSingleConversations(notifications.values().iterator().next());
+ mBuilder = buildSingleConversations(notifications.values().iterator().next(), notify);
modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
notify(NOTIFICATION_ID, mBuilder.build());
} else {
- mBuilder = buildMultipleConversation();
+ mBuilder = buildMultipleConversation(notify);
+ if (notifyOnlyOneChild) {
+ mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
+ }
modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
if (!summaryOnly) {
for (Map.Entry<String, ArrayList<Message>> entry : notifications.entrySet()) {
- Builder singleBuilder = buildSingleConversations(entry.getValue());
+ String uuid = entry.getKey();
+ Builder singleBuilder = buildSingleConversations(entry.getValue(), notifyOnlyOneChild ? conversations.contains(uuid) : notify);
+ if (!notifyOnlyOneChild) {
+ singleBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
+ }
singleBuilder.setGroup(CONVERSATIONS_GROUP);
setNotificationColor(singleBuilder);
notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build());
@@ -330,9 +451,8 @@ public class NotificationService {
}
}
- private Builder buildMultipleConversation() {
- final Builder mBuilder = new NotificationCompat.Builder(
- mXmppConnectionService);
+ private Builder buildMultipleConversation(final boolean notify) {
+ final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, notify ? "messages" : "silent_messages");
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(notifications.size()
+ " "
@@ -378,8 +498,8 @@ public class NotificationService {
return mBuilder;
}
- private Builder buildSingleConversations(final ArrayList<Message> messages) {
- final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
+ private Builder buildSingleConversations(final ArrayList<Message> messages, final boolean notify) {
+ final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, notify ? "messages" : "silent_messages");
if (messages.size() >= 1) {
final Conversation conversation = (Conversation) messages.get(0).getConversation();
final UnreadConversation.Builder mUnreadBuilder = new UnreadConversation.Builder(conversation.getName().toString());
@@ -407,8 +527,7 @@ public class NotificationService {
R.drawable.ic_reply_white_24dp,
replyLabel,
createReplyIntent(conversation, false)).addRemoteInput(remoteInput).build();
- NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(
- R.drawable.ic_wear_reply,
+ NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
replyLabel,
createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build();
mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
@@ -473,18 +592,6 @@ public class NotificationService {
return mBuilder;
}
- private static boolean displaySnoozeAction(List<Message> messages) {
- int numberOfMessagesWithoutReply = 0;
- for (Message message : messages) {
- if (message.getStatus() == Message.STATUS_RECEIVED) {
- ++numberOfMessagesWithoutReply;
- } else {
- return false;
- }
- }
- return numberOfMessagesWithoutReply >= 3;
- }
-
private void modifyForImage(final Builder builder, final UnreadConversation.Builder uBuilder,
final Message message, final ArrayList<Message> messages) {
try {
@@ -545,7 +652,7 @@ public class NotificationService {
styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
builder.setContentText(styledString);
} else {
- builder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages,count,count));
+ builder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
}
}
}
@@ -596,7 +703,7 @@ public class NotificationService {
private CharSequence getMergedBodies(final ArrayList<Message> messages) {
final StringBuilder text = new StringBuilder();
- for(Message message : messages) {
+ for (Message message : messages) {
if (text.length() != 0) {
text.append("\n");
}
@@ -606,7 +713,7 @@ public class NotificationService {
}
private PendingIntent createShowLocationIntent(final Message message) {
- Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message, this.mXmppConnectionService.getApplicationContext());
+ Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message, mXmppConnectionService);
for (Intent intent : intents) {
if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
return PendingIntent.getActivity(mXmppConnectionService, generateRequestCode(message.getConversation(), 18), intent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -676,7 +783,7 @@ public class NotificationService {
return PendingIntent.getService(mXmppConnectionService, generateRequestCode(conversation, 16), intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
- public PendingIntent createSnoozeIntent(Conversation conversation) {
+ private PendingIntent createSnoozeIntent(Conversation conversation) {
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
intent.setAction(XmppConnectionService.ACTION_SNOOZE);
intent.putExtra("uuid", conversation.getUuid());
@@ -736,52 +843,61 @@ public class NotificationService {
}
public Notification createForegroundNotification() {
- final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
- List<Account> accounts = mXmppConnectionService.getAccounts();
- String status;
- Account mAccount = null;
- Log.d(Config.LOGTAG, "Accounts size " + accounts.size());
- if (accounts.size() == 1) {
- mAccount = accounts.get(0);
- if (mAccount.getStatus() == Account.State.ONLINE) {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_online) + ")";
- } else if (mAccount.getStatus() == Account.State.CONNECTING) {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_connecting) + ")";
- } else {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
- }
- } else if (accounts.size() > 1) {
- status = ""; // todo: status for multiple accounts???
- } else {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
- }
- status = " " + status;
- Log.d(Config.LOGTAG, "Status: " + status);
- mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
- if (Config.SHOW_CONNECTED_ACCOUNTS) {
+ final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service));
+ if (Compatibility.twentySix() || Config.SHOW_CONNECTED_ACCOUNTS) {
+ List<Account> accounts = mXmppConnectionService.getAccounts();
int enabled = 0;
int connected = 0;
- for (Account account : accounts) {
- if (account.isOnlineAndConnected()) {
- connected++;
- enabled++;
- } else if (account.isEnabled()) {
- enabled++;
+ String status;
+ Account mAccount = null;
+ if (accounts != null) {
+ for (Account account : accounts) {
+ if (account.isOnlineAndConnected()) {
+ connected++;
+ enabled++;
+ } else if (account.isEnabled()) {
+ enabled++;
+ }
}
+ if (accounts.size() == 1) {
+ mAccount = accounts.get(0);
+ if (mAccount.getStatus() == Account.State.ONLINE) {
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_online) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
+ } else if (mAccount.getStatus() == Account.State.CONNECTING) {
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_connecting) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
+ } else {
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
+ }
+ } else if (accounts.size() > 1) {
+ mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
+ } else {
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
+ }
+ } else {
+ mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations));
}
- mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
} else {
mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations));
}
mBuilder.setContentIntent(createOpenConversationsIntent());
mBuilder.setWhen(0);
- mBuilder.setPriority(Config.SHOW_CONNECTED_ACCOUNTS ? NotificationCompat.PRIORITY_DEFAULT : NotificationCompat.PRIORITY_MIN);
- if (accounts.size() == 1 && accounts.get(0).getStatus() == Account.State.ONLINE) {
- mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp);
- } else if (accounts.size() > 1) {
- mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); // todo: status for multiple accounts???
- } else {
- mBuilder.setSmallIcon(R.drawable.ic_unlink_white_24dp);
+ mBuilder.setPriority(Notification.PRIORITY_LOW);
+ mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp);
+ if (Compatibility.twentySix()) {
+ mBuilder.setChannelId(FOREGROUND_CHANNEL_ID);
}
return mBuilder.build();
}
@@ -801,10 +917,10 @@ public class NotificationService {
errors.add(account);
}
}
- if (mXmppConnectionService.showForegroundService()) {
+ if (Compatibility.keepForegroundService(mXmppConnectionService)) {
notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
}
- final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
+ final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
if (errors.size() == 0) {
cancel(ERROR_NOTIFICATION_ID);
return;
@@ -819,38 +935,77 @@ public class NotificationService {
mXmppConnectionService.getString(R.string.try_again),
createTryAgainIntent());
mBuilder.setDeleteIntent(createDismissErrorIntent());
- mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
- mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
- mBuilder.setLocalOnly(true);
- mBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
- if (errors.size() == 1) {
- Intent intent = new Intent(mXmppConnectionService, EditAccountActivity.class);
- Account mAccount = mXmppConnectionService.getAccounts().get(0);
- intent.putExtra("jid", mAccount.getJid().asBareJid().toString());
- intent.putExtra("init", false);
- mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService,
- 145,
- intent,
- PendingIntent.FLAG_UPDATE_CURRENT));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
+ mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
} else {
- mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService,
- 145,
- new Intent(mXmppConnectionService, ManageAccountActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT));
+ mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
+ mBuilder.setLocalOnly(true);
+ }
+ mBuilder.setPriority(Notification.PRIORITY_LOW);
+ mBuilder.setContentIntent(PendingIntent.getActivity(mXmppConnectionService,
+ 145,
+ new Intent(mXmppConnectionService, ManageAccountActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ if (Compatibility.twentySix()) {
+ mBuilder.setChannelId(ERROR_CHANNEL_ID);
}
notify(ERROR_NOTIFICATION_ID, mBuilder.build());
}
- public Notification updateFileAddingNotification(int current, Message message) {
- final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mXmppConnectionService);
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
+ public void updateFileAddingNotification(int current, Message message) {
+ Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
mBuilder.setProgress(100, current, false);
mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
mBuilder.setContentIntent(createContentIntent(message.getConversation()));
+ mBuilder.setOngoing(true);
+ if (Compatibility.twentySix()) {
+ mBuilder.setChannelId(VIDEOCOMPRESSION_CHANNEL_ID);
+ }
Notification notification = mBuilder.build();
notify(FOREGROUND_NOTIFICATION_ID, notification);
- return notification;
+ }
+
+ public void dismissForcedForegroundNotification() {
+ cancel(FOREGROUND_NOTIFICATION_ID);
+ }
+
+ public Notification exportLogsNotification() {
+ Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
+ mBuilder.setContentText(mXmppConnectionService.getString(R.string.notification_export_logs_title));
+ mBuilder.setProgress(0, 0, true);
+ mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp);
+ mBuilder.setOngoing(true);
+ if (Compatibility.twentySix()) {
+ mBuilder.setChannelId(BACKUP_CHANNEL_ID);
+ }
+ return mBuilder.build();
+ }
+
+ public void exportLogsServiceNotification(Notification notification) {
+ notify(FOREGROUND_NOTIFICATION_ID, notification);
+ }
+
+ public Notification AppUpdateNotification(PendingIntent intent, String version, String filesize) {
+ Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
+ mBuilder.setContentText(mXmppConnectionService.getString(R.string.notification_export_logs_title));
+ mBuilder.setContentText(String.format(mXmppConnectionService.getString(R.string.update_available), version, filesize));
+ mBuilder.setSmallIcon(R.drawable.ic_update_notification);
+ mBuilder.setContentIntent(intent);
+ mBuilder.setOngoing(true);
+ if (Compatibility.twentySix()) {
+ mBuilder.setChannelId(UPDATE_CHANNEL_ID);
+ }
+ return mBuilder.build();
+ }
+
+ public void AppUpdateServiceNotification(Notification notification) {
+ notify(FOREGROUND_NOTIFICATION_ID, notification);
}
private void notify(String tag, int id, Notification notification) {
diff --git a/src/main/java/de/pixart/messenger/services/UpdateService.java b/src/main/java/de/pixart/messenger/services/UpdateService.java
index 3b3829290..bf5242939 100644
--- a/src/main/java/de/pixart/messenger/services/UpdateService.java
+++ b/src/main/java/de/pixart/messenger/services/UpdateService.java
@@ -1,14 +1,11 @@
package de.pixart.messenger.services;
-import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;
import android.widget.Toast;
@@ -27,12 +24,12 @@ import de.pixart.messenger.R;
import de.pixart.messenger.ui.UpdaterActivity;
import static de.pixart.messenger.http.HttpConnectionManager.getProxy;
-import static de.pixart.messenger.services.NotificationService.UPDATE_NOTIFICATION_ID;
public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapper> {
private boolean mUseTor;
private Context context;
private String store;
+ private NotificationService getNotificationService;
public UpdateService() {
}
@@ -40,6 +37,7 @@ public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapp
this.context = context;
this.store = Store;
this.mUseTor = mXmppConnectionService.useTorToConnect();
+ this.getNotificationService = mXmppConnectionService.getNotificationService();
}
@Override
@@ -145,14 +143,7 @@ public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapp
intent.putExtra("changelog", changelog);
intent.putExtra("store", store);
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
- mBuilder.setContentTitle(context.getString(R.string.update_service));
- mBuilder.setContentText(String.format(context.getString(R.string.update_available), version, filesize));
- mBuilder.setSmallIcon(R.drawable.ic_update_notification);
- mBuilder.setContentIntent(pi);
- Notification notification = mBuilder.build();
- notificationManager.notify(UPDATE_NOTIFICATION_ID, notification);
+ getNotificationService.AppUpdateServiceNotification(getNotificationService.AppUpdateNotification(pi, version, filesize));
}
private int checkVersion(String remoteVersion, String installedVersion) {
diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
index e50f0d9e2..525354957 100644
--- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
+++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
@@ -1,10 +1,13 @@
package de.pixart.messenger.services;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.AlarmManager;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -113,7 +116,9 @@ import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.ui.SettingsActivity;
import de.pixart.messenger.ui.UiCallback;
import de.pixart.messenger.ui.interfaces.OnAvatarPublication;
+import de.pixart.messenger.ui.interfaces.OnMediaLoaded;
import de.pixart.messenger.ui.interfaces.OnSearchResultsAvailable;
+import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.ConversationsFileObserver;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.ExceptionHelper;
@@ -150,6 +155,7 @@ import de.pixart.messenger.xmpp.jingle.OnJinglePacketReceived;
import de.pixart.messenger.xmpp.jingle.stanzas.JinglePacket;
import de.pixart.messenger.xmpp.mam.MamReference;
import de.pixart.messenger.xmpp.pep.Avatar;
+import de.pixart.messenger.xmpp.pep.PublishOptions;
import de.pixart.messenger.xmpp.stanzas.IqPacket;
import de.pixart.messenger.xmpp.stanzas.MessagePacket;
import de.pixart.messenger.xmpp.stanzas.PresencePacket;
@@ -167,11 +173,9 @@ public class XmppConnectionService extends Service {
public static final String ACTION_IDLE_PING = "idle_ping";
public static final String ACTION_FCM_TOKEN_REFRESH = "fcm_token_refresh";
public static final String ACTION_FCM_MESSAGE_RECEIVED = "fcm_message_received";
- private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
- private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp";
public static final String FDroid = "org.fdroid.fdroid";
public static final String PlayStore = "com.android.vending";
- public static final String Yalp = "com.github.yeriomin.yalpstore";
+ private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp";
static {
URL.setURLStreamHandlerFactory(new CustomURLStreamHandlerFactory());
@@ -198,29 +202,60 @@ public class XmppConnectionService extends Service {
}
}
};
+ //Ui callback listeners
+ private final Set<OnConversationUpdate> mOnConversationUpdates = Collections.newSetFromMap(new WeakHashMap<OnConversationUpdate, Boolean>());
+ private final Set<OnShowErrorToast> mOnShowErrorToasts = Collections.newSetFromMap(new WeakHashMap<OnShowErrorToast, Boolean>());
+ private final Set<OnAccountUpdate> mOnAccountUpdates = Collections.newSetFromMap(new WeakHashMap<OnAccountUpdate, Boolean>());
+ private final Set<OnCaptchaRequested> mOnCaptchaRequested = Collections.newSetFromMap(new WeakHashMap<OnCaptchaRequested, Boolean>());
+ private final Set<OnRosterUpdate> mOnRosterUpdates = Collections.newSetFromMap(new WeakHashMap<OnRosterUpdate, Boolean>());
+ private final Set<OnUpdateBlocklist> mOnUpdateBlocklist = Collections.newSetFromMap(new WeakHashMap<OnUpdateBlocklist, Boolean>());
+ private final Set<OnMucRosterUpdate> mOnMucRosterUpdate = Collections.newSetFromMap(new WeakHashMap<OnMucRosterUpdate, Boolean>());
+ private final Set<OnKeyStatusUpdated> mOnKeyStatusUpdated = Collections.newSetFromMap(new WeakHashMap<OnKeyStatusUpdated, Boolean>());
+ private final Object LISTENER_LOCK = new Object();
public DatabaseBackend databaseBackend;
- private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true);
- private long mLastActivity = 0;
- private ContentObserver contactObserver = new ContentObserver(null) {
+ private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
+
@Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- Intent intent = new Intent(getApplicationContext(),
- XmppConnectionService.class);
- intent.setAction(ACTION_MERGE_PHONE_CONTACTS);
- startService(intent);
+ public boolean onMessageAcknowledged(Account account, String uuid) {
+ for (final Conversation conversation : getConversations()) {
+ if (conversation.getAccount() == account) {
+ Message message = conversation.findUnsentMessageWithUuid(uuid);
+ if (message != null) {
+ message.setStatus(Message.STATUS_SEND);
+ message.setErrorMessage(null);
+ databaseBackend.updateMessage(message, false);
+ return true;
+ }
+ }
+ }
+ return false;
}
};
public FileBackend fileBackend = new FileBackend(this);
+ private final ConversationsFileObserver fileObserver = new ConversationsFileObserver(
+ Environment.getExternalStorageDirectory().getAbsolutePath()
+ ) {
+ @Override
+ public void onEvent(int event, String path) {
+ Log.d(Config.LOGTAG, "event " + event + " path=" + path);
+ markFileDeleted(path);
+ }
+ };
+ public HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this);
+ private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor(true);
+ private long mLastActivity = 0;
private MemorizingTrustManager mMemorizingTrustManager;
private NotificationService mNotificationService = new NotificationService(this);
private ShortcutService mShortcutService = new ShortcutService(this);
private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false);
- private AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
+ protected AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
private OnMessagePacketReceived mMessageParser = new MessageParser(this);
private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
private IqParser mIqParser = new IqParser(this);
private MessageGenerator mMessageGenerator = new MessageGenerator(this);
+ private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
+ private List<Account> accounts;
+ private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this);
public OnContactStatusChanged onContactStatusChanged = (contact, online) -> {
Conversation conversation = find(getConversations(), contact);
if (conversation != null) {
@@ -240,9 +275,6 @@ public class XmppConnectionService extends Service {
}
}
};
- private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
- private List<Account> accounts;
- private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this);
private final OnJinglePacketReceived jingleListener = new OnJinglePacketReceived() {
@Override
@@ -250,51 +282,9 @@ public class XmppConnectionService extends Service {
mJingleConnectionManager.deliverPacket(account, packet);
}
};
- public HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this);
private AvatarService mAvatarService = new AvatarService(this);
private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
private PushManagementService mPushManagementService = new PushManagementService(this);
- private final ConversationsFileObserver fileObserver = new ConversationsFileObserver(
- Environment.getExternalStorageDirectory().getAbsolutePath()
- ) {
- @Override
- public void onEvent(int event, String path) {
- markFileDeleted(path);
- }
- };
- private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() {
-
- @Override
- public boolean onMessageAcknowledged(Account account, String uuid) {
- for (final Conversation conversation : getConversations()) {
- if (conversation.getAccount() == account) {
- Message message = conversation.findUnsentMessageWithUuid(uuid);
- if (message != null) {
- message.setStatus(Message.STATUS_SEND);
- message.setErrorMessage(null);
- databaseBackend.updateMessage(message, false);
- return true;
- }
- }
- }
- return false;
- }
- };
-
- private int unreadCount = -1;
-
- //Ui callback listeners
- private final Set<OnConversationUpdate> mOnConversationUpdates = Collections.newSetFromMap(new WeakHashMap<OnConversationUpdate, Boolean>());
- private final Set<OnShowErrorToast> mOnShowErrorToasts = Collections.newSetFromMap(new WeakHashMap<OnShowErrorToast, Boolean>());
- private final Set<OnAccountUpdate> mOnAccountUpdates = Collections.newSetFromMap(new WeakHashMap<OnAccountUpdate, Boolean>());
- private final Set<OnCaptchaRequested> mOnCaptchaRequested = Collections.newSetFromMap(new WeakHashMap<OnCaptchaRequested, Boolean>());
- private final Set<OnRosterUpdate> mOnRosterUpdates = Collections.newSetFromMap(new WeakHashMap<OnRosterUpdate, Boolean>());
- private final Set<OnUpdateBlocklist> mOnUpdateBlocklist = Collections.newSetFromMap(new WeakHashMap<OnUpdateBlocklist, Boolean>());
- private final Set<OnMucRosterUpdate> mOnMucRosterUpdate = Collections.newSetFromMap(new WeakHashMap<OnMucRosterUpdate, Boolean>());
- private final Set<OnKeyStatusUpdated> mOnKeyStatusUpdated = Collections.newSetFromMap(new WeakHashMap<OnKeyStatusUpdated, Boolean>());
-
- private final Object LISTENER_LOCK = new Object();
-
private final OnBindListener mOnBindListener = new OnBindListener() {
@Override
@@ -315,7 +305,9 @@ public class XmppConnectionService extends Service {
account.getRoster().clearPresences();
mJingleConnectionManager.cancelInTransmission();
fetchRosterFromServer(account);
- fetchBookmarks(account);
+ if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
+ fetchBookmarks(account);
+ }
final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval();
final boolean catchup = getMessageArchiveService().inCatchup(account);
if (flexible && catchup) {
@@ -333,6 +325,7 @@ public class XmppConnectionService extends Service {
syncDirtyContacts(account);
}
};
+ private int unreadCount = -1;
private AtomicLong mLastExpiryRun = new AtomicLong(0);
private SecureRandom mRandom;
private LruCache<Pair<String, String>, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
@@ -418,7 +411,8 @@ public class XmppConnectionService extends Service {
private WakeLock wakeLock;
private PowerManager pm;
private LruCache<String, Bitmap> mBitmapCache;
- private EventReceiver mEventReceiver = new EventReceiver();
+ private BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver();
+ private BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver();
private static String generateFetchKey(Account account, final Avatar avatar) {
return account.getJid().asBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum;
@@ -569,6 +563,11 @@ public class XmppConnectionService extends Service {
return find(getConversations(), account, jid);
}
+ public boolean isMuc(final Account account, final Jid jid) {
+ final Conversation c = find(account, jid);
+ return c != null && c.getMode() == Conversational.MODE_MULTI;
+ }
+
public void search(List<String> term, OnSearchResultsAvailable onSearchResultsAvailable) {
MessageSearchTask.search(this, term, onSearchResultsAvailable);
}
@@ -586,11 +585,6 @@ public class XmppConnectionService extends Service {
resetAllAttemptCounts(true, false);
}
break;
- case ACTION_MERGE_PHONE_CONTACTS:
- if (restoredFromDatabaseLatch.getCount() == 0) {
- loadPhoneContacts();
- }
- return START_STICKY;
case Intent.ACTION_SHUTDOWN:
logoutAndSave(true);
return START_NOT_STICKY;
@@ -667,6 +661,7 @@ public class XmppConnectionService extends Service {
updateConversation(c);
});
case AudioManager.RINGER_MODE_CHANGED_ACTION:
+ case NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED:
if (dndOnSilentMode()) {
refreshAllPresences();
}
@@ -732,6 +727,7 @@ public class XmppConnectionService extends Service {
}
private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet<Account> pingCandidates) {
+ storeNumberOfAccounts(this.getAccounts().size());
boolean pingNow = false;
if (account.getStatus().isAttemptReconnect()) {
if (!hasInternetConnection()) {
@@ -807,6 +803,14 @@ public class XmppConnectionService extends Service {
return pingNow;
}
+ private void storeNumberOfAccounts(int accounts) {
+ //write No of accounts to file
+ final SharedPreferences.Editor editor = getPreferences().edit();
+ Log.d(Config.LOGTAG, "Number of accounts is " + accounts);
+ editor.putInt(SettingsActivity.NUMBER_OF_ACCOUNTS, accounts);
+ editor.apply();
+ }
+
public boolean isDataSaverDisabled() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
@@ -925,6 +929,10 @@ public class XmppConnectionService extends Service {
}
}
+ public boolean getAttachmentChoicePreference() {
+ return getBooleanPreference(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, R.bool.quick_share_attachment_choice);
+ }
+
private Presence.Status getTargetPresence() {
if (dndOnSilentMode() && isPhoneSilenced()) {
return Presence.Status.DND;
@@ -950,16 +958,25 @@ public class XmppConnectionService extends Service {
}
private boolean isPhoneSilenced() {
- AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ final boolean notificationDnd;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ final NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ final int filter = notificationManager == null ? NotificationManager.INTERRUPTION_FILTER_UNKNOWN : notificationManager.getCurrentInterruptionFilter();
+ notificationDnd = filter >= NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+ } else {
+ notificationDnd = false;
+ }
+ final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ final int ringerMode = audioManager == null ? AudioManager.RINGER_MODE_NORMAL : audioManager.getRingerMode();
try {
if (treatVibrateAsSilent()) {
- return audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+ return notificationDnd || ringerMode != AudioManager.RINGER_MODE_NORMAL;
} else {
- return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT;
+ return notificationDnd || ringerMode == AudioManager.RINGER_MODE_SILENT;
}
} catch (Throwable throwable) {
Log.d(Config.LOGTAG, "platform bug in isPhoneSilenced (" + throwable.getMessage() + ")");
- return false;
+ return notificationDnd;
}
}
@@ -1018,7 +1035,7 @@ public class XmppConnectionService extends Service {
final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
try {
final NetworkInfo activeNetwork = cm == null ? null : cm.getActiveNetworkInfo();
- return activeNetwork != null && activeNetwork.isConnected();
+ return activeNetwork != null && (activeNetwork.isConnected() || activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET);
} catch (RuntimeException e) {
Log.d(Config.LOGTAG, "unable to check for internet connection", e);
return true; //if internet connection can not be checked it is probably best to just try
@@ -1086,6 +1103,9 @@ public class XmppConnectionService extends Service {
Resolver.init(this);
this.mRandom = new SecureRandom();
updateMemorizingTrustmanager();
+ if (Compatibility.twentySix()) {
+ mNotificationService.initializeChannels();
+ }
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
@@ -1112,8 +1132,10 @@ public class XmppConnectionService extends Service {
editor.apply();
restoreFromDatabase();
- getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ startContactObserver();
+ }
+ if (Compatibility.hasStoragePermission(this)) {
Log.d(Config.LOGTAG, "starting file observer");
new Thread(fileObserver::startWatching).start();
}
@@ -1143,14 +1165,29 @@ public class XmppConnectionService extends Service {
toggleScreenEventReceiver();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
scheduleNextIdlePing();
+ IntentFilter intentFilter = new IntentFilter();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ }
+ intentFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+ registerReceiver(this.mInternalEventReceiver, intentFilter);
}
//start export log service every day at given time
ScheduleAutomaticExport();
// cancel scheduled exporter
CancelAutomaticExport(false);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- registerReceiver(this.mEventReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
- }
+ }
+
+ public void startContactObserver() {
+ getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (restoredFromDatabaseLatch.getCount() == 0) {
+ loadPhoneContacts();
+ }
+ }
+ });
}
@Override
@@ -1165,7 +1202,7 @@ public class XmppConnectionService extends Service {
@Override
public void onDestroy() {
try {
- unregisterReceiver(this.mEventReceiver);
+ unregisterReceiver(this.mInternalEventReceiver);
} catch (IllegalArgumentException e) {
//ignored
}
@@ -1182,12 +1219,13 @@ public class XmppConnectionService extends Service {
public void toggleScreenEventReceiver() {
if (awayWhenScreenOff() && !manuallyChangePresence()) {
- final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
- registerReceiver(this.mEventReceiver, filter);
+ registerReceiver(this.mInternalScreenEventReceiver, filter);
} else {
try {
- unregisterReceiver(this.mEventReceiver);
+ unregisterReceiver(this.mInternalScreenEventReceiver);
} catch (IllegalArgumentException e) {
//ignored
}
@@ -1195,19 +1233,22 @@ public class XmppConnectionService extends Service {
}
public void toggleForegroundService() {
- if (mForceForegroundService.get() || (showForegroundService())) {
+ final boolean status;
+ if (mForceForegroundService.get() || (Compatibility.keepForegroundService(this)/* && hasEnabledAccounts()*/)) {
startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification());
- Log.d(Config.LOGTAG, "started foreground service");
+ status = true;
} else {
stopForeground(true);
- Log.d(Config.LOGTAG, "stopped foreground service");
+ status = false;
}
+ mNotificationService.dismissForcedForegroundNotification(); //if the channel was changed the previous call might fail
+ Log.d(Config.LOGTAG, "ForegroundService: " + (status ? "on" : "off"));
}
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- if (showForegroundService() || mForceForegroundService.get()) {
+ if ((Compatibility.keepForegroundService(this) && hasEnabledAccounts()) || mForceForegroundService.get()) {
Log.d(Config.LOGTAG, "ignoring onTaskRemoved because foreground service is activated");
} else {
this.logoutAndSave(false);
@@ -1494,7 +1535,7 @@ public class XmppConnectionService extends Service {
@Override
public void onMessageFound(Message message) {
if (mHttpConnectionManager.getAutoAcceptFileSize() >= message.getFileParams().size) {
- Log.d(Config.LOGTAG, "Resend failed message with size " + message.getFileParams().size + " bytes for " + conversation.getJid());
+ Log.d(Config.LOGTAG, "Resend failed message with size " + message.getFileParams().size + " bytes for " + conversation.getJid());
resendMessage(message, true);
}
}
@@ -1520,45 +1561,53 @@ public class XmppConnectionService extends Service {
public void fetchBookmarks(final Account account) {
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
final Element query = iqPacket.query("jabber:iq:private");
- query.addChild("storage", "storage:bookmarks");
- final OnIqPacketReceived callback = new OnIqPacketReceived() {
+ query.addChild("storage", Namespace.BOOKMARKS);
+ final OnIqPacketReceived callback = (a, response) -> {
+ if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Element query1 = response.query();
+ final Element storage = query1.findChild("storage", "storage:bookmarks");
+ processBookmarks(a, storage);
+ } else {
+ Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": could not fetch bookmarks");
+ }
+ };
+ sendIqPacket(account, iqPacket, callback);
+ }
- @Override
- public void onIqPacketReceived(final Account account, final IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- final Element query = packet.query();
- final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
- final Element storage = query.findChild("storage", "storage:bookmarks");
- final boolean autojoin = respectAutojoin();
- if (storage != null) {
- for (final Element item : storage.getChildren()) {
- if (item.getName().equals("conference")) {
- final Bookmark bookmark = Bookmark.parse(item, account);
- Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
- if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
- bookmark.setBookmarkName(old.getBookmarkName());
- }
- Conversation conversation = find(bookmark);
- if (conversation != null) {
- bookmark.setConversation(conversation);
- } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) {
- conversation = findOrCreateConversation(account, bookmark.getJid(), true, true, false);
- bookmark.setConversation(conversation);
- }
- }
- }
+ public void processBookmarks(Account account, Element storage) {
+ final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
+ final boolean autojoin = respectAutojoin();
+ if (storage != null) {
+ for (final Element item : storage.getChildren()) {
+ if (item.getName().equals("conference")) {
+ final Bookmark bookmark = Bookmark.parse(item, account);
+ Bookmark old = bookmarks.put(bookmark.getJid(), bookmark);
+ if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
+ bookmark.setBookmarkName(old.getBookmarkName());
+ }
+ Conversation conversation = find(bookmark);
+ if (conversation != null) {
+ bookmark.setConversation(conversation);
+ } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) {
+ conversation = findOrCreateConversation(account, bookmark.getJid(), true, true, false);
+ bookmark.setConversation(conversation);
}
- account.setBookmarks(new CopyOnWriteArrayList<>(bookmarks.values()));
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fetch bookmarks");
}
}
- };
- sendIqPacket(account, iqPacket, callback);
+ }
+ account.setBookmarks(new CopyOnWriteArrayList<>(bookmarks.values()));
}
public void pushBookmarks(Account account) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks");
+ if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
+ pushBookmarksPep(account);
+ } else {
+ pushBookmarksPrivateXml(account);
+ }
+ }
+
+ private void pushBookmarksPrivateXml(Account account) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml");
IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks");
@@ -1568,6 +1617,46 @@ public class XmppConnectionService extends Service {
sendIqPacket(account, iqPacket, mDefaultIqHandler);
}
+ private void pushBookmarksPep(Account account) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via pep");
+ Element storage = new Element("storage", "storage:bookmarks");
+ for (Bookmark bookmark : account.getBookmarks()) {
+ storage.addChild(bookmark);
+ }
+ pushNodeAndEnforcePublishOptions(account, Namespace.BOOKMARKS, storage, PublishOptions.persistentWhitelistAccess());
+ }
+
+ private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final Bundle options) {
+ pushNodeAndEnforcePublishOptions(account, node, element, options, true);
+ }
+
+ private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final Bundle options, final boolean retry) {
+ IqPacket packet = mIqGenerator.publishElement(node, element, options);
+ Log.d(Config.LOGTAG, packet.toString());
+ sendIqPacket(account, packet, (a, response) -> {
+ if (response.getType() == IqPacket.TYPE.RESULT) {
+ return;
+ }
+ final Element error = response.getType() == IqPacket.TYPE.ERROR ? response.findChild("error") : null;
+ final boolean preconditionNotMet = error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR);
+ if (retry && preconditionNotMet) {
+ pushNodeConfiguration(account, node, options, new OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ pushNodeAndEnforcePublishOptions(account, node, element, options, false);
+ }
+
+ @Override
+ public void onPushFailed() {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to push node configuration (" + node + ")");
+ }
+ });
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error publishing bookmarks (retry=" + Boolean.toString(retry) + ") " + response);
+ }
+ });
+ }
+
private void restoreFromDatabase() {
synchronized (this.conversations) {
final Map<String, Account> accountLookupTable = new Hashtable<>();
@@ -1671,7 +1760,7 @@ public class XmppConnectionService extends Service {
}
Log.d(Config.LOGTAG, "finished merging phone contacts");
mShortcutService.refresh(mInitialAddressbookSyncCompleted.compareAndSet(false, true));
- updateAccountUi();
+ updateRosterUi();
}
}));
}
@@ -1860,7 +1949,7 @@ public class XmppConnectionService extends Service {
conversation.setContactJid(jid.asBareJid());
}
databaseBackend.updateConversation(conversation);
- loadMessagesFromDb = conversation.messagesLoaded.compareAndSet(true,false);
+ loadMessagesFromDb = conversation.messagesLoaded.compareAndSet(true, false);
} else {
String conversationName;
Contact contact = account.getRoster().getContact(jid);
@@ -1934,7 +2023,7 @@ public class XmppConnectionService extends Service {
conversation.setContactJid(jid.asBareJid());
}
databaseBackend.updateConversation(conversation);
- loadMessagesFromDb = conversation.messagesLoaded.compareAndSet(true,false);
+ loadMessagesFromDb = conversation.messagesLoaded.compareAndSet(true, false);
} else {
String conversationName;
Contact contact = account.getRoster().getContact(jid);
@@ -2139,6 +2228,7 @@ public class XmppConnectionService extends Service {
updateAccountUi();
getNotificationService().updateErrorNotification();
syncEnabledAccountSetting();
+ toggleForegroundService();
}
}
@@ -2147,7 +2237,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnConversationUpdates.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as ConversationListChangedListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as ConversationListChangedListener");
}
this.mNotificationService.setIsInForeground(this.mOnConversationUpdates.size() > 0);
}
@@ -2173,7 +2263,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnShowErrorToasts.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnShowErrorToastListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnShowErrorToastListener");
}
}
if (remainingListeners) {
@@ -2197,7 +2287,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnAccountUpdates.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnAccountListChangedtListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnAccountListChangedtListener");
}
}
if (remainingListeners) {
@@ -2221,7 +2311,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnCaptchaRequested.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnCaptchaRequestListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnCaptchaRequestListener");
}
}
if (remainingListeners) {
@@ -2245,7 +2335,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnRosterUpdates.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnRosterUpdateListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnRosterUpdateListener");
}
}
if (remainingListeners) {
@@ -2269,7 +2359,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnUpdateBlocklist.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnUpdateBlocklistListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnUpdateBlocklistListener");
}
}
if (remainingListeners) {
@@ -2293,7 +2383,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnKeyStatusUpdated.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnKeyStatusUpdateListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnKeyStatusUpdateListener");
}
}
if (remainingListeners) {
@@ -2317,7 +2407,7 @@ public class XmppConnectionService extends Service {
synchronized (LISTENER_LOCK) {
remainingListeners = checkListeners();
if (!this.mOnMucRosterUpdate.add(listener)) {
- Log.w(Config.LOGTAG,listener.getClass().getName()+" is already registered as OnMucRosterListener");
+ Log.w(Config.LOGTAG, listener.getClass().getName() + " is already registered as OnMucRosterListener");
}
}
if (remainingListeners) {
@@ -2581,7 +2671,10 @@ public class XmppConnectionService extends Service {
}
private boolean hasEnabledAccounts() {
- for(Account account : this.accounts) {
+ if (this.accounts == null) {
+ return false; // set to false if accounts could not be fetched - used for notifications
+ }
+ for (Account account : this.accounts) {
if (account.isEnabled()) {
return true;
}
@@ -2589,6 +2682,18 @@ public class XmppConnectionService extends Service {
return false;
}
+ public void getAttachments(final Conversation conversation, int limit, final OnMediaLoaded onMediaLoaded) {
+ getAttachments(conversation.getAccount(), conversation.getJid().asBareJid(), limit, onMediaLoaded);
+ }
+
+ public void getAttachments(final Account account, final Jid jid, final int limit, final OnMediaLoaded onMediaLoaded) {
+ getAttachments(account.getUuid(), jid.asBareJid(), limit, onMediaLoaded);
+ }
+
+ public void getAttachments(final String account, final Jid jid, final int limit, final OnMediaLoaded onMediaLoaded) {
+ new Thread(() -> onMediaLoaded.onMediaLoaded(fileBackend.convertToAttachments(databaseBackend.getRelativeFilePaths(account, jid, limit)))).start();
+ }
+
public void persistSelfNick(MucOptions.User self) {
final Conversation conversation = self.getConversation();
Jid full = self.getFullJid();
@@ -2744,13 +2849,13 @@ public class XmppConnectionService extends Service {
if (callback != null) {
callback.error(R.string.conference_creation_failed, null);
}
- return true;
+ return false;
}
} else {
if (callback != null) {
callback.error(R.string.not_connected_try_again, null);
}
- return true;
+ return false;
}
}
@@ -2796,6 +2901,7 @@ public class XmppConnectionService extends Service {
}
public void pushNodeConfiguration(Account account, final String node, final Bundle options, final OnConfigurationPushed callback) {
+ Log.d(Config.LOGTAG, "pushing node configuration");
pushNodeConfiguration(account, account.getJid().asBareJid(), node, options, callback);
}
@@ -3172,7 +3278,7 @@ public class XmppConnectionService extends Service {
getAvatarService().clear(account);
databaseBackend.updateAccount(account);
}
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": published avatar "+(avatar.size/1024)+"KiB");
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB");
if (callback != null) {
callback.onAvatarPublicationSucceeded();
}
@@ -3185,7 +3291,7 @@ public class XmppConnectionService extends Service {
});
} else {
Element error = result.findChild("error");
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": server rejected avatar "+(avatar.size/1024)+"KiB "+(error!=null?error.toString():""));
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server rejected avatar " + (avatar.size / 1024) + "KiB " + (error != null ? error.toString() : ""));
if (callback != null) {
callback.onAvatarPublicationFailed(R.string.error_publish_avatar_server_reject);
}
@@ -3539,7 +3645,7 @@ public class XmppConnectionService extends Service {
public long getLongPreference(String name, @IntegerRes int res) {
long defaultValue = getResources().getInteger(res);
try {
- return Long.parseLong(getPreferences().getString(name,String.valueOf(defaultValue)));
+ return Long.parseLong(getPreferences().getString(name, String.valueOf(defaultValue)));
} catch (NumberFormatException e) {
return defaultValue;
}
@@ -3777,7 +3883,7 @@ public class XmppConnectionService extends Service {
public void updateMemorizingTrustmanager() {
final MemorizingTrustManager tm;
- final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", getResources().getBoolean(R.bool.dont_trust_system_cas));
+ final boolean dontTrustSystemCAs = getBooleanPreference("dont_trust_system_cas", R.bool.dont_trust_system_cas);
if (dontTrustSystemCAs) {
tm = new MemorizingTrustManager(getApplicationContext(), null);
} else {
@@ -4133,7 +4239,7 @@ public class XmppConnectionService extends Service {
ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response);
if (presence.getVer().equals(discoveryResult.getVer())) {
databaseBackend.insertDiscoveryResult(discoveryResult);
- injectServiceDiscorveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult);
+ injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult);
} else {
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer());
}
@@ -4144,7 +4250,7 @@ public class XmppConnectionService extends Service {
}
}
- private void injectServiceDiscorveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) {
+ private void injectServiceDiscoveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) {
for (Contact contact : roster.getContacts()) {
for (Presence presence : contact.getPresences().getPresences().values()) {
if (hash.equals(presence.getHash()) && ver.equals(presence.getVer())) {
@@ -4270,11 +4376,7 @@ public class XmppConnectionService extends Service {
}
public boolean blindTrustBeforeVerification() {
- return getPreferences().getBoolean(SettingsActivity.BLIND_TRUST_BEFORE_VERIFICATION, getResources().getBoolean(R.bool.btbv));
- }
-
- public boolean showForegroundService() {
- return getPreferences().getBoolean(SettingsActivity.SHOW_FOREGROUND_SERVICE, getResources().getBoolean(R.bool.show_foreground_service));
+ return getBooleanPreference(SettingsActivity.BLIND_TRUST_BEFORE_VERIFICATION, R.bool.btbv);
}
public void ScheduleAutomaticExport() {
@@ -4293,7 +4395,7 @@ public class XmppConnectionService extends Service {
if (timetoexport.before(now)) {
SimpleDateFormat newDate = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
timetoexport.add(Calendar.DAY_OF_YEAR, 1); //DATE or DAY_OF_MONTH
- Log.d(Config.LOGTAG, "Schedule automatic export logs, for today, the export time is in the past, scheduling first export run for the next day ("+ newDate.format(timetoexport.getTimeInMillis()) +").");
+ Log.d(Config.LOGTAG, "Schedule automatic export logs, for today, the export time is in the past, scheduling first export run for the next day (" + newDate.format(timetoexport.getTimeInMillis()) + ").");
}
final PendingIntent ScheduleExportIntent = PendingIntent.getBroadcast(this, AlarmReceiver.SCHEDULE_ALARM_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
((AlarmManager) getSystemService(ALARM_SERVICE)).setInexactRepeating(AlarmManager.RTC_WAKEUP, timetoexport.getTimeInMillis(), AlarmManager.INTERVAL_DAY, ScheduleExportIntent);
@@ -4419,4 +4521,11 @@ public class XmppConnectionService extends Service {
return XmppConnectionService.this;
}
}
+
+ private class InternalEventReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onStartCommand(intent, 0, 0);
+ }
+ }
}
diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
index db70c8147..070914bb8 100644
--- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
@@ -32,6 +32,7 @@ import org.openintents.openpgp.util.OpenPgpUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -49,8 +50,14 @@ import de.pixart.messenger.entities.MucOptions.User;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate;
import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate;
+import de.pixart.messenger.ui.adapter.MediaAdapter;
+import de.pixart.messenger.ui.interfaces.OnMediaLoaded;
+import de.pixart.messenger.ui.util.Attachment;
+import de.pixart.messenger.ui.util.GridManager;
+import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper;
import de.pixart.messenger.ui.util.MyLinkify;
import de.pixart.messenger.ui.util.SoftKeyboardUtils;
+import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.EmojiWrapper;
import de.pixart.messenger.utils.MenuDoubleTabUtil;
import de.pixart.messenger.utils.StringUtils;
@@ -63,7 +70,7 @@ import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.entities.Bookmark.printableValue;
import static de.pixart.messenger.utils.StringUtils.changed;
-public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher {
+public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded {
public static final String ACTION_VIEW_MUC = "view_muc";
private static final float INACTIVE_ALPHA = 0.4684f; //compromise between dark and light theme
private Conversation mConversation;
@@ -95,6 +102,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
};
private ActivityMucDetailsBinding binding;
+ private MediaAdapter mMediaAdapter;
private String uuid = null;
private User mSelectedUser = null;
@@ -318,6 +326,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
this.binding.mucEditTitle.addTextChangedListener(this);
this.binding.mucEditSubject.addTextChangedListener(this);
this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject));
+ mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
+ this.binding.media.setAdapter(mMediaAdapter);
+ GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size);
}
@Override
@@ -327,6 +338,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
if (this.mTheme != theme) {
recreate();
}
+ binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
}
@Override
@@ -447,14 +459,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
if (tag instanceof User) {
getMenuInflater().inflate(R.menu.muc_details_context, menu);
final User user = (User) tag;
- final User self = mConversation.getMucOptions().getSelf();
- final Jid jid = user.getFullJid();
this.mSelectedUser = user;
String name;
final Contact contact = user.getContact();
if (contact != null && contact.showInRoster()) {
name = contact.getDisplayName();
- } else if (user.getRealJid() != null){
+ } else if (user.getRealJid() != null) {
name = user.getRealJid().asBareJid().toString();
} else {
name = user.getName();
@@ -463,129 +473,26 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message);
MenuItem highlightInMuc = menu.findItem(R.id.highlight_in_muc);
highlightInMuc.setVisible(true);
- if (user.getRealJid() != null) {
- MenuItem startConversation = menu.findItem(R.id.start_conversation);
- MenuItem giveMembership = menu.findItem(R.id.give_membership);
- MenuItem removeMembership = menu.findItem(R.id.remove_membership);
- MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges);
- MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges);
- MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room);
- MenuItem banFromConference = menu.findItem(R.id.ban_from_conference);
- MenuItem invite = menu.findItem(R.id.invite);
- startConversation.setVisible(true);
- if (user.getRole() == MucOptions.Role.NONE) {
- invite.setVisible(true);
- }
- if (jid != null && !jid.isBareJid()) {
- if (mConversation.getMucOptions().isUserInRoom(jid)) {
- sendPrivateMessage.setVisible(true);
- } else {
- sendPrivateMessage.setVisible(false);
- }
- }
- if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) &&
- self.getAffiliation().outranks(user.getAffiliation())) {
- if (mAdvancedMode) {
- if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) {
- giveAdminPrivileges.setVisible(true);
- } else {
- removeAdminPrivileges.setVisible(true);
- }
- } else {
- if (!Config.DISABLE_BAN || mConversation.getMucOptions().membersOnly()) {
- removeFromRoom.setVisible(true);
- }
- }
- if (user.getAffiliation() == MucOptions.Affiliation.NONE) {
- giveMembership.setVisible(true);
- } else {
- removeMembership.setVisible(true);
- }
- if (!Config.DISABLE_BAN) {
- banFromConference.setVisible(true);
- }
- }
- } else {
-
- sendPrivateMessage.setVisible(user.getRole().ranks(MucOptions.Role.VISITOR));
- }
-
+ MucDetailsContextMenuHelper.configureMucDetailsContextMenu(this, menu, mConversation, user);
}
super.onCreateContextMenu(menu, v, menuInfo);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
- Jid jid = mSelectedUser.getRealJid();
- switch (item.getItemId()) {
- case R.id.highlight_in_muc:
- highlightInMuc(mConversation, mSelectedUser.getName());
- return true;
- case R.id.send_private_message:
- if (mConversation.getMucOptions().allowPm()) {
- privateMsgInMuc(mConversation, mSelectedUser.getName());
- } else {
- Toast.makeText(this, R.string.private_messages_are_disabled, Toast.LENGTH_SHORT).show();
- }
- return true;
- case R.id.start_conversation:
- startConversation(mSelectedUser);
- return true;
- case R.id.give_admin_privileges:
- xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.ADMIN, this);
- return true;
- case R.id.give_membership:
- xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER, this);
- return true;
- case R.id.remove_membership:
- xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.NONE, this);
- return true;
- case R.id.remove_admin_privileges:
- xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER, this);
- return true;
- case R.id.remove_from_room:
- removeFromRoom(mSelectedUser);
- return true;
- case R.id.ban_from_conference:
- xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.OUTCAST, this);
- if (mSelectedUser.getRole() != MucOptions.Role.NONE) {
- xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, this);
- }
- return true;
- case R.id.invite:
- xmppConnectionService.directInvite(mConversation, jid);
- return true;
- default:
- return super.onContextItemSelected(item);
- }
- }
-
- private void removeFromRoom(final User user) {
- if (mConversation.getMucOptions().membersOnly()) {
- xmppConnectionService.changeAffiliationInConference(mConversation, user.getRealJid(), MucOptions.Affiliation.NONE, this);
- if (user.getRole() != MucOptions.Role.NONE) {
- xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this);
- }
- } else {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.ban_from_conference);
- builder.setMessage(getString(R.string.removing_from_public_conference, user.getName()));
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.ban_now, (dialog, which) -> {
- xmppConnectionService.changeAffiliationInConference(mConversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, ConferenceDetailsActivity.this);
- if (user.getRole() != MucOptions.Role.NONE) {
- xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this);
- }
- });
- builder.create().show();
+ if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mSelectedUser, mConversation, this)) {
+ return super.onContextItemSelected(item);
}
+ return true;
}
- protected void startConversation(User user) {
- if (user.getRealJid() != null) {
- Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(), user.getRealJid().asBareJid(), false, true);
- switchToConversation(conversation);
- }
+ @Override
+ public void onMediaLoaded(List<Attachment> attachments) {
+ runOnUiThread(() -> {
+ int limit = GridManager.getCurrentColumnCount(binding.media);
+ mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size())));
+ binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
+ });
}
protected void saveAsBookmark() {
@@ -616,6 +523,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
if (uuid != null) {
this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
if (this.mConversation != null) {
+ if (Compatibility.hasStoragePermission(this)) {
+ final int limit = GridManager.getCurrentColumnCount(this.binding.media);
+ xmppConnectionService.getAttachments(this.mConversation, limit, this);
+ this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation));
+ }
updateView();
}
}
diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
index 5dce3fca2..1dfc80e3c 100644
--- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
@@ -30,6 +30,8 @@ import android.widget.Toast;
import org.openintents.openpgp.util.OpenPgpUtils;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -45,6 +47,11 @@ import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.ListItem;
import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate;
import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate;
+import de.pixart.messenger.ui.adapter.MediaAdapter;
+import de.pixart.messenger.ui.interfaces.OnMediaLoaded;
+import de.pixart.messenger.ui.util.Attachment;
+import de.pixart.messenger.ui.util.GridManager;
+import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.IrregularUnicodeDetector;
import de.pixart.messenger.utils.MenuDoubleTabUtil;
@@ -57,12 +64,13 @@ import de.pixart.messenger.xmpp.OnUpdateBlocklist;
import de.pixart.messenger.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
-public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated {
+public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnMediaLoaded {
public static final String ACTION_VIEW_CONTACT = "view_contact";
private Contact contact;
private Conversation mConversation;
ActivityContactDetailsBinding binding;
+ private MediaAdapter mMediaAdapter;
private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
@Override
@@ -260,6 +268,9 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
this.mNotifyStatusButton = findViewById(R.id.notification_status_button);
this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener);
this.mNotifyStatusText = findViewById(R.id.notification_status_text);
+ mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
+ this.binding.media.setAdapter(mMediaAdapter);
+ GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size);
}
@Override
@@ -279,6 +290,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
this.showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, false);
this.showLastSeen = preferences.getBoolean("last_activity", false);
}
+ binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
+ mMediaAdapter.setAttachments(Collections.emptyList());
}
@Override
@@ -552,12 +565,20 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
if (Config.supportOmemo() && axolotlService != null) {
+ final Collection<XmppAxolotlSession> sessions = axolotlService.findSessionsForContact(contact);
+ boolean anyActive = false;
+ for(XmppAxolotlSession session : sessions) {
+ anyActive = session.getTrust().isActive();
+ if (anyActive) {
+ break;
+ }
+ }
boolean skippedInactive = false;
boolean showsInactive = false;
- for (final XmppAxolotlSession session : axolotlService.findSessionsForContact(contact)) {
+ for (final XmppAxolotlSession session : sessions) {
final FingerprintStatus trust = session.getTrust();
hasKeys |= !trust.isCompromised();
- if (!trust.isActive()) {
+ if (!trust.isActive() && anyActive) {
if (showInactiveOmemo) {
showsInactive = true;
} else {
@@ -657,6 +678,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
processFingerprintVerification(mPendingFingerprintVerificationUri);
mPendingFingerprintVerificationUri = null;
}
+ if (Compatibility.hasStoragePermission(this)) {
+ final int limit = GridManager.getCurrentColumnCount(this.binding.media);
+ xmppConnectionService.getAttachments(account, contact.getJid().asBareJid(), limit, this);
+ this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, contact));
+ }
populateView();
}
}
@@ -676,4 +702,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show();
}
}
+
+ @Override
+ public void onMediaLoaded(List<Attachment> attachments) {
+ runOnUiThread(() -> {
+ int limit = GridManager.getCurrentColumnCount(binding.media);
+ mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size())));
+ binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
+ });
+ }
}
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
index 0374e643c..d326058e7 100644
--- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
+++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
@@ -6,6 +6,7 @@ import android.animation.AnimatorInflater;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
+import android.app.FragmentManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
@@ -14,8 +15,6 @@ import android.content.IntentSender.SendIntentException;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.databinding.DataBindingUtil;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -26,7 +25,6 @@ import android.provider.MediaStore;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
-import android.support.media.ExifInterface;
import android.support.v13.view.inputmethod.InputConnectionCompat;
import android.support.v13.view.inputmethod.InputContentInfoCompat;
import android.support.v7.app.AlertDialog;
@@ -53,16 +51,12 @@ import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CheckBox;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import net.java.otr4j.session.SessionStatus;
-import java.io.File;
-import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -88,6 +82,7 @@ import de.pixart.messenger.entities.Conversational;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.entities.MucOptions.User;
import de.pixart.messenger.entities.Presence;
import de.pixart.messenger.entities.ReadByMarker;
import de.pixart.messenger.entities.Transferable;
@@ -96,13 +91,15 @@ import de.pixart.messenger.http.HttpDownloadConnection;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.services.MessageArchiveService;
import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.adapter.MediaPreviewAdapter;
import de.pixart.messenger.ui.adapter.MessageAdapter;
import de.pixart.messenger.ui.util.ActivityResult;
-import de.pixart.messenger.ui.util.AttachmentTool;
+import de.pixart.messenger.ui.util.Attachment;
import de.pixart.messenger.ui.util.ConversationMenuConfigurator;
import de.pixart.messenger.ui.util.DateSeparator;
import de.pixart.messenger.ui.util.EditMessageActionModeCallback;
import de.pixart.messenger.ui.util.ListViewUtils;
+import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper;
import de.pixart.messenger.ui.util.PendingItem;
import de.pixart.messenger.ui.util.PresenceSelector;
import de.pixart.messenger.ui.util.ScrollState;
@@ -110,7 +107,6 @@ import de.pixart.messenger.ui.util.SendButtonAction;
import de.pixart.messenger.ui.util.SendButtonTool;
import de.pixart.messenger.ui.util.ShareUtil;
import de.pixart.messenger.ui.widget.EditMessage;
-import de.pixart.messenger.utils.FileUtils;
import de.pixart.messenger.utils.MenuDoubleTabUtil;
import de.pixart.messenger.utils.MessageUtils;
import de.pixart.messenger.utils.NickValidityChecker;
@@ -135,55 +131,56 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
public static final int REQUEST_DECRYPT_PGP = 0x0202;
public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208;
- public static final int REQUEST_TRUST_KEYS_MENU = 0x0209;
+ public static final int REQUEST_TRUST_KEYS_ATTACHMENTS = 0x0209;
public static final int REQUEST_START_DOWNLOAD = 0x0210;
public static final int REQUEST_ADD_EDITOR_CONTENT = 0x0211;
public static final int ATTACHMENT_CHOICE = 0x0300;
public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
- public static final int ATTACHMENT_CHOICE_TAKE_FROM_CAMERA = 0x0302;
+ public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
public static final int ATTACHMENT_CHOICE_CHOOSE_VIDEO = 0x0306;
+ public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307;
public static final int ATTACHMENT_CHOICE_INVALID = 0x0399;
public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action";
public static final String STATE_CONVERSATION_UUID = ConversationFragment.class.getName() + ".uuid";
- public static final String STATE_SCROLL_POSITION = ConversationFragment.class.getName() + ".scroll_position";
- public static final String STATE_PHOTO_URI = ConversationFragment.class.getName() + ".take_photo_uri";
- public static final String STATE_VIDEO_URI = ConversationFragment.class.getName() + ".take_video_uri";
+ public static final String STATE_SCROLL_POSITION = ConversationFragment.class.getName() + ".scroll_position";
+ public static final String STATE_PHOTO_URI = ConversationFragment.class.getName() + ".media_previews";
+ public static final String STATE_MEDIA_PREVIEWS = ConversationFragment.class.getName() + ".take_photo_uri";
private static final String STATE_LAST_MESSAGE_UUID = "state_last_message_uuid";
private final List<Message> messageList = new ArrayList<>();
- final private List<Uri> mPendingImageUris = new ArrayList<>();
- private String lastMessageUuid = null;
- public Uri mPendingEditorContent = null;
private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
- private final PendingItem<String> pendingConversationsUuid = new PendingItem<>();
+ private final PendingItem<String> pendingConversationsUuid = new PendingItem<>();
+ private final PendingItem<ArrayList<Attachment>> pendingMediaPreviews = new PendingItem<>();
private final PendingItem<Bundle> pendingExtras = new PendingItem<>();
private final PendingItem<Uri> pendingTakePhotoUri = new PendingItem<>();
private final PendingItem<Uri> pendingTakeVideoUri = new PendingItem<>();
private final PendingItem<ScrollState> pendingScrollState = new PendingItem<>();
private final PendingItem<String> pendingLastMessageUuid = new PendingItem<>();
private final PendingItem<Message> pendingMessage = new PendingItem<>();
+ public Uri mPendingEditorContent = null;
+ public FragmentConversationBinding binding;
protected MessageAdapter messageListAdapter;
- private Conversation conversation;
- public FragmentConversationBinding binding;
protected Message lastHistoryMessage = null;
SimpleDateFormat sdf = new SimpleDateFormat("EEEE, dd. MMM yyyy", Locale.getDefault());
+ private String lastMessageUuid = null;
+ private Conversation conversation;
private Toast messageLoaderToast;
private ConversationsActivity activity;
- private boolean reInitRequiredOnStart = true;
-
- private SimpleFingerGestures gesturesDetector = new SimpleFingerGestures();
-
+ private Menu mOptionsMenu;
protected OnClickListener clickToVerify = new OnClickListener() {
@Override
public void onClick(View v) {
activity.verifyOtrSessionDialog(conversation, v);
}
};
+ private boolean reInitRequiredOnStart = true;
+ private MediaPreviewAdapter mediaPreviewAdapter;
+ private SimpleFingerGestures gesturesDetector = new SimpleFingerGestures();
private OnClickListener clickToMuc = new OnClickListener() {
@Override
@@ -462,7 +459,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
switch (action) {
case CHOOSE_ATTACHMENT:
choose_attachment(v);
- case TAKE_FROM_CAMERA:
+ case TAKE_PHOTO:
+ case RECORD_VIDEO:
case SEND_LOCATION:
case RECORD_VOICE:
case CHOOSE_PICTURE:
@@ -490,32 +488,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
};
-
- @SuppressLint("RestrictedApi")
- private void choose_attachment(View v) {
- SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity);
- final boolean hideVoice = p.getBoolean("show_record_voice_btn", activity.getResources().getBoolean(R.bool.show_record_voice_btn));
- PopupMenu popup = new PopupMenu(activity, v);
- popup.inflate(R.menu.choose_attachment);
- Menu menu = popup.getMenu();
- ConversationMenuConfigurator.configureQuickShareAttachmentMenu(conversation, menu, hideVoice);
- popup.setOnMenuItemClickListener(attachmentItem -> {
- switch (attachmentItem.getItemId()) {
- case R.id.attach_choose_picture:
- case R.id.attach_take_picture:
- case R.id.attach_choose_file:
- case R.id.attach_record_voice:
- case R.id.attach_location:
- handleAttachmentSelection(attachmentItem);
- default:
- return false;
- }
- });
- MenuPopupHelper menuHelper = new MenuPopupHelper(getActivity(), (MenuBuilder) menu, v);
- menuHelper.setForceShowIcon(true);
- menuHelper.show();
- }
-
private View.OnLongClickListener mSendButtonLongListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
@@ -589,6 +561,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
+ public static ConversationFragment get(Activity activity) {
+ FragmentManager fragmentManager = activity.getFragmentManager();
+ Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment);
+ if (fragment != null && fragment instanceof ConversationFragment) {
+ return (ConversationFragment) fragment;
+ } else {
+ fragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
+ return fragment != null && fragment instanceof ConversationFragment ? (ConversationFragment) fragment : null;
+ }
+ }
+
public static Conversation getConversationReliable(Activity activity) {
final Conversation conversation = getConversation(activity, R.id.secondary_fragment);
if (conversation != null) {
@@ -636,6 +619,32 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
+ @SuppressLint("RestrictedApi")
+ private void choose_attachment(View v) {
+ SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity);
+ final boolean hideVoice = p.getBoolean("show_record_voice_btn", activity.getResources().getBoolean(R.bool.show_record_voice_btn));
+ PopupMenu popup = new PopupMenu(activity, v);
+ popup.inflate(R.menu.choose_attachment);
+ Menu menu = popup.getMenu();
+ ConversationMenuConfigurator.configureQuickShareAttachmentMenu(conversation, menu, hideVoice);
+ popup.setOnMenuItemClickListener(attachmentItem -> {
+ switch (attachmentItem.getItemId()) {
+ case R.id.attach_choose_picture:
+ case R.id.attach_take_picture:
+ case R.id.attach_record_video:
+ case R.id.attach_choose_file:
+ case R.id.attach_record_voice:
+ case R.id.attach_location:
+ handleAttachmentSelection(attachmentItem);
+ default:
+ return false;
+ }
+ });
+ MenuPopupHelper menuHelper = new MenuPopupHelper(getActivity(), (MenuBuilder) menu, v);
+ menuHelper.setForceShowIcon(true);
+ menuHelper.show();
+ }
+
private void toggleScrollDownButton() {
toggleScrollDownButton(binding.messagesView);
}
@@ -836,94 +845,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
public void attachEditorContentToConversation(Uri uri) {
- this.attachFileToConversation(conversation, uri, null);
+ mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uri, Attachment.Type.FILE));
+ toggleInputMethod();
}
- private void attachImageToConversation(Conversation conversation, Uri uri, boolean sendAsIs) {
+ private void attachImageToConversation(Conversation conversation, Uri uri) {
if (conversation == null) {
return;
}
- if (sendAsIs) {
- sendImage(conversation, uri);
- return;
- }
- final Conversation conversation_preview = conversation;
- final Uri uri_preview = uri;
- Bitmap bitmap = BitmapFactory.decodeFile(FileUtils.getPath(activity, uri));
- File file = null;
- ExifInterface exif = null;
- int orientation = 0;
- try {
- file = new File(FileUtils.getPath(activity, uri));
- } catch (Exception e) {
- e.printStackTrace();
- }
- if (file != null) {
- try {
- exif = new ExifInterface(file.toString());
- } catch (IOException e) {
- e.printStackTrace();
- }
- orientation = exif != null ? exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) : 0;
- }
- Log.d(Config.LOGTAG, "EXIF: " + orientation);
- Bitmap rotated_image = null;
- Log.d(Config.LOGTAG, "Rotate image");
- rotated_image = FileBackend.rotateBitmap(file, bitmap, orientation);
- if (rotated_image != null) {
- int scaleSize = 600;
- int originalWidth = rotated_image.getWidth();
- int originalHeight = rotated_image.getHeight();
- int newWidth = -1;
- int newHeight = -1;
- float multFactor;
- if (originalHeight > originalWidth) {
- newHeight = scaleSize;
- multFactor = (float) originalWidth / (float) originalHeight;
- newWidth = (int) (newHeight * multFactor);
- } else if (originalWidth > originalHeight) {
- newWidth = scaleSize;
- multFactor = (float) originalHeight / (float) originalWidth;
- newHeight = (int) (newWidth * multFactor);
- } else if (originalHeight == originalWidth) {
- newHeight = scaleSize;
- newWidth = scaleSize;
- }
- Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px");
- Bitmap preview = Bitmap.createScaledBitmap(rotated_image, newWidth, newHeight, false);
- ImageView ImagePreview = new ImageView(activity);
- LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
- ImagePreview.setLayoutParams(vp);
- ImagePreview.setMaxWidth(newWidth);
- ImagePreview.setMaxHeight(newHeight);
- //ImagePreview.setScaleType(ImageView.ScaleType.FIT_XY);
- //ImagePreview.setAdjustViewBounds(true);
- ImagePreview.setPadding(5, 5, 5, 5);
- ImagePreview.setImageBitmap(preview);
- getActivity().runOnUiThread(() -> {
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
- builder.setView(ImagePreview);
- builder.setTitle(R.string.send_image);
- builder.setPositiveButton(R.string.ok, (dialog, which) -> sendImage(conversation_preview, uri_preview));
- builder.setOnCancelListener(dialog -> mPendingImageUris.clear());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- builder.setOnDismissListener(dialog -> mPendingImageUris.clear());
- }
- AlertDialog alertDialog = builder.create();
- alertDialog.show();
- });
- } else {
- getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show());
- }
- }
-
- private void sendImage(Conversation conversation, Uri uri) {
final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
prepareFileToast.show();
activity.delegateUriPermissionsToService(uri);
activity.xmppConnectionService.attachImageToConversation(conversation, uri,
new UiCallback<Message>() {
-
@Override
public void userInputRequried(PendingIntent pi, Message object) {
hidePrepareFileToast(prepareFileToast);
@@ -949,6 +883,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private void sendMessage() {
+ if (mediaPreviewAdapter.hasAttachments()) {
+ commitAttachments();
+ return;
+ }
final String body = binding.textinput.getText().toString();
final Conversation conversation = this.conversation;
if (body.length() == 0 || conversation == null) {
@@ -989,10 +927,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
protected boolean trustKeysIfNeeded(int requestCode) {
- return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
- }
-
- protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
final List<Jid> targets = axolotlService.getCryptoTargets(conversation);
boolean hasUnaccepted = !conversation.getAcceptedCryptoTargets().containsAll(targets);
@@ -1010,7 +944,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
intent.putExtra("contacts", contacts);
intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString());
- intent.putExtra("choice", attachmentChoice);
intent.putExtra("conversation", conversation.getUuid());
startActivityForResult(intent, requestCode);
return true;
@@ -1054,63 +987,43 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private void handlePositiveActivityResult(int requestCode, final Intent data) {
- final String type = data == null ? null : data.getType();
switch (requestCode) {
case REQUEST_TRUST_KEYS_TEXT:
final String body = binding.textinput.getText().toString();
Message message = new Message(conversation, body, conversation.getNextEncryption());
sendMessage(message);
break;
- case REQUEST_TRUST_KEYS_MENU:
- int choice = data.getIntExtra("choice", ATTACHMENT_CHOICE_INVALID);
- selectPresenceToAttachFile(choice);
+ case REQUEST_TRUST_KEYS_ATTACHMENTS:
+ commitAttachments();
break;
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
- final List<Uri> imageUris = AttachmentTool.extractUriFromIntent(data);
- final int ImageUrisCount = imageUris.size();
- Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image - number of uris: " + ImageUrisCount);
- if (ImageUrisCount == 1) {
- Uri uri = imageUris.get(0);
- Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE");
- attachImageToConversation(conversation, uri, false);
- } else {
- for (Iterator<Uri> i = imageUris.iterator(); i.hasNext(); i.remove()) {
- Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching images to conversations. CHOOSE_IMAGES");
- attachImagesToConversation(conversation, i.next());
- }
- }
+ final List<Attachment> imageUris = Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE);
+ mediaPreviewAdapter.addMediaPreviews(imageUris);
+ toggleInputMethod();
break;
- case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA:
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
final Uri takePhotoUri = pendingTakePhotoUri.pop();
- final Uri takeVideoUri = pendingTakeVideoUri.pop();
if (takePhotoUri != null) {
- attachPhotoToConversation(conversation, takePhotoUri);
- } else if (takeVideoUri != null) {
- attachFileToConversation(conversation, takeVideoUri, type);
+ mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), takePhotoUri, Attachment.Type.IMAGE));
+ toggleInputMethod();
} else {
- Log.d(Config.LOGTAG, "lost take uri. unable to to attach");
+ Log.d(Config.LOGTAG, "lost take photo uri. unable to to attach");
}
break;
case ATTACHMENT_CHOICE_CHOOSE_FILE:
+ case ATTACHMENT_CHOICE_RECORD_VIDEO:
case ATTACHMENT_CHOICE_RECORD_VOICE:
- final List<Uri> fileUris = AttachmentTool.extractUriFromIntent(data);
- final PresenceSelector.OnPresenceSelected callback = () -> {
- for (Iterator<Uri> i = fileUris.iterator(); i.hasNext(); i.remove()) {
- Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE");
- attachFileToConversation(conversation, i.next(), type);
- }
- };
- if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), fileUris, getMaxHttpUploadSize(conversation))) {
- callback.onPresenceSelected();
- } else {
- activity.selectPresence(conversation, callback);
- }
+ final Attachment.Type type = requestCode == ATTACHMENT_CHOICE_RECORD_VOICE ? Attachment.Type.RECORDING : Attachment.Type.FILE;
+ final List<Attachment> fileUris = Attachment.extractAttachments(getActivity(), data, type);
+ mediaPreviewAdapter.addMediaPreviews(fileUris);
+ toggleInputMethod();
break;
case ATTACHMENT_CHOICE_LOCATION:
double latitude = data.getDoubleExtra("latitude", 0);
double longitude = data.getDoubleExtra("longitude", 0);
Uri geo = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude));
- attachLocationToConversation(conversation, geo);
+ mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), geo, Attachment.Type.LOCATION));
+ toggleInputMethod();
break;
case REQUEST_INVITE_TO_CONVERSATION:
XmppActivity.ConferenceInvite invite = XmppActivity.ConferenceInvite.parse(data);
@@ -1124,9 +1037,51 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
+ private void commitAttachments() {
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(REQUEST_TRUST_KEYS_ATTACHMENTS)) {
+ return;
+ }
+ final List<Attachment> attachments = mediaPreviewAdapter.getAttachments();
+ final PresenceSelector.OnPresenceSelected callback = () -> {
+ for (Iterator<Attachment> i = attachments.iterator(); i.hasNext(); i.remove()) {
+ final Attachment attachment = i.next();
+ if (attachment.getType() == Attachment.Type.LOCATION) {
+ attachLocationToConversation(conversation, attachment.getUri());
+ } else if (attachment.getType() == Attachment.Type.IMAGE) {
+ Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE");
+ attachImageToConversation(conversation, attachment.getUri());
+ } else {
+ Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
+ attachFileToConversation(conversation, attachment.getUri(), attachment.getMime());
+ }
+ }
+ mediaPreviewAdapter.notifyDataSetChanged();
+ toggleInputMethod();
+ };
+ if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), attachments, getMaxHttpUploadSize(conversation))) {
+ callback.onPresenceSelected();
+ } else {
+ activity.selectPresence(conversation, callback);
+ }
+ }
+
+ public void toggleInputMethod() {
+ boolean hasAttachments = mediaPreviewAdapter.hasAttachments();
+ binding.textinput.setVisibility(hasAttachments ? View.GONE : View.VISIBLE);
+ binding.mediaPreview.setVisibility(hasAttachments ? View.VISIBLE : View.GONE);
+ if (mOptionsMenu != null) {
+ ConversationMenuConfigurator.configureAttachmentMenu(conversation, mOptionsMenu, activity.xmppConnectionService.getAttachmentChoicePreference(), hasAttachments);
+ }
+ updateSendButton();
+ }
+
private void handleNegativeActivityResult(int requestCode) {
switch (requestCode) {
- //nothing to do for now
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
+ if (pendingTakePhotoUri.clear()) {
+ Log.d(Config.LOGTAG, "cleared pending photo uri after negative activity result");
+ }
+ break;
}
}
@@ -1170,27 +1125,38 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
+ mOptionsMenu = menu;
+ boolean hasAttachments = mediaPreviewAdapter != null && mediaPreviewAdapter.hasAttachments();
menuInflater.inflate(R.menu.fragment_conversation, menu);
final MenuItem menuInviteContact = menu.findItem(R.id.action_invite);
final MenuItem menuNeedHelp = menu.findItem(R.id.action_create_issue);
final MenuItem menuSearchUpdates = menu.findItem(R.id.action_check_updates);
final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat);
+ final MenuItem menuGroupDetails = menu.findItem(R.id.action_group_details);
+ final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details);
if (conversation != null) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
menuInviteContact.setVisible(true);
menuArchiveChat.setTitle(R.string.action_end_conversation_muc);
+ menuGroupDetails.setVisible(true);
+ menuContactDetails.setVisible(false);
} else {
menuInviteContact.setVisible(false);
menuArchiveChat.setTitle(R.string.action_end_conversation);
+ menuGroupDetails.setVisible(false);
+ menuContactDetails.setVisible(true);
}
menuNeedHelp.setVisible(true);
menuSearchUpdates.setVisible(false);
- ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu, Config.QUICK_SHARE_ATTACHMENT_CHOICE);
+ ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu, activity.xmppConnectionService.getAttachmentChoicePreference(), hasAttachments);
ConversationMenuConfigurator.configureEncryptionMenu(conversation, menu);
} else {
menuNeedHelp.setVisible(false);
menuSearchUpdates.setVisible(true);
+ menuInviteContact.setVisible(false);
+ menuGroupDetails.setVisible(false);
+ menuContactDetails.setVisible(false);
}
super.onCreateOptionsMenu(menu, menuInflater);
}
@@ -1203,7 +1169,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
binding.textinput.addTextChangedListener(new StylingHelper.MessageEditorStyler(binding.textinput));
binding.textinput.setOnEditorActionListener(mEditorActionListener);
binding.textinput.setRichContentListener(new String[]{"image/*"}, mEditorContentListener);
- binding.textinput.setBackgroundResource(activity.isDarkTheme() ? R.drawable.message_bubble_sent_blue_dark : R.drawable.message_bubble_sent_blue);
+ binding.textinput.setBackgroundResource(messageInputBubble());
binding.textSendButton.setOnClickListener(this.mSendButtonListener);
binding.textSendButton.setOnLongClickListener(this.mSendButtonLongListener);
@@ -1212,6 +1178,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
binding.messagesView.setOnScrollListener(mOnScrollListener);
binding.messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
+ mediaPreviewAdapter = new MediaPreviewAdapter(this);
+ binding.mediaPreview.setAdapter(mediaPreviewAdapter);
+ binding.mediaPreview.setBackgroundResource(messageInputBubble());
messageListAdapter = new MessageAdapter((XmppActivity) getActivity(), this.messageList);
messageListAdapter.setOnContactPictureClicked(message -> {
String fingerprint;
@@ -1223,11 +1192,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final boolean received = message.getStatus() <= Message.STATUS_RECEIVED;
if (received) {
if (message.getConversation() instanceof Conversation && message.getConversation().getMode() == Conversation.MODE_MULTI) {
+ Jid tcp = message.getTrueCounterpart();
Jid user = message.getCounterpart();
if (user != null && !user.isBareJid()) {
final MucOptions mucOptions = ((Conversation) message.getConversation()).getMucOptions();
if (mucOptions.participating() || ((Conversation) message.getConversation()).getNextCounterpart() != null) {
- if (!mucOptions.isUserInRoom(user)) {
+ if (!mucOptions.isUserInRoom(user) && mucOptions.findUserByRealJid(tcp == null ? null : tcp.asBareJid()) == null) {
Toast.makeText(getActivity(), activity.getString(R.string.user_has_left_conference, user.getResource()), Toast.LENGTH_SHORT).show();
}
highlightInConference(user.getResource());
@@ -1245,21 +1215,20 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
activity.switchToAccount(message.getConversation().getAccount(), fingerprint);
});
- messageListAdapter.setOnContactPictureLongClicked(message -> {
+ messageListAdapter.setOnContactPictureLongClicked((v, message) -> {
if (message.getStatus() <= Message.STATUS_RECEIVED) {
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
- final MucOptions mucOptions = conversation.getMucOptions();
- if (!mucOptions.allowPm()) {
- Toast.makeText(getActivity(), R.string.private_messages_are_disabled, Toast.LENGTH_SHORT).show();
- return;
- }
- Jid user = message.getCounterpart();
- if (user != null && !user.isBareJid()) {
- if (mucOptions.isUserInRoom(user)) {
- privateMessageWith(user);
- } else {
- Toast.makeText(getActivity(), activity.getString(R.string.user_has_left_conference, user.getResource()), Toast.LENGTH_SHORT).show();
- }
+ Jid tcp = message.getTrueCounterpart();
+ Jid cp = message.getCounterpart();
+ if (cp != null && !cp.isBareJid()) {
+ User userByRealJid = tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp) : null;
+ final User user = userByRealJid != null ? userByRealJid : conversation.getMucOptions().findUserByFullJid(cp);
+ final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
+ popupMenu.inflate(R.menu.muc_details_context);
+ final Menu menu = popupMenu.getMenu();
+ MucDetailsContextMenuHelper.configureMucDetailsContextMenu(activity, menu, conversation, user);
+ popupMenu.setOnMenuItemClickListener(menuItem -> MucDetailsContextMenuHelper.onContextItemSelected(menuItem, user, conversation, activity));
+ popupMenu.show();
}
}
} else {
@@ -1407,7 +1376,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (m.isFileOrImage() && !deleted) {
String path = m.getRelativeFilePath();
Log.d(Config.LOGTAG, "Path = " + path);
- if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null", false))) {
+ if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null"))) {
deleteFile.setVisible(true);
deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m)));
}
@@ -1481,6 +1450,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
break;
case R.id.attach_choose_picture:
case R.id.attach_take_picture:
+ case R.id.attach_record_video:
case R.id.attach_choose_file:
case R.id.attach_record_voice:
case R.id.attach_location:
@@ -1509,6 +1479,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
case R.id.action_clear_history:
clearHistoryDialog(conversation);
break;
+ case R.id.action_group_details:
+ Intent intent = new Intent(activity, ConferenceDetailsActivity.class);
+ intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
+ intent.putExtra("uuid", conversation.getUuid());
+ startActivity(intent);
+ break;
+ case R.id.action_contact_details:
+ activity.switchToContactDetails(conversation.getContact());
+ break;
case R.id.action_block:
case R.id.action_unblock:
final Activity activity = getActivity();
@@ -1528,7 +1507,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
break;
case R.id.attach_take_picture:
- attachFile(ATTACHMENT_CHOICE_TAKE_FROM_CAMERA);
+ attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
+ break;
+ case R.id.attach_record_video:
+ attachFile(ATTACHMENT_CHOICE_RECORD_VIDEO);
break;
case R.id.attach_choose_file:
attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE);
@@ -1588,7 +1570,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO)) {
return;
}
- } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_FROM_CAMERA) {
+ } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) {
if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)) {
return;
}
@@ -1652,9 +1634,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
activity.showInstallPgpDialog();
}
} else {
- if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) {
- selectPresenceToAttachFile(attachmentChoice);
- }
+ selectPresenceToAttachFile(attachmentChoice);
}
}
@@ -1774,7 +1754,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final int encryption = conversation.getNextEncryption();
final Account account = conversation.getAccount();
final PresenceSelector.OnPresenceSelected callback = () -> {
- final Intent intent = new Intent();
+ Intent intent = new Intent();
boolean chooser = false;
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
@@ -1791,46 +1771,29 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setAction(Intent.ACTION_GET_CONTENT);
break;
- case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA:
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(getString(R.string.attach_take_from_camera));
- builder.setNegativeButton(getString(R.string.action_take_photo),
- (dialog, which) -> {
- final Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri();
- pendingTakePhotoUri.push(uri);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
- startActivityForResult(intent, attachmentChoice);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- });
- builder.setPositiveButton(getString(R.string.action_take_video),
- (dialog, which) -> {
- final Uri uri = activity.xmppConnectionService.getFileBackend().getTakeVideoUri();
- pendingTakeVideoUri.push(uri);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
- intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
- startActivityForResult(intent, attachmentChoice);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- });
- builder.create().show();
+ case ATTACHMENT_CHOICE_RECORD_VIDEO:
+ intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
+ break;
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
+ final Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri();
+ pendingTakePhotoUri.push(uri);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
break;
case ATTACHMENT_CHOICE_CHOOSE_FILE:
chooser = true;
intent.setType("*/*");
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setAction(Intent.ACTION_GET_CONTENT);
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
- startActivityForResult(new Intent(getActivity(), RecordingActivity.class), attachmentChoice);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ intent = new Intent(getActivity(), RecordingActivity.class);
break;
case ATTACHMENT_CHOICE_LOCATION:
- startActivityForResult(new Intent(getActivity(), ShareLocationActivity.class), attachmentChoice);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ intent = new Intent(getActivity(), ShareLocationActivity.class);
break;
}
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
@@ -1839,13 +1802,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
startActivityForResult(
Intent.createChooser(intent, getString(R.string.perform_action_with)),
attachmentChoice);
+ activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
} else {
startActivityForResult(intent, attachmentChoice);
+ activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
}
}
};
if (account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) {
- if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) {
+ if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) {
conversation.setNextCounterpart(null);
callback.onPresenceSelected();
} else {
@@ -1997,7 +1962,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
conversation.getAccount().getPgpDecryptionService().decrypt(message, false);
}
- private void privateMessageWith(final Jid counterpart) {
+ public void privateMessageWith(final Jid counterpart) {
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
activity.xmppConnectionService.sendChatState(conversation);
}
@@ -2047,23 +2012,32 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
@Override
+ public void startActivityForResult(Intent intent, int requestCode) {
+ final Activity activity = getActivity();
+ if (activity instanceof ConversationsActivity) {
+ ((ConversationsActivity) activity).clearPendingViewIntent();
+ }
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ @Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (conversation != null) {
outState.putString(STATE_CONVERSATION_UUID, conversation.getUuid());
outState.putString(STATE_LAST_MESSAGE_UUID, lastMessageUuid);
- final Uri PhotoUri = pendingTakePhotoUri.peek();
- final Uri VideoUri = pendingTakeVideoUri.peek();
- if (PhotoUri != null) {
- outState.putString(STATE_PHOTO_URI, PhotoUri.toString());
- }
- if (VideoUri != null) {
- outState.putString(STATE_VIDEO_URI, VideoUri.toString());
+ final Uri uri = pendingTakePhotoUri.peek();
+ if (uri != null) {
+ outState.putString(STATE_PHOTO_URI, uri.toString());
}
final ScrollState scrollState = getScrollPosition();
if (scrollState != null) {
outState.putParcelable(STATE_SCROLL_POSITION, scrollState);
}
+ final ArrayList<Attachment> attachments = mediaPreviewAdapter.getAttachments();
+ if (attachments.size() > 0) {
+ outState.putParcelableArrayList(STATE_MEDIA_PREVIEWS, attachments);
+ }
}
}
@@ -2074,18 +2048,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
String uuid = savedInstanceState.getString(STATE_CONVERSATION_UUID);
+ ArrayList<Attachment> attachments = savedInstanceState.getParcelableArrayList(STATE_MEDIA_PREVIEWS);
pendingLastMessageUuid.push(savedInstanceState.getString(STATE_LAST_MESSAGE_UUID, null));
if (uuid != null) {
QuickLoader.set(uuid);
this.pendingConversationsUuid.push(uuid);
+ if (attachments != null && attachments.size() > 0) {
+ this.pendingMediaPreviews.push(attachments);
+ }
String takePhotoUri = savedInstanceState.getString(STATE_PHOTO_URI);
if (takePhotoUri != null) {
pendingTakePhotoUri.push(Uri.parse(takePhotoUri));
}
- String takeVideoUri = savedInstanceState.getString(STATE_VIDEO_URI);
- if (takeVideoUri != null) {
- pendingTakeVideoUri.push(Uri.parse(takeVideoUri));
- }
pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION));
}
}
@@ -2112,16 +2086,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
public void onStop() {
super.onStop();
final Activity activity = getActivity();
+ messageListAdapter.unregisterListenerInAudioPlayer();
if (activity == null || !activity.isChangingConfigurations()) {
hideSoftKeyboard(activity);
messageListAdapter.stopAudioPlayer();
}
if (this.conversation != null) {
final String msg = this.binding.textinput.getText().toString();
- final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating();
- if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && participating && this.conversation.setNextMessage(msg)) {
- this.activity.xmppConnectionService.updateConversation(this.conversation);
- }
+ storeNextMessage(msg);
updateChatState(this.conversation, msg);
this.activity.xmppConnectionService.getNotificationService().setOpenConversation(null);
}
@@ -2143,17 +2115,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
Log.d(Config.LOGTAG, "ConversationFragment.saveMessageDraftStopAudioPlayer()");
final String msg = this.binding.textinput.getText().toString();
- final boolean participating = previousConversation.getMode() == Conversational.MODE_SINGLE || previousConversation.getMucOptions().participating();
- if (participating && previousConversation.setNextMessage(msg)) {
- activity.xmppConnectionService.updateConversation(previousConversation);
- }
+ storeNextMessage(msg);
updateChatState(this.conversation, msg);
messageListAdapter.stopAudioPlayer();
+ mediaPreviewAdapter.clearPreviews();
+ toggleInputMethod();
}
public void reInit(Conversation conversation, Bundle extras) {
QuickLoader.set(conversation.getUuid());
this.saveMessageDraftStopAudioPlayer();
+ this.clearPending();
if (this.reInit(conversation, extras != null)) {
if (extras != null) {
processExtras(extras);
@@ -2310,6 +2282,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final String nick = extras.getString(ConversationsActivity.EXTRA_NICK);
final boolean asQuote = extras.getBoolean(ConversationsActivity.EXTRA_AS_QUOTE);
final boolean pm = extras.getBoolean(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, false);
+ final List<Uri> uris = extractUris(extras);
+ if (uris != null && uris.size() > 0) {
+ mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uris));
+ toggleInputMethod();
+ return;
+ }
if (nick != null) {
if (pm) {
Jid jid = conversation.getJid();
@@ -2338,6 +2316,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
+ private List<Uri> extractUris(Bundle extras) {
+ final List<Uri> uris = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
+ if (uris != null) {
+ return uris;
+ }
+ final Uri uri = extras.getParcelable(Intent.EXTRA_STREAM);
+ if (uri != null) {
+ return Collections.singletonList(uri);
+ } else {
+ return null;
+ }
+ }
+
private boolean showBlockSubmenu(View view) {
final Jid jid = conversation.getJid();
if (jid.getLocal() == null) {
@@ -2411,7 +2402,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
break;
case UNKNOWN:
- showSnackbar(R.string.conference_unknown_error, R.string.leave, joinMuc);
+ showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc);
break;
case INVALID_NICK:
showSnackbar(R.string.invalid_muc_nick, R.string.edit, clickToMuc);
@@ -2518,10 +2509,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
this.binding.textinput.append(conversation.getDraftMessage());
conversation.setDraftMessage(null);
}
- final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating();
- if (participating && conversation.setNextMessage(this.binding.textinput.getText().toString())) {
- activity.xmppConnectionService.databaseBackend.updateConversation(conversation);
- }
+ storeNextMessage();
updateChatMsgHint();
SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity);
final boolean prefScrollToBottom = p.getBoolean("scroll_to_bottom", activity.getResources().getBoolean(R.bool.scroll_to_bottom));
@@ -2533,6 +2521,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
+ private boolean storeNextMessage() {
+ return storeNextMessage(this.binding.textinput.getText().toString());
+ }
+
+ private boolean storeNextMessage(String msg) {
+ final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating();
+ if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && participating && this.conversation.setNextMessage(msg)) {
+ this.activity.xmppConnectionService.updateConversation(this.conversation);
+ return true;
+ }
+ return false;
+ }
+
public void doneSendingPgpMessage() {
mSendingPgpMessage.set(false);
}
@@ -2552,11 +2553,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
public void updateSendButton() {
+ boolean hasAttachments = mediaPreviewAdapter != null && mediaPreviewAdapter.hasAttachments();
boolean useSendButtonToIndicateStatus = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status));
final Conversation c = this.conversation;
final Presence.Status status;
final String text = this.binding.textinput == null ? "" : this.binding.textinput.getText().toString();
- final SendButtonAction action = SendButtonTool.getAction(getActivity(), c, text);
+ final SendButtonAction action;
+ if (hasAttachments) {
+ action = SendButtonAction.TEXT;
+ } else {
+ action = SendButtonTool.getAction(getActivity(), c, text);
+ }
if (useSendButtonToIndicateStatus && c.getAccount().getStatus() == Account.State.ONLINE) {
if (activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) {
status = Presence.Status.OFFLINE;
@@ -2889,6 +2896,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
service.sendChatState(conversation);
}
+ if (storeNextMessage()) {
+ activity.onConversationsListItemUpdated();
+ }
updateSendButton();
}
@@ -3048,23 +3058,33 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
reInit(conversation);
ScrollState scrollState = pendingScrollState.pop();
String lastMessageUuid = pendingLastMessageUuid.pop();
+ List<Attachment> attachments = pendingMediaPreviews.pop();
if (scrollState != null) {
setScrollPosition(scrollState, lastMessageUuid);
}
+ if (attachments != null && attachments.size() > 0) {
+ Log.d(Config.LOGTAG, "had attachments on restore");
+ mediaPreviewAdapter.addMediaPreviews(attachments);
+ toggleInputMethod();
+ }
return true;
}
private void clearPending() {
- if (postponedActivityResult.pop() != null) {
+ if (postponedActivityResult.clear()) {
Log.e(Config.LOGTAG, "cleared pending intent with unhandled result left");
}
- pendingScrollState.pop();
- if (pendingTakePhotoUri.pop() != null) {
+ if (pendingScrollState.clear()) {
+ Log.e(Config.LOGTAG,"cleared scroll state");
+ }
+ if (pendingTakePhotoUri.clear()) {
Log.e(Config.LOGTAG, "cleared pending photo uri");
}
}
-
+ private int messageInputBubble() {
+ return activity.isDarkTheme() ? R.drawable.message_bubble_sent_blue_dark : R.drawable.message_bubble_sent_blue;
+ }
public Conversation getConversation() {
return conversation;
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java
index 30c54990f..8d8e5386a 100644
--- a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java
@@ -63,6 +63,7 @@ import net.java.otr4j.session.SessionStatus;
import org.openintents.openpgp.util.OpenPgpApi;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -92,13 +93,14 @@ import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.utils.XmppUri;
import de.pixart.messenger.xmpp.OnUpdateBlocklist;
import de.pixart.messenger.xmpp.chatstate.ChatState;
+import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.services.XmppConnectionService.FDroid;
import static de.pixart.messenger.services.XmppConnectionService.PlayStore;
import static de.pixart.messenger.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
-public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
+public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged {
public static final String ACTION_VIEW_CONVERSATION = "de.pixart.messenger.VIEW";
public static final String EXTRA_CONVERSATION = "conversationUuid";
@@ -110,6 +112,11 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
public static final String ACTION_DESTROY_MUC = "de.pixart.messenger.DESTROY_MUC";
public static final int REQUEST_OPEN_MESSAGE = 0x9876;
public static final int REQUEST_PLAY_PAUSE = 0x5432;
+ private static List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
+ ACTION_VIEW_CONVERSATION,
+ Intent.ACTION_SEND,
+ Intent.ACTION_SEND_MULTIPLE
+ );
private boolean showLastSeen = false;
@@ -125,8 +132,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
private boolean mActivityPaused = true;
private AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
- private static boolean isViewIntent(Intent i) {
- return i != null && ACTION_VIEW_CONVERSATION.equals(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
+ private static boolean isViewOrShareIntent(Intent i) {
+ Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction()));
+ return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
}
private static Intent createLauncherIntent(Context context) {
@@ -450,7 +458,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
} else {
intent = savedInstanceState.getParcelable("intent");
}
- if (isViewIntent(intent)) {
+ if (isViewOrShareIntent(intent)) {
pendingViewIntent.push(intent);
setIntent(createLauncherIntent(this));
}
@@ -482,6 +490,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
@Override
public void onConversationSelected(Conversation conversation) {
+ clearPendingViewIntent();
if (ConversationFragment.getConversation(this) == conversation) {
Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
return;
@@ -489,6 +498,34 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
openConversation(conversation, null);
}
+ public void clearPendingViewIntent() {
+ if (pendingViewIntent.clear()) {
+ Log.e(Config.LOGTAG, "cleared pending view intent");
+ }
+ }
+
+ private void displayToast(final String msg) {
+ runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
+ }
+
+ @Override
+ public void onAffiliationChangedSuccessful(Jid jid) {
+ }
+
+ @Override
+ public void onAffiliationChangeFailed(Jid jid, int resId) {
+ displayToast(getString(resId, jid.asBareJid().toString()));
+ }
+
+ @Override
+ public void onRoleChangedSuccessful(String nick) {
+ }
+
+ @Override
+ public void onRoleChangeFailed(String nick, int resId) {
+ displayToast(getString(resId, nick));
+ }
+
private void openConversation(Conversation conversation, Bundle extras) {
ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
final boolean mainNeedsRefresh;
@@ -548,7 +585,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
if (fm.getBackStackEntryCount() > 0) {
try {
fm.popBackStack();
- } catch (IllegalArgumentException e) {
+ } catch (IllegalStateException e) {
Log.w(Config.LOGTAG, "Unable to pop back stack after pressing home button");
}
return true;
@@ -559,7 +596,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
return true;
case R.id.action_check_updates:
if (xmppConnectionService.hasInternetConnection()) {
- if (!installFromUnknownSourceAllowed() && !xmppConnectionService.installedFrom().equals(PlayStore)) {
+ if (!installFromUnknownSourceAllowed() && xmppConnectionService.installedFrom() == null) {
+ openInstallFromUnknownSourcesDialogIfNeeded();
+ } else if (!installFromUnknownSourceAllowed() && (xmppConnectionService.installedFrom() != null && !xmppConnectionService.installedFrom().equalsIgnoreCase(PlayStore))) {
openInstallFromUnknownSourcesDialogIfNeeded();
} else {
UpdateService task = new UpdateService(this, xmppConnectionService.installedFrom(), xmppConnectionService);
@@ -601,7 +640,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
@Override
protected void onNewIntent(final Intent intent) {
- if (isViewIntent(intent)) {
+ if (isViewOrShareIntent(intent)) {
if (xmppConnectionService != null) {
processViewIntent(intent);
} else {
@@ -950,7 +989,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
Log.d(Config.LOGTAG, "AppUpdater: CurrentTime: " + lastUpdateTime);
if (!installFromUnknownSourceAllowed() && Store == null) {
openInstallFromUnknownSourcesDialogIfNeeded();
- } else if (Store != null && (Store.equals(PlayStore) || Store.equals(FDroid))) {
+ } else if (Store != null && (Store.equalsIgnoreCase(PlayStore) || Store.equalsIgnoreCase(FDroid))) {
Log.d(Config.LOGTAG, "AppUpdater aborted because app store is " + Store);
} else {
UpdateService task = new UpdateService(this, Store, xmppConnectionService);
diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
index e33de0eb4..be4222e78 100644
--- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
@@ -22,6 +22,7 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
@@ -327,6 +328,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
+ updatePortLayout();
updateSaveButton();
updateInfoButtons();
}
@@ -432,7 +434,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
protected void updateInfoButtons() {
if (this.binding.accountRegisterNew.isChecked() && this.binding.accountJid.getText().length() > 0 && !this.binding.accountJid.getText().toString().contains("@")) {
- if (!mUsernameMode && Jid.of(this.binding.accountJid.getText()).getDomain().toLowerCase().equals("pix-art.de")) {
+ final String jid = this.binding.accountJid.getText().toString();
+ if (!mUsernameMode && Jid.of(jid).getDomain().toLowerCase().equals("pix-art.de")) {
this.binding.showPrivacyPolicy.setVisibility(View.VISIBLE);
this.binding.showTermsOfUse.setVisibility(View.VISIBLE);
}
@@ -442,6 +445,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
}
+ private void updatePortLayout() {
+ String hostname = this.binding.hostname.getText().toString();
+ this.binding.portLayout.setEnabled(!TextUtils.isEmpty(hostname));
+ }
+
protected void updateSaveButton() {
boolean accountInfoEdited = accountInfoEdited();
@@ -551,6 +559,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
+ updatePortLayout();
updateSaveButton();
updateInfoButtons();
}
@@ -734,6 +743,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.binding.cancelButton.setEnabled(false);
}
if (mUsernameMode) {
+ this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
this.binding.accountJid.setHint(R.string.username_hint);
} else {
final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
diff --git a/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java b/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java
new file mode 100644
index 000000000..4b32b42ab
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java
@@ -0,0 +1,77 @@
+package de.pixart.messenger.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
+
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.ActivityMediaBrowserBinding;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.ui.adapter.MediaAdapter;
+import de.pixart.messenger.ui.interfaces.OnMediaLoaded;
+import de.pixart.messenger.ui.util.Attachment;
+import de.pixart.messenger.ui.util.GridManager;
+import rocks.xmpp.addr.Jid;
+
+
+public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded {
+
+ private ActivityMediaBrowserBinding binding;
+
+ private MediaAdapter mMediaAdapter;
+
+ public static void launch(Context context, Contact contact) {
+ launch(context, contact.getAccount(), contact.getJid().asBareJid().toEscapedString());
+ }
+
+ public static void launch(Context context, Conversation conversation) {
+ launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toEscapedString());
+ }
+
+ private static void launch(Context context, Account account, String jid) {
+ final Intent intent = new Intent(context, MediaBrowserActivity.class);
+ intent.putExtra("account", account.getUuid());
+ intent.putExtra("jid", jid);
+ context.startActivity(intent);
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_browser);
+ setSupportActionBar((Toolbar) binding.toolbar);
+ configureActionBar(getSupportActionBar());
+ mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
+ this.binding.media.setAdapter(mMediaAdapter);
+ GridManager.setupLayoutManager(this, this.binding.media, R.dimen.browser_media_size);
+
+ }
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ void onBackendConnected() {
+ Intent intent = getIntent();
+ String account = intent == null ? null : intent.getStringExtra("account");
+ String jid = intent == null ? null : intent.getStringExtra("jid");
+ if (account != null && jid != null) {
+ xmppConnectionService.getAttachments(account, Jid.of(jid), 0, this);
+ }
+ }
+
+ @Override
+ public void onMediaLoaded(List<Attachment> attachments) {
+ runOnUiThread(() -> {
+ mMediaAdapter.setAttachments(attachments);
+ });
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java
index 15d193886..31dc1681a 100644
--- a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java
@@ -65,7 +65,8 @@ public class RecordingActivity extends Activity implements View.OnClickListener
super.onStart();
if (!startRecording()) {
this.binding.shareButton.setEnabled(false);
- Toast.makeText(this, R.string.unable_to_start_recording, Toast.LENGTH_SHORT).show();
+ this.binding.timer.setTextAppearance(this, R.style.TextAppearance_Conversations_Title);
+ this.binding.timer.setText(R.string.unable_to_start_recording);
}
}
@@ -108,12 +109,15 @@ public class RecordingActivity extends Activity implements View.OnClickListener
mShouldFinishAfterWrite = saveFile;
try {
mRecorder.stop();
- } catch (RuntimeException stopException) {
- //handle cleanup here
+ mRecorder.release();
+ } catch (Exception e) {
+ if (saveFile) {
+ Toast.makeText(this,R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show();
+ }
+ } finally {
+ mRecorder = null;
+ mStartTime = 0;
}
- mRecorder.release();
- mRecorder = null;
- mStartTime = 0;
if (!saveFile && mOutputFile != null) {
if (mOutputFile.delete()) {
Log.d(Config.LOGTAG,"deleted canceled recording");
@@ -123,7 +127,7 @@ public class RecordingActivity extends Activity implements View.OnClickListener
private static File generateOutputFilename(Context context) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
- return new File(FileBackend.getConversationsDirectory("Audios", true) + "/"
+ return new File(FileBackend.getConversationsDirectory("Audios")
+ dateFormat.format(new Date())
+ ".m4a");
}
diff --git a/src/main/java/de/pixart/messenger/ui/SearchActivity.java b/src/main/java/de/pixart/messenger/ui/SearchActivity.java
index cc3172670..e9ba1d426 100644
--- a/src/main/java/de/pixart/messenger/ui/SearchActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/SearchActivity.java
@@ -56,12 +56,11 @@ import de.pixart.messenger.services.MessageSearchTask;
import de.pixart.messenger.ui.adapter.MessageAdapter;
import de.pixart.messenger.ui.interfaces.OnSearchResultsAvailable;
import de.pixart.messenger.ui.util.ChangeWatcher;
-import de.pixart.messenger.ui.util.Color;
import de.pixart.messenger.ui.util.DateSeparator;
-import de.pixart.messenger.ui.util.Drawable;
import de.pixart.messenger.ui.util.ListViewUtils;
import de.pixart.messenger.ui.util.PendingItem;
import de.pixart.messenger.ui.util.ShareUtil;
+import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.utils.FtsUtils;
import de.pixart.messenger.utils.MessageUtils;
@@ -213,12 +212,12 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
private void changeBackground(boolean hasSearch, boolean hasResults) {
if (hasSearch) {
if (hasResults) {
- binding.searchResults.setBackgroundColor(Color.get(this, R.attr.color_background_secondary));
+ binding.searchResults.setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_secondary));
} else {
- binding.searchResults.setBackground(Drawable.get(this, R.attr.activity_background_no_results));
+ binding.searchResults.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_background_no_results));
}
} else {
- binding.searchResults.setBackground(Drawable.get(this, R.attr.activity_background_search));
+ binding.searchResults.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_background_search));
}
}
diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
index 9ee11bd0b..1c0992cf1 100644
--- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
@@ -17,6 +17,7 @@ import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater;
@@ -37,7 +38,7 @@ import de.pixart.messenger.crypto.OmemoSetting;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.services.ExportLogsService;
import de.pixart.messenger.services.MemorizingTrustManager;
-import de.pixart.messenger.ui.util.Color;
+import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.utils.TimeframeUtils;
import rocks.xmpp.addr.Jid;
@@ -58,12 +59,16 @@ public class SettingsActivity extends XmppActivity implements
public static final String SHOW_FOREGROUND_SERVICE = "show_foreground_service";
public static final String USE_BUNDLED_EMOJIS = "use_bundled_emoji";
public static final String USE_MULTI_ACCOUNTS = "use_multi_accounts";
+ public static final String QUICK_SHARE_ATTACHMENT_CHOICE = "quick_share_attachment_choice";
+ public static final String NUMBER_OF_ACCOUNTS = "number_of_accounts";
public static final int REQUEST_WRITE_LOGS = 0xbf8701;
Preference multiAccountPreference;
Preference BundledEmojiPreference;
+ Preference QuickShareAttachmentChoicePreference;
boolean isMultiAccountChecked = false;
boolean isBundledEmojiChecked;
+ boolean isQuickShareAttachmentChoiceChecked = false;
private SettingsFragment mSettingsFragment;
@Override
@@ -71,6 +76,7 @@ public class SettingsActivity extends XmppActivity implements
super.onCreate(savedInstanceState);
this.mTheme = findTheme();
setTheme(this.mTheme);
+ updateTheme();
setContentView(R.layout.activity_settings);
FragmentManager fm = getFragmentManager();
mSettingsFragment = (SettingsFragment) fm.findFragmentById(R.id.settings_content);
@@ -79,7 +85,7 @@ public class SettingsActivity extends XmppActivity implements
fm.beginTransaction().replace(R.id.settings_content, mSettingsFragment).commit();
}
mSettingsFragment.setActivityIntent(getIntent());
- getWindow().getDecorView().setBackgroundColor(Color.get(this, R.attr.color_background_secondary));
+ getWindow().getDecorView().setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_secondary));
setSupportActionBar(findViewById(R.id.toolbar));
configureActionBar(getSupportActionBar());
}
@@ -92,12 +98,11 @@ public class SettingsActivity extends XmppActivity implements
@Override
public void onStart() {
super.onStart();
- updateTheme();
-
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
multiAccountPreference = mSettingsFragment.findPreference("enable_multi_accounts");
if (multiAccountPreference != null) {
isMultiAccountChecked = ((CheckBoxPreference) multiAccountPreference).isChecked();
+ handleMultiAccountChanges();
}
BundledEmojiPreference = mSettingsFragment.findPreference("use_bundled_emoji");
@@ -105,6 +110,15 @@ public class SettingsActivity extends XmppActivity implements
isBundledEmojiChecked = ((CheckBoxPreference) BundledEmojiPreference).isChecked();
}
+ QuickShareAttachmentChoicePreference = mSettingsFragment.findPreference("quick_share_attachment_choice");
+ if (QuickShareAttachmentChoicePreference != null) {
+ QuickShareAttachmentChoicePreference.setOnPreferenceChangeListener((preference, newValue) -> {
+ refreshUiReal();
+ return true;
+ });
+ isQuickShareAttachmentChoiceChecked = ((CheckBoxPreference) QuickShareAttachmentChoicePreference).isChecked();
+ }
+
changeOmemoSettingSummary();
if (Config.FORCE_ORBOT) {
@@ -173,7 +187,7 @@ public class SettingsActivity extends XmppActivity implements
quickAction.setEntryValues(entryValues.toArray(new CharSequence[entryValues.size()]));
}
- if (Config.QUICK_SHARE_ATTACHMENT_CHOICE) {
+ if (isQuickShareAttachmentChoiceChecked) {
if (UIPreferenceScreen != null && quickAction != null) {
UIPreferenceScreen.removePreference(quickAction);
}
@@ -274,21 +288,20 @@ public class SettingsActivity extends XmppActivity implements
Log.d(Config.LOGTAG, "Multi account checkbox checked: " + isMultiAccountChecked);
if (isMultiAccountChecked) {
enableMultiAccountsPreference.setEnabled(false);
- if (xmppConnectionService != null) {
- final List<Account> accounts = xmppConnectionService.getAccounts();
- Log.d(Config.LOGTAG, "Disabled multi account: Number of accounts " + accounts.size());
- if (accounts.size() > 1) {
- Log.d(Config.LOGTAG, "Disabled multi account not possible because you have more than one account");
+ int accounts = getNumberOfAccounts();
+ Log.d(Config.LOGTAG, "Disabled multi account: Number of accounts " + accounts);
+ if (accounts > 1) {
+ Log.d(Config.LOGTAG, "Disabling multi account not possible because you have more than one account");
enableMultiAccountsPreference.setEnabled(false);
} else {
- Log.d(Config.LOGTAG, "Disabled multi account possible because you have one account");
+ Log.d(Config.LOGTAG, "Disabling multi account possible because you have only one account");
enableMultiAccountsPreference.setEnabled(true);
+ enableMultiAccountsPreference.setOnPreferenceClickListener(preference -> {
+ refreshUiReal();
+ return true;
+ });
}
} else {
- Log.d(Config.LOGTAG, "Disabled multi account not possible because XmppConnectionService == null");
- enableMultiAccountsPreference.setEnabled(false);
- }
- } else {
enableMultiAccountsPreference.setEnabled(true);
enableMultiAccountsPreference.setOnPreferenceClickListener(preference -> {
enableMultiAccounts();
@@ -301,7 +314,7 @@ public class SettingsActivity extends XmppActivity implements
private void updateTheme() {
final int theme = findTheme();
if (this.mTheme != theme) {
- recreate();
+ refreshUiReal();
}
}
@@ -465,12 +478,17 @@ public class SettingsActivity extends XmppActivity implements
SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = multiaccount_prefs.edit();
editor.putString("BackupPW", pw1);
- editor.commit();
+ boolean passwordstored = editor.commit();
+ Log.d(Config.LOGTAG, "saving multiaccount password " + passwordstored);
+ if (passwordstored) {
+ recreate();
+ } else {
+ handleMultiAccountChanges();
+ }
}
})
.setNegativeButton(R.string.cancel, null);
alertDialogBuilder.create().show();
-
}
@@ -529,7 +547,7 @@ public class SettingsActivity extends XmppActivity implements
}
private void startExport() {
- startService(new Intent(getApplicationContext(), ExportLogsService.class));
+ ContextCompat.startForegroundService(this, new Intent(this, ExportLogsService.class));
}
private void displayToast(final String msg) {
@@ -545,6 +563,39 @@ public class SettingsActivity extends XmppActivity implements
}
public void refreshUiReal() {
- //nothing to do. This Activity doesn't implement any listeners
+ recreate();
+ handleMultiAccountChanges();
+ }
+
+ private void handleMultiAccountChanges() {
+ multiAccountPreference = mSettingsFragment.findPreference("enable_multi_accounts");
+ if (multiAccountPreference != null) {
+ //check if password = null
+ final SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE);
+ if (multiaccount_prefs != null && multiaccount_prefs.getString("BackupPW", null) == null) {
+ Log.d(Config.LOGTAG, "uncheck multiaccount because password = null");
+ if (multiAccountPreference != null) {
+ ((CheckBoxPreference) multiAccountPreference).setChecked(false);
+ }
+ }
+ //if multiAccountDisabled reset password
+ final Preference enableMultiAccountsPreference = mSettingsFragment.findPreference("enable_multi_accounts");
+ if (enableMultiAccountsPreference != null && !isMultiAccountChecked) {
+ SharedPreferences.Editor editor = multiaccount_prefs.edit();
+ editor.putString("BackupPW", null);
+ if (editor.commit()) {
+ Log.d(Config.LOGTAG, "resetting multiaccount password because multiaccount = unchecked");
+ } else {
+ Log.d(Config.LOGTAG, "resetting multiaccount password failed");
+ }
+ }
+ }
+ }
+
+ private int getNumberOfAccounts() {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ int NumberOfAccounts = preferences.getInt(NUMBER_OF_ACCOUNTS, 0);
+ Log.d(Config.LOGTAG, "Get number of accounts from file: " + NumberOfAccounts);
+ return NumberOfAccounts;
}
}
diff --git a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java
index b40875f7c..ec4898f89 100644
--- a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java
+++ b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java
@@ -10,6 +10,7 @@ import android.text.TextUtils;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
+import de.pixart.messenger.utils.Compatibility;
public class SettingsFragment extends PreferenceFragment {
@@ -30,6 +31,7 @@ public class SettingsFragment extends PreferenceFragment {
mCategory.removePreference(cleanPrivateStorage);
}
}
+ Compatibility.removeUnusedPreferences(this);
if (!TextUtils.isEmpty(page)) {
openPreferenceScreen(page);
diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
index a820378a3..da3d2ff8f 100644
--- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
@@ -1,6 +1,5 @@
package de.pixart.messenger.ui;
-import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
@@ -12,116 +11,32 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
-import java.net.URLConnection;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Conversation;
-import de.pixart.messenger.entities.Message;
-import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.services.EmojiService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.adapter.ConversationAdapter;
-import de.pixart.messenger.ui.util.PresenceSelector;
-import de.pixart.messenger.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
-import static java.lang.String.format;
public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
private static final int REQUEST_STORAGE_PERMISSION = 0x733f32;
- private boolean mReturnToPrevious = false;
+ private static final int REQUEST_START_NEW_CONVERSATION = 0x0501;
private Conversation mPendingConversation = null;
-
- @Override
- public void onConversationUpdate() {
- refreshUi();
- }
-
- private class Share {
- public List<Uri> uris = new ArrayList<>();
- public boolean image;
- public String account;
- public String contact;
- public String text;
- public String uuid;
- public boolean multiple = false;
- public String type;
- }
-
private Share share;
-
- private static final int REQUEST_START_NEW_CONVERSATION = 0x0501;
- private RecyclerView mListView;
private ConversationAdapter mAdapter;
private List<Conversation> mConversations = new ArrayList<>();
- private Toast mToast;
- private AtomicInteger attachmentCounter = new AtomicInteger(0);
-
- private UiInformableCallback<Message> attachFileCallback = new UiInformableCallback<Message>() {
-
- @Override
- public void inform(final String text) {
- runOnUiThread(() -> replaceToast(text));
- }
-
- @Override
- public void userInputRequried(PendingIntent pi, Message object) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void success(final Message message) {
- runOnUiThread(() -> {
- if (attachmentCounter.decrementAndGet() <= 0) {
- int resId;
- if (share.image && share.multiple) {
- resId = R.string.shared_images_with_x;
- } else if (share.image) {
- resId = R.string.shared_image_with_x;
- } else {
- resId = R.string.shared_file_with_x;
- }
- Conversation conversation = (Conversation) message.getConversation();
- replaceToast(getString(resId, conversation.getName()));
- if (mReturnToPrevious) {
- finish();
- } else {
- switchToConversation(conversation);
- }
- }
- });
- }
-
- @Override
- public void error(final int errorCode, Message object) {
- runOnUiThread(() -> {
- replaceToast(getString(errorCode));
- if (attachmentCounter.decrementAndGet() <= 0) {
- finish();
- }
- });
- }
- };
-
- protected void hideToast() {
- if (mToast != null) {
- mToast.cancel();
- }
- }
- protected void replaceToast(String msg) {
- hideToast();
- mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
- mToast.show();
+ @Override
+ public void onConversationUpdate() {
+ refreshUi();
}
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
@@ -160,20 +75,16 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
super.onCreate(savedInstanceState);
boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji));
new EmojiService(this).init(useBundledEmoji);
-
setContentView(R.layout.activity_share_with);
-
setSupportActionBar(findViewById(R.id.toolbar));
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
-
setTitle(getString(R.string.title_activity_sharewith));
-
- mListView = findViewById(R.id.choose_conversation_list);
+ RecyclerView mListView = findViewById(R.id.choose_conversation_list);
mAdapter = new ConversationAdapter(this, this.mConversations);
- mListView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
+ mListView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
mListView.setAdapter(mAdapter);
mAdapter.setConversationClickListener((view, conversation) -> share(conversation));
this.share = new Share();
@@ -203,57 +114,28 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
if (intent == null) {
return;
}
- this.mReturnToPrevious = getBooleanPreference("return_to_previous", R.bool.return_to_previous);
final String type = intent.getType();
final String action = intent.getAction();
- Log.d(Config.LOGTAG, "action: " + action + ", type:" + type);
- share.uuid = intent.getStringExtra("uuid");
if (Intent.ACTION_SEND.equals(action)) {
- final String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
final String text = intent.getStringExtra(Intent.EXTRA_TEXT);
final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (type != null && uri != null && (text == null || !type.equals("text/plain"))) {
this.share.uris.clear();
this.share.uris.add(uri);
- this.share.image = type.startsWith("image/") || isImage(uri);
- this.share.type = type;
} else {
- if (subject != null) {
- this.share.text = format("[%s]%n%s", subject, text);
- } else {
- this.share.text = text;
- }
+ this.share.text = text;
}
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
- this.share.image = type != null && type.startsWith("image/");
- if (!this.share.image) {
- return;
- }
this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
if (xmppConnectionServiceBound) {
- if (share.uuid != null) {
- share();
- } else {
- xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
- }
- }
-
- }
-
- protected boolean isImage(Uri uri) {
- try {
- String guess = URLConnection.guessContentTypeFromName(uri.toString());
- return (guess != null && guess.startsWith("image/"));
- } catch (final StringIndexOutOfBoundsException ignored) {
- return false;
+ xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
}
}
@Override
void onBackendConnected() {
- if (xmppConnectionServiceBound && share != null
- && ((share.contact != null && share.account != null) || share.uuid != null)) {
+ if (xmppConnectionServiceBound && share != null && ((share.contact != null && share.account != null))) {
share();
return;
}
@@ -262,28 +144,19 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
private void share() {
final Conversation conversation;
- if (share.uuid != null) {
- conversation = xmppConnectionService.findConversationByUuid(share.uuid);
- if (conversation == null) {
- return;
- }
- } else {
- Account account;
- try {
- account = xmppConnectionService.findAccountByJid(Jid.of(share.account));
- } catch (final IllegalArgumentException e) {
- account = null;
- }
- if (account == null) {
- return;
- }
-
- try {
- conversation = xmppConnectionService
- .findOrCreateConversation(account, Jid.of(share.contact), false, true);
- } catch (final IllegalArgumentException e) {
- return;
- }
+ Account account;
+ try {
+ account = xmppConnectionService.findAccountByJid(Jid.of(share.account));
+ } catch (final IllegalArgumentException e) {
+ account = null;
+ }
+ if (account == null) {
+ return;
+ }
+ try {
+ conversation = xmppConnectionService.findOrCreateConversation(account, Jid.of(share.contact), false, true);
+ } catch (final IllegalArgumentException e) {
+ return;
}
share(conversation);
}
@@ -293,110 +166,18 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
mPendingConversation = conversation;
return;
}
- final Account account = conversation.getAccount();
- final XmppConnection connection = account.getXmppConnection();
- final long max = connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize();
- mListView.setEnabled(false);
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) {
- if (share.uuid == null) {
- showInstallPgpDialog();
- } else {
- Toast.makeText(this, R.string.openkeychain_not_installed, Toast.LENGTH_SHORT).show();
- finish();
- }
- return;
+ Intent intent = new Intent(this, ConversationsActivity.class);
+ intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
+ if (share.uris.size() > 0) {
+ intent.setAction(Intent.ACTION_SEND_MULTIPLE);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } else if (share.text != null) {
+ intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
+ intent.putExtra(ConversationsActivity.EXTRA_TEXT, share.text);
}
- if (share.uris.size() != 0) {
- PresenceSelector.OnPresenceSelected callback = () -> {
- attachmentCounter.set(share.uris.size());
- if (share.image) {
- Log.d(Config.LOGTAG, "ShareWithActivity share() image " + share.uris.size() + " uri(s) " + share.uris.toString());
- share.multiple = share.uris.size() > 1;
- replaceToast(getString(share.multiple ? R.string.preparing_images : R.string.preparing_image));
- for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
- final Uri uri = i.next();
- delegateUriPermissionsToService(uri);
- xmppConnectionService.attachImageToConversation(conversation, uri, attachFileCallback);
- }
- } else {
- Log.d(Config.LOGTAG, "ShareWithActivity share() file " + share.uris.size() + " uri(s) " + share.uris.toString());
- replaceToast(getString(R.string.preparing_file));
- final Uri uri = share.uris.get(0);
- delegateUriPermissionsToService(uri);
- xmppConnectionService.attachFileToConversation(conversation, uri, share.type, attachFileCallback);
- finish();
- }
- };
- if (account.httpUploadAvailable()
- && ((share.image && !neverCompressPictures())
- || conversation.getMode() == Conversation.MODE_MULTI
- || FileBackend.allFilesUnderSize(this, share.uris, max))
- && conversation.getNextEncryption() != Message.ENCRYPTION_OTR) {
- callback.onPresenceSelected();
- } else {
- selectPresence(conversation, callback);
- }
- } else {
- if (mReturnToPrevious && this.share.text != null && !this.share.text.isEmpty()) {
- final PresenceSelector.OnPresenceSelected callback = new PresenceSelector.OnPresenceSelected() {
-
- private void finishAndSend(Message message) {
- replaceToast(getString(R.string.shared_text_with_x, conversation.getName()));
- finish();
- }
-
- private UiCallback<Message> messageEncryptionCallback = new UiCallback<Message>() {
- @Override
- public void success(final Message message) {
- runOnUiThread(() -> finishAndSend(message));
- }
-
- @Override
- public void error(final int errorCode, Message object) {
- runOnUiThread(() -> {
- replaceToast(getString(errorCode));
- finish();
- });
- }
-
- @Override
- public void userInputRequried(PendingIntent pi, Message object) {
- finish();
- }
- };
-
- @Override
- public void onPresenceSelected() {
-
- final int encryption = conversation.getNextEncryption();
-
- Message message = new Message(conversation,share.text, encryption);
-
- Log.d(Config.LOGTAG,"on presence selected encrpytion="+encryption);
-
- if (encryption == Message.ENCRYPTION_PGP) {
- replaceToast(getString(R.string.encrypting_message));
- xmppConnectionService.getPgpEngine().encrypt(message,messageEncryptionCallback);
- return;
- }
-
- if (encryption == Message.ENCRYPTION_OTR) {
- message.setCounterpart(conversation.getNextCounterpart());
- }
- xmppConnectionService.sendMessage(message);
- finishAndSend(message);
- }
- };
- if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
- selectPresence(conversation, callback);
- } else {
- callback.onPresenceSelected();
- }
- } else {
- switchToConversation(conversation, this.share.text, true);
- }
- }
-
+ startActivity(intent);
+ finish();
}
public void refreshUiReal() {
@@ -404,12 +185,10 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
mAdapter.notifyDataSetChanged();
}
- @Override
- public void onBackPressed() {
- if (attachmentCounter.get() >= 1) {
- replaceToast(getString(R.string.sharing_files_please_wait));
- } else {
- super.onBackPressed();
- }
+ private class Share {
+ public String account;
+ public String contact;
+ public String text;
+ ArrayList<Uri> uris = new ArrayList<>();
}
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
index a58c2e0c0..52bff613c 100644
--- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
@@ -463,6 +463,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (prev != null) {
ft.remove(prev);
}
+ boolean multiAccount = false;
+ try {
+ multiAccount = xmppConnectionService.multipleAccounts();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
ft.addToBackStack(null);
EnterJidDialog dialog = EnterJidDialog.newInstance(
mActivatedAccounts,
@@ -471,7 +477,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
prefilledJid,
null,
invite == null || !invite.hasFingerprints(),
- xmppConnectionService.multipleAccounts()
+ multiAccount
);
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid) -> {
@@ -684,21 +690,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.sync_with_contacts);
builder.setMessage(R.string.sync_with_contacts_long);
- builder.setPositiveButton(R.string.next, (dialog, which) -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
- }
- });
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- builder.setOnDismissListener(dialog -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
- }
- });
- }
+ builder.setPositiveButton(R.string.next, (dialog, which) -> requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS));
+ builder.setOnDismissListener(dialog -> requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS));
builder.create().show();
} else {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
+ requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
}
}
}
@@ -711,7 +707,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
- xmppConnectionService.loadPhoneContacts();
+ xmppConnectionService.startContactObserver();
}
}
}
@@ -868,11 +864,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
protected void filterContacts(String needle) {
this.contacts.clear();
- for (Account account : xmppConnectionService.getAccounts()) {
+ final List<Account> accounts = xmppConnectionService.getAccounts();
+ final boolean singleAccountActive = isSingleAccountActive(accounts);
+ for (Account account : accounts) {
if (account.getStatus() != Account.State.DISABLED) {
for (Contact contact : account.getRoster().getContacts()) {
Presence.Status s = contact.getShownStatus();
- if (contact.showInRoster() && contact.match(this, needle)
+ if ((contact.showInRoster() || (singleAccountActive && contact.showInPhoneBook())) && contact.match(this, needle)
&& (!this.mHideOfflineContacts
|| (needle != null && !needle.trim().isEmpty())
|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
@@ -885,6 +883,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
mContactsAdapter.notifyDataSetChanged();
}
+ private static boolean isSingleAccountActive(final List<Account> accounts) {
+ int i = 0;
+ for(Account account : accounts) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ ++i;
+ }
+ }
+ return i == 1;
+ }
+
protected void filterConferences(String needle) {
this.conferences.clear();
for (Account account : xmppConnectionService.getAccounts()) {
@@ -1053,9 +1061,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
final Contact contact = (Contact) activity.contacts.get(acmi.position);
final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
+ final MenuItem deleteContactMenuItem = menu.findItem(R.id.context_delete_contact);
if (contact.isSelf()) {
showContactDetailsItem.setVisible(false);
}
+ deleteContactMenuItem.setVisible(contact.showInRoster());
XmppConnection xmpp = contact.getAccount().getXmppConnection();
if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
if (contact.isBlocked()) {
diff --git a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java
index 0c59b06c6..9e35d7ebf 100644
--- a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java
@@ -57,7 +57,10 @@ public class UpdaterActivity extends XmppActivity {
//set activity
setContentView(R.layout.activity_updater);
- this.mUseTor = mXmppConnectionService.useTorToConnect();
+ this.mUseTor = false;
+ if (mXmppConnectionService != null) {
+ this.mUseTor = mXmppConnectionService.useTorToConnect();
+ }
this.mTheme = findTheme();
setTheme(this.mTheme);
@@ -111,7 +114,7 @@ public class UpdaterActivity extends XmppActivity {
store = null;
}
//delete old downloaded localVersion files
- File dir = new File(FileBackend.getConversationsDirectory("Update", false));
+ File dir = new File(FileBackend.getAppUpdateDirectory());
if (dir.isDirectory()) {
String[] children = dir.list();
for (String aChildren : children) {
@@ -131,7 +134,7 @@ public class UpdaterActivity extends XmppActivity {
//ask for permissions on devices >= SDK 23
if (isStoragePermissionGranted() && isNetworkAvailable(getApplicationContext())) {
//start downloading the file using the download manager
- if (store.equals(PlayStore)) {
+ if (store != null && store.equalsIgnoreCase(PlayStore)) {
Uri uri = Uri.parse("market://details?id=de.pixart.messenger");
Intent marketIntent = new Intent(Intent.ACTION_VIEW, uri);
PackageManager manager = getApplicationContext().getPackageManager();
@@ -145,7 +148,7 @@ public class UpdaterActivity extends XmppActivity {
startActivity(browserIntent);
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
}
- } else if (store.equals(FDroid)) {
+ } else if (store != null && store.equalsIgnoreCase(FDroid)) {
Uri uri = Uri.parse("https://f-droid.org/de/packages/de.pixart.messenger/");
Intent marketIntent = new Intent(Intent.ACTION_VIEW, uri);
PackageManager manager = getApplicationContext().getPackageManager();
@@ -288,7 +291,7 @@ public class UpdaterActivity extends XmppActivity {
private class DownloadTask extends AsyncTask<String, Integer, String> {
- File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + FileBackend.getDirectoryName("Update", false));
+ File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + FileBackend.getAppUpdateDirectory());
File file = new File(dir, FileName);
private Context context;
private PowerManager.WakeLock mWakeLock;
diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
index 3c03542ee..e2bf9d8e9 100644
--- a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
@@ -152,6 +152,8 @@ public class WelcomeActivity extends XmppActivity {
checkDatabase(password);
} catch (IOException e) {
e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
}
pd.dismiss();
}
@@ -191,7 +193,7 @@ public class WelcomeActivity extends XmppActivity {
private boolean BackupAvailable() {
// Set the folder on the SDcard
- File filePath = new File(FileBackend.getConversationsDirectory("Database", false) + "database.db.crypt");
+ File filePath = new File(FileBackend.getBackupDirectory() + "database.db.crypt");
Log.d(Config.LOGTAG, "DB Path: " + filePath.toString());
if (filePath.exists()) {
Log.d(Config.LOGTAG, "DB Path existing");
@@ -204,11 +206,11 @@ public class WelcomeActivity extends XmppActivity {
private void checkDatabase(String DecryptionKey) throws IOException {
// Set the folder on the SDcard
- File directory = new File(FileBackend.getConversationsDirectory("Database", false));
+ File directory = new File(FileBackend.getBackupDirectory());
// Set the input file stream up:
- FileInputStream InputFile = new FileInputStream(directory.getPath() + "/database.db.crypt");
+ FileInputStream InputFile = new FileInputStream(directory.getPath() + "database.db.crypt");
// Temp output for DB checks
- File TempFile = new File(directory.getPath() + "/database.bak");
+ File TempFile = new File(directory.getPath() + "database.bak");
FileOutputStream OutputTemp = new FileOutputStream(TempFile);
try {
@@ -225,6 +227,9 @@ public class WelcomeActivity extends XmppActivity {
} catch (IOException e) {
Log.d(Config.LOGTAG, "Database importer: decryption failed (IO) with " + e);
e.printStackTrace();
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "Database importer: Error " + e);
+ e.printStackTrace();
}
SQLiteDatabase checkDB = null;
@@ -236,10 +241,11 @@ public class WelcomeActivity extends XmppActivity {
checkDB = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
Backup_DB_Version = checkDB.getVersion();
Log.d(Config.LOGTAG, "Backup found: " + checkDB + " Version: " + checkDB.getVersion());
-
} catch (SQLiteException e) {
//database does't exist yet.
Log.d(Config.LOGTAG, "No backup found: " + checkDB);
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "Error importing backup: " + e);
}
if (checkDB != null) {
@@ -280,11 +286,11 @@ public class WelcomeActivity extends XmppActivity {
// Set location for the db:
final OutputStream OutputFile = new FileOutputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
// Set the folder on the SDcard
- File directory = new File(FileBackend.getConversationsDirectory("Database", false));
+ File directory = new File(FileBackend.getBackupDirectory());
// Set the input file stream up:
- final InputStream InputFile = new FileInputStream(directory.getPath() + "/database.bak");
+ final InputStream InputFile = new FileInputStream(directory.getPath() + "database.bak");
//set temp file
- File TempFile = new File(directory.getPath() + "/database.bak");
+ File TempFile = new File(directory.getPath() + "database.bak");
// Transfer bytes from the input file to the output file
byte[] buffer = new byte[1024];
diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java
index f52b2d72c..acb970e82 100644
--- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java
@@ -536,7 +536,11 @@ public abstract class XmppActivity extends ActionBarActivity {
intent.setAction(Intent.ACTION_SEND);
intent.setData(uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startService(intent);
+ try {
+ startService(intent);
+ } catch (Exception e) {
+ Log.e(Config.LOGTAG, "unable to delegate uri permission", e);
+ }
}
protected void inviteToConversation(Conversation conversation) {
@@ -943,7 +947,11 @@ public abstract class XmppActivity extends ActionBarActivity {
}
public void inviteUser() {
- if (xmppConnectionServiceBound && !xmppConnectionService.multipleAccounts()) {
+ if (!xmppConnectionServiceBound) {
+ Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (!xmppConnectionService.multipleAccounts()) {
Account mAccount = xmppConnectionService.getAccounts().get(0);
String user = Jid.of(mAccount.getJid()).getLocal();
String domain = Jid.of(mAccount.getJid()).getDomain();
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
index 01b432d65..e37ea6121 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
@@ -24,7 +24,7 @@ import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.ui.ManageAccountActivity;
import de.pixart.messenger.ui.XmppActivity;
-import de.pixart.messenger.ui.util.Color;
+import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.utils.UIHelper;
public class AccountAdapter extends ArrayAdapter<Account> {
@@ -65,14 +65,14 @@ public class AccountAdapter extends ArrayAdapter<Account> {
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
- statusView.setTextColor(Color.get(activity, R.attr.TextColorOnline));
+ statusView.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline));
break;
case DISABLED:
case CONNECTING:
- statusView.setTextColor(Color.get(activity, android.R.attr.textColorSecondary));
+ statusView.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary));
break;
default:
- statusView.setTextColor(Color.get(activity, R.attr.TextColorError));
+ statusView.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError));
break;
}
final SwitchCompat tglAccountState = view.findViewById(R.id.tgl_account_status);
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
index 5294ba7cd..55dd6d65f 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
@@ -31,7 +31,7 @@ import de.pixart.messenger.entities.MucOptions;
import de.pixart.messenger.entities.Transferable;
import de.pixart.messenger.ui.ConversationFragment;
import de.pixart.messenger.ui.XmppActivity;
-import de.pixart.messenger.ui.util.Color;
+import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.ui.widget.UnreadCountCustomView;
import de.pixart.messenger.utils.EmojiWrapper;
import de.pixart.messenger.utils.IrregularUnicodeDetector;
@@ -97,9 +97,9 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
}
if (conversation == ConversationFragment.getConversation(activity)) {
- viewHolder.frame.setBackgroundColor(Color.get(activity, R.attr.color_background_tertiary));
+ viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary));
} else {
- viewHolder.frame.setBackgroundColor(Color.get(activity,R.attr.color_background_primary));
+ viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_secondary));
}
Message message = conversation.getLatestMessage();
@@ -250,11 +250,11 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
viewHolder.name.setTextColor(ContextCompat.getColor(activity, R.color.notavailable));
break;
default:
- viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main));
+ viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
break;
}
} else {
- viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main));
+ viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
}
if (activity.xmppConnectionService.indicateReceived()) {
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java
index 9f4a52d95..2e0bfccc6 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java
@@ -7,8 +7,6 @@ import android.widget.Filter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.Locale;
public class KnownHostsAdapter extends ArrayAdapter<String> {
@@ -17,34 +15,31 @@ public class KnownHostsAdapter extends ArrayAdapter<String> {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
- if (constraint != null) {
- ArrayList<String> suggestions = new ArrayList<>();
- final String[] split = constraint.toString().split("@");
- if (split.length == 1) {
- for (String domain : domains) {
- suggestions.add(split[0].toLowerCase(Locale
- .getDefault()) + "@" + domain);
- }
- } else if (split.length == 2) {
- for (String domain : domains) {
- if (domain.contentEquals(split[1])) {
- suggestions.clear();
- break;
- } else if (domain.contains(split[1])) {
- suggestions.add(split[0].toLowerCase(Locale
- .getDefault()) + "@" + domain);
- }
- }
- } else {
+ final ArrayList<String> suggestions = new ArrayList<>();
+ final String[] split = constraint == null ? new String[0] : constraint.toString().split("@");
+ if (split.length == 1) {
+ final String local = split[0].toLowerCase(Locale.ENGLISH);
+ for (String domain : domains) {
+ suggestions.add(local + "@" + domain);
+ }
+ } else if (split.length == 2) {
+ final String localPart = split[0].toLowerCase(Locale.ENGLISH);
+ final String domainPart = split[1].toLowerCase(Locale.ENGLISH);
+ if (domains.contains(domainPart)) {
return new FilterResults();
}
- FilterResults filterResults = new FilterResults();
- filterResults.values = suggestions;
- filterResults.count = suggestions.size();
- return filterResults;
+ for (String domain : domains) {
+ if (domain.contains(domainPart)) {
+ suggestions.add(localPart + "@" + domain);
+ }
+ }
} else {
return new FilterResults();
}
+ FilterResults filterResults = new FilterResults();
+ filterResults.values = suggestions;
+ filterResults.count = suggestions.size();
+ return filterResults;
}
@Override
@@ -52,9 +47,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> {
ArrayList filteredList = (ArrayList) results.values;
if (results.count > 0) {
clear();
- for (Object c : filteredList) {
- add((String) c);
- }
+ addAll(filteredList);
notifyDataSetChanged();
}
}
@@ -62,140 +55,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> {
public KnownHostsAdapter(Context context, int viewResourceId, Collection<String> mKnownHosts) {
super(context, viewResourceId, new ArrayList<>());
-
- if (mKnownHosts == null) {
- domains = new ArrayList<>();
- } else {
- domains = new ArrayList<>(mKnownHosts);
- }
-
- HashSet<String> hashSet = new HashSet<>();
-
- // get servers from https://conversations.im/compliance/
- new Thread(() -> {
- domains.add("pix-art.de");
- domains.add("conversations.im");
- domains.add("jabber.cat");
- domains.add("jabjab.de");
- domains.add("im.koderoot.net");
- domains.add("riotcat.org");
- domains.add("magicbroccoli.de");
- domains.add("kode.im");
- domains.add("jabber-germany.de");
- domains.add("simplewire.de");
- domains.add("suchat.org");
- domains.add("jabber.at");
- domains.add("trashserver.net");
- domains.add("wiuwiu.de");
- domains.add("5222.de");
- domains.add("dismail.de");
- domains.add("chat.sum7.eu");
- domains.add("xmpp.zone");
- domains.add("libranet.de");
- domains.add("laborversuch.de");
- domains.add("creep.im");
- domains.add("jabber.systemausfall.org");
- domains.add("jabber.hot-chilli.net");
- domains.add("jabber.fr");
- domains.add("jabber.de");
- domains.add("draugr.de");
- domains.add("elaon.de");
- domains.add("high-way.me");
- domains.add("jabber.rwth-aachen.de");
- domains.add("deshalbfrei.org");
- domains.add("mail.de");
- domains.add("bommboo.de");
- domains.add("jabber.systemli.org");
- domains.add("jabb.im");
- domains.add("mailbox.org");
- domains.add("hot-chilli.net");
- domains.add("jabberpl.org");
- domains.add("chinwag.im");
- domains.add("tchncs.de");
- domains.add("zsim.de");
- domains.add("patchcord.be");
- domains.add("gajim.org");
- domains.add("talker.to");
- domains.add("pimux.de");
- domains.add("jabber.home.vdlinde.org");
- domains.add("im.apinc.org");
- domains.add("chatme.im");
- domains.add("fusselkater.org");
- domains.add("datenknoten.me");
- domains.add("fysh.in");
- domains.add("jabber.chaos-darmstadt.de");
- domains.add("yax.im");
- domains.add("neko.im");
- domains.add("jabberzac.org");
- domains.add("xmpp.is");
- domains.add("home.zom.im");
- domains.add("jabber.ccc.de");
- domains.add("jwchat.org");
- domains.add("kdetalk.net");
- domains.add("kde.org");
- domains.add("riseup.net");
- domains.add("ruhr-uni-bochum.de");
- domains.add("njs.netlab.cz");
- domains.add("schokokeks.org");
- domains.add("jabber.cz");
- domains.add("ubuntu-jabber.de");
- domains.add("xabber.de");
- domains.add("ubuntu-jabber.net");
- domains.add("jabber.ru");
- domains.add("darknet.nz");
- domains.add("movim.eu");
- domains.add("404.city");
- domains.add("igniterealtime.org");
- domains.add("kapsi.fi");
- domains.add("jabbel.net");
- domains.add("joindiaspora.com");
- domains.add("alpha-labs.net");
- domains.add("xmppnet.de");
- domains.add("hoth.one");
- domains.add("blah.im");
- domains.add("xmpp.jp");
- domains.add("jabber.uni-mainz.de");
- domains.add("richim.org");
- domains.add("tigase.im");
- domains.add("jappix.com");
- domains.add("member.fsf.org");
- domains.add("jabber.rueckgr.at");
- domains.add("swissjabber.ch");
- domains.add("twattle.net");
- domains.add("jabber.calyxinstitute.org");
- domains.add("sapo.pt");
- domains.add("uprod.biz");
- domains.add("krautspace.de");
- domains.add("kraut.space");
- domains.add("null.pm");
- domains.add("anonymitaet-im-inter.net");
- domains.add("0nl1ne.at");
- domains.add("linuxlovers.at");
- domains.add("jabber.org");
- domains.add("jabber.no-sense.net");
- domains.add("swissjabber.eu");
- domains.add("swissjabber.org");
- domains.add("swissjabber.de");
- domains.add("swissjabber.li");
- domains.add("jabber.no");
- domains.add("cypherpunks.it");
- domains.add("adastra.re");
- domains.add("jabber-br.org");
- domains.add("einfachjabber.de");
- domains.add("jabber.smash-net.org");
- domains.add("freifunk.im");
- domains.add("openmailbox.org");
- domains.add("jabber.otr.im");
- domains.add("evil.im");
- domains.add("xmpp.slack.com");
- domains.add("chat.hipchat.com");
- domains.add("googlemail.com");
-
- hashSet.addAll(domains);
- domains.clear();
- domains.addAll(hashSet);
- Collections.sort(domains, String::compareToIgnoreCase);
- }).start();
+ domains = new ArrayList<>(mKnownHosts);
}
public KnownHostsAdapter(Context context, int viewResourceId) {
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
index 64d432041..17479cf99 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
@@ -27,7 +27,7 @@ import de.pixart.messenger.databinding.ContactBinding;
import de.pixart.messenger.entities.ListItem;
import de.pixart.messenger.ui.SettingsActivity;
import de.pixart.messenger.ui.XmppActivity;
-import de.pixart.messenger.ui.util.Color;
+import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.utils.IrregularUnicodeDetector;
import de.pixart.messenger.utils.UIHelper;
import rocks.xmpp.addr.Jid;
@@ -105,16 +105,16 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
}
}
if (offline) {
- viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main));
+ viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
viewHolder.name.setAlpha(INACTIVE_ALPHA);
viewHolder.jid.setAlpha(INACTIVE_ALPHA);
viewHolder.avatar.setAlpha(INACTIVE_ALPHA);
viewHolder.tags.setAlpha(INACTIVE_ALPHA);
} else {
if (ShowPresenceColoredNames()) {
- viewHolder.name.setTextColor(color != 0 ? color : Color.get(activity, R.attr.text_Color_Main));
+ viewHolder.name.setTextColor(color != 0 ? color : StyledAttributes.getColor(activity, R.attr.text_Color_Main));
} else {
- viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main));
+ viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
}
viewHolder.name.setAlpha(ACTIVE_ALPHA);
viewHolder.jid.setAlpha(ACTIVE_ALPHA);
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java
new file mode 100644
index 000000000..0472c6d11
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java
@@ -0,0 +1,226 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.databinding.DataBindingUtil;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.support.annotation.AttrRes;
+import android.support.annotation.DimenRes;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.MediaBinding;
+import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.Attachment;
+import de.pixart.messenger.ui.util.StyledAttributes;
+import de.pixart.messenger.ui.util.ViewUtil;
+
+public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHolder> {
+
+ private static final List<String> DOCUMENT_MIMES = Arrays.asList(
+ "application/pdf",
+ "application/vnd.oasis.opendocument.text",
+ "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "text/x-tex",
+ "text/plain"
+ );
+
+ private final ArrayList<Attachment> attachments = new ArrayList<>();
+
+ private final XmppActivity activity;
+
+ private int mediaSize = 0;
+
+ public MediaAdapter(XmppActivity activity, @DimenRes int mediaSize) {
+ this.activity = activity;
+ this.mediaSize = Math.round(activity.getResources().getDimension(mediaSize));
+ }
+
+ public static void setMediaSize(RecyclerView recyclerView, int mediaSize) {
+ RecyclerView.Adapter adapter = recyclerView.getAdapter();
+ if (adapter instanceof MediaAdapter) {
+ ((MediaAdapter) adapter).setMediaSize(mediaSize);
+ }
+ }
+
+ private static @AttrRes
+ int getImageAttr(Attachment attachment) {
+ final @AttrRes int attr;
+ if (attachment.getType() == Attachment.Type.LOCATION) {
+ attr = R.attr.media_preview_location;
+ } else if (attachment.getType() == Attachment.Type.RECORDING) {
+ attr = R.attr.media_preview_recording;
+ } else {
+ final String mime = attachment.getMime();
+ if (mime == null) {
+ attr = R.attr.media_preview_unknown;
+ } else if (mime.startsWith("audio/")) {
+ attr = R.attr.media_preview_audio;
+ } else if (mime.equals("text/calendar") || (mime.equals("text/x-vcalendar"))) {
+ attr = R.attr.media_preview_calendar;
+ } else if (mime.equals("text/x-vcard")) {
+ attr = R.attr.media_preview_contact;
+ } else if (mime.equals("application/vnd.android.package-archive")) {
+ attr = R.attr.media_preview_app;
+ } else if (mime.equals("application/zip") || mime.equals("application/rar")) {
+ attr = R.attr.media_preview_archive;
+ } else if (DOCUMENT_MIMES.contains(mime)) {
+ attr = R.attr.media_preview_document;
+ } else {
+ attr = R.attr.media_preview_unknown;
+ }
+ }
+ return attr;
+ }
+
+ public static void renderPreview(Context context, Attachment attachment, ImageView imageView) {
+ imageView.setBackgroundColor(StyledAttributes.getColor(context, R.attr.color_background_tertiary));
+ imageView.setImageAlpha(Math.round(StyledAttributes.getFloat(context, R.attr.icon_alpha) * 255));
+ imageView.setImageDrawable(StyledAttributes.getDrawable(context, getImageAttr(attachment)));
+ }
+
+ private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Attachment oldAttachment = bitmapWorkerTask.attachment;
+ if (oldAttachment == null || !oldAttachment.equals(attachment)) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ MediaBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media, parent, false);
+ return new MediaViewHolder(binding);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) {
+ final Attachment attachment = attachments.get(position);
+ if (attachment.renderThumbnail()) {
+ holder.binding.media.setImageAlpha(255);
+ loadPreview(attachment, holder.binding.media);
+ } else {
+ cancelPotentialWork(attachment, holder.binding.media);
+ renderPreview(activity, attachment, holder.binding.media);
+ }
+ holder.binding.media.setOnClickListener(v -> ViewUtil.view(activity, attachment));
+ }
+
+ public void setAttachments(List<Attachment> attachments) {
+ this.attachments.clear();
+ this.attachments.addAll(attachments);
+ notifyDataSetChanged();
+ }
+
+ private void setMediaSize(int mediaSize) {
+ this.mediaSize = mediaSize;
+ }
+
+ private void loadPreview(Attachment attachment, ImageView imageView) {
+ if (cancelPotentialWork(attachment, imageView)) {
+ final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment, mediaSize, true);
+ if (bm != null) {
+ cancelPotentialWork(attachment, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(0xff333333);
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(attachment);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return attachments.size();
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+ class MediaViewHolder extends RecyclerView.ViewHolder {
+
+ private final MediaBinding binding;
+
+ MediaViewHolder(MediaBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+
+ class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Attachment attachment = null;
+
+ BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Attachment... params) {
+ this.attachment = params[0];
+ return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, mediaSize, false);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java
new file mode 100644
index 000000000..ae4c42816
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java
@@ -0,0 +1,188 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.databinding.DataBindingUtil;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.MediaPreviewBinding;
+import de.pixart.messenger.ui.ConversationFragment;
+import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.Attachment;
+
+public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapter.MediaPreviewViewHolder> {
+
+ private final ArrayList<Attachment> mediaPreviews = new ArrayList<>();
+
+ private final ConversationFragment conversationFragment;
+
+ public MediaPreviewAdapter(ConversationFragment fragment) {
+ this.conversationFragment = fragment;
+ }
+
+ private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Attachment oldAttachment = bitmapWorkerTask.attachment;
+ if (oldAttachment == null || !oldAttachment.equals(attachment)) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ @NonNull
+ @Override
+ public MediaPreviewViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
+ MediaPreviewBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media_preview, parent, false);
+ return new MediaPreviewViewHolder(binding);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MediaPreviewViewHolder holder, int position) {
+ final Context context = conversationFragment.getActivity();
+ final Attachment attachment = mediaPreviews.get(position);
+ if (attachment.renderThumbnail()) {
+ holder.binding.mediaPreview.setImageAlpha(255);
+ loadPreview(attachment, holder.binding.mediaPreview);
+ } else {
+ cancelPotentialWork(attachment, holder.binding.mediaPreview);
+ MediaAdapter.renderPreview(context, attachment, holder.binding.mediaPreview);
+ }
+ holder.binding.deleteButton.setOnClickListener(v -> {
+ int pos = mediaPreviews.indexOf(attachment);
+ mediaPreviews.remove(pos);
+ notifyItemRemoved(pos);
+ conversationFragment.toggleInputMethod();
+ });
+ }
+
+ public void addMediaPreviews(List<Attachment> attachments) {
+ this.mediaPreviews.addAll(attachments);
+ notifyDataSetChanged();
+ }
+
+ private void loadPreview(Attachment attachment, ImageView imageView) {
+ if (cancelPotentialWork(attachment, imageView)) {
+ XmppActivity activity = (XmppActivity) conversationFragment.getActivity();
+ final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment, Math.round(activity.getResources().getDimension(R.dimen.media_preview_size)), true);
+ if (bm != null) {
+ cancelPotentialWork(attachment, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(0xff333333);
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(conversationFragment.getActivity().getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(attachment);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return mediaPreviews.size();
+ }
+
+ public boolean hasAttachments() {
+ return mediaPreviews.size() > 0;
+ }
+
+ public ArrayList<Attachment> getAttachments() {
+ return mediaPreviews;
+ }
+
+ public void clearPreviews() {
+ this.mediaPreviews.clear();
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+ class MediaPreviewViewHolder extends RecyclerView.ViewHolder {
+
+ private final MediaPreviewBinding binding;
+
+ MediaPreviewViewHolder(MediaPreviewBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+
+ class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Attachment attachment = null;
+
+ BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Attachment... params) {
+ Activity activity = conversationFragment.getActivity();
+ if (activity instanceof XmppActivity) {
+ final XmppActivity xmppActivity = (XmppActivity) activity;
+ this.attachment = params[0];
+ return xmppActivity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, Math.round(xmppActivity.getResources().getDimension(R.dimen.media_preview_size)), false);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
index 519b01c1a..b13ccb910 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
@@ -2,11 +2,9 @@ package de.pixart.messenger.ui.adapter;
import android.Manifest;
import android.app.Activity;
-import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
@@ -29,7 +27,6 @@ import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.util.Base64;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
@@ -72,11 +69,11 @@ import de.pixart.messenger.services.MessageArchiveService;
import de.pixart.messenger.services.NotificationService;
import de.pixart.messenger.ui.ConversationFragment;
import de.pixart.messenger.ui.ConversationsActivity;
-import de.pixart.messenger.ui.ShowFullscreenMessageActivity;
import de.pixart.messenger.ui.XmppActivity;
import de.pixart.messenger.ui.text.DividerSpan;
import de.pixart.messenger.ui.text.QuoteSpan;
import de.pixart.messenger.ui.util.MyLinkify;
+import de.pixart.messenger.ui.util.ViewUtil;
import de.pixart.messenger.ui.widget.ClickableMovementMethod;
import de.pixart.messenger.ui.widget.CopyTextView;
import de.pixart.messenger.ui.widget.ListSelectionManager;
@@ -112,8 +109,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
public MessageAdapter(XmppActivity activity, List<Message> messages) {
super(activity, 0, messages);
- this.audioPlayer = new AudioPlayer(this);
this.activity = activity;
+ this.audioPlayer = new AudioPlayer(this);
metrics = getContext().getResources().getDisplayMetrics();
updatePreferences();
}
@@ -149,6 +146,14 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
}
+ public void flagDisableInputs() {
+ activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ }
+
+ public void flagEnableInputs() {
+ activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ }
+
public void flagScreenOn() {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@@ -490,7 +495,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} else {
viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1);
}
- viewHolder.messageBody.setHighlightColor(darkBackground ? type == SENT ? ContextCompat.getColor(activity, R.color.black26) : ContextCompat.getColor(activity, R.color.grey800) : ContextCompat.getColor(activity, R.color.grey500));
+ viewHolder.messageBody.setHighlightColor(darkBackground ? type == SENT ? ContextCompat.getColor(activity, R.color.accent) : ContextCompat.getColor(activity, R.color.accent) : ContextCompat.getColor(activity, R.color.accent));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
if (message.getBody() != null) {
final String nick = UIHelper.getMessageDisplayName(message);
@@ -870,7 +875,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
.setOnLongClickListener(v -> {
if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
MessageAdapter.this.mOnContactPictureLongClickedListener
- .onContactPictureLongClicked(message);
+ .onContactPictureLongClicked(v, message);
return true;
} else {
return false;
@@ -1019,6 +1024,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
audioPlayer.stop();
}
+ public void unregisterListenerInAudioPlayer() {
+ audioPlayer.unregisterListener();
+ }
+
public void startStopPending() {
audioPlayer.startStopPending();
}
@@ -1035,52 +1044,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
return;
}
String mime = file.getMimeType();
- if (mime.startsWith("image/")) {
- Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class);
- intent.putExtra("image", Uri.fromFile(file));
- try {
- activity.startActivity(intent);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- return;
- } catch (ActivityNotFoundException e) {
- //ignored
- }
- } else if (mime.startsWith("video/")) {
- Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class);
- intent.putExtra("video", Uri.fromFile(file));
- try {
- activity.startActivity(intent);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- return;
- } catch (ActivityNotFoundException e) {
- //ignored
- }
- }
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
if (mime == null) {
mime = "*/*";
}
- Uri uri;
- try {
- uri = FileBackend.getUriForFile(activity, file);
- } catch (SecurityException e) {
- Log.d(Config.LOGTAG, "No permission to access " + file.getAbsolutePath(), e);
- Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
- return;
- }
- openIntent.setDataAndType(uri, mime);
- openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- PackageManager manager = activity.getPackageManager();
- List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0);
- if (info.size() == 0) {
- openIntent.setDataAndType(uri,"*/*");
- }
- try {
- getContext().startActivity(openIntent);
- activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
- }
+ ViewUtil.view(activity, file, mime);
}
public void showLocation(Message message) {
@@ -1139,7 +1106,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
public interface OnContactPictureLongClicked {
- void onContactPictureLongClicked(Message message);
+ void onContactPictureLongClicked(View v, Message message);
}
private static class ViewHolder {
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java
index adcc11809..0dfd86327 100644
--- a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java
@@ -1,7 +1,6 @@
package de.pixart.messenger.ui.forms;
import android.content.Context;
-import android.support.v4.content.ContextCompat;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
@@ -11,6 +10,7 @@ import android.view.View;
import java.util.List;
import de.pixart.messenger.R;
+import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.xmpp.forms.Field;
public abstract class FormFieldWrapper {
@@ -18,9 +18,9 @@ public abstract class FormFieldWrapper {
protected final Context context;
protected final Field field;
protected final View view;
- protected OnFormFieldValuesEdited onFormFieldValuesEditedListener;
+ OnFormFieldValuesEdited onFormFieldValuesEditedListener;
- protected FormFieldWrapper(Context context, Field field) {
+ FormFieldWrapper(Context context, Field field) {
this.context = context;
this.field = field;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -58,7 +58,7 @@ public abstract class FormFieldWrapper {
int start = label.length();
int end = label.length() + 2;
spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0);
- spannableString.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.accent)), start, end, 0);
+ spannableString.setSpan(new ForegroundColorSpan(StyledAttributes.getColor(context, R.color.accent)), start, end, 0);
}
return spannableString;
}
diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java
new file mode 100644
index 000000000..0441be96d
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java
@@ -0,0 +1,10 @@
+package de.pixart.messenger.ui.interfaces;
+
+import java.util.List;
+
+import de.pixart.messenger.ui.util.Attachment;
+
+public interface OnMediaLoaded {
+
+ void onMediaLoaded(List<Attachment> attachments);
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/Attachment.java b/src/main/java/de/pixart/messenger/ui/util/Attachment.java
new file mode 100644
index 000000000..96159c1bd
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/util/Attachment.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2018, Daniel Gultsch All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation and/or
+ * other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package de.pixart.messenger.ui.util;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.utils.MimeUtils;
+
+public class Attachment implements Parcelable {
+ public static final Creator<Attachment> CREATOR = new Parcelable.Creator<Attachment>() {
+ @Override
+ public Attachment createFromParcel(Parcel in) {
+ return new Attachment(in);
+ }
+
+ @Override
+ public Attachment[] newArray(int size) {
+ return new Attachment[size];
+ }
+ };
+ private final Uri uri;
+ private final Type type;
+ private final UUID uuid;
+ private final String mime;
+
+ Attachment(Parcel in) {
+ uri = in.readParcelable(Uri.class.getClassLoader());
+ mime = in.readString();
+ uuid = UUID.fromString(in.readString());
+ type = Type.valueOf(in.readString());
+ }
+
+ private Attachment(UUID uuid, Uri uri, Type type, String mime) {
+ this.uri = uri;
+ this.type = type;
+ this.mime = mime;
+ this.uuid = uuid;
+ }
+
+ private Attachment(Uri uri, Type type, String mime) {
+ this.uri = uri;
+ this.type = type;
+ this.mime = mime;
+ this.uuid = UUID.randomUUID();
+ }
+
+ public static List<Attachment> of(final Context context, Uri uri, Type type) {
+ final String mime = type == Type.LOCATION ? null : MimeUtils.guessMimeTypeFromUri(context, uri);
+ return Collections.singletonList(new Attachment(uri, type, mime));
+ }
+
+ public static List<Attachment> of(final Context context, List<Uri> uris) {
+ List<Attachment> attachments = new ArrayList<>();
+ for (Uri uri : uris) {
+ final String mime = MimeUtils.guessMimeTypeFromUri(context, uri);
+ attachments.add(new Attachment(uri, mime != null && mime.startsWith("image/") ? Type.IMAGE : Type.FILE, mime));
+ }
+ return attachments;
+ }
+
+ public static Attachment of(UUID uuid, final File file, String mime) {
+ return new Attachment(uuid, Uri.fromFile(file), mime != null && (mime.startsWith("image/") || mime.startsWith("video/")) ? Type.IMAGE : Type.FILE, mime);
+ }
+
+ public static List<Attachment> extractAttachments(final Context context, final Intent intent, Type type) {
+ List<Attachment> uris = new ArrayList<>();
+ if (intent == null) {
+ return uris;
+ }
+ final String contentType = intent.getType();
+ final Uri data = intent.getData();
+ if (data == null) {
+ final ClipData clipData = intent.getClipData();
+ if (clipData != null) {
+ for (int i = 0; i < clipData.getItemCount(); ++i) {
+ final Uri uri = clipData.getItemAt(i).getUri();
+ Log.d(Config.LOGTAG, "uri=" + uri + " contentType=" + contentType);
+ final String mime = contentType != null ? contentType : MimeUtils.guessMimeTypeFromUri(context, uri);
+ Log.d(Config.LOGTAG, "mime=" + mime);
+ uris.add(new Attachment(uri, type, mime));
+ }
+ }
+ } else {
+ final String mime = contentType != null ? contentType : MimeUtils.guessMimeTypeFromUri(context, data);
+ uris.add(new Attachment(data, type, mime));
+ }
+ return uris;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(uri, flags);
+ dest.writeString(mime);
+ dest.writeString(uuid.toString());
+ dest.writeString(type.toString());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public String getMime() {
+ return mime;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public boolean renderThumbnail() {
+ return type == Type.IMAGE || (type == Type.FILE && mime != null && (mime.startsWith("video/") || mime.startsWith("image/")));
+ }
+
+ public Uri getUri() {
+ return uri;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public enum Type {
+ FILE, IMAGE, LOCATION, RECORDING
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java b/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java
deleted file mode 100644
index c56f27ebf..000000000
--- a/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2018, Daniel Gultsch All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its contributors
- * may be used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package de.pixart.messenger.ui.util;
-
-import android.annotation.SuppressLint;
-import android.content.ClipData;
-import android.content.Intent;
-import android.net.Uri;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AttachmentTool {
- @SuppressLint("NewApi")
- public static List<Uri> extractUriFromIntent(final Intent intent) {
- List<Uri> uris = new ArrayList<>();
- if (intent == null) {
- return uris;
- }
- final Uri uri = intent.getData();
- if (uri == null) {
- final ClipData clipData = intent.getClipData();
- if (clipData != null) {
- for (int i = 0; i < clipData.getItemCount(); ++i) {
- uris.add(clipData.getItemAt(i).getUri());
- }
- }
- } else {
- uris.add(uri);
- }
- return uris;
- }
-} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java
index 5c86b4a1b..edca88f78 100644
--- a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java
+++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java
@@ -65,12 +65,13 @@ public class ConversationMenuConfigurator {
menu.findItem(R.id.attach_location).setVisible(locationAvailable);
}
- public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu, Boolean Quick_share_attachment_choice) {
+ public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu, Boolean Quick_share_attachment_choice, boolean hasAttachments) {
final MenuItem menuAttach = menu.findItem(R.id.action_attach_file);
- if (Quick_share_attachment_choice) {
+ if (Quick_share_attachment_choice && !hasAttachments) {
menuAttach.setVisible(false);
return;
}
+
final boolean visible;
if (conversation.getMode() == Conversation.MODE_MULTI) {
visible = conversation.getAccount().httpUploadAvailable() && conversation.getMucOptions().participating();
diff --git a/src/main/java/de/pixart/messenger/ui/util/Drawable.java b/src/main/java/de/pixart/messenger/ui/util/Drawable.java
deleted file mode 100644
index 5e0c770d4..000000000
--- a/src/main/java/de/pixart/messenger/ui/util/Drawable.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2018, Daniel Gultsch All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its contributors
- * may be used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package de.pixart.messenger.ui.util;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.support.annotation.AttrRes;
-
-public class Drawable {
- public static android.graphics.drawable.Drawable get(Context context, @AttrRes int id) {
- TypedArray typedArray = context.obtainStyledAttributes(new int[]{id});
- android.graphics.drawable.Drawable drawable = typedArray.getDrawable(0);
- typedArray.recycle();
- return drawable;
- }
-} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/GridManager.java b/src/main/java/de/pixart/messenger/ui/util/GridManager.java
new file mode 100644
index 000000000..ae615b051
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/util/GridManager.java
@@ -0,0 +1,76 @@
+package de.pixart.messenger.ui.util;
+
+import android.content.Context;
+import android.support.annotation.DimenRes;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.ViewTreeObserver;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.ui.adapter.MediaAdapter;
+
+public class GridManager {
+
+ public static void setupLayoutManager(final Context context, RecyclerView recyclerView, @DimenRes int desiredSize) {
+ int maxWidth = context.getResources().getDisplayMetrics().widthPixels;
+ ColumnInfo columnInfo = calculateColumnCount(context, maxWidth, desiredSize);
+ Log.d(Config.LOGTAG, "preliminary count=" + columnInfo.count);
+ MediaAdapter.setMediaSize(recyclerView, columnInfo.width);
+ recyclerView.setLayoutManager(new GridLayoutManager(context, columnInfo.count));
+ recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ final int availableWidth = recyclerView.getMeasuredWidth();
+ if (availableWidth == 0) {
+ Log.e(Config.LOGTAG, "GridManager: available width was 0; probably because layout was hidden");
+ return;
+ }
+ final ColumnInfo columnInfo = calculateColumnCount(context, recyclerView.getMeasuredWidth(), desiredSize);
+ Log.d(Config.LOGTAG, "final count " + columnInfo.count);
+ if (recyclerView.getAdapter().getItemCount() != 0) {
+ Log.e(Config.LOGTAG, "adapter already has items; just go with it now");
+ return;
+ }
+ setupLayoutManagerInternal(recyclerView, columnInfo);
+ MediaAdapter.setMediaSize(recyclerView, columnInfo.width);
+ }
+ });
+ }
+
+ private static void setupLayoutManagerInternal(RecyclerView recyclerView, final ColumnInfo columnInfo) {
+ RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof GridLayoutManager) {
+ ((GridLayoutManager) layoutManager).setSpanCount(columnInfo.count);
+ }
+ }
+
+ private static ColumnInfo calculateColumnCount(Context context, int availableWidth, @DimenRes int desiredSize) {
+ final float desiredWidth = context.getResources().getDimension(desiredSize);
+ final int columns = Math.round(availableWidth / desiredWidth);
+ final int realWidth = availableWidth / columns;
+ Log.d(Config.LOGTAG, "desired=" + desiredWidth + " real=" + realWidth);
+ return new ColumnInfo(columns, realWidth);
+ }
+
+ public static int getCurrentColumnCount(RecyclerView recyclerView) {
+ RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof GridLayoutManager) {
+ return ((GridLayoutManager) layoutManager).getSpanCount();
+ } else {
+ return 0;
+ }
+ }
+
+ public static class ColumnInfo {
+ private final int count;
+ private final int width;
+
+ private ColumnInfo(int count, int width) {
+ this.count = count;
+ this.width = width;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java
new file mode 100644
index 000000000..dc338e02c
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java
@@ -0,0 +1,163 @@
+package de.pixart.messenger.ui.util;
+
+import android.app.Activity;
+import android.preference.PreferenceManager;
+import android.support.v7.app.AlertDialog;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.TypefaceSpan;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.entities.MucOptions.User;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.ConferenceDetailsActivity;
+import de.pixart.messenger.ui.ConversationFragment;
+import de.pixart.messenger.ui.ConversationsActivity;
+import de.pixart.messenger.ui.XmppActivity;
+import rocks.xmpp.addr.Jid;
+
+public final class MucDetailsContextMenuHelper {
+ public static void configureMucDetailsContextMenu(Activity activity, Menu menu, Conversation conversation, User user) {
+ final boolean advancedMode = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("advanced_muc_mode", false);
+ if (user != null && user.getRealJid() != null) {
+ MenuItem showContactDetails = menu.findItem(R.id.action_contact_details);
+ MenuItem startConversation = menu.findItem(R.id.start_conversation);
+ MenuItem giveMembership = menu.findItem(R.id.give_membership);
+ MenuItem removeMembership = menu.findItem(R.id.remove_membership);
+ MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges);
+ MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges);
+ MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room);
+ MenuItem banFromConference = menu.findItem(R.id.ban_from_conference);
+ MenuItem invite = menu.findItem(R.id.invite);
+ startConversation.setVisible(true);
+ final Contact contact = user.getContact();
+ final User self = conversation.getMucOptions().getSelf();
+ if (contact != null && contact.showInRoster()) {
+ showContactDetails.setVisible(!contact.isSelf());
+ }
+ if (activity instanceof ConferenceDetailsActivity && user.getRole() == MucOptions.Role.NONE) {
+ invite.setVisible(true);
+ }
+ if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) &&
+ self.getAffiliation().outranks(user.getAffiliation())) {
+ if (advancedMode) {
+ if (user.getAffiliation() == MucOptions.Affiliation.NONE) {
+ giveMembership.setVisible(true);
+ } else {
+ removeMembership.setVisible(true);
+ }
+ if (!Config.DISABLE_BAN) {
+ banFromConference.setVisible(true);
+ }
+ } else {
+ if (!Config.DISABLE_BAN || conversation.getMucOptions().membersOnly()) {
+ removeFromRoom.setVisible(true);
+ }
+ }
+ if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) {
+ giveAdminPrivileges.setVisible(true);
+ } else {
+ removeAdminPrivileges.setVisible(true);
+ }
+ }
+ } else {
+ final MucOptions mucOptions = conversation.getMucOptions();
+ MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message);
+ sendPrivateMessage.setVisible(true);
+ sendPrivateMessage.setEnabled(user != null && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR));
+ }
+ }
+
+ public static boolean onContextItemSelected(MenuItem item, User user, Conversation conversation, XmppActivity activity) {
+ final XmppConnectionService.OnAffiliationChanged onAffiliationChanged = activity instanceof XmppConnectionService.OnAffiliationChanged ? (XmppConnectionService.OnAffiliationChanged) activity : null;
+ final XmppConnectionService.OnRoleChanged onRoleChanged = activity instanceof XmppConnectionService.OnRoleChanged ? (XmppConnectionService.OnRoleChanged) activity : null;
+ Jid jid = user.getRealJid();
+ switch (item.getItemId()) {
+ case R.id.action_contact_details:
+ Contact contact = user.getContact();
+ if (contact != null) {
+ activity.switchToContactDetails(contact);
+ }
+ return true;
+ case R.id.start_conversation:
+ startConversation(user, conversation, activity);
+ return true;
+ case R.id.give_admin_privileges:
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.ADMIN, onAffiliationChanged);
+ return true;
+ case R.id.give_membership:
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
+ return true;
+ case R.id.remove_membership:
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.NONE, onAffiliationChanged);
+ return true;
+ case R.id.remove_admin_privileges:
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
+ return true;
+ case R.id.remove_from_room:
+ removeFromRoom(user, conversation, activity, onAffiliationChanged, onRoleChanged);
+ return true;
+ case R.id.ban_from_conference:
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.OUTCAST, onAffiliationChanged);
+ if (user.getRole() != MucOptions.Role.NONE) {
+ activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE, onRoleChanged);
+ }
+ return true;
+ case R.id.send_private_message:
+ if (activity instanceof ConversationsActivity) {
+ ConversationFragment conversationFragment = ConversationFragment.get(activity);
+ if (conversationFragment != null) {
+ conversationFragment.privateMessageWith(user.getFullJid());
+ return true;
+ }
+ }
+ activity.privateMsgInMuc(conversation, user.getName());
+ return true;
+ case R.id.invite:
+ activity.xmppConnectionService.directInvite(conversation, jid);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static void removeFromRoom(final User user, Conversation conversation, XmppActivity activity, XmppConnectionService.OnAffiliationChanged onAffiliationChanged, XmppConnectionService.OnRoleChanged onRoleChanged) {
+ if (conversation.getMucOptions().membersOnly()) {
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.NONE, onAffiliationChanged);
+ if (user.getRole() != MucOptions.Role.NONE) {
+ activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE, onRoleChanged);
+ }
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setTitle(R.string.ban_from_conference);
+ String jid = user.getRealJid().asBareJid().toString();
+ SpannableString message = new SpannableString(activity.getString(R.string.removing_from_public_conference, jid));
+ int start = message.toString().indexOf(jid);
+ if (start >= 0) {
+ message.setSpan(new TypefaceSpan("monospace"), start, start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ builder.setMessage(message);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ban_now, (dialog, which) -> {
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, onAffiliationChanged);
+ if (user.getRole() != MucOptions.Role.NONE) {
+ activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE, onRoleChanged);
+ }
+ });
+ builder.create().show();
+ }
+ }
+
+ public static void startConversation(User user, Conversation conversation, XmppActivity activity) {
+ if (user.getRealJid() != null) {
+ Conversation newConversation = activity.xmppConnectionService.findOrCreateConversation(conversation.getAccount(), user.getRealJid().asBareJid(), false, true);
+ activity.switchToConversation(newConversation);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/PendingItem.java b/src/main/java/de/pixart/messenger/ui/util/PendingItem.java
index eaddf3a8c..d06f5bfc9 100644
--- a/src/main/java/de/pixart/messenger/ui/util/PendingItem.java
+++ b/src/main/java/de/pixart/messenger/ui/util/PendingItem.java
@@ -46,4 +46,10 @@ public class PendingItem<T> {
public synchronized T peek() {
return item;
}
+
+ public synchronized boolean clear() {
+ boolean notNull = this.item != null;
+ this.item = null;
+ return notNull;
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java
index 9f9fabc5a..c7feb2105 100644
--- a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java
+++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java
@@ -33,10 +33,11 @@ import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_CHOOSE_IMAGE;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_LOCATION;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VOICE;
-import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_FROM_CAMERA;
+import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VIDEO;
+import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_PHOTO;
public enum SendButtonAction {
- TEXT, TAKE_FROM_CAMERA, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE, CHOOSE_ATTACHMENT;
+ TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE, RECORD_VIDEO, CHOOSE_ATTACHMENT;
public static SendButtonAction valueOfOrDefault(String setting, SendButtonAction text) {
try {
@@ -52,8 +53,10 @@ public enum SendButtonAction {
return SEND_LOCATION;
case ATTACHMENT_CHOICE_RECORD_VOICE:
return RECORD_VOICE;
- case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA:
- return TAKE_FROM_CAMERA;
+ case ATTACHMENT_CHOICE_RECORD_VIDEO:
+ return RECORD_VIDEO;
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
+ return TAKE_PHOTO;
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
return CHOOSE_PICTURE;
case ATTACHMENT_CHOICE:
@@ -65,8 +68,10 @@ public enum SendButtonAction {
public int toChoice() {
switch (this) {
- case TAKE_FROM_CAMERA:
- return ATTACHMENT_CHOICE_TAKE_FROM_CAMERA;
+ case TAKE_PHOTO:
+ return ATTACHMENT_CHOICE_TAKE_PHOTO;
+ case RECORD_VIDEO:
+ return ATTACHMENT_CHOICE_RECORD_VIDEO;
case SEND_LOCATION:
return ATTACHMENT_CHOICE_LOCATION;
case RECORD_VOICE:
diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java
index 291edb822..96bcad183 100644
--- a/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java
+++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java
@@ -38,14 +38,12 @@ import de.pixart.messenger.R;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Presence;
import de.pixart.messenger.ui.ConversationFragment;
+import de.pixart.messenger.ui.SettingsActivity;
import de.pixart.messenger.utils.UIHelper;
-import static de.pixart.messenger.Config.QUICK_SHARE_ATTACHMENT_CHOICE;
-
public class SendButtonTool {
public static SendButtonAction getAction(Activity activity, Conversation c, String text) {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
final boolean empty = text.length() == 0;
final boolean conference = c.getMode() == Conversation.MODE_MULTI;
if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) {
@@ -58,15 +56,16 @@ public class SendButtonTool {
}
} else {
if (empty) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
if (conference && c.getNextCounterpart() != null) {
return SendButtonAction.CANCEL;
} else {
- if (QUICK_SHARE_ATTACHMENT_CHOICE && AttachmentsVisible(c)) {
+ String setting = preferences.getString("quick_action", activity.getResources().getString(R.string.quick_action));
+ if (quickShareChoice(activity) && AttachmentsVisible(c)) {
return SendButtonAction.CHOOSE_ATTACHMENT;
- } else if (QUICK_SHARE_ATTACHMENT_CHOICE && !AttachmentsVisible(c)) {
+ } else if (quickShareChoice(activity) && !AttachmentsVisible(c)) {
return SendButtonAction.TEXT;
} else {
- String setting = preferences.getString("quick_action", activity.getResources().getString(R.string.quick_action));
if (!setting.equals("none") && UIHelper.receivedLocationQuestion(c.getLatestMessage())) {
return SendButtonAction.SEND_LOCATION;
} else {
@@ -106,7 +105,20 @@ public class SendButtonTool {
default:
return getThemeResource(activity, R.attr.ic_send_text_offline, R.drawable.ic_send_text_offline);
}
- case TAKE_FROM_CAMERA:
+ case RECORD_VIDEO:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_videocam_online;
+ case AWAY:
+ return R.drawable.ic_send_videocam_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_videocam_dnd;
+ default:
+ return getThemeResource(activity, R.attr.ic_send_videocam_offline, R.drawable.ic_send_videocam_offline);
+ }
+ case TAKE_PHOTO:
switch (status) {
case CHAT:
case ONLINE:
@@ -199,4 +211,8 @@ public class SendButtonTool {
return res;
}
+ public static boolean quickShareChoice(Activity activity) {
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ return preferences.getBoolean(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, activity.getResources().getBoolean(R.bool.quick_share_attachment_choice));
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/Color.java b/src/main/java/de/pixart/messenger/ui/util/StyledAttributes.java
index 58ce6a465..cd4ad3e49 100644
--- a/src/main/java/de/pixart/messenger/ui/util/Color.java
+++ b/src/main/java/de/pixart/messenger/ui/util/StyledAttributes.java
@@ -35,14 +35,26 @@ import android.content.res.TypedArray;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
-public class Color {
+public class StyledAttributes {
+ public static android.graphics.drawable.Drawable getDrawable(Context context, @AttrRes int id) {
+ TypedArray typedArray = context.obtainStyledAttributes(new int[]{id});
+ android.graphics.drawable.Drawable drawable = typedArray.getDrawable(0);
+ typedArray.recycle();
+ return drawable;
+ }
+
+ public static float getFloat(Context context, @AttrRes int id) {
+ TypedArray typedArray = context.obtainStyledAttributes(new int[]{id});
+ float value = typedArray.getFloat(0, 0f);
+ typedArray.recycle();
+ return value;
+ }
public static @ColorInt
- int get(Context context, @AttrRes int attr) {
+ int getColor(Context context, @AttrRes int attr) {
TypedArray typedArray = context.obtainStyledAttributes(new int[]{attr});
int color = typedArray.getColor(0, 0);
typedArray.recycle();
return color;
}
-
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java
new file mode 100644
index 000000000..b1905479b
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java
@@ -0,0 +1,72 @@
+package de.pixart.messenger.ui.util;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.File;
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.ui.ShowFullscreenMessageActivity;
+
+public class ViewUtil {
+
+ public static void view(Context context, Attachment attachment) {
+ File file = new File(attachment.getUri().getPath());
+ final String mime = attachment.getMime() == null ? "*/*" : attachment.getMime();
+ view(context, file, mime);
+ }
+
+ public static void view(Context context, File file, String mime) {
+ Uri uri;
+ try {
+ uri = FileBackend.getUriForFile(context, file);
+ } catch (SecurityException e) {
+ Log.d(Config.LOGTAG, "No permission to access " + file.getAbsolutePath(), e);
+ Toast.makeText(context, context.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
+ return;
+ }
+ // use internal viewer for images and videos
+ if (mime.startsWith("image/")) {
+ Intent intent = new Intent(context, ShowFullscreenMessageActivity.class);
+ intent.putExtra("image", Uri.fromFile(file));
+ try {
+ context.startActivity(intent);
+ return;
+ } catch (ActivityNotFoundException e) {
+ //ignored
+ }
+ } else if (mime.startsWith("video/")) {
+ Intent intent = new Intent(context, ShowFullscreenMessageActivity.class);
+ intent.putExtra("video", Uri.fromFile(file));
+ try {
+ context.startActivity(intent);
+ return;
+ } catch (ActivityNotFoundException e) {
+ //ignored
+ }
+ } else {
+ Intent openIntent = new Intent(Intent.ACTION_VIEW);
+ openIntent.setDataAndType(uri, mime);
+ openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ PackageManager manager = context.getPackageManager();
+ List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0);
+ if (info.size() == 0) {
+ openIntent.setDataAndType(uri, "*/*");
+ }
+ try {
+ context.startActivity(openIntent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java b/src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java
new file mode 100644
index 000000000..4d2fcf92d
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java
@@ -0,0 +1,25 @@
+package de.pixart.messenger.ui.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class SquareFrameLayout extends FrameLayout {
+ public SquareFrameLayout(Context context) {
+ super(context);
+ }
+
+ public SquareFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SquareFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ //noinspection SuspiciousNameCombination
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/Compatibility.java b/src/main/java/de/pixart/messenger/utils/Compatibility.java
new file mode 100644
index 000000000..3ebc7c4f6
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/utils/Compatibility.java
@@ -0,0 +1,76 @@
+package de.pixart.messenger.utils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.support.annotation.BoolRes;
+import android.support.v4.content.ContextCompat;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.ui.SettingsActivity;
+import de.pixart.messenger.ui.SettingsFragment;
+
+public class Compatibility {
+ private static final List<String> UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList(
+ SettingsActivity.SHOW_FOREGROUND_SERVICE,
+ "led",
+ "notification_ringtone",
+ "notification_headsup",
+ "vibrate_on_notification");
+ private static final List<String> UNUESD_SETTINGS_PRE_TWENTYSIX = Collections.singletonList("more_notification_settings");
+
+ public static boolean hasStoragePermission(Context context) {
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ public static boolean twentySix() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+ }
+
+ public static boolean twentyTwo() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1;
+ }
+
+ private static boolean getBooleanPreference(Context context, String name, @BoolRes int res) {
+ return getPreferences(context).getBoolean(name, context.getResources().getBoolean(res));
+ }
+
+ private static SharedPreferences getPreferences(final Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ public static boolean keepForegroundService(Context context) {
+ return twentySix() || getBooleanPreference(context, SettingsActivity.SHOW_FOREGROUND_SERVICE, R.bool.show_foreground_service);
+ }
+
+ public static void removeUnusedPreferences(SettingsFragment settingsFragment) {
+ List<PreferenceScreen> screens = Arrays.asList(
+ (PreferenceScreen) settingsFragment.findPreference("notifications"));
+ List<PreferenceCategory> categories = Arrays.asList(
+ (PreferenceCategory) settingsFragment.findPreference("general"));
+ for (String key : (twentySix() ? UNUSED_SETTINGS_POST_TWENTYSIX : UNUESD_SETTINGS_PRE_TWENTYSIX)) {
+ Preference preference = settingsFragment.findPreference(key);
+ if (preference != null) {
+ for (PreferenceScreen screen : screens) {
+ if (screen != null) {
+ screen.removePreference(preference);
+ }
+ }
+ for (PreferenceCategory category : categories) {
+ if (category != null) {
+ category.removePreference(preference);
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java
index 4e275df3f..73d5589e4 100644
--- a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java
+++ b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java
@@ -2,12 +2,15 @@ package de.pixart.messenger.utils;
import android.os.FileObserver;
+import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
+import de.pixart.messenger.Config;
+
/**
* Copyright (C) 2012 Bartek Przybylski
* Copyright (C) 2015 ownCloud Inc.
@@ -19,7 +22,7 @@ public abstract class ConversationsFileObserver {
private final String path;
private final List<SingleFileObserver> mObservers = new ArrayList<>();
- public ConversationsFileObserver(String path) {
+ protected ConversationsFileObserver(String path) {
this.path = path;
}
@@ -88,13 +91,17 @@ public abstract class ConversationsFileObserver {
private class SingleFileObserver extends FileObserver {
private final String path;
- public SingleFileObserver(String path, int mask) {
+ SingleFileObserver(String path, int mask) {
super(path, mask);
this.path = path;
}
@Override
public void onEvent(int event, String filename) {
+ if (filename == null) {
+ Log.d(Config.LOGTAG, "ignored file event with NULL filename (event=" + event + ")");
+ return;
+ }
ConversationsFileObserver.this.onEvent(event, path + '/' + filename);
}
diff --git a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java
index b54f8d074..3cd715a4c 100644
--- a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java
+++ b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java
@@ -50,7 +50,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.pixart.messenger.R;
-import de.pixart.messenger.ui.util.Color;
+import de.pixart.messenger.ui.util.StyledAttributes;
import rocks.xmpp.addr.Jid;
public class IrregularUnicodeDetector {
@@ -73,7 +73,7 @@ public class IrregularUnicodeDetector {
}
public static Spannable style(Context context, Jid jid) {
- return style(jid, Color.get(context, R.attr.color_warning));
+ return style(jid, StyledAttributes.getColor(context, R.attr.color_warning));
}
private static Spannable style(Jid jid, @ColorInt int color) {
diff --git a/src/main/java/de/pixart/messenger/utils/Namespace.java b/src/main/java/de/pixart/messenger/utils/Namespace.java
index d76e4fede..146a9784d 100644
--- a/src/main/java/de/pixart/messenger/utils/Namespace.java
+++ b/src/main/java/de/pixart/messenger/utils/Namespace.java
@@ -20,4 +20,6 @@ public final class Namespace {
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
public static final String P1_S3_FILE_TRANSFER = "p1:s3filetransfer";
+ public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0";
+ public static final String BOOKMARKS = "storage:bookmarks";
}
diff --git a/src/main/java/de/pixart/messenger/utils/Resolver.java b/src/main/java/de/pixart/messenger/utils/Resolver.java
index 8f8138cb3..4bae6fae7 100644
--- a/src/main/java/de/pixart/messenger/utils/Resolver.java
+++ b/src/main/java/de/pixart/messenger/utils/Resolver.java
@@ -6,14 +6,15 @@ import android.support.annotation.NonNull;
import android.util.Log;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
+import de.measite.minidns.AbstractDNSClient;
import de.measite.minidns.DNSClient;
import de.measite.minidns.DNSName;
import de.measite.minidns.Question;
@@ -23,13 +24,13 @@ import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
import de.measite.minidns.hla.DnssecResolverApi;
import de.measite.minidns.hla.ResolverApi;
import de.measite.minidns.hla.ResolverResult;
+import de.measite.minidns.iterative.ReliableDNSClient;
import de.measite.minidns.record.A;
import de.measite.minidns.record.AAAA;
import de.measite.minidns.record.CNAME;
import de.measite.minidns.record.Data;
import de.measite.minidns.record.InternetAddressRR;
import de.measite.minidns.record.SRV;
-import de.measite.minidns.util.MultipleIoException;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.services.XmppConnectionService;
@@ -39,8 +40,6 @@ public class Resolver {
private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
private static final String STARTTLS_SERICE = "_xmpp-client";
- private static final String NETWORK_IS_UNREACHABLE = "Network is unreachable";
-
private static XmppConnectionService SERVICE = null;
@@ -49,74 +48,122 @@ public class Resolver {
DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
+ final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
+ if (client instanceof ReliableDNSClient) {
+ disableHardcodedDnsServers((ReliableDNSClient) client);
+ }
}
- public static List<Result> resolve(String domain) throws NetworkIsUnreachableException {
- List<Result> results = new ArrayList<>();
- HashSet<String> messages = new HashSet<>();
- try {
- results.addAll(resolveSrv(domain, true));
- } catch (MultipleIoException e) {
- messages.addAll(extractMessages(e));
- } catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
- }
+ private static void disableHardcodedDnsServers(ReliableDNSClient reliableDNSClient) {
try {
- results.addAll(resolveSrv(domain, false));
- } catch (MultipleIoException e) {
- messages.addAll(extractMessages(e));
- } catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
- }
- if (results.size() == 0) {
- if (messages.size() == 1 && messages.contains(NETWORK_IS_UNREACHABLE)) {
- throw new NetworkIsUnreachableException();
- }
- results.addAll(resolveNoSrvRecords(DNSName.from(domain), true));
+ final Field dnsClientField = ReliableDNSClient.class.getDeclaredField("dnsClient");
+ dnsClientField.setAccessible(true);
+ final DNSClient dnsClient = (DNSClient) dnsClientField.get(reliableDNSClient);
+ dnsClient.getDataSource().setTimeout(3000);
+ final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers");
+ useHardcodedDnsServers.setAccessible(true);
+ useHardcodedDnsServers.setBoolean(dnsClient, false);
+ } catch (NoSuchFieldException e) {
+ Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e);
+ } catch (IllegalAccessException e) {
+ Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e);
}
- Collections.sort(results);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString());
- return results;
}
- private static HashSet<String> extractMessages(MultipleIoException e) {
- HashSet<String> messages = new HashSet<>();
- for (Exception inner : e.getExceptions()) {
- if (inner instanceof MultipleIoException) {
- messages.addAll(extractMessages((MultipleIoException) inner));
+ public static List<Result> resolve(String domain) {
+ final List<Result> results = new ArrayList<>();
+ final List<Result> fallbackResults = new ArrayList<>();
+ Thread[] threads = new Thread[3];
+ threads[0] = new Thread(() -> {
+ try {
+ final List<Result> list = resolveSrv(domain, true);
+ synchronized (results) {
+ results.addAll(list);
+ }
+ } catch (Throwable throwable) {
+ Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
+ }
+ });
+ threads[1] = new Thread(() -> {
+ try {
+ final List<Result> list = resolveSrv(domain, false);
+ synchronized (results) {
+ results.addAll(list);
+ }
+ } catch (Throwable throwable) {
+ Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
+ }
+ });
+ threads[2] = new Thread(() -> {
+ List<Result> list = resolveNoSrvRecords(DNSName.from(domain), true);
+ synchronized (fallbackResults) {
+ fallbackResults.addAll(list);
+ }
+ });
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ try {
+ threads[0].join();
+ threads[1].join();
+ if (results.size() > 0) {
+ threads[2].interrupt();
+ Collections.sort(results);
+ Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString());
+ return results;
} else {
- messages.add(inner.getMessage());
+ threads[2].join();
+ Collections.sort(fallbackResults);
+ Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults.toString());
+ return fallbackResults;
}
+ } catch (InterruptedException e) {
+ return results;
}
- return messages;
}
private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
- if (Thread.currentThread().isInterrupted()) {
- return Collections.emptyList();
- }
DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERICE) + "._tcp." + domain);
ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class);
- List<Result> results = new ArrayList<>();
+ final List<Result> results = new ArrayList<>();
+ final List<Thread> threads = new ArrayList<>();
for (SRV record : result.getAnswersOrEmptySet()) {
if (record.name.length() == 0 && record.priority == 0) {
continue;
}
- final boolean addedIPv4 = results.addAll(resolveIp(record, A.class, result.isAuthenticData(), directTls));
- results.addAll(resolveIp(record, AAAA.class, result.isAuthenticData(), directTls));
- if (!addedIPv4 && !Thread.currentThread().isInterrupted()) {
- Result resolverResult = Result.fromRecord(record, directTls);
- resolverResult.authenticated = resolverResult.isAuthenticated();
- results.add(resolverResult);
+ threads.add(new Thread(() -> {
+ final List<Result> ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls);
+ if (ipv4s.size() == 0) {
+ Result resolverResult = Result.fromRecord(record, directTls);
+ resolverResult.authenticated = resolverResult.isAuthenticated();
+ ipv4s.add(resolverResult);
+ }
+ synchronized (results) {
+ results.addAll(ipv4s);
+ }
+
+ }));
+ threads.add(new Thread(() -> {
+ final List<Result> ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls);
+ synchronized (results) {
+ results.addAll(ipv6s);
+ }
+ }));
+ }
+ for (Thread thread : threads) {
+ thread.start();
+ }
+ for (Thread thread : threads) {
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ return Collections.emptyList();
}
}
return results;
}
private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
- if (Thread.currentThread().isInterrupted()) {
- return Collections.emptyList();
- }
List<Result> list = new ArrayList<>();
try {
ResolverResult<D> results = resolveWithFallback(srv.name, type, authenticated);
@@ -179,6 +226,56 @@ public class Resolver {
}
public static class Result implements Comparable<Result> {
+ public static final String DOMAIN = "domain";
+ public static final String IP = "ip";
+ public static final String HOSTNAME = "hostname";
+ public static final String PORT = "port";
+ public static final String PRIORITY = "priority";
+ public static final String DIRECT_TLS = "directTls";
+ public static final String AUTHENTICATED = "authenticated";
+ private InetAddress ip;
+ private DNSName hostname;
+ private int port = 5222;
+ private boolean directTls = false;
+ private boolean authenticated = false;
+ private int priority;
+
+ static Result fromRecord(SRV srv, boolean directTls) {
+ Result result = new Result();
+ result.port = srv.port;
+ result.hostname = srv.name;
+ result.directTls = directTls;
+ result.priority = srv.priority;
+ return result;
+ }
+
+ static Result createDefault(DNSName hostname, InetAddress ip) {
+ Result result = new Result();
+ result.port = 5222;
+ result.hostname = hostname;
+ result.ip = ip;
+ return result;
+ }
+
+ static Result createDefault(DNSName hostname) {
+ return createDefault(hostname, null);
+ }
+
+ public static Result fromCursor(Cursor cursor) {
+ final Result result = new Result();
+ try {
+ result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
+ } catch (UnknownHostException e) {
+ result.ip = null;
+ }
+ result.hostname = DNSName.from(cursor.getString(cursor.getColumnIndex(HOSTNAME)));
+ result.port = cursor.getInt(cursor.getColumnIndex(PORT));
+ result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
+ result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
+ result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
+ return result;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -205,22 +302,6 @@ public class Resolver {
return result;
}
- public static final String DOMAIN = "domain";
-
- public static final String IP = "ip";
- public static final String HOSTNAME = "hostname";
- public static final String PORT = "port";
- public static final String PRIORITY = "priority";
- public static final String DIRECT_TLS = "directTls";
- public static final String AUTHENTICATED = "authenticated";
-
- private InetAddress ip;
- private DNSName hostname;
- private int port = 5222;
- private boolean directTls = false;
- private boolean authenticated = false;
- private int priority;
-
public InetAddress getIp() {
return ip;
}
@@ -276,42 +357,6 @@ public class Resolver {
}
}
- public static Result fromRecord(SRV srv, boolean directTls) {
- Result result = new Result();
- result.port = srv.port;
- result.hostname = srv.name;
- result.directTls = directTls;
- result.priority = srv.priority;
- return result;
- }
-
- public static Result createDefault(DNSName hostname, InetAddress ip) {
- Result result = new Result();
- result.port = 5222;
- result.hostname = hostname;
- result.ip = ip;
- return result;
- }
-
- public static Result createDefault(DNSName hostname) {
- return createDefault(hostname, null);
- }
-
- public static Result fromCursor(Cursor cursor) {
- final Result result = new Result();
- try {
- result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
- } catch (UnknownHostException e) {
- result.ip = null;
- }
- result.hostname = DNSName.from(cursor.getString(cursor.getColumnIndex(HOSTNAME)));
- result.port = cursor.getInt(cursor.getColumnIndex(PORT));
- result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
- result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
- result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
- return result;
- }
-
public ContentValues toContentValues() {
final ContentValues contentValues = new ContentValues();
contentValues.put(IP, ip == null ? null : ip.getAddress());
@@ -324,7 +369,4 @@ public class Resolver {
}
}
- public static class NetworkIsUnreachableException extends Exception {
-
- }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java
index f5730e981..b603f68ee 100644
--- a/src/main/java/de/pixart/messenger/utils/UIHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java
@@ -222,6 +222,9 @@ public class UIHelper {
}
public static int getColorForName(String name, boolean safe) {
+ if (Config.XEP_0392) {
+ return XEP0392Helper.rgbFromNick(name);
+ }
if (name == null || name.isEmpty()) {
return 0xFF202020;
}
diff --git a/src/main/java/de/pixart/messenger/utils/XEP0392Helper.java b/src/main/java/de/pixart/messenger/utils/XEP0392Helper.java
new file mode 100644
index 000000000..bbfe50898
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/utils/XEP0392Helper.java
@@ -0,0 +1,30 @@
+package de.pixart.messenger.utils;
+
+import android.graphics.Color;
+
+import org.hsluv.HUSLColorConverter;
+
+import java.security.MessageDigest;
+
+public class XEP0392Helper {
+
+ private static double angle(String nickname) {
+ try {
+ MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+ byte[] digest = sha1.digest(nickname.getBytes("UTF-8"));
+ int angle = ((int) (digest[0]) & 0xff) + ((int) (digest[1]) & 0xff) * 256;
+ return angle / 65536.;
+ } catch (Exception e) {
+ return 0.0;
+ }
+ }
+
+ public static int rgbFromNick(String name) {
+ double[] hsluv = new double[3];
+ hsluv[0] = angle(name) * 360;
+ hsluv[1] = 100;
+ hsluv[2] = 50;
+ double[] rgb = HUSLColorConverter.hsluvToRgb(hsluv);
+ return Color.rgb((int) Math.round(rgb[0] * 255), (int) Math.round(rgb[1] * 255), (int) Math.round(rgb[2] * 255));
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/xmpp/InvalidJid.java b/src/main/java/de/pixart/messenger/xmpp/InvalidJid.java
index ce381abdc..fdaadeab2 100644
--- a/src/main/java/de/pixart/messenger/xmpp/InvalidJid.java
+++ b/src/main/java/de/pixart/messenger/xmpp/InvalidJid.java
@@ -69,6 +69,11 @@ public class InvalidJid implements Jid {
}
@Override
+ public boolean isDomainJid() {
+ throw new AssertionError("Not implemented");
+ }
+
+ @Override
public Jid asBareJid() {
throw new AssertionError("Not implemented");
}
diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
index a6fe3756a..cbfdbc365 100644
--- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
+++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
@@ -183,13 +183,12 @@ public class XmppConnection implements Runnable {
private SaslMechanism saslMechanism;
private URL redirectionUrl = null;
private String verifiedHostname = null;
- private Thread mThread;
+ private volatile Thread mThread;
private CountDownLatch mStreamCountDownLatch;
public XmppConnection(final Account account, final XmppConnectionService service) {
this.account = account;
- final String tag = account.getJid().asBareJid().toString();
- mXmppConnectionService = service;
+ this.mXmppConnectionService = service;
}
private static void fixResource(Context context, Account account) {
@@ -443,8 +442,6 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION);
} catch (final StateChangingException e) {
this.changeStatus(e.state);
- } catch (final Resolver.NetworkIsUnreachableException e) {
- this.changeStatus(Account.State.NETWORK_IS_UNREACHABLE);
} catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
@@ -482,6 +479,9 @@ public class XmppConnection implements Runnable {
tagWriter.beginDocument();
sendStartStream();
final Tag tag = tagReader.readTag();
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
return tag != null && tag.isStart("stream");
}
@@ -514,7 +514,7 @@ public class XmppConnection implements Runnable {
connect();
}
- private void processStream() throws XmlPullParserException, IOException, NoSuchAlgorithmException {
+ private void processStream() throws XmlPullParserException, IOException {
final CountDownLatch streamCountDownLatch = new CountDownLatch(1);
this.mStreamCountDownLatch = streamCountDownLatch;
Tag nextTag = tagReader.readTag();
@@ -1789,6 +1789,10 @@ public class XmppConnection implements Runnable {
return hasDiscoFeature(Jid.of(account.getServer()), "urn:xmpp:carbons:2");
}
+ public boolean bookmarksConversion() {
+ return hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS_CONVERSION) && pepPublishOptions();
+ }
+
public boolean blocking() {
return hasDiscoFeature(Jid.of(account.getServer()), Namespace.BLOCKING);
}
diff --git a/src/main/java/de/pixart/messenger/xmpp/pep/PublishOptions.java b/src/main/java/de/pixart/messenger/xmpp/pep/PublishOptions.java
index 534f4005c..56b85169c 100644
--- a/src/main/java/de/pixart/messenger/xmpp/pep/PublishOptions.java
+++ b/src/main/java/de/pixart/messenger/xmpp/pep/PublishOptions.java
@@ -14,4 +14,11 @@ public class PublishOptions {
return options;
}
+ public static Bundle persistentWhitelistAccess() {
+ final Bundle options = new Bundle();
+ options.putString("pubsub#persist_items", "true");
+ options.putString("pubsub#access_model", "whitelist");
+ return options;
+ }
+
} \ No newline at end of file
diff --git a/src/main/res/drawable-hdpi/ic_android_black_48dp.png b/src/main/res/drawable-hdpi/ic_android_black_48dp.png
new file mode 100644
index 000000000..38f6c05e8
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_android_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_android_white_48dp.png b/src/main/res/drawable-hdpi/ic_android_white_48dp.png
new file mode 100644
index 000000000..13108b25b
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_android_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_archive_black_48dp.png b/src/main/res/drawable-hdpi/ic_archive_black_48dp.png
new file mode 100644
index 000000000..1faf474cd
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_archive_white_48dp.png b/src/main/res/drawable-hdpi/ic_archive_white_48dp.png
new file mode 100644
index 000000000..462016ade
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_archive_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_attach_videocam.png b/src/main/res/drawable-hdpi/ic_attach_videocam.png
new file mode 100644
index 000000000..f95861633
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_attach_videocam.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_contact_white_24dp.png b/src/main/res/drawable-hdpi/ic_contact_white_24dp.png
new file mode 100644
index 000000000..7c046b21c
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_contact_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_description_black_48dp.png b/src/main/res/drawable-hdpi/ic_description_black_48dp.png
new file mode 100644
index 000000000..193ac1011
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_description_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_description_white_48dp.png b/src/main/res/drawable-hdpi/ic_description_white_48dp.png
new file mode 100644
index 000000000..bfb5baa97
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_description_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_event_black_48dp.png b/src/main/res/drawable-hdpi/ic_event_black_48dp.png
new file mode 100644
index 000000000..5cd32f4c2
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_event_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_event_white_48dp.png b/src/main/res/drawable-hdpi/ic_event_white_48dp.png
new file mode 100644
index 000000000..653d15b2b
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_event_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_headset_black_48dp.png b/src/main/res/drawable-hdpi/ic_headset_black_48dp.png
new file mode 100644
index 000000000..8a97fe7ec
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_headset_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_headset_white_48dp.png b/src/main/res/drawable-hdpi/ic_headset_white_48dp.png
new file mode 100644
index 000000000..a4810f277
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_headset_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_help_black_48dp.png b/src/main/res/drawable-hdpi/ic_help_black_48dp.png
new file mode 100644
index 000000000..b2ea7914a
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_help_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_help_white_48dp.png b/src/main/res/drawable-hdpi/ic_help_white_48dp.png
new file mode 100644
index 000000000..6827c3fac
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_help_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_mic_black_48dp.png b/src/main/res/drawable-hdpi/ic_mic_black_48dp.png
new file mode 100644
index 000000000..4ed4168cd
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_mic_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_mic_white_48dp.png b/src/main/res/drawable-hdpi/ic_mic_white_48dp.png
new file mode 100644
index 000000000..2d729e773
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_mic_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_person_black_48dp.png b/src/main/res/drawable-hdpi/ic_person_black_48dp.png
new file mode 100644
index 000000000..7401e00b5
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_person_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_person_white_48dp.png b/src/main/res/drawable-hdpi/ic_person_white_48dp.png
new file mode 100644
index 000000000..667c220dd
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_person_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_room_black_48dp.png b/src/main/res/drawable-hdpi/ic_room_black_48dp.png
new file mode 100644
index 000000000..2ebc54430
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_room_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_room_white_48dp.png b/src/main/res/drawable-hdpi/ic_room_white_48dp.png
new file mode 100644
index 000000000..29d2981b2
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_room_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_send_videocam_away.png b/src/main/res/drawable-hdpi/ic_send_videocam_away.png
new file mode 100644
index 000000000..950db9d9e
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_send_videocam_away.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_send_videocam_dnd.png b/src/main/res/drawable-hdpi/ic_send_videocam_dnd.png
new file mode 100644
index 000000000..8a3588678
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_send_videocam_dnd.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_send_videocam_online.png b/src/main/res/drawable-hdpi/ic_send_videocam_online.png
new file mode 100644
index 000000000..ac5af607f
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_send_videocam_online.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_android_black_48dp.png b/src/main/res/drawable-mdpi/ic_android_black_48dp.png
new file mode 100644
index 000000000..4f25964be
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_android_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_android_white_48dp.png b/src/main/res/drawable-mdpi/ic_android_white_48dp.png
new file mode 100644
index 000000000..032fe2781
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_android_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_archive_black_48dp.png b/src/main/res/drawable-mdpi/ic_archive_black_48dp.png
new file mode 100644
index 000000000..e9d5ed1b4
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_archive_white_48dp.png b/src/main/res/drawable-mdpi/ic_archive_white_48dp.png
new file mode 100644
index 000000000..82be16407
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_archive_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_attach_videocam.png b/src/main/res/drawable-mdpi/ic_attach_videocam.png
new file mode 100644
index 000000000..66aa2fe49
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_attach_videocam.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_contact_white_24dp.png b/src/main/res/drawable-mdpi/ic_contact_white_24dp.png
new file mode 100644
index 000000000..c61747dc2
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_contact_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_description_black_48dp.png b/src/main/res/drawable-mdpi/ic_description_black_48dp.png
new file mode 100644
index 000000000..7bc6a6846
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_description_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_description_white_48dp.png b/src/main/res/drawable-mdpi/ic_description_white_48dp.png
new file mode 100644
index 000000000..9759bde29
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_description_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_event_black_48dp.png b/src/main/res/drawable-mdpi/ic_event_black_48dp.png
new file mode 100644
index 000000000..8266743b4
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_event_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_event_white_48dp.png b/src/main/res/drawable-mdpi/ic_event_white_48dp.png
new file mode 100644
index 000000000..2fb9a7a2c
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_event_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_headset_black_48dp.png b/src/main/res/drawable-mdpi/ic_headset_black_48dp.png
new file mode 100644
index 000000000..36b513948
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_headset_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_headset_white_48dp.png b/src/main/res/drawable-mdpi/ic_headset_white_48dp.png
new file mode 100644
index 000000000..798e229c7
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_headset_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_help_black_48dp.png b/src/main/res/drawable-mdpi/ic_help_black_48dp.png
new file mode 100644
index 000000000..3678f6f23
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_help_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_help_white_48dp.png b/src/main/res/drawable-mdpi/ic_help_white_48dp.png
new file mode 100644
index 000000000..68e4b1563
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_help_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_mic_black_48dp.png b/src/main/res/drawable-mdpi/ic_mic_black_48dp.png
new file mode 100644
index 000000000..9c7caa295
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_mic_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_mic_white_48dp.png b/src/main/res/drawable-mdpi/ic_mic_white_48dp.png
new file mode 100644
index 000000000..80d851e95
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_mic_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_person_black_48dp.png b/src/main/res/drawable-mdpi/ic_person_black_48dp.png
new file mode 100644
index 000000000..76858f701
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_person_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_person_white_48dp.png b/src/main/res/drawable-mdpi/ic_person_white_48dp.png
new file mode 100644
index 000000000..496699985
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_person_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_room_black_48dp.png b/src/main/res/drawable-mdpi/ic_room_black_48dp.png
new file mode 100644
index 000000000..e60454537
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_room_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_room_white_48dp.png b/src/main/res/drawable-mdpi/ic_room_white_48dp.png
new file mode 100644
index 000000000..8c51d01ad
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_room_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_send_videocam_away.png b/src/main/res/drawable-mdpi/ic_send_videocam_away.png
new file mode 100644
index 000000000..55399ac8d
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_send_videocam_away.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_send_videocam_dnd.png b/src/main/res/drawable-mdpi/ic_send_videocam_dnd.png
new file mode 100644
index 000000000..608947d59
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_send_videocam_dnd.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_send_videocam_online.png b/src/main/res/drawable-mdpi/ic_send_videocam_online.png
new file mode 100644
index 000000000..90a152504
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_send_videocam_online.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_android_black_48dp.png b/src/main/res/drawable-xhdpi/ic_android_black_48dp.png
new file mode 100644
index 000000000..9f85a42bf
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_android_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_android_white_48dp.png b/src/main/res/drawable-xhdpi/ic_android_white_48dp.png
new file mode 100644
index 000000000..913ebd69f
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_android_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_archive_black_48dp.png b/src/main/res/drawable-xhdpi/ic_archive_black_48dp.png
new file mode 100644
index 000000000..6c383ee81
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_archive_white_48dp.png b/src/main/res/drawable-xhdpi/ic_archive_white_48dp.png
new file mode 100644
index 000000000..dbbf9f870
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_archive_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_attach_videocam.png b/src/main/res/drawable-xhdpi/ic_attach_videocam.png
new file mode 100644
index 000000000..5f6054b81
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_attach_videocam.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_contact_white_24dp.png b/src/main/res/drawable-xhdpi/ic_contact_white_24dp.png
new file mode 100644
index 000000000..eedda1b40
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_contact_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_description_black_48dp.png b/src/main/res/drawable-xhdpi/ic_description_black_48dp.png
new file mode 100644
index 000000000..0da5f7798
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_description_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_description_white_48dp.png b/src/main/res/drawable-xhdpi/ic_description_white_48dp.png
new file mode 100644
index 000000000..e1b8e9aec
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_description_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_event_black_48dp.png b/src/main/res/drawable-xhdpi/ic_event_black_48dp.png
new file mode 100644
index 000000000..8e43444b7
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_event_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_event_white_48dp.png b/src/main/res/drawable-xhdpi/ic_event_white_48dp.png
new file mode 100644
index 000000000..be533de42
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_event_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_headset_black_48dp.png b/src/main/res/drawable-xhdpi/ic_headset_black_48dp.png
new file mode 100644
index 000000000..7f85eb8bb
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_headset_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_headset_white_48dp.png b/src/main/res/drawable-xhdpi/ic_headset_white_48dp.png
new file mode 100644
index 000000000..b815a1944
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_headset_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_help_black_48dp.png b/src/main/res/drawable-xhdpi/ic_help_black_48dp.png
new file mode 100644
index 000000000..066c88bbe
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_help_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_help_white_48dp.png b/src/main/res/drawable-xhdpi/ic_help_white_48dp.png
new file mode 100644
index 000000000..805ea1efb
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_help_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_mic_black_48dp.png b/src/main/res/drawable-xhdpi/ic_mic_black_48dp.png
new file mode 100644
index 000000000..e39b8170c
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_mic_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_mic_white_48dp.png b/src/main/res/drawable-xhdpi/ic_mic_white_48dp.png
new file mode 100644
index 000000000..ec4405d44
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_mic_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_person_black_48dp.png b/src/main/res/drawable-xhdpi/ic_person_black_48dp.png
new file mode 100644
index 000000000..2751da525
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_person_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_person_white_48dp.png b/src/main/res/drawable-xhdpi/ic_person_white_48dp.png
new file mode 100644
index 000000000..6a77a6a38
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_person_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_room_black_48dp.png b/src/main/res/drawable-xhdpi/ic_room_black_48dp.png
new file mode 100644
index 000000000..9d46eb841
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_room_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_room_white_48dp.png b/src/main/res/drawable-xhdpi/ic_room_white_48dp.png
new file mode 100644
index 000000000..b7a61a2f5
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_room_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_send_videocam_away.png b/src/main/res/drawable-xhdpi/ic_send_videocam_away.png
new file mode 100644
index 000000000..69e340800
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_send_videocam_away.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_send_videocam_dnd.png b/src/main/res/drawable-xhdpi/ic_send_videocam_dnd.png
new file mode 100644
index 000000000..e7f784d8b
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_send_videocam_dnd.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_send_videocam_online.png b/src/main/res/drawable-xhdpi/ic_send_videocam_online.png
new file mode 100644
index 000000000..7cda9e6f0
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_send_videocam_online.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_android_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_android_black_48dp.png
new file mode 100644
index 000000000..61b806d38
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_android_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_android_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_android_white_48dp.png
new file mode 100644
index 000000000..c3bf2bdc3
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_android_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_archive_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_archive_black_48dp.png
new file mode 100644
index 000000000..0155c8104
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_archive_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_archive_white_48dp.png
new file mode 100644
index 000000000..5a18abe12
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_archive_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_attach_videocam.png b/src/main/res/drawable-xxhdpi/ic_attach_videocam.png
new file mode 100644
index 000000000..e74d2a776
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_attach_videocam.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_contact_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_contact_white_24dp.png
new file mode 100644
index 000000000..91e0d9a6b
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_contact_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_description_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_description_black_48dp.png
new file mode 100644
index 000000000..9ddffa1f3
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_description_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_description_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_description_white_48dp.png
new file mode 100644
index 000000000..b42cec8eb
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_description_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_event_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_event_black_48dp.png
new file mode 100644
index 000000000..3eb5d3073
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_event_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_event_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_event_white_48dp.png
new file mode 100644
index 000000000..44ead726f
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_event_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_headset_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_headset_black_48dp.png
new file mode 100644
index 000000000..2a8236f35
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_headset_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_headset_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_headset_white_48dp.png
new file mode 100644
index 000000000..2b6b2644f
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_headset_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_help_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_help_black_48dp.png
new file mode 100644
index 000000000..2f8af6e41
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_help_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_help_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_help_white_48dp.png
new file mode 100644
index 000000000..1a3e87205
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_help_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_mic_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_mic_black_48dp.png
new file mode 100644
index 000000000..44ef2fb4a
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_mic_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_mic_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_mic_white_48dp.png
new file mode 100644
index 000000000..0fc42bfad
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_mic_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_person_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_person_black_48dp.png
new file mode 100644
index 000000000..0001aee6b
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_person_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_person_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_person_white_48dp.png
new file mode 100644
index 000000000..32fe521d0
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_person_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_room_black_48dp.png b/src/main/res/drawable-xxhdpi/ic_room_black_48dp.png
new file mode 100644
index 000000000..e83824a17
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_room_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_room_white_48dp.png b/src/main/res/drawable-xxhdpi/ic_room_white_48dp.png
new file mode 100644
index 000000000..dd8eb11d3
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_room_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_send_videocam_away.png b/src/main/res/drawable-xxhdpi/ic_send_videocam_away.png
new file mode 100644
index 000000000..ce04c7790
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_send_videocam_away.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_send_videocam_dnd.png b/src/main/res/drawable-xxhdpi/ic_send_videocam_dnd.png
new file mode 100644
index 000000000..24890da09
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_send_videocam_dnd.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_send_videocam_online.png b/src/main/res/drawable-xxhdpi/ic_send_videocam_online.png
new file mode 100644
index 000000000..079cdef19
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_send_videocam_online.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_android_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_android_black_48dp.png
new file mode 100644
index 000000000..1d1492b08
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_android_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_android_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_android_white_48dp.png
new file mode 100644
index 000000000..d040e8639
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_android_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.png
new file mode 100644
index 000000000..4dd5e44ab
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_archive_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_archive_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_archive_white_48dp.png
new file mode 100644
index 000000000..5ac82a8df
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_archive_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_attach_videocam.png b/src/main/res/drawable-xxxhdpi/ic_attach_videocam.png
new file mode 100644
index 000000000..02fddb640
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_attach_videocam.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_contact_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_contact_white_24dp.png
new file mode 100644
index 000000000..f36920906
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_contact_white_24dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_description_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_description_black_48dp.png
new file mode 100644
index 000000000..f405b2931
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_description_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_description_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_description_white_48dp.png
new file mode 100644
index 000000000..1d32d8841
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_description_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_event_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_event_black_48dp.png
new file mode 100644
index 000000000..038df57d5
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_event_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_event_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_event_white_48dp.png
new file mode 100644
index 000000000..1937048aa
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_event_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_headset_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_headset_black_48dp.png
new file mode 100644
index 000000000..9a9a1b7f6
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_headset_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_headset_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_headset_white_48dp.png
new file mode 100644
index 000000000..ba48f28a1
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_headset_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_help_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_help_black_48dp.png
new file mode 100644
index 000000000..45e2e7b21
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_help_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_help_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_help_white_48dp.png
new file mode 100644
index 000000000..dc996ae81
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_help_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_mic_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_mic_black_48dp.png
new file mode 100644
index 000000000..f2247b7ff
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_mic_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_mic_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_mic_white_48dp.png
new file mode 100644
index 000000000..e9079bb3b
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_mic_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_person_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_person_black_48dp.png
new file mode 100644
index 000000000..6d377123a
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_person_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_person_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_person_white_48dp.png
new file mode 100644
index 000000000..5bb65aed1
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_person_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_room_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_room_black_48dp.png
new file mode 100644
index 000000000..51d418cf7
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_room_black_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_room_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_room_white_48dp.png
new file mode 100644
index 000000000..0f7de3f5d
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_room_white_48dp.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_send_videocam_away.png b/src/main/res/drawable-xxxhdpi/ic_send_videocam_away.png
new file mode 100644
index 000000000..7e517e6be
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_send_videocam_away.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_send_videocam_dnd.png b/src/main/res/drawable-xxxhdpi/ic_send_videocam_dnd.png
new file mode 100644
index 000000000..3077ee233
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_send_videocam_dnd.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_send_videocam_online.png b/src/main/res/drawable-xxxhdpi/ic_send_videocam_online.png
new file mode 100644
index 000000000..0baef17f4
--- /dev/null
+++ b/src/main/res/drawable-xxxhdpi/ic_send_videocam_online.png
Binary files differ
diff --git a/src/main/res/drawable/date_bubble.xml b/src/main/res/drawable/date_bubble.xml
index 3edf349e9..0ea7b0761 100644
--- a/src/main/res/drawable/date_bubble.xml
+++ b/src/main/res/drawable/date_bubble.xml
@@ -11,11 +11,7 @@
android:left="6dp"
android:right="6dp"
android:top="6dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey500">
- </stroke>
<solid
android:color="@color/lightgreen">
</solid>
-</shape> \ No newline at end of file
+</shape>
diff --git a/src/main/res/drawable/date_bubble_dark.xml b/src/main/res/drawable/date_bubble_dark.xml
index 345c315c1..64f5c1515 100644
--- a/src/main/res/drawable/date_bubble_dark.xml
+++ b/src/main/res/drawable/date_bubble_dark.xml
@@ -11,11 +11,7 @@
android:left="6dp"
android:right="6dp"
android:top="6dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey800">
- </stroke>
<solid
android:color="@color/darkgreen">
</solid>
-</shape> \ No newline at end of file
+</shape>
diff --git a/src/main/res/drawable/message_bubble_received_light.xml b/src/main/res/drawable/message_bubble_received_light.xml
index 32e146460..6619984b3 100644
--- a/src/main/res/drawable/message_bubble_received_light.xml
+++ b/src/main/res/drawable/message_bubble_received_light.xml
@@ -1,21 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <corners
- android:topLeftRadius="0dp"
- android:topRightRadius="5dp"
- android:bottomRightRadius="5dp"
- android:bottomLeftRadius="5dp">
- </corners>
- <padding
- android:bottom="2dp"
- android:left="6dp"
- android:right="6dp"
- android:top="2dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey500">
- </stroke>
- <solid
- android:color="@color/lightwhite">
- </solid>
-</shape> \ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="top|left">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="36"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="@color/lightwhite"
+ android:pathData="m2,0 c6,0 10,6 10,10L18,0z" />
+ </vector>
+ </item>
+ <item android:left="12dp">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="5dp"
+ android:bottomRightRadius="5dp"
+ android:bottomLeftRadius="5dp"/>
+ <padding
+ android:bottom="2dp"
+ android:left="18dp"
+ android:right="6dp"
+ android:top="2dp" />
+ <solid android:color="@color/lightwhite" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/src/main/res/drawable/message_bubble_received_light_dark.xml b/src/main/res/drawable/message_bubble_received_light_dark.xml
index 8f9132566..9514bc757 100644
--- a/src/main/res/drawable/message_bubble_received_light_dark.xml
+++ b/src/main/res/drawable/message_bubble_received_light_dark.xml
@@ -1,21 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <corners
- android:topLeftRadius="0dp"
- android:topRightRadius="5dp"
- android:bottomRightRadius="5dp"
- android:bottomLeftRadius="5dp">
- </corners>
- <padding
- android:bottom="2dp"
- android:left="6dp"
- android:right="6dp"
- android:top="2dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey700">
- </stroke>
- <solid
- android:color="@color/darkwhite">
- </solid>
-</shape> \ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="top|left">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="36"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="@color/darkwhite"
+ android:pathData="m2,0 c6,0 10,6 10,10L18,0z" />
+ </vector>
+ </item>
+ <item android:left="12dp">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="5dp"
+ android:bottomRightRadius="5dp"
+ android:bottomLeftRadius="5dp" />
+ <padding
+ android:bottom="2dp"
+ android:left="18dp"
+ android:right="6dp"
+ android:top="2dp" />
+ <solid android:color="@color/darkwhite" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/src/main/res/drawable/message_bubble_received_warning.xml b/src/main/res/drawable/message_bubble_received_warning.xml
index b3bf6d451..ae3222a9e 100644
--- a/src/main/res/drawable/message_bubble_received_warning.xml
+++ b/src/main/res/drawable/message_bubble_received_warning.xml
@@ -1,21 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <corners
- android:topLeftRadius="0dp"
- android:topRightRadius="5dp"
- android:bottomRightRadius="5dp"
- android:bottomLeftRadius="5dp">
- </corners>
- <padding
- android:bottom="4dp"
- android:left="6dp"
- android:right="6dp"
- android:top="4dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey500">
- </stroke>
- <solid
- android:color="@color/lightred">
- </solid>
-</shape> \ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="top|left">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="36"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="@color/lightred"
+ android:pathData="m2,0 c6,0 10,6 10,10L18,0z" />
+ </vector>
+ </item>
+ <item android:left="12dp">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="5dp"
+ android:bottomRightRadius="5dp"
+ android:bottomLeftRadius="5dp" />
+ <padding
+ android:bottom="2dp"
+ android:left="18dp"
+ android:right="6dp"
+ android:top="2dp" />
+ <solid android:color="@color/lightred" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/src/main/res/drawable/message_bubble_received_warning_dark.xml b/src/main/res/drawable/message_bubble_received_warning_dark.xml
index 5867f67ce..9f8aa779e 100644
--- a/src/main/res/drawable/message_bubble_received_warning_dark.xml
+++ b/src/main/res/drawable/message_bubble_received_warning_dark.xml
@@ -1,21 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <corners
- android:topLeftRadius="0dp"
- android:topRightRadius="5dp"
- android:bottomRightRadius="5dp"
- android:bottomLeftRadius="5dp">
- </corners>
- <padding
- android:bottom="4dp"
- android:left="6dp"
- android:right="6dp"
- android:top="4dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey700">
- </stroke>
- <solid
- android:color="@color/darkred">
- </solid>
-</shape> \ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="top|left">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="36"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="@color/darkred"
+ android:pathData="m2,0 c6,0 10,6 10,10L18,0z" />
+ </vector>
+ </item>
+ <item android:left="12dp">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <corners
+ android:topLeftRadius="0dp"
+ android:topRightRadius="5dp"
+ android:bottomRightRadius="5dp"
+ android:bottomLeftRadius="5dp" />
+ <padding
+ android:bottom="2dp"
+ android:left="18dp"
+ android:right="6dp"
+ android:top="2dp" />
+ <solid android:color="@color/darkred" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/src/main/res/drawable/message_bubble_sent_blue.xml b/src/main/res/drawable/message_bubble_sent_blue.xml
index 1ee3ad0c8..8e519ff6b 100644
--- a/src/main/res/drawable/message_bubble_sent_blue.xml
+++ b/src/main/res/drawable/message_bubble_sent_blue.xml
@@ -1,21 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <corners
- android:topLeftRadius="5dp"
- android:topRightRadius="5dp"
- android:bottomRightRadius="0dp"
- android:bottomLeftRadius="5dp">
- </corners>
- <padding
- android:bottom="4dp"
- android:left="6dp"
- android:right="6dp"
- android:top="4dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey500">
- </stroke>
- <solid
- android:color="@color/lightblue">
- </solid>
-</shape> \ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="bottom|right">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="36"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="@color/lightblue"
+ android:pathData="m 34,24 c-6,0 -10,-6 -10,-10 l -6,12 z" />
+
+ </vector>
+ </item>
+ <item android:right="12dp">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <corners
+ android:topLeftRadius="5dp"
+ android:topRightRadius="5dp"
+ android:bottomRightRadius="0dp"
+ android:bottomLeftRadius="5dp" />
+ <padding
+ android:bottom="2dp"
+ android:left="6dp"
+ android:right="18dp"
+ android:top="2dp" />
+ <solid android:color="@color/lightblue" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/src/main/res/drawable/message_bubble_sent_blue_dark.xml b/src/main/res/drawable/message_bubble_sent_blue_dark.xml
index d9853bf74..e6f3c88c1 100644
--- a/src/main/res/drawable/message_bubble_sent_blue_dark.xml
+++ b/src/main/res/drawable/message_bubble_sent_blue_dark.xml
@@ -1,21 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
- <corners
- android:topLeftRadius="5dp"
- android:topRightRadius="5dp"
- android:bottomRightRadius="0dp"
- android:bottomLeftRadius="5dp">
- </corners>
- <padding
- android:bottom="4dp"
- android:left="6dp"
- android:right="6dp"
- android:top="4dp" />
- <stroke
- android:width="1dp"
- android:color="@color/grey700">
- </stroke>
- <solid
- android:color="@color/darkblue">
- </solid>
-</shape> \ No newline at end of file
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:gravity="bottom|right">
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="24dp"
+ android:viewportWidth="36"
+ android:viewportHeight="24" >
+ <path
+ android:fillColor="@color/darkblue"
+ android:pathData="m 34,24 c-6,0 -10,-6 -10,-10 l -6,12 z" />
+ </vector>
+ </item>
+ <item android:right="12dp">
+ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <corners
+ android:topLeftRadius="5dp"
+ android:topRightRadius="5dp"
+ android:bottomRightRadius="0dp"
+ android:bottomLeftRadius="5dp" />
+ <padding
+ android:bottom="2dp"
+ android:left="6dp"
+ android:right="18dp"
+ android:top="2dp" />
+ <solid android:color="@color/darkblue" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml
index e56ca6a95..9b638172a 100644
--- a/src/main/res/layout/account_row.xml
+++ b/src/main/res/layout/account_row.xml
@@ -15,8 +15,6 @@
android:layout_alignParentLeft="true"
android:contentDescription="@string/account_image_description"
android:padding="1dp"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<LinearLayout
diff --git a/src/main/res/layout/activity_choose_contact.xml b/src/main/res/layout/activity_choose_contact.xml
index f720864a3..26f1c7494 100644
--- a/src/main/res/layout/activity_choose_contact.xml
+++ b/src/main/res/layout/activity_choose_contact.xml
@@ -13,7 +13,7 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/color_background_primary">
+ android:background="?attr/color_background_secondary">
<ListView
android:id="@+id/choose_contact_list"
diff --git a/src/main/res/layout/activity_contact_details.xml b/src/main/res/layout/activity_contact_details.xml
index dd3ff2c31..a6a720995 100644
--- a/src/main/res/layout/activity_contact_details.xml
+++ b/src/main/res/layout/activity_contact_details.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
@@ -102,8 +101,7 @@
android:layout_marginBottom="4dp"
android:layout_marginLeft="-2dp"
android:layout_marginTop="4dp"
- android:orientation="horizontal">
- </com.wefika.flowlayout.FlowLayout>
+ android:orientation="horizontal"></com.wefika.flowlayout.FlowLayout>
<TextView
android:id="@+id/details_lastseen"
@@ -235,6 +233,52 @@
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
+ android:id="@+id/media_wrapper"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/media"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-2dp"
+ android:layout_marginStart="-2dp"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/card_padding_list"
+ android:paddingEnd="@dimen/card_padding_regular"
+ android:paddingStart="@dimen/card_padding_regular"
+ android:paddingTop="@dimen/card_padding_regular" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/show_media"
+ style="@style/Widget.Conversations.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="0dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:text="@string/view_media"
+ android:textColor="?attr/colorAccent" />
+ </LinearLayout>
+ </LinearLayout>
+ </android.support.v7.widget.CardView>
+
+ <android.support.v7.widget.CardView
android:id="@+id/keys_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -254,7 +298,7 @@
android:layout_height="wrap_content"
android:divider="?android:dividerHorizontal"
android:orientation="vertical"
- android:padding="@dimen/card_padding_list"></LinearLayout>
+ android:padding="@dimen/card_padding_list" />
<LinearLayout
android:layout_width="wrap_content"
diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml
index 41e00b4ba..02d03b941 100644
--- a/src/main/res/layout/activity_edit_account.xml
+++ b/src/main/res/layout/activity_edit_account.xml
@@ -53,8 +53,6 @@
android:maxHeight="384dp"
android:maxWidth="384dp"
android:padding="1dp"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<LinearLayout
@@ -128,7 +126,7 @@
style="@style/Widget.Conversations.EditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:inputType="textNoSuggestions" />
+ android:inputType="textWebEmailAddress" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
diff --git a/src/main/res/layout/activity_manage_accounts.xml b/src/main/res/layout/activity_manage_accounts.xml
index e80aa01e0..2dc266bf3 100644
--- a/src/main/res/layout/activity_manage_accounts.xml
+++ b/src/main/res/layout/activity_manage_accounts.xml
@@ -2,7 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
- android:background="?attr/color_background_primary"
+ android:background="?attr/color_background_secondary"
android:orientation="vertical">
<include layout="@layout/toolbar" />
diff --git a/src/main/res/layout/activity_media_browser.xml b/src/main/res/layout/activity_media_browser.xml
new file mode 100644
index 000000000..149f2c164
--- /dev/null
+++ b/src/main/res/layout/activity_media_browser.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/color_background_primary"
+ android:orientation="vertical">
+
+ <include
+ android:id="@+id/toolbar"
+ layout="@layout/toolbar" />
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/media"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:orientation="horizontal"
+ android:padding="2dp"
+ android:scrollbars="vertical" />
+
+ </LinearLayout>
+</layout> \ No newline at end of file
diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml
index 2a450f964..b46ad8032 100644
--- a/src/main/res/layout/activity_muc_details.xml
+++ b/src/main/res/layout/activity_muc_details.xml
@@ -155,8 +155,6 @@
android:maxWidth="384dp"
android:padding="1dp"
android:scaleType="centerCrop"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
</RelativeLayout>
@@ -278,6 +276,52 @@
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
+ android:id="@+id/media_wrapper"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/activity_vertical_margin"
+ android:layout_marginLeft="@dimen/activity_horizontal_margin"
+ android:layout_marginRight="@dimen/activity_horizontal_margin"
+ android:layout_marginTop="@dimen/activity_vertical_margin">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/media"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="-2dp"
+ android:layout_marginStart="-2dp"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/card_padding_list"
+ android:paddingEnd="@dimen/card_padding_regular"
+ android:paddingStart="@dimen/card_padding_regular"
+ android:paddingTop="@dimen/card_padding_regular" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/show_media"
+ style="@style/Widget.Conversations.Button.Borderless"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="0dp"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:text="@string/view_media"
+ android:textColor="?attr/colorAccent" />
+ </LinearLayout>
+ </LinearLayout>
+ </android.support.v7.widget.CardView>
+
+ <android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
@@ -311,8 +355,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentRight="false"
android:padding="1dp"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<LinearLayout
diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml
index eebfa76a6..90dde5b01 100644
--- a/src/main/res/layout/activity_publish_profile_picture.xml
+++ b/src/main/res/layout/activity_publish_profile_picture.xml
@@ -43,8 +43,6 @@
android:maxWidth="384dp"
android:padding="1dp"
android:scaleType="centerCrop"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
</FrameLayout>
diff --git a/src/main/res/layout/activity_recording.xml b/src/main/res/layout/activity_recording.xml
index 35097fef4..e9dd345c6 100644
--- a/src/main/res/layout/activity_recording.xml
+++ b/src/main/res/layout/activity_recording.xml
@@ -46,6 +46,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
+ android:layout_margin="8dp"
android:text="0:00.0"
android:textColor="?attr/colorAccent"
android:textSize="50sp"
diff --git a/src/main/res/layout/activity_start_conversation.xml b/src/main/res/layout/activity_start_conversation.xml
index 1cb95b456..7adb8714a 100644
--- a/src/main/res/layout/activity_start_conversation.xml
+++ b/src/main/res/layout/activity_start_conversation.xml
@@ -29,7 +29,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tab_layout"
- android:background="?attr/color_background_primary" />
+ android:background="?attr/color_background_secondary" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
diff --git a/src/main/res/layout/contact.xml b/src/main/res/layout/contact.xml
index 0218e299b..f5bd1506b 100644
--- a/src/main/res/layout/contact.xml
+++ b/src/main/res/layout/contact.xml
@@ -13,8 +13,6 @@
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentLeft="true"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<LinearLayout
diff --git a/src/main/res/layout/conversation_list_row.xml b/src/main/res/layout/conversation_list_row.xml
index b00be5a4b..cbd884ae2 100644
--- a/src/main/res/layout/conversation_list_row.xml
+++ b/src/main/res/layout/conversation_list_row.xml
@@ -8,12 +8,12 @@
android:id="@+id/frame"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:background="?attr/color_background_primary">
+ android:background="?attr/color_background_secondary">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:background="?attr/color_background_secondary"
+ android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="8dp">
@@ -24,8 +24,6 @@
android:layout_alignParentLeft="true"
android:padding="1dp"
android:scaleType="centerCrop"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<RelativeLayout
diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml
index d081b6543..16f48bde2 100644
--- a/src/main/res/layout/fragment_conversation.xml
+++ b/src/main/res/layout/fragment_conversation.xml
@@ -5,7 +5,7 @@
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/color_background_secondary"
+ android:background="?attr/color_background_tertiary"
android:clickable="false">
<ListView
@@ -14,8 +14,9 @@
android:layout_height="match_parent"
android:layout_above="@+id/snackbar"
android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
- android:background="?attr/color_background_secondary"
+ android:background="?attr/color_background_tertiary"
android:divider="@null"
android:dividerHeight="0dp"
android:listSelector="@android:color/transparent"
@@ -53,7 +54,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true">
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true">
<RelativeLayout
android:id="@+id/textsend"
@@ -61,7 +63,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
- android:background="?attr/color_background_secondary"
+ android:background="?attr/color_background_tertiary"
android:clickable="true"
android:paddingBottom="2dp"
android:paddingLeft="2dp"
@@ -73,18 +75,34 @@
android:layout_height="48dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
- android:background="?attr/color_background_secondary"
+ android:background="?attr/color_background_tertiary"
android:contentDescription="@string/attach_record_voice"
android:src="@drawable/ic_send_voice_offline"
android:visibility="gone" />
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/media_preview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_toStartOf="@+id/textSendButton"
+ android:background="@drawable/message_bubble_sent_blue"
+ android:orientation="horizontal"
+ android:paddingTop="8dp"
+ android:requiresFadingEdge="horizontal"
+ android:visibility="gone"
+ app:layoutManager="android.support.v7.widget.LinearLayoutManager"
+ tools:listitem="@layout/media_preview"></android.support.v7.widget.RecyclerView>
+
<de.pixart.messenger.ui.widget.EditMessage
android:id="@+id/textinput"
style="@style/Widget.Conversations.EditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_toEndOf="@+id/recordVoiceButton"
android:layout_toLeftOf="@+id/textSendButton"
android:layout_toRightOf="@+id/recordVoiceButton"
+ android:layout_toStartOf="@+id/textSendButton"
android:background="@drawable/message_bubble_sent_blue"
android:ems="10"
android:imeOptions="flagNoExtractUi|actionSend"
@@ -105,9 +123,10 @@
android:id="@+id/textSendButton"
android:layout_width="48dp"
android:layout_height="48dp"
+ android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
- android:background="?attr/color_background_secondary"
+ android:background="?attr/color_background_tertiary"
android:contentDescription="@string/send_message"
android:src="@drawable/ic_send_text_offline" />
</RelativeLayout>
@@ -130,15 +149,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/snackbar_action"
+ android:layout_toStartOf="@+id/snackbar_action"
android:paddingLeft="24dp"
+ android:paddingStart="24dp"
android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark" />
<TextView
android:id="@+id/snackbar_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:paddingBottom="16dp"
@@ -166,8 +189,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:paddingLeft="24dp"
+ android:paddingStart="24dp"
android:textColor="@color/realblack"
android:textSize="?attr/TextSizeBody" />
</RelativeLayout>
diff --git a/src/main/res/layout/media.xml b/src/main/res/layout/media.xml
new file mode 100644
index 000000000..f95ddeda3
--- /dev/null
+++ b/src/main/res/layout/media.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <de.pixart.messenger.ui.widget.SquareFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="2dp">
+
+ <ImageView
+ android:id="@+id/media"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/black54"
+ android:scaleType="centerInside" />
+ </de.pixart.messenger.ui.widget.SquareFrameLayout>
+</layout> \ No newline at end of file
diff --git a/src/main/res/layout/media_preview.xml b/src/main/res/layout/media_preview.xml
new file mode 100644
index 000000000..8849e7b4a
--- /dev/null
+++ b/src/main/res/layout/media_preview.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/media_preview"
+ android:layout_width="@dimen/media_preview_size"
+ android:layout_height="@dimen/media_preview_size"
+ android:layout_centerInParent="true"
+ android:layout_margin="12dp"
+ android:background="@color/black54"
+ android:scaleType="center" />
+
+ <ImageButton
+ android:id="@+id/delete_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentTop="true"
+ android:alpha="?attr/delete_icon_alpha"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:src="?attr/icon_cancel" />
+ </RelativeLayout>
+</layout> \ No newline at end of file
diff --git a/src/main/res/layout/message_content.xml b/src/main/res/layout/message_content.xml
index 044f8c64d..296944565 100644
--- a/src/main/res/layout/message_content.xml
+++ b/src/main/res/layout/message_content.xml
@@ -13,8 +13,6 @@
android:maxHeight="500dp"
android:maxWidth="500dp"
android:scaleType="centerCrop"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<de.pixart.messenger.ui.widget.CopyTextView
diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml
index 2c6fd044a..f8b5fce95 100644
--- a/src/main/res/layout/message_received.xml
+++ b/src/main/res/layout/message_received.xml
@@ -16,8 +16,6 @@
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:scaleType="fitXY"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dip"
app:riv_corner_radius="@dimen/rounded_image_border" />
<LinearLayout
@@ -25,7 +23,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
- android:layout_marginLeft="4dp"
+ android:layout_marginLeft="-7dp"
android:layout_marginRight="4dp"
android:layout_toRightOf="@+id/message_photo"
android:background="@drawable/message_bubble_received_light"
diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml
index 63ce65b76..188e466d5 100644
--- a/src/main/res/layout/message_sent.xml
+++ b/src/main/res/layout/message_sent.xml
@@ -9,17 +9,27 @@
android:paddingRight="8dp"
android:paddingTop="3dp">
- <com.makeramen.roundedimageview.RoundedImageView
- android:id="@+id/message_photo"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ <LinearLayout
+ android:id="@+id/message_photo_box"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
- android:scaleType="fitXY"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
- app:riv_corner_radius="@dimen/rounded_image_border" />
+ android:orientation="vertical">
+
+ <com.makeramen.roundedimageview.RoundedImageView
+ android:id="@+id/message_photo"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:scaleType="fitXY"
+ app:riv_corner_radius="@dimen/rounded_image_border" />
+
+ <View
+ android:id="@+id/placeholder"
+ android:layout_width="48dp"
+ android:layout_height="3dp" />
+ </LinearLayout>
<LinearLayout
android:id="@+id/message_box"
@@ -27,9 +37,9 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="4dp"
- android:layout_marginRight="4dp"
- android:layout_toLeftOf="@+id/message_photo"
- android:layout_toStartOf="@+id/message_photo"
+ android:layout_marginRight="-7dp"
+ android:layout_toLeftOf="@+id/message_photo_box"
+ android:layout_toStartOf="@+id/message_photo_box"
android:background="@drawable/message_bubble_sent_blue"
android:longClickable="true"
android:minHeight="48dp">
diff --git a/src/main/res/layout/message_status.xml b/src/main/res/layout/message_status.xml
index d42637175..bd731cbe4 100644
--- a/src/main/res/layout/message_status.xml
+++ b/src/main/res/layout/message_status.xml
@@ -29,8 +29,6 @@
android:padding="0dp"
android:scaleType="fitXY"
android:visibility="gone"
- app:riv_border_color="?attr/color_border"
- app:riv_border_width="1dp"
app:riv_corner_radius="@dimen/rounded_image_border" />
<TextView
diff --git a/src/main/res/menu/choose_attachment.xml b/src/main/res/menu/choose_attachment.xml
index ae6f47a95..a9a57404d 100644
--- a/src/main/res/menu/choose_attachment.xml
+++ b/src/main/res/menu/choose_attachment.xml
@@ -14,7 +14,12 @@
<item
android:id="@+id/attach_take_picture"
android:icon="?attr/ic_attach_camera"
- android:title="@string/attach_take_from_camera" />
+ android:title="@string/action_take_photo" />
+
+ <item
+ android:id="@+id/attach_record_video"
+ android:icon="?attr/ic_attach_videocam"
+ android:title="@string/action_take_video" />
<item
android:id="@+id/attach_record_voice"
android:icon="?attr/ic_attach_record"
diff --git a/src/main/res/menu/fragment_conversation.xml b/src/main/res/menu/fragment_conversation.xml
index d67bf5253..088501bcb 100644
--- a/src/main/res/menu/fragment_conversation.xml
+++ b/src/main/res/menu/fragment_conversation.xml
@@ -5,8 +5,8 @@
android:id="@+id/action_security"
android:icon="?attr/icon_not_secure"
android:orderInCategory="20"
- app:showAsAction="always"
- android:title="@string/action_secure" >
+ android:title="@string/action_secure"
+ app:showAsAction="always">
<menu>
<group android:checkableBehavior="single">
<item
@@ -28,8 +28,8 @@
android:id="@+id/action_attach_file"
android:icon="?attr/icon_new_attachment"
android:orderInCategory="30"
- app:showAsAction="always"
- android:title="@string/attach_file" >
+ android:title="@string/attach_file"
+ app:showAsAction="always">
<menu>
<item
@@ -45,7 +45,13 @@
<item
android:id="@+id/attach_take_picture"
android:icon="?attr/ic_attach_camera"
- android:title="@string/attach_take_from_camera" />
+ android:title="@string/action_take_photo" />
+
+ <item
+ android:id="@+id/attach_record_video"
+ android:icon="?attr/ic_attach_videocam"
+ android:title="@string/action_take_video" />
+
<item
android:id="@+id/attach_record_voice"
android:icon="?attr/ic_attach_record"
@@ -60,21 +66,33 @@
<item
android:id="@+id/action_invite"
android:orderInCategory="45"
- app:showAsAction="never"
- android:title="@string/invite_contact" />
+ android:title="@string/invite_contact"
+ app:showAsAction="never" />
<item
android:id="@+id/action_clear_history"
android:orderInCategory="50"
- app:showAsAction="never"
- android:title="@string/action_clear_history" />
+ android:title="@string/action_clear_history"
+ app:showAsAction="never" />
<item
android:id="@+id/action_archive_chat"
android:orderInCategory="60"
- app:showAsAction="never"
- android:title="@string/action_end_conversation" />
+ android:title="@string/action_end_conversation"
+ app:showAsAction="never" />
<item
android:id="@+id/action_create_issue"
android:orderInCategory="100"
- app:showAsAction="never"
- android:title="@string/create_issue" />
+ android:title="@string/create_issue"
+ app:showAsAction="never" />
+ <item
+ android:id="@+id/action_group_details"
+ android:icon="@drawable/ic_group_white_24dp"
+ android:orderInCategory="100"
+ android:title="@string/action_group_details"
+ app:showAsAction="always" />
+ <item
+ android:id="@+id/action_contact_details"
+ android:icon="@drawable/ic_contact_white_24dp"
+ android:orderInCategory="100"
+ android:title="@string/action_contact_details"
+ app:showAsAction="always" />
</menu> \ No newline at end of file
diff --git a/src/main/res/values-h360dp/dimens.xml b/src/main/res/values-h360dp/dimens.xml
new file mode 100644
index 000000000..5e432c034
--- /dev/null
+++ b/src/main/res/values-h360dp/dimens.xml
@@ -0,0 +1,4 @@
+<resources>
+ <dimen name="publish_avatar_top_margin">16dp</dimen>
+ <dimen name="publish_avatar_size">128dp</dimen>
+</resources> \ No newline at end of file
diff --git a/src/main/res/values-h500dp/dimens.xml b/src/main/res/values-h500dp/dimens.xml
new file mode 100644
index 000000000..deea1124f
--- /dev/null
+++ b/src/main/res/values-h500dp/dimens.xml
@@ -0,0 +1,4 @@
+<resources>
+ <dimen name="publish_avatar_top_margin">24dp</dimen>
+ <dimen name="publish_avatar_size">192dp</dimen>
+</resources> \ No newline at end of file
diff --git a/src/main/res/values-w384dp/dimens.xml b/src/main/res/values-w384dp/dimens.xml
index 430845b1c..e9350addd 100644
--- a/src/main/res/values-w384dp/dimens.xml
+++ b/src/main/res/values-w384dp/dimens.xml
@@ -1,5 +1,9 @@
<resources>
<!-- 384dp is the screen width of the Nexus 4. Something like a Moto G is smaller but a Nexus 5X is larger -->
<!-- https://material.io/devices/ -->
+ <dimen name="fineprint_size">12sp</dimen>
+ <dimen name="swipe_handle_size">48dp</dimen>
<dimen name="audio_player_width">288dp</dimen>
+ <dimen name="avatar_on_details_screen_size">72dp</dimen>
+ <dimen name="media_size">64dp</dimen> <!-- ideally not larger than avatar_on_details_screen -->
</resources> \ No newline at end of file
diff --git a/src/main/res/values-w585dp/dimens.xml b/src/main/res/values-w585dp/dimens.xml
index 38ca33a11..067401781 100644
--- a/src/main/res/values-w585dp/dimens.xml
+++ b/src/main/res/values-w585dp/dimens.xml
@@ -1,3 +1,6 @@
<resources>
<dimen name="activity_horizontal_margin">32dp</dimen>
+ <dimen name="avatar_on_details_screen_size">96dp</dimen>
+ <dimen name="media_size">80dp</dimen> <!-- ideally not larger than avatar_on_details_screen -->
+ <dimen name="browser_media_size">128dp</dimen>
</resources>
diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml
index ff36fe0ae..93da9bac4 100644
--- a/src/main/res/values/arrays.xml
+++ b/src/main/res/values/arrays.xml
@@ -37,7 +37,8 @@
<string-array name="quick_actions">
<item>@string/none</item>
<item>@string/recently_used</item>
- <item>@string/attach_take_from_camera</item>
+ <item>@string/action_take_photo</item>
+ <item>@string/action_take_video</item>
<item>@string/attach_choose_picture</item>
<item>@string/attach_record_voice</item>
<item>@string/send_location</item>
@@ -46,7 +47,8 @@
<string-array name="quick_action_values">
<item>none</item>
<item>recent</item>
- <item>TAKE_FROM_CAMERA</item>
+ <item>TAKE_PHOTO</item>
+ <item>RECORD_VIDEO</item>
<item>CHOOSE_PICTURE</item>
<item>RECORD_VOICE</item>
<item>SEND_LOCATION</item>
diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml
index e89ed2944..672cb00fb 100644
--- a/src/main/res/values/attrs.xml
+++ b/src/main/res/values/attrs.xml
@@ -42,6 +42,16 @@
<attr name="conversations_overview_background" format="reference|color" />
+ <attr name="media_preview_document" format="reference" />
+ <attr name="media_preview_recording" format="reference" />
+ <attr name="media_preview_audio" format="reference" />
+ <attr name="media_preview_location" format="reference" />
+ <attr name="media_preview_contact" format="reference" />
+ <attr name="media_preview_app" format="reference" />
+ <attr name="media_preview_calendar" format="reference" />
+ <attr name="media_preview_archive" format="reference" />
+ <attr name="media_preview_unknown" format="reference" />
+
<attr name="icon_add_group" format="reference" />
<attr name="icon_add_person" format="reference" />
<attr name="icon_cancel" format="reference" />
@@ -83,6 +93,7 @@
<attr name="ic_attach_photo" format="reference" />
<attr name="ic_attach_record" format="reference" />
<attr name="ic_attach_video" format="reference" />
+ <attr name="ic_attach_videocam" format="reference" />
<attr name="ic_file_apk" format="reference" />
<attr name="ic_file_pdf" format="reference" />
@@ -90,6 +101,7 @@
<attr name="ic_file_calendar" format="reference" />
<attr name="icon_alpha" format="float" />
+ <attr name="delete_icon_alpha" format="float" />
<attr name="popupOverlayStyle" format="reference" />
diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml
index 29a256afb..e213ea773 100644
--- a/src/main/res/values/defaults.xml
+++ b/src/main/res/values/defaults.xml
@@ -62,7 +62,6 @@
<integer name="auto_accept_filesize_wifi">10485760</integer>
<integer name="auto_accept_filesize_mobile">524288</integer>
<integer name="auto_accept_filesize_roaming">0</integer>
- <bool name="show_notification">true</bool>
<bool name="vibrate_on_notification">true</bool>
<bool name="led">true</bool>
<string name="notification_ringtone">content://settings/system/notification_sound</string>
@@ -87,7 +86,6 @@
<bool name="btbv">true</bool>
<bool name="send_button_status">true</bool>
<string name="quick_action">recent</string>
- <bool name="return_to_previous">false</bool>
<bool name="display_enter_key">false</bool>
<bool name="show_dynamic_tags">false</bool>
<integer name="grace_period">144</integer>
@@ -108,11 +106,13 @@
<string name="default_font_size">small</string>
<bool name="start_searching">false</bool>
<bool name="show_record_voice_btn">true</bool>
+ <bool name="quick_share_attachment_choice">true</bool>
<string-array name="domains">
<item>pix-art.de</item>
<item>conversations.im</item>
<item>jabber.cat</item>
+ <item>jabbers.one</item>
<item>jabjab.de</item>
<item>im.koderoot.net</item>
<item>riotcat.org</item>
diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml
index 50f955c89..526eb7d40 100644
--- a/src/main/res/values/dimens.xml
+++ b/src/main/res/values/dimens.xml
@@ -10,6 +10,9 @@
<dimen name="audio_player_width">224dp</dimen>
<dimen name="avatar_item_distance">16dp</dimen>
+ <dimen name="media_preview_size">80dp</dimen>
+ <dimen name="media_size">56dp</dimen> <!-- ideally not larger than avatar_on_details_screen -->
+ <dimen name="browser_media_size">96dp</dimen>
<dimen name="toolbar_elevation">4dp</dimen>
<dimen name="publish_avatar_top_margin">8dp</dimen>
@@ -24,6 +27,5 @@
<dimen name="scan_dot_size">8dp</dimen>
<dimen name="background_image_opacity">0.12</dimen>
-
- <dimen name="rounded_image_border">5dp</dimen>
+ <dimen name="rounded_image_border">8dp</dimen>
</resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 321a5f97a..62b415cf3 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -107,8 +107,6 @@
<string name="pref_accept_files_summary_mobileroaming">When connected with mobile data in roaming automatically accept files smaller than…</string>
<string name="pref_attachments">Attachments</string>
<string name="pref_notification_settings">Notification</string>
- <string name="pref_notifications">Notifications</string>
- <string name="pref_notifications_summary">Notify when a new message arrives</string>
<string name="pref_vibrate">Vibrate</string>
<string name="pref_vibrate_summary">Vibrate when a new message arrives</string>
<string name="pref_led">LED Notification</string>
@@ -701,7 +699,6 @@
<string name="yesterday">Yesterday</string>
<string name="pref_validate_hostname">Validate hostname with DNSSEC</string>
<string name="pref_validate_hostname_summary">Server certificates that contain the validated hostname are considered verified</string>
- <string name="network_is_unreachable">Network is unreachable</string>
<string name="certificate_does_not_contain_jid">Certificate does not contain a Jabber ID</string>
<string name="error_no_keys_to_trust_presence">There are no usable keys available for this contact.\nMake sure you have mutual presence subscription.</string>
<string name="mark_as_read">Mark as read</string>
@@ -715,8 +712,6 @@
<string name="contacts_have_read_up_to_this_point">%s have read up to this point</string>
<string name="contact_has_read_up_to_this_point">%s has read up to this point</string>
<string name="contacts_and_n_more_have_read_up_to_this_point">%1$s and %2$d more have read up to this point</string>
- <string name="pref_return_to_previous">Quick Sharing</string>
- <string name="pref_return_to_previous_summary">Immediately return to previous activity instead of opening the conversation after sharing something</string>
<string name="conversation_unencrypted_hint">This chat is unencrypted, for security reasons you should activate message encryption by using the lock icon. The preferable encryption is OMEMO.</string>
<string name="pref_warn_unencrypted_chat">Warn if chat is unencrypted</string>
<string name="pref_warn_unencrypted_chat_summary">If message encryption is available, you should use it. If you don\'t use message encryption, show a warning message inside the chat.</string>
@@ -791,7 +786,7 @@
<string name="show_termsofuse">Terms of Use</string>
<string name="p1_s3_filetransfer">HTTP File Sharing for S3</string>
<string name="pref_start_search">Direct Search</string>
- <string name="pref_start_search_summary">At 'Start Conversation' screen open keyboard and place cursor in search field</string>
+ <string name="pref_start_search_summary">At ‘Start Conversation’ screen open keyboard and place cursor in search field</string>
<string name="paste_as_quote">Paste as quote</string>
<string name="group_chat_avatar">Group chat avatar</string>
<string name="host_does_not_support_group_chat_avatars">Host does not support group chat avatars</string>
@@ -810,4 +805,25 @@
<string name="no_location_permission">Pix-Art Messenger needs access to location services</string>
<string name="pref_show_record_voice_btn_summary">Show record voice button in chats as quick action</string>
<string name="pref_show_record_voice_btn">Show record voice button</string>
+ <string name="pref_use_quick_share_attachment_choice_summary">Replace send button with attachment choice if no message is typed. Otherwise show quick actions, which can be cofigured in the following setting.</string>
+ <string name="pref_quick_share_attachment_choice">Attachment choice</string>
+ <string name="phone_book">Address book</string>
+ <string name="unable_to_save_recording">Unable to save recording</string>
+ <string name="foreground_service_channel_name">Foreground service</string>
+ <string name="foreground_service_channel_description">This notification category is used to display a permanent notification indicating that Pix-Art Messenger is running.</string>
+ <string name="notification_group_status_information">Status Information</string>
+ <string name="error_channel_name">Connectivity Problems</string>
+ <string name="error_channel_description">This notification category is used to display a notification in case there is a problem connecting to an account.</string>
+ <string name="notification_group_messages">Messages</string>
+ <string name="messages_channel_name">Messages</string>
+ <string name="silent_messages_channel_name">Silent messages</string>
+ <string name="silent_messages_channel_description">This notification category is used to display notifications that should not trigger any sound. For example when being active on another device (Grace Period).</string>
+ <string name="pref_more_notification_settings">Notification Settings</string>
+ <string name="pref_more_notification_settings_summary">Importance, Sound, Vibrate</string>
+ <string name="video_compression_channel_name">Video compression</string>
+ <string name="backup_channel_name">Database backup</string>
+ <string name="app_update_channel_name">App update</string>
+ <string name="action_group_details">Group details</string>
+ <string name="view_media">View media</string>
+ <string name="media_browser">Media browser</string>
</resources>
diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml
index c8a0a97a1..50cf8e93a 100644
--- a/src/main/res/values/themes.xml
+++ b/src/main/res/values/themes.xml
@@ -65,6 +65,16 @@
</item>
<item name="ic_send_voice_offline" type="reference">@drawable/ic_send_voice_offline</item>
+ <item name="media_preview_document" type="reference">@drawable/ic_description_black_48dp</item>
+ <item name="media_preview_recording" type="reference">@drawable/ic_mic_black_48dp</item>
+ <item name="media_preview_audio" type="reference">@drawable/ic_headset_black_48dp</item>
+ <item name="media_preview_location" type="reference">@drawable/ic_room_black_48dp</item>
+ <item name="media_preview_contact" type="reference">@drawable/ic_person_black_48dp</item>
+ <item name="media_preview_app" type="reference">@drawable/ic_android_black_48dp</item>
+ <item name="media_preview_calendar" type="reference">@drawable/ic_event_black_48dp</item>
+ <item name="media_preview_archive" type="reference">@drawable/ic_archive_black_48dp</item>
+ <item name="media_preview_unknown" type="reference">@drawable/ic_help_black_48dp</item>
+
<item name="icon_add_group" type="reference">@drawable/ic_group_add_white_24dp</item>
<item name="icon_add_person" type="reference">@drawable/ic_person_add_white_24dp</item>
<item name="icon_cancel" type="reference">@drawable/ic_cancel_black_24dp</item>
@@ -106,8 +116,10 @@
<item name="dialog_horizontal_padding">16dp</item>
<item name="dialog_vertical_padding">16dp</item>
<item name="icon_alpha" type="float">0.54</item>
+ <item name="delete_icon_alpha" type="float">0.70</item>
<item name="ic_attach_camera" type="reference">@drawable/ic_attach_camera</item>
+ <item type="reference" name="ic_attach_videocam">@drawable/ic_attach_videocam</item>
<item name="ic_attach_document" type="reference">@drawable/ic_attach_document</item>
<item name="ic_attach_location" type="reference">@drawable/ic_attach_location</item>
<item name="ic_attach_photo" type="reference">@drawable/ic_attach_photo</item>
@@ -150,9 +162,9 @@
<item name="colorControlActivated">@color/accent</item>
<item name="text_Color_Main">@color/realwhite</item>
- <item name="color_background_primary">@color/grey800</item>
- <item name="color_background_secondary">@color/grey900</item>
- <item name="color_background_tertiary">@color/grey700</item>
+ <item name="color_background_primary">@color/grey700</item>
+ <item name="color_background_secondary">@color/grey800</item>
+ <item name="color_background_tertiary">@color/grey900</item>
<item name="color_warning">@color/red_a700</item>
<item name="TextColorOnline">@color/green500</item>
<item name="TextColorError">@color/red500</item>
@@ -210,6 +222,7 @@
</item>
<item name="ic_attach_camera" type="reference">@drawable/ic_attach_camera_white</item>
+ <item name="ic_attach_videocam" type="reference">@drawable/ic_attach_videocam_white</item>
<item name="ic_attach_video" type="reference">@drawable/ic_attach_videocam_white</item>
<item name="ic_attach_document" type="reference">@drawable/ic_attach_document_white</item>
<item name="ic_attach_location" type="reference">@drawable/ic_attach_location_white</item>
@@ -219,10 +232,21 @@
<item name="conversations_overview_background">@color/primary_dark</item>
<item name="icon_alpha" type="float">0.7</item>
+ <item name="delete_icon_alpha" type="float">0.7</item>
<item name="dialog_horizontal_padding">24dp</item>
<item name="dialog_vertical_padding">16dp</item>
+ <item name="media_preview_document" type="reference">@drawable/ic_description_white_48dp</item>
+ <item name="media_preview_recording" type="reference">@drawable/ic_mic_white_48dp</item>
+ <item name="media_preview_audio" type="reference">@drawable/ic_headset_white_48dp</item>
+ <item name="media_preview_location" type="reference">@drawable/ic_room_white_48dp</item>
+ <item name="media_preview_contact" type="reference">@drawable/ic_person_white_48dp</item>
+ <item name="media_preview_app" type="reference">@drawable/ic_android_white_48dp</item>
+ <item name="media_preview_calendar" type="reference">@drawable/ic_event_white_48dp</item>
+ <item name="media_preview_archive" type="reference">@drawable/ic_archive_white_48dp</item>
+ <item name="media_preview_unknown" type="reference">@drawable/ic_help_white_48dp</item>
+
<item name="icon_add_group" type="reference">@drawable/ic_group_add_white_24dp</item>
<item name="icon_add_person" type="reference">@drawable/ic_person_add_white_24dp</item>
<item name="icon_cancel" type="reference">@drawable/ic_cancel_white_24dp</item>
@@ -284,6 +308,7 @@
<item name="colorAccent">@color/accent</item>
<item name="color_background_primary">@color/grey50</item>
<item name="divider">@color/black12</item>
+ <item name="TextSizeTitle">18sp</item>
<item name="TextSizeBody2">14sp</item>
<item name="TextSizeDisplay2">45sp</item>
<item name="android:windowNoTitle">true</item>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index 31ca6158b..819fcded9 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -38,6 +38,11 @@
android:key="presence_colored_names"
android:summary="@string/pref_use_colored_names_to_indicate_status_summary"
android:title="@string/pref_use_colored_names_to_indicate_status" />
+ <CheckBoxPreference
+ android:defaultValue="@bool/quick_share_attachment_choice"
+ android:key="quick_share_attachment_choice"
+ android:summary="@string/pref_use_quick_share_attachment_choice_summary"
+ android:title="@string/pref_quick_share_attachment_choice" />
<ListPreference
android:defaultValue="@string/quick_action"
android:dialogTitle="@string/choose_quick_action"
@@ -81,18 +86,11 @@
android:value="notifications" />
</intent>
<CheckBoxPreference
- android:defaultValue="@bool/show_notification"
- android:key="show_notification"
- android:summary="@string/pref_notifications_summary"
- android:title="@string/pref_notifications" />
- <CheckBoxPreference
android:defaultValue="@bool/headsup_notifications"
- android:dependency="show_notification"
android:key="notification_headsup"
android:summary="@string/pref_headsup_notifications_summary"
android:title="@string/pref_headsup_notifications" />
<PreferenceScreen
- android:dependency="show_notification"
android:key="quiet_hours"
android:summary="@string/pref_quiet_hours_summary"
android:title="@string/title_pref_quiet_hours">
@@ -124,29 +122,38 @@
</PreferenceScreen>
<CheckBoxPreference
android:defaultValue="@bool/vibrate_on_notification"
- android:dependency="show_notification"
android:key="vibrate_on_notification"
android:summary="@string/pref_vibrate_summary"
android:title="@string/pref_vibrate" />
<CheckBoxPreference
android:defaultValue="@bool/led"
- android:dependency="show_notification"
android:key="led"
android:summary="@string/pref_led_summary"
android:title="@string/pref_led" />
<RingtonePreference
android:defaultValue="@string/notification_ringtone"
- android:dependency="show_notification"
android:key="notification_ringtone"
android:ringtoneType="notification"
android:summary="@string/pref_sound_summary"
android:title="@string/pref_sound" />
<CheckBoxPreference
android:defaultValue="@bool/notifications_from_strangers"
- android:dependency="show_notification"
android:key="notifications_from_strangers"
android:summary="@string/pref_notifications_from_strangers_summary"
android:title="@string/pref_notifications_from_strangers" />
+ <PreferenceScreen
+ android:key="more_notification_settings"
+ android:summary="@string/pref_more_notification_settings_summary"
+ android:title="@string/pref_more_notification_settings">
+ <intent android:action="android.settings.CHANNEL_NOTIFICATION_SETTINGS">
+ <extra
+ android:name="android.provider.extra.APP_PACKAGE"
+ android:value="@string/applicationId" />
+ <extra
+ android:name="android.provider.extra.CHANNEL_ID"
+ android:value="messages" />
+ </intent>
+ </PreferenceScreen>
</PreferenceScreen>
<!--Attachments-->
<PreferenceScreen
@@ -196,11 +203,6 @@
android:key="video_compression"
android:summary="@string/pref_video_compression_summary"
android:title="@string/pref_video_compression" />
- <CheckBoxPreference
- android:defaultValue="@bool/return_to_previous"
- android:key="return_to_previous"
- android:summary="@string/pref_return_to_previous_summary"
- android:title="@string/pref_return_to_previous" />
</PreferenceScreen>
<!--status-->
<PreferenceScreen