aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/.project33
-rw-r--r--src/main/AndroidManifest.xml18
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java102
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java332
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java65
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/enums/UserDecision.java10
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java13
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java16
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java22
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java28
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java62
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/dialogs/AbstractAlertDialog.java125
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java180
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java70
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java186
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShareWithResizePictureUserDecisionListener.java48
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java47
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/UserDecisionListener.java12
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/preferences/LogInformationPreference.java31
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java163
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/FileHelper.java82
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java372
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java94
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java63
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java52
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java34
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java124
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java29
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacket.java33
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketGenerator.java32
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketParser.java27
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java33
-rw-r--r--src/main/java/de/tzur/conversations/Settings.java76
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java9
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/OtrService.java21
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java8
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/PgpEngine.java28
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java1030
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java1022
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java196
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java12
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java10
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/sasl/External.java1
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java26
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Bookmark.java7
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java8
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java24
-rw-r--r--src/main/java/eu/siacs/conversations/entities/ListItem.java2
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java46
-rw-r--r--src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java2
-rw-r--r--src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java37
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java53
-rw-r--r--src/main/java/eu/siacs/conversations/generator/MessageGenerator.java24
-rw-r--r--src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java5
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java2
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java184
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java37
-rw-r--r--src/main/java/eu/siacs/conversations/parser/AbstractParser.java35
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java14
-rw-r--r--src/main/java/eu/siacs/conversations/parser/MessageParser.java78
-rw-r--r--src/main/java/eu/siacs/conversations/parser/PresenceParser.java32
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java19
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java549
-rw-r--r--src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java10
-rw-r--r--src/main/java/eu/siacs/conversations/services/AvatarService.java297
-rw-r--r--src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java6
-rw-r--r--src/main/java/eu/siacs/conversations/services/ExportLogsService.java1
-rw-r--r--src/main/java/eu/siacs/conversations/services/MessageArchiveService.java13
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java69
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java548
-rw-r--r--src/main/java/eu/siacs/conversations/ui/AboutPreference.java4
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java11
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java5
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java11
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java80
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java340
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java21
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditMessage.java6
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java4
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java9
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java97
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java56
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java18
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java57
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java3
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java61
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java18
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java107
-rw-r--r--src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java152
-rw-r--r--src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java92
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java259
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java18
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExifHelper.java14
-rw-r--r--src/main/java/eu/siacs/conversations/utils/FileUtils.java5
-rw-r--r--src/main/java/eu/siacs/conversations/utils/PRNGFixes.java5
-rw-r--r--src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java4
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java47
-rw-r--r--src/main/java/eu/siacs/conversations/xml/Element.java5
-rw-r--r--src/main/java/eu/siacs/conversations/xml/XmlReader.java4
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java184
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java94
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java20
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java49
-rw-r--r--src/main/project.properties14
-rw-r--r--src/main/res/drawable-hdpi/ic_action_keyboard.pngbin0 -> 359 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_launcher.pngbin4581 -> 4334 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.pngbin264 -> 0 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.pngbin299 -> 0 bytes
-rw-r--r--src/main/res/drawable-hdpi/ic_notification.pngbin798 -> 1708 bytes
-rw-r--r--src/main/res/drawable-hdpi/smiley.pngbin0 -> 1679 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_action_keyboard.pngbin0 -> 433 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_launcher.pngbin2841 -> 2656 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.pngbin229 -> 0 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.pngbin249 -> 0 bytes
-rw-r--r--src/main/res/drawable-mdpi/ic_notification.pngbin554 -> 887 bytes
-rw-r--r--src/main/res/drawable-mdpi/smiley.pngbin0 -> 987 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_action_keyboard.pngbin0 -> 492 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_launcher.pngbin6549 -> 6157 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.pngbin291 -> 0 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.pngbin351 -> 0 bytes
-rw-r--r--src/main/res/drawable-xhdpi/ic_notification.pngbin1075 -> 2488 bytes
-rw-r--r--src/main/res/drawable-xhdpi/smiley.pngbin0 -> 1898 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_action_keyboard.pngbin0 -> 472 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_launcher.pngbin10495 -> 10033 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.pngbin336 -> 0 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.pngbin436 -> 0 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/ic_notification.pngbin1618 -> 4234 bytes
-rw-r--r--src/main/res/drawable-xxhdpi/smiley.pngbin0 -> 3140 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_launcher.pngbin14630 -> 14018 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.pngbin366 -> 0 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.pngbin490 -> 0 bytes
-rw-r--r--src/main/res/drawable-xxxhdpi/ic_notification.pngbin2117 -> 6209 bytes
-rw-r--r--src/main/res/layout/account_row.xml2
-rw-r--r--src/main/res/layout/activity_about.xml2
-rw-r--r--src/main/res/layout/activity_change_password.xml18
-rw-r--r--src/main/res/layout/activity_contact_details.xml11
-rw-r--r--src/main/res/layout/activity_edit_account.xml117
-rw-r--r--src/main/res/layout/activity_logcatoutput.xml19
-rw-r--r--src/main/res/layout/activity_muc_details.xml18
-rw-r--r--src/main/res/layout/activity_publish_profile_picture.xml12
-rw-r--r--src/main/res/layout/activity_verify_otr.xml20
-rw-r--r--src/main/res/layout/contact.xml14
-rw-r--r--src/main/res/layout/contact_key.xml6
-rw-r--r--src/main/res/layout/conversation_list_row.xml53
-rw-r--r--src/main/res/layout/dialog_message_details.xml168
-rw-r--r--src/main/res/layout/dialog_resources_status.xml13
-rw-r--r--src/main/res/layout/dialog_userdecision.xml20
-rw-r--r--src/main/res/layout/enter_jid_dialog.xml8
-rw-r--r--src/main/res/layout/fragment_conversation.xml23
-rw-r--r--src/main/res/layout/join_conference_dialog.xml10
-rw-r--r--src/main/res/layout/list_item_logcatoutput.xml7
-rw-r--r--src/main/res/layout/message_received.xml26
-rw-r--r--src/main/res/layout/message_sent.xml25
-rw-r--r--src/main/res/layout/message_status.xml11
-rw-r--r--src/main/res/layout/quickedit.xml2
-rw-r--r--src/main/res/menu/encryption_choices.xml3
-rw-r--r--src/main/res/menu/message_context.xml11
-rw-r--r--src/main/res/values-bg/strings.xml8
-rw-r--r--src/main/res/values-cs/strings.xml8
-rw-r--r--src/main/res/values-de/strings.xml81
-rw-r--r--src/main/res/values-es/strings.xml10
-rw-r--r--src/main/res/values-eu/strings.xml10
-rw-r--r--src/main/res/values-fr/strings.xml10
-rw-r--r--src/main/res/values-gl/strings.xml2
-rw-r--r--src/main/res/values-it/strings.xml12
-rw-r--r--src/main/res/values-iw/strings.xml4
-rw-r--r--src/main/res/values-ja/strings.xml10
-rw-r--r--src/main/res/values-ko/strings.xml3
-rw-r--r--src/main/res/values-nb-rNO/strings.xml15
-rw-r--r--src/main/res/values-nl/strings.xml10
-rw-r--r--src/main/res/values-pl/strings.xml10
-rw-r--r--src/main/res/values-pt-rBR/strings.xml6
-rw-r--r--src/main/res/values-ro-rRO/strings.xml10
-rw-r--r--src/main/res/values-ru/strings.xml6
-rw-r--r--src/main/res/values-sr/strings.xml14
-rw-r--r--src/main/res/values-sv/strings.xml10
-rw-r--r--src/main/res/values-tr-rTR/strings.xml10
-rw-r--r--src/main/res/values-vi/strings.xml3
-rw-r--r--src/main/res/values-zh-rCN/strings.xml10
-rw-r--r--src/main/res/values-zh-rTW/strings.xml2
-rw-r--r--src/main/res/values/arrays.xml30
-rw-r--r--src/main/res/values/attrs.xml5
-rw-r--r--src/main/res/values/colors.xml14
-rw-r--r--src/main/res/values/dimens.xml4
-rw-r--r--src/main/res/values/encryption_settings.xml4
-rw-r--r--src/main/res/values/strings.xml83
-rw-r--r--src/main/res/xml/preferences.xml192
189 files changed, 6437 insertions, 3739 deletions
diff --git a/src/main/.project b/src/main/.project
new file mode 100644
index 00000000..d2f05ef9
--- /dev/null
+++ b/src/main/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>ConversationActivity</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index dda0fbe9..71a0a94a 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -2,7 +2,8 @@
<manifest
package="eu.siacs.conversations"
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools">
+ xmlns:tools="http://schemas.android.com/tools"
+ android:installLocation="auto">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@@ -25,8 +26,9 @@
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/ConversationsTheme"
- tools:replace="android:label">
- <service android:name=".services.XmppConnectionService"/>
+ tools:replace="android:label"
+ android:name="de.thedevstack.conversationsplus.ConversationsPlusApplication">
+ <service android:name=".services.XmppConnectionService" />
<receiver android:name=".services.EventReceiver">
<intent-filter>
@@ -155,7 +157,15 @@
android:parentActivityName=".ui.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
- android:value="eu.siacs.conversations.ui.SettingsActivity"/>
+ android:value=".ui.SettingsActivity"/>
+ </activity>
+ <activity
+ android:name="de.thedevstack.conversationsplus.ui.LogCatOutputActivity"
+ android:label="@string/title_activity_loginformation"
+ android:parentActivityName=".ui.SettingsActivity" >
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".ui.SettingsActivity" />
</activity>
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
<service android:name=".services.ExportLogsService"/>
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
new file mode 100644
index 00000000..51637bc2
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusApplication.java
@@ -0,0 +1,102 @@
+package de.thedevstack.conversationsplus;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.preference.PreferenceManager;
+
+import java.io.File;
+
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
+
+/**
+ * This class is used to provide static access to the applicationcontext.
+ */
+public class ConversationsPlusApplication extends Application {
+ /**
+ * Application instance for static access
+ */
+ private static ConversationsPlusApplication instance;
+
+ private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
+ private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
+
+ /**
+ * Initializes the application and saves its instance.
+ */
+ public void onCreate(){
+ super.onCreate();
+ ConversationsPlusApplication.instance = this;
+ ConversationsPlusPreferences.init(PreferenceManager.getDefaultSharedPreferences(getAppContext()));
+ ImageUtil.initBitmapCache();
+ }
+
+ /**
+ * Returns the instance of the application
+ * @return this application instance
+ */
+ public static ConversationsPlusApplication getInstance() {
+ return ConversationsPlusApplication.instance;
+ }
+
+ public static void executeFileAdding(Runnable r) {
+ getInstance().mFileAddingExecutor.execute(r);
+ }
+
+ public static void executeDatabaseOperation(Runnable r) {
+ getInstance().mDatabaseExecutor.execute(r);
+ }
+
+ /**
+ * Returns the application's context.
+ * @return Context the application's context
+ */
+ public static Context getAppContext() {
+ return ConversationsPlusApplication.instance.getApplicationContext();
+ }
+
+ /**
+ * Returns the application's private data directory.
+ * @return File the application's private data dir
+ */
+ public static File getPrivateFilesDir() {
+ return ConversationsPlusApplication.instance.getFilesDir();
+ }
+
+ /**
+ * Returns the version of the application.
+ * @see android.content.pm.PackageInfo#versionName
+ * @return a string representation of the version stored in packageInfo
+ */
+ public static String getVersion() {
+ final String packageName = ConversationsPlusApplication.getAppContext().getPackageName();
+ if (packageName != null) {
+ try {
+ return ConversationsPlusApplication.getAppContext().getPackageManager().getPackageInfo(packageName, 0).versionName;
+ } catch (final PackageManager.NameNotFoundException e) {
+ return "unknown";
+ }
+ } else {
+ return "unknown";
+ }
+ }
+
+ /**
+ * Returns the application's name.
+ * @return the name as it is defined in R.string.app_name
+ */
+ public static String getName() {
+ return ConversationsPlusApplication.getAppContext().getString(R.string.app_name);
+ }
+
+ /**
+ * Returns the name and the version of this application.
+ * @see #getName() and #getVersion
+ * @return a concatination of name and version with a whitespace in between
+ */
+ public static String getNameAndVersion() {
+ return getName() + " " + getVersion();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java
new file mode 100644
index 00000000..dda4208c
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java
@@ -0,0 +1,332 @@
+package de.thedevstack.conversationsplus;
+
+import android.content.SharedPreferences;
+
+import java.util.Set;
+
+import de.thedevstack.conversationsplus.enums.UserDecision;
+import de.tzur.conversations.Settings;
+
+/**
+ * Utility Class to access shared preferences of Conversations+.
+ */
+public class ConversationsPlusPreferences extends Settings {
+ private static ConversationsPlusPreferences instance;
+ private final SharedPreferences sharedPreferences;
+
+ public static boolean omemoEnabled() {
+ return getBoolean("omemo_enabled", false);
+ }
+
+ public static String imgTransferFolder() {
+ return getString("img_transfer_folder", getString("app_name", "Conversations+"));
+ }
+
+ public static String fileTransferFolder() {
+ return getString("file_transfer_folder", getString("app_name", "Conversations+"));
+ }
+
+ public static UserDecision resizePicture() {
+ return getEnumFromStringPref("resize_picture", UserDecision.ASK);
+ }
+
+ public static void applyResizePicture(UserDecision decision) {
+ applyString("resize_picture", decision.name());
+ }
+
+ /**
+ * Whether automatic downloads should only be done when connected to Wifi or not.
+ * @return
+ */
+ public static boolean autoDownloadFileWLAN() {
+ return getBoolean("auto_download_file_wlan", true);
+ }
+ /**
+ * Whether image-links should be downloaded or not.
+ * @return
+ */
+ public static boolean autoDownloadFileLink() {
+ return getBoolean("auto_download_file_link", true);
+ }
+
+ public static boolean showDynamicTags() {
+ return getBoolean("show_dynamic_tags", false);
+ }
+
+ /**
+ * Whether to send report to developer or not.
+ * @return
+ */
+ public static boolean neverSend() {
+ return getBoolean("never_send", false);
+ }
+
+ public static void applyNeverSend(boolean neverSend) {
+ applyBoolean("never_send", neverSend);
+ }
+
+ /**
+ * The name used for the resource part of the accounts' JID.
+ * @return the resource name, <i>mobile</i> as default value
+ */
+ public static String resource() {
+ return getString("resource", "mobile");
+ }
+
+ /**
+ * Whether to enable legacy SSL support.
+ * @return <code>true</code>if legacy support for SSL is enabled, <i>false</i> as default value
+ */
+ public static boolean enableLegacySSL() {
+ return getBoolean("enable_legacy_ssl", false);
+ }
+
+ public static boolean useSubject() {
+ return getBoolean("use_subject", true);
+ }
+
+ public static boolean displayEnterKey() {
+ return getBoolean("display_enter_key", false);
+ }
+
+ public static boolean useLargerFont() {
+ return getBoolean("use_larger_font", false);
+ }
+
+ public static boolean hideOffline() {
+ return getBoolean("hide_offline", false);
+ }
+
+ public static void commitHideOffline(boolean hideOffline) {
+ commitBoolean("hide_offline", hideOffline);
+ }
+
+ public static String recentlyUsedQuickAction() {
+ return getString("recently_used_quick_action", "text");
+ }
+
+ public static void applyRecentlyUsedQuickAction(String recentlyUsedQuickAction) {
+ applyString("recently_used_quick_action", recentlyUsedQuickAction);
+ }
+
+ public static String quickAction() {
+ return getString("quick_action", "recent");
+ }
+
+ public static boolean sendButtonStatus() {
+ return getBoolean("send_button_status", false);
+ }
+
+ public static boolean enterIsSend() {
+ return getBoolean("enter_is_send", false);
+ }
+
+ public static long autoAcceptFileSize() {
+ return getLongFromStringPref("auto_accept_file_size", 524288);
+ }
+
+ public static boolean vibrateOnNotification() {
+ return getBoolean("vibrate_on_notification", true);
+ }
+
+ public static String notificationRingtone() {
+ return getString("notification_ringtone", null);
+ }
+
+ public static boolean showNotification() {
+ return getBoolean("show_notification", true);
+ }
+
+ public static long quietHoursEnd() {
+ return getLong("quiet_hours_end", 0);
+ }
+
+ public static long quietHoursStart() {
+ return getLong("quiet_hours_start", 0);
+ }
+
+ public static boolean enableQuietHours() {
+ return getBoolean("enable_quiet_hours", false);
+ }
+
+ public static boolean dontTrustSystemCAs() {
+ return getBoolean("dont_trust_system_cas", false);
+ }
+
+ public static boolean grantNewContacts() {
+ return getBoolean("grant_new_contacts", true);
+ }
+
+ public static boolean keepForegroundService() {
+ return getBoolean("keep_foreground_service", false);
+ }
+
+ public static void commitKeepForegroundService(boolean keepForegroundService) {
+ commitBoolean("keep_foreground_service", keepForegroundService);
+ }
+
+ public static boolean forceEncryption() {
+ return getBoolean("force_encryption", false);
+ }
+
+ public static boolean dontSaveEncrypted() {
+ return getBoolean("dont_save_encrypted", false);
+ }
+
+ /**
+ * Whether the chat states should be send or not.
+ * @return
+ */
+ public static boolean chatStates() {
+ return getBoolean("chat_states", false);
+ }
+
+ /**
+ * Whether the receipient notification should be requested from the counterpart or not.
+ * <br>Default value is <code>false</code>
+ * @return <code>true</code> if the receipt should be requested, <code>false</code> otherwise
+ */
+ public static boolean indicateReceived() {
+ return getBoolean("indicate_received", false);
+ }
+
+ public static boolean allowMessageCorrection() {
+ return getBoolean("allow_message_correction", true);
+ }
+
+ private ConversationsPlusPreferences(SharedPreferences sharedPreferences) {
+ this.sharedPreferences = sharedPreferences;
+ }
+
+ public synchronized static void init(SharedPreferences sharedPreferences) {
+ if (null == instance) {
+ instance = new ConversationsPlusPreferences(sharedPreferences);
+ initSettingsClassWithPreferences(sharedPreferences);
+ }
+ }
+
+ private static SharedPreferences getSharedPreferences() {
+ return instance.sharedPreferences;
+ }
+
+ private static SharedPreferences.Editor getSharedPreferencesEditor() {
+ return getSharedPreferences().edit();
+ }
+
+ private static String getString(String key, String defValue) {
+ return getSharedPreferences().getString(key, defValue);
+ }
+
+ private static float getFloat(String key, float defValue) {
+ return getSharedPreferences().getFloat(key, defValue);
+ }
+
+ private static float getFloatFromStringPref(String key, float defValue) {
+ try {
+ return Float.parseFloat(getString(key, String.valueOf(defValue)));
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ private static int getInt(String key, int defValue) {
+ return getSharedPreferences().getInt(key, defValue);
+ }
+
+ private static int getIntFromStringPref(String key, int defValue) {
+ try {
+ return Integer.parseInt(getString(key, String.valueOf(defValue)));
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ private static Set<String> getStringSet(String key, Set<String> defValues) {
+ return getSharedPreferences().getStringSet(key, defValues);
+ }
+
+ private static boolean contains(String key) {
+ return getSharedPreferences().contains(key);
+ }
+
+ private static long getLong(String key, long defValue) {
+ return getSharedPreferences().getLong(key, defValue);
+ }
+
+ private static long getLongFromStringPref(String key, long defValue) {
+ try {
+ return Long.parseLong(getString(key, String.valueOf(defValue)));
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ protected static <T extends Enum<T>> T getEnumFromStringPref(String key, T defaultValue) {
+ String enumValueAsString = getString(key, defaultValue.name());
+ return (T) Enum.valueOf(defaultValue.getClass(), enumValueAsString);
+ }
+
+ private static boolean getBoolean(String key, boolean defValue) {
+ return getSharedPreferences().getBoolean(key, defValue);
+ }
+
+ private static void commitBoolean(String key, boolean value) {
+ putBoolean(key, value).commit();
+ }
+
+ private static void applyBoolean(String key, boolean value) {
+ putBoolean(key, value).apply();
+ }
+
+ private static SharedPreferences.Editor putBoolean(String key, boolean value) {
+ return getSharedPreferencesEditor().putBoolean(key, value);
+ }
+
+ private static void commitString(String key, String value) {
+ putString(key, value).commit();
+ }
+
+ private static void applyString(String key, String value) {
+ putString(key, value).apply();
+ }
+
+ private static SharedPreferences.Editor putString(String key, String value) {
+ return getSharedPreferencesEditor().putString(key, value);
+ }
+
+ private static void commitInt(String key, int value) {
+ putInt(key, value).commit();
+ }
+
+ private static void applyInt(String key, int value) {
+ putInt(key, value).apply();
+ }
+
+ private static SharedPreferences.Editor putInt(String key, int value) {
+ return getSharedPreferencesEditor().putInt(key, value);
+ }
+
+ private static void commitLong(String key, long value) {
+ putLong(key, value).commit();
+ }
+
+ private static void applyLong(String key, long value) {
+ putLong(key, value).apply();
+ }
+
+ private static SharedPreferences.Editor putLong(String key, long value) {
+ return getSharedPreferencesEditor().putLong(key, value);
+ }
+
+ private static void commitFloat(String key, float value) {
+ putFloat(key, value).commit();
+ }
+
+ private static void applyLong(String key, float value) {
+ putFloat(key, value).apply();
+ }
+
+ private static SharedPreferences.Editor putFloat(String key, float value) {
+ return getSharedPreferencesEditor().putFloat(key, value);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java b/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java
new file mode 100644
index 00000000..1e0eebc7
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/dto/SrvRecord.java
@@ -0,0 +1,65 @@
+package de.thedevstack.conversationsplus.dto;
+
+/**
+ * An SRV record as it is currently used in Conversations Plus.
+ * The weight of the SRV record is skipped.
+ */
+public class SrvRecord implements Comparable<SrvRecord> {
+ private int priority;
+ private String name;
+ private int port;
+ private boolean useTls = false;
+
+ public SrvRecord(int priority, String name, int port) {
+ this.priority = priority;
+ this.name = name;
+ this.port = port;
+ }
+
+ public SrvRecord(int priority, String name, int port, boolean useTls) {
+ this.priority = priority;
+ this.name = name;
+ this.port = port;
+ this.useTls = useTls;
+ }
+
+ /**
+ * Compares this record to the specified record to determine their relative
+ * order.
+ *
+ * @param another the object to compare to this instance.
+ * @return a negative integer if the priority of this record is lower than the priority of {@code another};
+ * a positive integer if the priority of this record is higher than
+ * {@code another}; 0 if the priority of this record is equal to the priority of
+ * {@code another}.
+ */
+ @Override
+ public int compareTo(SrvRecord another) {
+ return this.getPriority() < another.getPriority() ? -1 : (this.getPriority() == another.getPriority() ? 0 : 1);
+ }
+
+ @Override
+ public String toString() {
+ return "SrvRecord{" +
+ "priority=" + priority +
+ ", name='" + name + '\'' +
+ ", port=" + port +
+ '}';
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public boolean isUseTls() {
+ return useTls;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/enums/UserDecision.java b/src/main/java/de/thedevstack/conversationsplus/enums/UserDecision.java
new file mode 100644
index 00000000..ccb658d5
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/enums/UserDecision.java
@@ -0,0 +1,10 @@
+package de.thedevstack.conversationsplus.enums;
+
+/**
+ * Created by tzur on 30.10.2015.
+ */
+public enum UserDecision {
+ ASK,
+ ALWAYS,
+ NEVER;
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java b/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java
new file mode 100644
index 00000000..858b4563
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/exceptions/FileCopyException.java
@@ -0,0 +1,13 @@
+package de.thedevstack.conversationsplus.exceptions;
+
+public class FileCopyException extends UiException {
+ private static final long serialVersionUID = -1010013599132881427L;
+
+ public FileCopyException(int resId) {
+ super(resId);
+ }
+
+ public FileCopyException(int resId, Throwable e) {
+ super(resId, e);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java b/src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java
new file mode 100644
index 00000000..b5786990
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/exceptions/ImageResizeException.java
@@ -0,0 +1,16 @@
+package de.thedevstack.conversationsplus.exceptions;
+
+/**
+ * Created by tzur on 15.12.2015.
+ */
+public class ImageResizeException extends UiException {
+ private static final long serialVersionUID = -1010013599112881427L;
+
+ public ImageResizeException(int resId) {
+ super(resId);
+ }
+
+ public ImageResizeException(int resId, Throwable e) {
+ super(resId, e);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java b/src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java
new file mode 100644
index 00000000..b05c5025
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/exceptions/UiException.java
@@ -0,0 +1,22 @@
+package de.thedevstack.conversationsplus.exceptions;
+
+/**
+ * Exception to be shown in UI.
+ */
+public class UiException extends Exception {
+ private static final long serialVersionUID = -1010015239132881427L;
+ private int resId;
+
+ public UiException(int resId) {
+ this.resId = resId;
+ }
+
+ public UiException(int resId, Throwable e) {
+ super(e);
+ this.resId = resId;
+ }
+
+ public int getResId() {
+ return resId;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java
new file mode 100644
index 00000000..52891a91
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java
@@ -0,0 +1,28 @@
+package de.thedevstack.conversationsplus.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.ListView;
+
+import de.thedevstack.android.logcat.adapters.LogCatArrayAdapter;
+import de.thedevstack.android.logcat.tasks.ReadLogCatAsyncTask;
+import de.thedevstack.android.logcat.ui.LogCatOutputCopyOnClickListener;
+import eu.siacs.conversations.R;
+
+/**
+ * Created by tzur on 07.10.2015.
+ */
+public class LogCatOutputActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_logcatoutput);
+ ListView lv = (ListView)findViewById(R.id.actLogInfoOutput);
+ LogCatArrayAdapter logCatOutputAdapter = new LogCatArrayAdapter(this, R.layout.list_item_logcatoutput);
+ lv.setAdapter(logCatOutputAdapter);
+ new ReadLogCatAsyncTask(logCatOutputAdapter).execute();
+ Button copyButton = (Button) findViewById(R.id.actLogOutputCopyButton);
+ copyButton.setOnClickListener(new LogCatOutputCopyOnClickListener(this, logCatOutputAdapter, R.string.cplus_copied_to_clipboard, R.string.cplus_not_copied_to_clipboard_empty));
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java
new file mode 100644
index 00000000..e14d7f6b
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/PresencesArrayAdapter.java
@@ -0,0 +1,62 @@
+package de.thedevstack.conversationsplus.ui.adapter;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Presences;
+import eu.siacs.conversations.utils.UIHelper;
+
+/**
+ * Created by tzur on 27.09.2015.
+ */
+public class PresencesArrayAdapter extends ArrayAdapter<Presence> {
+ private final Context context;
+ private final Presence[] values;
+
+ public PresencesArrayAdapter(Context context, Presences presences) {
+ super(context, R.layout.dialog_resources_status);
+ this.context = context;
+ this.values = getPresenceArray(presences);
+ addAll(this.values);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View rowView = inflater.inflate(R.layout.dialog_resources_status, parent, false);
+ TextView textView = (TextView) rowView.findViewById(R.id.dlg_res_stat_resource_name);
+ textView.setText(this.values[position].resource);
+ textView.setTextColor(Color.parseColor(UIHelper.getStatusColor(this.values[position].status)));
+
+ return rowView;
+ }
+
+ private static Presence[] getPresenceArray(Presences presences) {
+ ArrayList<Presence> presenceArrayList = new ArrayList<>();
+ if (null != presences && null != presences.getPresences() && !presences.getPresences().isEmpty()) {
+ for (Map.Entry<String, eu.siacs.conversations.entities.Presence> entry : presences.getPresences().entrySet()) {
+ Presence p = new Presence();
+ p.resource = entry.getKey();
+ p.status = entry.getValue().getStatus();
+ presenceArrayList.add(p);
+ }
+ presenceArrayList.trimToSize();
+ }
+ return presenceArrayList.toArray(new Presence[0]);
+ }
+}
+
+class Presence {
+ String resource;
+ eu.siacs.conversations.entities.Presence.Status status;
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/AbstractAlertDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/AbstractAlertDialog.java
new file mode 100644
index 00000000..2f394fb3
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/AbstractAlertDialog.java
@@ -0,0 +1,125 @@
+package de.thedevstack.conversationsplus.ui.dialogs;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+
+import eu.siacs.conversations.R;
+
+/**
+ * Created by tzur on 29.09.2015.
+ */
+public class AbstractAlertDialog {
+ protected AlertDialog.Builder builder;
+
+ public AbstractAlertDialog(Context context, String title) {
+ this.builder = new AlertDialog.Builder(context);
+ this.builder.setTitle(title);
+ this.builder.setPositiveButton(R.string.cplus_ok, null);
+ }
+
+ public AbstractAlertDialog(Context context, int titleTextId) {
+ this(context, context.getString(titleTextId));
+ }
+
+ public void show() {
+ this.builder.show();
+ }
+
+ public Context getContext() {
+ return builder.getContext();
+ }
+
+ public AlertDialog.Builder setTitle(int titleId) {
+ return builder.setTitle(titleId);
+ }
+
+ public AlertDialog.Builder setTitle(CharSequence title) {
+ return builder.setTitle(title);
+ }
+
+ public AlertDialog.Builder setIcon(int iconId) {
+ return builder.setIcon(iconId);
+ }
+
+ public AlertDialog.Builder setIcon(Drawable icon) {
+ return builder.setIcon(icon);
+ }
+
+ public AlertDialog.Builder setMessage(CharSequence message) {
+ return builder.setMessage(message);
+ }
+
+ public AlertDialog.Builder setMessage(int messageId) {
+ return builder.setMessage(messageId);
+ }
+
+ public AlertDialog.Builder setIconAttribute(int attrId) {
+ return builder.setIconAttribute(attrId);
+ }
+
+ public AlertDialog.Builder setPositiveButton(int textId, DialogInterface.OnClickListener listener) {
+ return builder.setPositiveButton(textId, listener);
+ }
+
+ public AlertDialog.Builder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
+ return builder.setPositiveButton(text, listener);
+ }
+
+ public AlertDialog.Builder setNegativeButton(int textId, DialogInterface.OnClickListener listener) {
+ return builder.setNegativeButton(textId, listener);
+ }
+
+ public AlertDialog.Builder setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
+ return builder.setNegativeButton(text, listener);
+ }
+
+ public AlertDialog.Builder setNeutralButton(int textId, DialogInterface.OnClickListener listener) {
+ return builder.setNeutralButton(textId, listener);
+ }
+
+ public AlertDialog.Builder setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
+ return builder.setNeutralButton(text, listener);
+ }
+
+ public AlertDialog.Builder setCancelable(boolean cancelable) {
+ return builder.setCancelable(cancelable);
+ }
+
+ public AlertDialog.Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
+ return builder.setOnCancelListener(onCancelListener);
+ }
+
+ public AlertDialog.Builder setOnDismissListener(DialogInterface.OnDismissListener onDismissListener) {
+ return builder.setOnDismissListener(onDismissListener);
+ }
+
+ public AlertDialog.Builder setOnKeyListener(DialogInterface.OnKeyListener onKeyListener) {
+ return builder.setOnKeyListener(onKeyListener);
+ }
+
+ public AlertDialog.Builder setAdapter(ListAdapter adapter, DialogInterface.OnClickListener listener) {
+ return builder.setAdapter(adapter, listener);
+ }
+
+ public AlertDialog.Builder setCursor(Cursor cursor, DialogInterface.OnClickListener listener, String labelColumn) {
+ return builder.setCursor(cursor, listener, labelColumn);
+ }
+
+ public AlertDialog.Builder setView(View view) {
+ return builder.setView(view);
+ }
+
+ public AlertDialog.Builder setView(int layoutResId) {
+ return builder.setView(layoutResId);
+ }
+
+ public AlertDialog.Builder setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) {
+ return builder.setOnItemSelectedListener(listener);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java
new file mode 100644
index 00000000..1a51edd5
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java
@@ -0,0 +1,180 @@
+package de.thedevstack.conversationsplus.ui.dialogs;
+
+import android.app.Activity;
+import android.text.format.DateFormat;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.Date;
+
+import de.thedevstack.android.logcat.Logging;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.utils.UIHelper;
+
+/**
+ * Fills the contents to the message details dialog.
+ * The view definition is done in R.layout.dialog_message_details.
+ */
+public class MessageDetailsDialog extends AbstractAlertDialog {
+
+ /**
+ * Initializes the Message Details Dialog.
+ * @param context the context of this alert dialog (the parent activity).
+ * @param message the message to be displayed
+ */
+ public MessageDetailsDialog(Activity context, Message message) {
+ super(context, R.string.dlg_msg_details_title);
+ this.createView(context, message);
+ }
+
+ /**
+ * Creates the view for the message details alert dialog.
+ * @param context the context of this alert dialog (the parent activity).
+ * @param message the message to be displayed
+ */
+ protected void createView(Activity context, Message message) {
+ int viewId = R.layout.dialog_message_details;
+ View view = context.getLayoutInflater().inflate(viewId, null);
+
+ displayMessageSentTime(view, message);
+ displaySenderAndReceiver(view, message);
+ displayMessageTypeInfo(view, message);
+ displayMessageStatusInfo(view, message);
+ displayFileInfo(view, message);
+
+ this.setView(view);
+ }
+
+ /**
+ * Publishes file information, if message contains an image to view.
+ * @param view the dialog view
+ * @param message the message to display in dialog
+ */
+ protected void displayFileInfo(View view, Message message) {
+ if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
+ Logging.d("messagedetailsfile", "File is stored in path: " + message.getRelativeFilePath());
+ view.findViewById(R.id.dlgMsgDetFileTable).setVisibility(View.VISIBLE);
+ if (null != message.getFileParams()) {
+ Message.FileParams params = message.getFileParams();
+ TextView tvFilesize = (TextView) view.findViewById(R.id.dlgMsgDetFileSize);
+ tvFilesize.setText(UIHelper.getHumanReadableFileSize(params.size));
+ }
+ TextView mimetype = (TextView) view.findViewById(R.id.dlgMsgDetFileMimeType);
+ mimetype.setText(message.getMimeType());
+ }
+ }
+
+ /**
+ * Displays message status info to view.
+ * @param view the dialog view
+ * @param message the message to display in dialog
+ */
+ protected void displayMessageStatusInfo(View view, Message message) {
+ TextView msgStatusTextView = (TextView) view.findViewById(R.id.dlgMsgDetMsgStatus);
+ int msgStatusResId;
+ int msgStatusColorResId = R.color.primaryText;
+ switch (message.getStatus()) {
+ case Message.STATUS_WAITING:
+ msgStatusResId = R.string.dlg_msg_details_msg_status_waiting;
+ break;
+ case Message.STATUS_UNSEND:
+ msgStatusResId = R.string.dlg_msg_details_msg_status_unsend;
+ break;
+ case Message.STATUS_OFFERED:
+ msgStatusResId = R.string.dlg_msg_details_msg_status_offered;
+ break;
+ case Message.STATUS_SEND_FAILED:
+ msgStatusResId = R.string.dlg_msg_details_msg_status_failed;
+ msgStatusColorResId = R.color.error;
+ break;
+ case Message.STATUS_RECEIVED:
+ msgStatusResId = R.string.dlg_msg_details_msg_status_received;
+ break;
+ case Message.STATUS_SEND:
+ case Message.STATUS_SEND_DISPLAYED:
+ case Message.STATUS_SEND_RECEIVED:
+ default:
+ msgStatusResId = R.string.dlg_msg_details_msg_status_sent;
+ }
+ msgStatusTextView.setText(msgStatusResId);
+ msgStatusTextView.setTextColor(getContext().getResources().getColor(msgStatusColorResId));
+ }
+
+ /**
+ * Publishes message type information to view.
+ * @param view the dialog view
+ * @param message the message to display in dialog
+ */
+ protected void displayMessageTypeInfo(View view, Message message) {
+ TextView msgTypeTextView = (TextView) view.findViewById(R.id.dlgMsgDetMsgType);
+ int msgTypeResId;
+ switch (message.getType()) {
+ case Message.TYPE_PRIVATE:
+ msgTypeResId = R.string.dlg_msg_details_msg_type_private;
+ break;
+ case Message.TYPE_FILE:
+ msgTypeResId = R.string.dlg_msg_details_msg_type_file;
+ break;
+ case Message.TYPE_IMAGE:
+ msgTypeResId = R.string.dlg_msg_details_msg_type_image;
+ break;
+ case Message.TYPE_STATUS:
+ msgTypeResId = R.string.dlg_msg_details_msg_type_status;
+ break;
+ case Message.TYPE_TEXT:
+ default:
+ msgTypeResId = R.string.dlg_msg_details_msg_type_text;
+ }
+ msgTypeTextView.setText(msgTypeResId);
+ }
+
+ /**
+ * Publishes information about sending and receiving parties to view.
+ * @param view the dialog view
+ * @param message the message to display in dialog
+ */
+ protected void displaySenderAndReceiver(View view, Message message) {
+ Conversation conversation = message.getConversation();
+ // Get own resource name -> What about msg written on other client?
+ String me = conversation.getAccount().getJid().getResourcepart();
+ // Get resource name of chat partner, if available
+ String other = (null == message.getCounterpart() || message.getCounterpart().isBareJid()) ? "" : message.getCounterpart().getResourcepart();
+ Logging.d("MesageDialog", "Me: " + me + ", other: " + other);
+ TextView sender = (TextView) view.findViewById(R.id.dlgMsgDetSender);
+ TextView receipient = (TextView) view.findViewById(R.id.dlgMsgDetReceipient);
+
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ // Change label of sending and receiving party to MUC terminology
+ TextView senderLabel = (TextView) view.findViewById(R.id.dlgMsgDetLblSender);
+ senderLabel.setText(R.string.dlg_msg_details_sender_nick);
+ TextView receipientLabel = (TextView) view.findViewById(R.id.dlgMsgDetLblReceipient);
+ receipientLabel.setText(R.string.dlg_msg_details_receipient_nick);
+
+ // Get own nick for MUC
+ me = conversation.getMucOptions().getActualNick();
+ }
+ if (Message.STATUS_RECEIVED == message.getStatus()) {
+ // Sender was chat partner, if the status is for my account received
+ sender.setText(other);
+ // Set receipient to myself in case of normal chat or private message in MUC
+ if (conversation.getMode() == Conversation.MODE_SINGLE || Message.TYPE_PRIVATE == message.getType()) {
+ receipient.setText(me);
+ }
+ } else {
+ sender.setText(me);
+ receipient.setText(other);
+ }
+ }
+
+ /**
+ * Publishes information about message sent time to view.
+ * @param view the dialog view
+ * @param message the message to display in dialog
+ */
+ protected void displayMessageSentTime(View view, Message message) {
+ TextView timeSent = (TextView) view.findViewById(R.id.dlgMsgDetTimeSent);
+ timeSent.setText(DateFormat.format("dd.MM.yyyy kk:mm:ss", new Date(message.getMergedTimeSent())));
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java
new file mode 100644
index 00000000..2d919528
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/UserDecisionDialog.java
@@ -0,0 +1,70 @@
+package de.thedevstack.conversationsplus.ui.dialogs;
+
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import de.thedevstack.conversationsplus.enums.UserDecision;
+import de.thedevstack.conversationsplus.ui.listeners.UserDecisionListener;
+import eu.siacs.conversations.R;
+
+/**
+ * Created by tzur on 31.10.2015.
+ */
+public class UserDecisionDialog extends AbstractAlertDialog {
+ protected final UserDecisionListener listener;
+ protected final CheckBox rememberCheckBox;
+
+ public UserDecisionDialog(Activity context, int questionResourceId, UserDecisionListener userDecisionListener) {
+ super(context, "User Decision");
+ this.listener = userDecisionListener;
+
+ int viewId = R.layout.dialog_userdecision;
+ View view = context.getLayoutInflater().inflate(viewId, null);
+
+ ((TextView)view.findViewById(R.id.dlgUserDecQuestion)).setText(questionResourceId);
+ this.rememberCheckBox = (CheckBox) view.findViewById(R.id.dlgUserDecRemember);
+
+ this.setPositiveButton(R.string.cplus_yes, new PositiveOnClickListener());
+ this.setNegativeButton(R.string.cplus_no, new NegativeOnClickListener());
+ this.setView(view);
+ }
+
+ public void decide(UserDecision baseDecision) {
+ switch (baseDecision) {
+ case ALWAYS:
+ this.listener.onYes();
+ break;
+ case NEVER:
+ this.listener.onNo();
+ break;
+ case ASK:
+ this.show();
+ break;
+ }
+ }
+
+ class PositiveOnClickListener implements DialogInterface.OnClickListener {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ listener.onYes();
+ if (rememberCheckBox.isChecked()) {
+ listener.onRemember(UserDecision.ALWAYS);
+ }
+ }
+ }
+
+ class NegativeOnClickListener implements DialogInterface.OnClickListener {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ listener.onNo();
+ if (rememberCheckBox.isChecked()) {
+ listener.onRemember(UserDecision.NEVER);
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
new file mode 100644
index 00000000..3436d322
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
@@ -0,0 +1,186 @@
+package de.thedevstack.conversationsplus.ui.listeners;
+
+import android.app.PendingIntent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.widget.Toast;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.enums.UserDecision;
+import de.thedevstack.conversationsplus.exceptions.UiException;
+import de.thedevstack.conversationsplus.utils.FileHelper;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.ui.XmppActivity;
+
+/**
+ * Created by tzur on 31.10.2015.
+ */
+public class ResizePictureUserDecisionListener implements UserDecisionListener {
+ protected Uri uri;
+ protected final Conversation conversation;
+ protected final UiCallback<Message> callback;
+ protected final XmppConnectionService xmppConnectionService;
+ protected final Toast prepareFileToast;
+ protected final XmppActivity activity;
+
+ public ResizePictureUserDecisionListener(XmppActivity activity, Conversation conversation, XmppConnectionService xmppConnectionService) {
+ this.xmppConnectionService = xmppConnectionService;
+ this.conversation = conversation;
+ this.activity = activity;
+ this.prepareFileToast = Toast.makeText(ConversationsPlusApplication.getAppContext(), ConversationsPlusApplication.getInstance().getText(R.string.preparing_image), Toast.LENGTH_LONG);
+ this.callback = new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi,
+ Message object) {
+ hidePrepareFileToast();
+ }
+
+ @Override
+ public void success(Message message) {
+ ResizePictureUserDecisionListener.this.xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(int error, Message message) {
+ hidePrepareFileToast();
+ //TODO Find another way to display an error dialog
+ ResizePictureUserDecisionListener.this.activity.displayErrorDialog(error);
+ }
+
+ protected void hidePrepareFileToast() {
+ ResizePictureUserDecisionListener.this.activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ResizePictureUserDecisionListener.this.prepareFileToast.cancel();
+ }
+ });
+ }
+ };
+ }
+
+ public ResizePictureUserDecisionListener(XmppActivity activity, Conversation conversation, Uri uri, XmppConnectionService xmppConnectionService) {
+ this(activity, conversation, xmppConnectionService);
+ this.uri = uri;
+ }
+
+ public void setUri(Uri uri) {
+ this.uri = uri;
+ }
+
+ protected void showPrepareFileToast() {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ prepareFileToast.show();
+ }
+ });
+ }
+
+ @Override
+ public void onYes() {
+ this.showPrepareFileToast();
+ final Message message;
+ final boolean forceEncryption = ConversationsPlusPreferences.forceEncryption();
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
+ } else {
+ message = new Message(conversation, "", conversation.getNextEncryption());
+ }
+ message.setCounterpart(conversation.getNextCounterpart());
+ message.setType(Message.TYPE_IMAGE);
+ ConversationsPlusApplication.executeFileAdding(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Bitmap resizedAndRotatedImage = ImageUtil.resizeAndRotateImage(uri);
+ DownloadableFile file = FileBackend.compressImageAndCopyToPrivateStorage(message, resizedAndRotatedImage);
+ String filePath = file.getAbsolutePath();
+ long imageSize = file.getSize();
+ int imageWidth = resizedAndRotatedImage.getWidth();
+ int imageHeight = resizedAndRotatedImage.getHeight();
+ MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight);
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ xmppConnectionService.getPgpEngine().encrypt(message, callback);
+ } else {
+ callback.success(message);
+ }
+ } catch (final UiException e) {
+ Logging.e("pictureresizesending", "Error while sending resized picture. " + e.getMessage());
+ callback.error(e.getResId(), message);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onNo() {
+ this.showPrepareFileToast();
+ final Message message;
+ final boolean forceEncryption = ConversationsPlusPreferences.forceEncryption();
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
+ } else {
+ message = new Message(conversation, "", conversation.getNextEncryption());
+ }
+ message.setCounterpart(conversation.getNextCounterpart());
+ message.setType(Message.TYPE_IMAGE);
+ ConversationsPlusApplication.executeFileAdding(new Runnable() {
+ @Override
+ public void run() {
+ InputStream is = null;
+ try {
+ is = ConversationsPlusApplication.getInstance().getContentResolver().openInputStream(uri);
+ long imageSize = is.available();
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ int imageHeight = options.outHeight;
+ int imageWidth = options.outWidth;
+ String filePath = FileHelper.getRealPathFromUri(uri);
+ MessageUtil.updateMessageWithImageDetails(message, filePath, imageSize, imageWidth, imageHeight);
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
+ xmppConnectionService.getPgpEngine().encrypt(message, callback);
+ } else {
+ callback.success(message);
+ }
+ } catch (FileNotFoundException e) {
+ Logging.e("picturesending", "File not found to send not resized. " + e.getMessage());
+ callback.error(R.string.error_file_not_found, message);
+ } catch (IOException e) {
+ Logging.e("picturesending", "Error while sending not resized picture. " + e.getMessage());
+ callback.error(R.string.error_io_exception, message);
+ } finally {
+ if (null != is) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ Logging.w("picturesending", "Error while closing stream for sending not resized picture. " + e.getMessage());
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onRemember(UserDecision decision) {
+ ConversationsPlusPreferences.applyResizePicture(decision);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShareWithResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShareWithResizePictureUserDecisionListener.java
new file mode 100644
index 00000000..7455cf97
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShareWithResizePictureUserDecisionListener.java
@@ -0,0 +1,48 @@
+package de.thedevstack.conversationsplus.ui.listeners;
+
+import android.net.Uri;
+
+import java.util.List;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.XmppActivity;
+
+/**
+ * Created by tzur on 03.11.2015.
+ */
+public class ShareWithResizePictureUserDecisionListener extends ResizePictureUserDecisionListener {
+ protected final List<Uri> uris;
+
+ public ShareWithResizePictureUserDecisionListener(XmppActivity activity, Conversation conversation, XmppConnectionService xmppConnectionService, List<Uri> uris) {
+ super(activity, conversation, xmppConnectionService);
+ this.uris = uris;
+ }
+
+ @Override
+ public void onYes() {
+ if (null != this.uris && !this.uris.isEmpty()) {
+ for (Uri uri : this.uris) {
+ this.setUri(uri);
+ super.onYes();
+ }
+ }
+ this.finishSharing();
+ }
+
+ @Override
+ public void onNo() {
+ if (null != this.uris && !this.uris.isEmpty()) {
+ for (Uri uri : this.uris) {
+ this.setUri(uri);
+ super.onNo();
+ }
+ }
+ this.finishSharing();
+ }
+
+ protected void finishSharing() {
+ this.activity.switchToConversation(conversation, null, true);
+ this.activity.finish();
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java
new file mode 100644
index 00000000..1c16095c
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ShowResourcesListDialogListener.java
@@ -0,0 +1,47 @@
+package de.thedevstack.conversationsplus.ui.listeners;
+
+import android.content.Context;
+import android.view.View;
+
+import de.thedevstack.conversationsplus.ui.adapter.PresencesArrayAdapter;
+import de.thedevstack.conversationsplus.ui.dialogs.AbstractAlertDialog;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Contact;
+
+/**
+ * This listener shows the dialog with the resources of a contact.
+ * The resources are shown with the color of their current online mode.
+ * This listener implements OnClickListener and OnLongClickListener.
+ */
+public class ShowResourcesListDialogListener extends AbstractAlertDialog implements View.OnClickListener, View.OnLongClickListener {
+ private Contact contact;
+
+ public ShowResourcesListDialogListener(Context context, Contact contact) {
+ super(context, getTitle(context, contact));
+ this.contact = contact;
+ this.init();
+ }
+
+ private static final String getTitle(Context context, Contact contact) {
+ if (null != contact && null != contact.getJid() && null != contact.getJid().toBareJid()) {
+ int presenceCount = null != contact.getPresences() ? contact.getPresences().size() : 0;
+ return context.getString(R.string.dlg_resources_title, contact.getJid().toBareJid().toString(), presenceCount);
+ }
+ return null != contact ? contact.toString() : "";
+ }
+
+ protected void init() {
+ this.builder.setAdapter(new PresencesArrayAdapter(getContext(), this.contact.getPresences()), null);
+ }
+
+ @Override
+ public void onClick(View v) {
+ this.show();
+ }
+
+ @Override
+ public boolean onLongClick(View view) {
+ this.show();
+ return true;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/UserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/UserDecisionListener.java
new file mode 100644
index 00000000..fbee6290
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/UserDecisionListener.java
@@ -0,0 +1,12 @@
+package de.thedevstack.conversationsplus.ui.listeners;
+
+import de.thedevstack.conversationsplus.enums.UserDecision;
+
+/**
+ * Created by tzur on 31.10.2015.
+ */
+public interface UserDecisionListener {
+ void onYes();
+ void onNo();
+ void onRemember(UserDecision decision);
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/preferences/LogInformationPreference.java b/src/main/java/de/thedevstack/conversationsplus/ui/preferences/LogInformationPreference.java
new file mode 100644
index 00000000..5dcfc607
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/preferences/LogInformationPreference.java
@@ -0,0 +1,31 @@
+package de.thedevstack.conversationsplus.ui.preferences;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.Preference;
+import android.util.AttributeSet;
+
+import de.thedevstack.conversationsplus.ui.LogCatOutputActivity;
+
+/**
+ * Created by tzur on 07.10.2015.
+ */
+public class LogInformationPreference extends Preference {
+ public LogInformationPreference(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public LogInformationPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ }
+ public LogInformationPreference(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+ final Intent intent = new Intent(getContext(), LogCatOutputActivity.class);
+ getContext().startActivity(intent);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java
new file mode 100644
index 00000000..63dc320e
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java
@@ -0,0 +1,163 @@
+package de.thedevstack.conversationsplus.utils;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.Base64;
+import android.util.Base64OutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xmpp.pep.Avatar;
+
+/**
+ * This util provides access to saved avatars, creating avatars.
+ */
+public final class AvatarUtil {
+
+ /**
+ * Get the PEP Avatar.
+ * TODO: Why PEP Avatar?
+ * @param image the uri to the avatar's image
+ * @param size the image width/height to resize to
+ * @param format the format for the avatar
+ * @return the avatar
+ */
+ public static Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
+ try {
+ Avatar avatar = new Avatar();
+ Bitmap bm = ImageUtil.cropCenterSquare(image, size);
+ if (bm == null) {
+ return null;
+ }
+ ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
+ Base64OutputStream mBase64OutputSttream = new Base64OutputStream(
+ mByteArrayOutputStream, Base64.DEFAULT);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(
+ mBase64OutputSttream, digest);
+ if (!bm.compress(format, 75, mDigestOutputStream)) {
+ return null;
+ }
+ mDigestOutputStream.flush();
+ mDigestOutputStream.close();
+ avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ avatar.image = new String(mByteArrayOutputStream.toByteArray());
+ return avatar;
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns whether the avatar is cached or not.
+ * @param avatar the avatar to check the existance
+ * @return <code>true</code> if the file of the avatar exists, <code>false</code> otherwise
+ */
+ public static boolean isAvatarCached(Avatar avatar) {
+ File file = new File(getAvatarPath(avatar.getFilename()));
+ return file.exists();
+ }
+
+ /**
+ * Saves an avatar to the file system.
+ * All exceptions are silently ignored.
+ * TODO: Move real saving operation to FileBackend
+ * @param avatar the avatar to save
+ * @return <code>true</code> if the avatar was saved successfully, <code>false</code> otherwise.
+ */
+ public static boolean save(Avatar avatar) {
+ File file;
+ if (isAvatarCached(avatar)) {
+ file = new File(getAvatarPath(avatar.getFilename()));
+ } else {
+ String filename = getAvatarPath(avatar.getFilename());
+ file = new File(filename + ".tmp");
+ file.getParentFile().mkdirs();
+ OutputStream os = null;
+ try {
+ file.createNewFile();
+ os = new FileOutputStream(file);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
+ mDigestOutputStream.write(avatar.getImageAsBytes());
+ mDigestOutputStream.flush();
+ mDigestOutputStream.close();
+ String sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ if (sha1sum.equals(avatar.sha1sum)) {
+ file.renameTo(new File(filename));
+ } else {
+ Logging.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
+ file.delete();
+ return false;
+ }
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ } catch (NoSuchAlgorithmException e) {
+ return false;
+ } finally {
+ StreamUtil.close(os);
+ }
+ }
+ avatar.size = file.length();
+ return true;
+ }
+
+ /**
+ * Returns the avatar for an uri.
+ * @param avatar the avatar's uri
+ * @param size the height/width the avatar should have
+ * @return the bitmap of the uri
+ */
+ public static Bitmap getAvatar(String avatar, int size) {
+ if (avatar == null) {
+ return null;
+ }
+ Bitmap bm = ImageUtil.cropCenter(getAvatarUri(avatar), size, size);
+ if (bm == null) {
+ return null;
+ }
+ return bm;
+ }
+
+ /**
+ * Returns the path to an avatar
+ * @param avatar the name of the avatar.
+ * @return the path as string
+ */
+ public static String getAvatarPath(String avatar) {
+ return ConversationsPlusApplication.getInstance().getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
+ }
+
+ /**
+ * Returns the path to an avatar as an uri.
+ * @param avatar the name of the avatar
+ * @return the path as uri
+ */
+ public static Uri getAvatarUri(String avatar) {
+ return Uri.parse("file:" + getAvatarPath(avatar));
+ }
+
+ /**
+ * Avoid instantiation it's an helper class.
+ */
+ private AvatarUtil() {
+ // Static helper class
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/FileHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/FileHelper.java
new file mode 100644
index 00000000..5cba24b8
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/FileHelper.java
@@ -0,0 +1,82 @@
+package de.thedevstack.conversationsplus.utils;
+
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+
+/**
+ * Created by tzur on 30.10.2015.
+ */
+public final class FileHelper {
+
+ /**
+ * taken from: http://stackoverflow.com/a/29164361
+ * @param uri
+ * @return
+ */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static String getRealPathFromUriLollipop(Uri uri) {
+ String path = null;
+
+ String wholeID = DocumentsContract.getDocumentId(uri);
+
+ // Split at colon, use second item in the array
+ String id = wholeID.split(":")[1];
+
+ String[] column = { MediaStore.Images.Media.DATA };
+
+ // where id is equal to
+ String sel = MediaStore.Images.Media._ID + "=?";
+
+ Cursor cursor = ConversationsPlusApplication.getInstance().getContentResolver().
+ query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ column, sel, new String[]{ id }, null);
+
+ int columnIndex = cursor.getColumnIndex(column[0]);
+
+ if (cursor.moveToFirst()) {
+ path = cursor.getString(columnIndex);
+ }
+ cursor.close();
+ return path;
+ }
+
+ /**
+ * Get the real path from an Uri.
+ * @param uri the uri to convert to the real path
+ * @return the real path or <code>null</code>
+ */
+ public static String getRealPathFromUri(Uri uri) {
+ String path = null;
+ if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ return uri.getPath();
+ } else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ String[] projection = {MediaStore.MediaColumns.DATA};
+ Cursor metaCursor = ConversationsPlusApplication.getInstance().getContentResolver().query(uri,
+ projection, null, null, null);
+ if (metaCursor != null) {
+ try {
+ if (metaCursor.moveToFirst()) {
+ path = metaCursor.getString(0);
+ }
+ } finally {
+ metaCursor.close();
+ }
+ }
+ }
+ if (path == null) {
+ path = getRealPathFromUriLollipop(uri);
+ }
+ return path;
+ }
+
+ private FileHelper() {
+ // Utility class - do not instantiate
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
new file mode 100644
index 00000000..b28e6f1c
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java
@@ -0,0 +1,372 @@
+package de.thedevstack.conversationsplus.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.util.LruCache;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.exceptions.ImageResizeException;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.utils.ExifHelper;
+
+/**
+ * This util provides
+ */
+public final class ImageUtil {
+
+ private static int IMAGE_SIZE = 1920;
+ private static LruCache<String, Bitmap> BITMAP_CACHE;
+
+ /**
+ * Returns a bitmap from the cache.
+ * @see LruCache#get(Object) for details
+ * @param key the key of the bitmap to get
+ * @return the bitmap
+ */
+ public static Bitmap getBitmapFromCache(String key) {
+ return BITMAP_CACHE.get(key);
+ }
+
+ /**
+ * Adds a bitmap with the given key to the cache.
+ * @see LruCache#put(Object, Object) for details
+ * @param key the key to identify this bitmap
+ * @param bitmap the bitmap to cache
+ */
+ public static void addBitmapToCache(String key, Bitmap bitmap) {
+ BITMAP_CACHE.put(key, bitmap);
+ }
+
+ /**
+ * Removes the bitmap with given key from the cache.
+ * @param key the key of the bitmap to remove
+ */
+ public static void removeBitmapFromCache(String key) {
+ BITMAP_CACHE.remove(key);
+ }
+
+ /**
+ * Clears the cache.
+ * @see LruCache#evictAll() for more details.
+ */
+ public static void evictBitmapCache() {
+ BITMAP_CACHE.evictAll();
+ }
+
+ /**
+ * Initializes the bitmap cache.
+ * This has to be executed once on application start.
+ * @see LruCache#LruCache(int) for details
+ */
+ public static void initBitmapCache() {
+ Logging.i("Conversations+ImageUtil", "Initializing BitmapCache");
+ final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
+ final int cacheSize = maxMemory / 8;
+ BITMAP_CACHE = new LruCache<String, Bitmap>(cacheSize) {
+ @Override
+ protected int sizeOf(final String key, final Bitmap bitmap) {
+ return bitmap.getByteCount() / 1024;
+ }
+ };
+ }
+
+ /**
+ * Resizes a given bitmap and return a new and resized bitmap.
+ * The bitmap is only resized if either the width or the height of the original bitmap is smaller than the given size.
+ * @param originalBitmap the bitmap to resize
+ * @param size the size to scale to
+ * @return new and resized bitmap or the original bitmap if width and height are smaller than size
+ */
+ public static Bitmap resize(Bitmap originalBitmap, int size) {
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ if (Math.max(w, h) > size) {
+ int scalledW;
+ int scalledH;
+ if (w <= h) {
+ scalledW = (int) (w / ((double) h / size));
+ scalledH = size;
+ } else {
+ scalledW = size;
+ scalledH = (int) (h / ((double) w / size));
+ }
+ return Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
+ } else {
+ return originalBitmap;
+ }
+ }
+
+ /**
+ * Resizes and rotates an image given by uri and returns the bitmap.
+ * @param image the uri of the image to be resized and rotated
+ * @return resized and rotated bitmap
+ * @throws ImageResizeException
+ */
+ public static Bitmap resizeAndRotateImage(Uri image) throws ImageResizeException {
+ return ImageUtil.resizeAndRotateImage(image, 0);
+ }
+
+ /**
+ * Resizes and rotates an image given by uri and returns the bitmap.
+ * @param image the uri of the image to be resized and rotated
+ * @return resized and rotated bitmap
+ * @throws ImageResizeException
+ */
+ private static Bitmap resizeAndRotateImage(Uri image, int sampleSize) throws ImageResizeException {
+ InputStream imageInputStream = null;
+ try {
+ imageInputStream = StreamUtil.openInputStreamFromContentResolver(image);
+ Bitmap originalBitmap;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ int inSampleSize = (int) Math.pow(2, sampleSize);
+ Logging.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
+ options.inSampleSize = inSampleSize;
+ originalBitmap = BitmapFactory.decodeStream(imageInputStream, null, options);
+ imageInputStream.close();
+ if (originalBitmap == null) {
+ throw new ImageResizeException(R.string.error_not_an_image_file);
+ }
+ Bitmap scaledBitmap = ImageUtil.resize(originalBitmap, IMAGE_SIZE);
+ int rotation = ImageUtil.getRotation(image);
+ if (rotation > 0) {
+ scaledBitmap = ImageUtil.rotate(scaledBitmap, rotation);
+ }
+
+ return scaledBitmap;
+ } catch (FileNotFoundException e) {
+ throw new ImageResizeException(R.string.error_file_not_found);
+ } catch (IOException e) {
+ throw new ImageResizeException(R.string.error_io_exception);
+ } catch (OutOfMemoryError e) {
+ ++sampleSize;
+ if (sampleSize <= 3) {
+ return resizeAndRotateImage(image, sampleSize);
+ } else {
+ throw new ImageResizeException(R.string.error_out_of_memory);
+ }
+ } finally {
+ StreamUtil.close(imageInputStream);
+ }
+ }
+
+ /**
+ * Returns the rotation from the exif information of an image identified with the given uri.
+ * The orientation is retrieved by parsing the stream of the image.
+ * FileNotFoundException is silently ignored.
+ * @param image the uri of the image to get the rotation
+ * @return the rotation value for the image, <code>0</code> if the file cannot be found.
+ */
+ public static int getRotation(Uri image) {
+ InputStream is = null;
+ try {
+ is = StreamUtil.openInputStreamFromContentResolver(image);
+ return ExifHelper.getOrientation(is);
+ } catch (FileNotFoundException e) {
+ return 0;
+ } finally {
+ StreamUtil.close(is);
+ }
+ }
+
+ /**
+ * Returns a thumbnail for a bitmap in a message.
+ * @param message the message to get the thumbnail for
+ * @param size the size to resize the original image to
+ * @param cacheOnly whether only cached images should be returned or not
+ * @return the resized thumbail
+ * @throws FileNotFoundException if the original image does not exist anymore or an IOException occurs.
+ */
+ public static Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
+ throws FileNotFoundException {
+ Bitmap thumbnail = ImageUtil.getBitmapFromCache(message.getUuid());
+ if ((thumbnail == null) && (!cacheOnly)) {
+ File file = FileBackend.getFile(message);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(file, size);
+ Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
+ if (fullsize == null) {
+ throw new FileNotFoundException();
+ }
+ thumbnail = resize(fullsize, size);
+ thumbnail = rotate(thumbnail, file.getAbsolutePath());
+
+ ImageUtil.addBitmapToCache(message.getUuid(), thumbnail);
+ }
+ return thumbnail;
+ }
+
+ /**
+ * Rotates an bitmap. Only the values 90°, 180° and 270° are considered to rotate the image.
+ * The orientation information is read using the ExifInterface.
+ * @param original the original bitmap
+ * @param srcPath the path to the original bitmap (used to read the exif information)
+ * @return rotated bitmap, or original bitmap if criteria are not met
+ * @throws IOException
+ */
+ public static Bitmap rotate(Bitmap original, String srcPath) {
+ try {
+ ExifInterface exif = new ExifInterface(srcPath);
+ int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+ int rotation = 0;
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ rotation = 90;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ rotation = 180;
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ rotation = 270;
+ break;
+ }
+ if (rotation > 0) {
+ return rotate(original, rotation);
+ }
+ } catch (IOException e) {
+ Logging.w("filebackend", "Error while rotating image, returning original (" + e.getMessage() + ")");
+ }
+ return original;
+ }
+
+ /**
+ * Rotates a bitmap with given degrees.
+ * @param bitmap the bitmap to be rotated
+ * @param degree the degrees to rotate the bitmap
+ * @return a newly created bitmap
+ */
+ public static Bitmap rotate(Bitmap bitmap, int degree) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ Matrix mtx = new Matrix();
+ mtx.postRotate(degree);
+ return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
+ }
+
+
+ public static Bitmap cropCenterSquare(Uri image, int size) {
+ if (image == null) {
+ return null;
+ }
+ InputStream is = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(image, size);
+ is = StreamUtil.openInputStreamFromContentResolver(image);
+ Bitmap input = BitmapFactory.decodeStream(is, null, options);
+ if (input == null) {
+ return null;
+ } else {
+ int rotation = getRotation(image);
+ if (rotation > 0) {
+ input = rotate(input, rotation);
+ }
+ return cropCenterSquare(input, size);
+ }
+ } catch (FileNotFoundException e) {
+ return null;
+ } finally {
+ StreamUtil.close(is);
+ }
+ }
+
+ public static Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
+ if (image == null) {
+ return null;
+ }
+ InputStream is = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(image,Math.max(newHeight, newWidth));
+ is = StreamUtil.openInputStreamFromContentResolver(image);
+ Bitmap source = BitmapFactory.decodeStream(is, null, options);
+ if (source == null) {
+ return null;
+ }
+ int sourceWidth = source.getWidth();
+ int sourceHeight = source.getHeight();
+ float xScale = (float) newWidth / sourceWidth;
+ float yScale = (float) newHeight / sourceHeight;
+ float scale = Math.max(xScale, yScale);
+ float scaledWidth = scale * sourceWidth;
+ float scaledHeight = scale * sourceHeight;
+ float left = (newWidth - scaledWidth) / 2;
+ float top = (newHeight - scaledHeight) / 2;
+
+ RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
+ Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(dest);
+ canvas.drawBitmap(source, null, targetRect, null);
+ return dest;
+ } catch (FileNotFoundException e) {
+ return null;
+ } finally {
+ StreamUtil.close(is);
+ }
+ }
+
+ public static Bitmap cropCenterSquare(Bitmap input, int size) {
+ int w = input.getWidth();
+ int h = input.getHeight();
+
+ float scale = Math.max((float) size / h, (float) size / w);
+
+ float outWidth = scale * w;
+ float outHeight = scale * h;
+ float left = (size - outWidth) / 2;
+ float top = (size - outHeight) / 2;
+ RectF target = new RectF(left, top, left + outWidth, top + outHeight);
+
+ Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+ canvas.drawBitmap(input, null, target, null);
+ return output;
+ }
+
+ public static int calcSampleSize(Uri image, int size) throws FileNotFoundException {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(StreamUtil.openInputStreamFromContentResolver(image), null, options);
+ return calcSampleSize(options, size);
+ }
+
+ public static int calcSampleSize(File image, int size) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(image.getAbsolutePath(), options);
+ return calcSampleSize(options, size);
+ }
+
+ public static int calcSampleSize(BitmapFactory.Options options, int size) {
+ int height = options.outHeight;
+ int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > size || width > size) {
+ int halfHeight = height / 2;
+ int halfWidth = width / 2;
+
+ while ((halfHeight / inSampleSize) > size
+ && (halfWidth / inSampleSize) > size) {
+ inSampleSize *= 2;
+ }
+ }
+ return inSampleSize;
+ }
+
+ private ImageUtil() {
+ // Static helper class
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
new file mode 100644
index 00000000..81f5b843
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
@@ -0,0 +1,94 @@
+package de.thedevstack.conversationsplus.utils;
+
+import android.graphics.BitmapFactory;
+
+import java.net.URL;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import eu.siacs.conversations.entities.DownloadableFile;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.persistance.FileBackend;
+
+/**
+ * Created by tzur on 15.12.2015.
+ */
+public final class MessageUtil {
+ public static boolean wasHighlightedOrPrivate(final Message message) {
+ final String nick = message.getConversation().getMucOptions().getActualNick();
+ final Pattern highlight = generateNickHighlightPattern(nick);
+ if (message.getBody() == null || nick == null) {
+ return false;
+ }
+ final Matcher m = highlight.matcher(message.getBody());
+ return (m.find() || message.getType() == Message.TYPE_PRIVATE);
+ }
+
+ private static Pattern generateNickHighlightPattern(final String nick) {
+ // We expect a word boundary, i.e. space or start of string, followed by
+ // the
+ // nick (matched in case-insensitive manner), followed by optional
+ // punctuation (for example "bob: i disagree" or "how are you alice?"),
+ // followed by another word boundary.
+ return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
+ Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
+ }
+
+ public static void updateMessageWithImageDetails(Message message, String filePath, long size, int imageWidth, int imageHeight) {
+ message.setRelativeFilePath(filePath);
+ MessageUtil.updateMessageBodyWithImageParams(message, size, imageWidth, imageHeight);
+ }
+
+ public static void updateFileParams(Message message) {
+ updateFileParams(message, null);
+ }
+
+ public static void updateFileParams(Message message, URL url) {
+ DownloadableFile file = FileBackend.getFile(message);
+ int imageWidth = -1;
+ int imageHeight = -1;
+ if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ imageHeight = options.outHeight;
+ imageWidth = options.outWidth;
+ }
+
+ MessageUtil.updateMessageBodyWithFileParams(message, url, file.getSize(), imageWidth, imageHeight);
+ }
+
+ private static void updateMessageBodyWithFileParams(Message message, URL url, long fileSize, int imageWidth, int imageHeight) {
+ message.setBody(MessageUtil.getMessageBodyWithImageParams(url, fileSize, imageWidth, imageHeight));
+ }
+
+ private static void updateMessageBodyWithImageParams(Message message, long size, int imageWidth, int imageHeight) {
+ MessageUtil.updateMessageBodyWithImageParams(message, null, size, imageWidth, imageHeight);
+ }
+
+ private static void updateMessageBodyWithImageParams(Message message, URL url, long size, int imageWidth, int imageHeight) {
+ message.setBody(MessageUtil.getMessageBodyWithImageParams(url, size, imageWidth, imageHeight));
+ }
+
+ private static String getMessageBodyWithImageParams(URL url, long size, int imageWidth, int imageHeight) {
+ StringBuilder sb = new StringBuilder();
+ if (null != url) {
+ sb.append(url.toString());
+ sb.append('|');
+ }
+ sb.append(size);
+ if (-1 < imageWidth) {
+ sb.append('|');
+ sb.append(imageWidth);
+ }
+ if (-1 < imageHeight) {
+ sb.append('|');
+ sb.append(imageHeight);
+ }
+ return sb.toString();
+ }
+
+ private MessageUtil() {
+ // Static helper class
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java
new file mode 100644
index 00000000..729bdf11
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java
@@ -0,0 +1,63 @@
+package de.thedevstack.conversationsplus.utils;
+
+import android.net.Uri;
+
+import java.io.Closeable;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+
+/**
+ * Util to handle streams.
+ */
+public final class StreamUtil {
+
+ /**
+ * Opens an InputStream from Uri using the ContentResolver from application.
+ * @see android.content.ContentResolver#openInputStream(Uri)
+ * @param uri the uri to open
+ * @return the InputStream for given uri
+ * @throws FileNotFoundException if the provided URI could not be opened.
+ */
+ public static InputStream openInputStreamFromContentResolver(Uri uri) throws FileNotFoundException {
+ return ConversationsPlusApplication.getInstance().getContentResolver().openInputStream(uri);
+ }
+
+ /**
+ * Closes a stream.
+ * IOException is silently ignored.
+ * @param stream the stream to close
+ */
+ public static void close(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Closes a socket.
+ * IOException is silently ignored.
+ * @param socket the socket to close
+ */
+ public static void close(Socket socket) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Avoid instantiation of util class.
+ */
+ private StreamUtil() {
+ // Static helper class
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java
new file mode 100644
index 00000000..84ce200a
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java
@@ -0,0 +1,52 @@
+package de.thedevstack.conversationsplus.utils;
+
+import de.thedevstack.android.logcat.Logging;
+import eu.siacs.conversations.services.XmppConnectionService;
+
+/**
+ * Helper class to avoid passing the xmppConnectionService to everywhere just to update the UI.
+ * TODO: Make even this helper class work without XmppConnectionService
+ */
+public class UiUpdateHelper {
+ private static XmppConnectionService xmppConnectionService;
+
+ public static void initXmppConnectionService(XmppConnectionService xmppConnectionService) {
+ if (null == UiUpdateHelper.xmppConnectionService) {
+ UiUpdateHelper.xmppConnectionService = xmppConnectionService;
+ } else {
+ Logging.e("UiUpdateHelper", "XMPP Connection Service already instantiated.");
+ }
+ }
+
+ public static void updateConversationUi() {
+ if (null != UiUpdateHelper.xmppConnectionService) {
+ UiUpdateHelper.xmppConnectionService.updateConversationUi();
+ } else {
+ Logging.e("UiUpdateHelper", "XMPP Connection Service not initialized. Conversation Ui not updated.");
+ }
+ }
+
+ public static void updateAccountUi() {
+ if (null != UiUpdateHelper.xmppConnectionService) {
+ UiUpdateHelper.xmppConnectionService.updateAccountUi();
+ } else {
+ Logging.e("UiUpdateHelper", "XMPP Connection Service not initialized. Account Ui not updated.");
+ }
+ }
+
+ public static void updateRosterUi() {
+ if (null != UiUpdateHelper.xmppConnectionService) {
+ UiUpdateHelper.xmppConnectionService.updateRosterUi();
+ } else {
+ Logging.e("UiUpdateHelper", "XMPP Connection Service not initialized. Roster Ui not updated.");
+ }
+ }
+
+ public static void updateMucRosterUi() {
+ if (null != UiUpdateHelper.xmppConnectionService) {
+ UiUpdateHelper.xmppConnectionService.updateMucRosterUi();
+ } else {
+ Logging.e("UiUpdateHelper", "XMPP Connection Service not initialized. MUC Roster Ui not updated.");
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java
new file mode 100644
index 00000000..d4a555f2
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java
@@ -0,0 +1,34 @@
+package de.thedevstack.conversationsplus.utils;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.XmppConnection;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
+import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
+
+/**
+ * Created by tzur on 09.01.2016.
+ */
+public class XmppSendUtil {
+ public static void sendIqPacket(Account account, IqPacket packet, OnIqPacketReceived callback) {
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendIqPacket(packet, callback);
+ }
+ }
+
+ public static void sendPresencePacket(Account account, PresencePacket packet) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendPresencePacket(packet);
+ }
+ }
+
+ public static void sendMessagePacket(Account account, MessagePacket packet) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendMessagePacket(packet);
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java
new file mode 100644
index 00000000..46e2e642
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketGenerator.java
@@ -0,0 +1,124 @@
+package de.thedevstack.conversationsplus.xmpp.avatar;
+
+import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketGenerator;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.pep.Avatar;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+/**
+ * Generates the IQ Packets for handling Avatars
+ * as defined in XEP-0084.
+ * @see {@link http://xmpp.org/extensions/xep-0084.html}
+ */
+public final class AvatarPacketGenerator {
+ public static final String NAMESPACE_AVATAR_DATA = "urn:xmpp:avatar:data";
+ public static final String NAMESPACE_AVATAR_METADATA = "urn:xmpp:avatar:metadata";
+
+ /**
+ * Generates an IqPacket for publishing avatar data.
+ * The attributes from and id are not set in here - this is added while sending the packet.
+ * <pre>
+ * <iq type='set'>
+ * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ * <publish node='urn:xmpp:avatar:data'>
+ * <item id='111f4b3c50d7b0df729d299bc6f8e9ef9066971f'>
+ * <data xmlns='urn:xmpp:avatar:data'>
+ * qANQR1DBwU4DX7jmYZnncm...
+ * </data>
+ * </item>
+ * </publish>
+ * </pubsub>
+ * </iq>
+ * </pre>
+ * @param avatar the avatar to publish
+ * @return the IqPacket
+ */
+ public static IqPacket generatePublishAvatarPacket(Avatar avatar) {
+ final Element item = new Element("item");
+ item.setAttribute("id", avatar.sha1sum);
+ final Element data = item.addChild("data", NAMESPACE_AVATAR_DATA);
+ data.setContent(avatar.image);
+ return PubSubPacketGenerator.generatePubSubPublishPacket(NAMESPACE_AVATAR_DATA, item);
+ }
+
+ /**
+ * Generates an IqPacket to retrieve avatar data.
+ * The attributes from and id are not set in here - this is added while sending the packet.
+ * <pre>
+ * <iq type='get'>
+ * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ * <items node='urn:xmpp:avatar:data'>
+ * <item id='111f4b3c50d7b0df729d299bc6f8e9ef9066971f'/>
+ * </items>
+ * </pubsub>
+ * </iq>
+ * </pre>
+ * @param avatar the avatar to retrieve
+ * @return the IqPacket
+ */
+ public static IqPacket generateRetrieveAvatarPacket(Avatar avatar) {
+ final Element item = new Element("item");
+ item.setAttribute("id", avatar.sha1sum);
+ final IqPacket packet = PubSubPacketGenerator.generatePubSubRetrievePacket(NAMESPACE_AVATAR_DATA, item);
+ packet.setTo(avatar.owner);
+ return packet;
+ }
+
+ /**
+ * Generates an IqPacket to publish metadata for an avatar.
+ * The attributes from and id are not set in here - this is added while sending the packet.
+ * <pre>
+ * <iq type='set'>
+ * <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ * <publish node='urn:xmpp:avatar:metadata'>
+ * <item id='111f4b3c50d7b0df729d299bc6f8e9ef9066971f'>
+ * <metadata xmlns='urn:xmpp:avatar:metadata'>
+ * <info bytes='12345'
+ * id='111f4b3c50d7b0df729d299bc6f8e9ef9066971f'
+ * height='64'
+ * type='image/png'
+ * width='64'/>
+ * </metadata>
+ * </item>
+ * </publish>
+ * </pubsub>
+ * </iq>
+ * </pre>
+ * @param avatar the avatar to publish the metadata
+ * @return the IqPacket
+ */
+ public static IqPacket generatePublishAvatarMetadataPacket(Avatar avatar) {
+ final Element item = new Element("item");
+ item.setAttribute("id", avatar.sha1sum);
+ final Element metadata = item.addChild("metadata", NAMESPACE_AVATAR_METADATA);
+ final Element info = metadata.addChild("info");
+ info.setAttribute("bytes", avatar.size);
+ info.setAttribute("id", avatar.sha1sum);
+ info.setAttribute("height", avatar.height);
+ info.setAttribute("width", avatar.height);
+ info.setAttribute("type", avatar.type);
+ return PubSubPacketGenerator.generatePubSubPublishPacket(NAMESPACE_AVATAR_METADATA, item);
+ }
+
+ /**
+ * Generates an IqPacket to retrieve metadata of an avatar.
+ * The attributes from and id are not set in here - this is added while sending the packet.
+ * @param to the Jid to deliver the metadata to
+ * @return the IqPacket
+ */
+ public static IqPacket generateRetrieveAvatarMetadataPacket(Jid to) {
+ final IqPacket packet = PubSubPacketGenerator.generatePubSubRetrievePacket(NAMESPACE_AVATAR_METADATA, null);
+ if (to != null) {
+ packet.setTo(to);
+ }
+ return packet;
+ }
+
+ /**
+ * Helper class - private constructor to avoid instantiation
+ */
+ private AvatarPacketGenerator() {
+ // avoid instantiation
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java
new file mode 100644
index 00000000..48045a3c
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/avatar/AvatarPacketParser.java
@@ -0,0 +1,29 @@
+package de.thedevstack.conversationsplus.xmpp.avatar;
+
+import de.thedevstack.conversationsplus.xmpp.pubsub.PubSubPacketParser;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+/**
+ * Parses the IQ Packets for handling Avatars
+ * as defined in XEP-0084.
+ * @see {@link http://xmpp.org/extensions/xep-0084.html}
+ */
+public class AvatarPacketParser {
+ /**
+ * Extracts the base64 encoded avatar data from an IqPacket.
+ * @param packet the IqPacket to be parsed.
+ * @return base64 encoded avatar data
+ */
+ public static String parseAvatarData(IqPacket packet) {
+ Element items = PubSubPacketParser.findItems(packet);
+ String base64Avatar = null;
+ if (null != items) {
+ Element item = items.findChild("item");
+ if (null != item) {
+ base64Avatar = item.findChildContent("data", AvatarPacketGenerator.NAMESPACE_AVATAR_DATA);
+ }
+ }
+ return base64Avatar;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacket.java
new file mode 100644
index 00000000..961277cb
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacket.java
@@ -0,0 +1,33 @@
+package de.thedevstack.conversationsplus.xmpp.pubsub;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by tzur on 15.01.2016.
+ */
+public class PubSubPacket extends IqPacket {
+ public static final String NAMESPACE = "http://jabber.org/protocol/pubsub";
+ public static final String ELEMENT_NAME = "pubsub";
+ private Element pubSubElement;
+
+ public PubSubPacket(IqPacket.TYPE type) {
+ super(type);
+ this.pubSubElement = super.addChild(PubSubPacket.ELEMENT_NAME, PubSubPacket.NAMESPACE);
+ }
+
+ @Override
+ public Element addChild(Element child) {
+ return this.pubSubElement.addChild(child);
+ }
+
+ @Override
+ public Element addChild(String name) {
+ return this.pubSubElement.addChild(name);
+ }
+
+ @Override
+ public Element addChild(String name, String xmlns) {
+ return this.pubSubElement.addChild(name, xmlns);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketGenerator.java
new file mode 100644
index 00000000..398ec032
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketGenerator.java
@@ -0,0 +1,32 @@
+package de.thedevstack.conversationsplus.xmpp.pubsub;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by tzur on 15.01.2016.
+ */
+public final class PubSubPacketGenerator {
+
+ public static PubSubPacket generatePubSubPublishPacket(String nodeName, Element item) {
+ final PubSubPacket pubsub = new PubSubPacket(IqPacket.TYPE.SET);
+ final Element publish = pubsub.addChild("publish");
+ publish.setAttribute("node", nodeName);
+ publish.addChild(item);
+ return pubsub;
+ }
+
+ public static PubSubPacket generatePubSubRetrievePacket(String nodeName, Element item) {
+ final PubSubPacket pubsub = new PubSubPacket(IqPacket.TYPE.GET);
+ final Element items = pubsub.addChild("items");
+ items.setAttribute("node", nodeName);
+ if (item != null) {
+ items.addChild(item);
+ }
+ return pubsub;
+ }
+
+ private PubSubPacketGenerator() {
+ // Avoid instantiation
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketParser.java
new file mode 100644
index 00000000..394fb5b2
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/pubsub/PubSubPacketParser.java
@@ -0,0 +1,27 @@
+package de.thedevstack.conversationsplus.xmpp.pubsub;
+
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by tzur on 15.01.2016.
+ */
+public class PubSubPacketParser {
+ public static Element findPubSubPacket(IqPacket packet){
+ if (null == packet) {
+ return null;
+ }
+ return packet.findChild("pubsub", "http://jabber.org/protocol/pubsub");
+ }
+
+ public static Element findItemsFromPubSubElement(Element pubSubPacket) {
+ if (null == pubSubPacket) {
+ return null;
+ }
+ return pubSubPacket.findChild("items");
+ }
+
+ public static Element findItems(IqPacket packet) {
+ return PubSubPacketParser.findItemsFromPubSubElement(PubSubPacketParser.findPubSubPacket(packet));
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java
new file mode 100644
index 00000000..bdf0f4b0
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java
@@ -0,0 +1,33 @@
+package de.thedevstack.conversationsplus.xmpp.stanzas;
+
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+/**
+ * Created by tzur on 15.01.2016.
+ */
+public final class IqPacketGenerator {
+
+ private static IqPacket generateIqPacket(IqPacket.TYPE type) {
+ return new IqPacket(type);
+ }
+
+ public static IqPacket generateIqSetPacket() {
+ return generateIqPacket(IqPacket.TYPE.SET);
+ }
+
+ public static IqPacket generateIqGetPacket() {
+ return generateIqPacket(IqPacket.TYPE.GET);
+ }
+
+ public static IqPacket generateIqResultPacket() {
+ return generateIqPacket(IqPacket.TYPE.RESULT);
+ }
+
+ public static IqPacket generateIqErrorPacket() {
+ return generateIqPacket(IqPacket.TYPE.ERROR);
+ }
+
+ private IqPacketGenerator() {
+ // avoid Instantiation
+ }
+}
diff --git a/src/main/java/de/tzur/conversations/Settings.java b/src/main/java/de/tzur/conversations/Settings.java
new file mode 100644
index 00000000..5d64f084
--- /dev/null
+++ b/src/main/java/de/tzur/conversations/Settings.java
@@ -0,0 +1,76 @@
+package de.tzur.conversations;
+
+import android.content.SharedPreferences;
+
+import de.thedevstack.android.logcat.Logging;
+
+/**
+ * This class is used to provide access to settings which have to be accessed frequently.
+ * Every setting in this class has to be updated using @see SettingsActivity#onSharedPreferenceChanged.
+ */
+public abstract class Settings {
+
+ /**
+ * Initializes the settings provided via this static class.
+ * @param preferences the shared preferences of the app.
+ */
+ public static void initSettingsClassWithPreferences(SharedPreferences preferences) {
+ Logging.d("SETTING", "Initializing settings");
+ String[] preferenceNames = { "parse_emoticons", "send_button_status", "led_notification_color", "auto_download_file_wlan", "auto_download_file_link", "confirm_messages_list" };
+ for (String name : preferenceNames) {
+ Settings.synchronizeSettingsClassWithPreferences(preferences, name);
+ }
+ }
+
+ /**
+ * Synchronizes the setting value in this class on settings update in SettingsActivity.
+ * @param preferences the shared preferences of the app.
+ * @param name the name of the setting to synchronize.
+ */
+ public static void synchronizeSettingsClassWithPreferences(SharedPreferences preferences, String name) {
+ Logging.d("SETTING", "Synchronizing settings");
+ switch (name) {
+ case "parse_emoticons":
+ Settings.PARSE_EMOTICONS = preferences.getBoolean(name, Settings.PARSE_EMOTICONS);
+ break;
+ case "send_button_status":
+ Settings.SHOW_ONLINE_STATUS = preferences.getBoolean(name, Settings.SHOW_ONLINE_STATUS);
+ break;
+ case "led_notify_color":
+ Settings.LED_COLOR = preferences.getInt(name, Settings.LED_COLOR);
+ break;
+ case "confirm_messages_list":
+ int iPref = Settings.CONFIRM_MESSAGE_RECEIVED && Settings.CONFIRM_MESSAGE_READ ? 2 : Settings.CONFIRM_MESSAGE_RECEIVED ? 1 : 0;
+ try {
+ iPref = Integer.valueOf(preferences.getString(name, new Integer(iPref).toString()));
+ } catch (NumberFormatException e) {
+ // ignored, fallback-value set above
+ }
+ Settings.CONFIRM_MESSAGE_RECEIVED = iPref >= 1;
+ Settings.CONFIRM_MESSAGE_READ = iPref >= 2;
+ break;
+ }
+ }
+
+ /**
+ * Boolean if emoticons should be parsed to emoticons or not.
+ */
+ public static boolean PARSE_EMOTICONS = true;
+ /**
+ * Boolean if online status should be shown or not.
+ */
+ public static boolean SHOW_ONLINE_STATUS = true;
+ /**
+ * LED Color
+ */
+ public static int LED_COLOR = 0xffffffff;
+ /**
+ * Boolean if confirm received messages
+ */
+ public static boolean CONFIRM_MESSAGE_RECEIVED = true;
+ /**
+ * Boolean if confirm read message
+ */
+ public static boolean CONFIRM_MESSAGE_READ = true;
+
+}
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index e39cd654..25f08f30 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations;
import android.graphics.Bitmap;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
public final class Config {
@@ -27,7 +28,7 @@ public final class Config {
}
public static boolean supportOmemo() {
- return (ENCRYPTION_MASK & OMEMO) != 0;
+ return ConversationsPlusPreferences.omemoEnabled();
}
public static boolean multipleEncryptionChoices() {
@@ -45,7 +46,6 @@ public final class Config {
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true
- public static final boolean FORCE_ORBOT = false; // always use TOR
public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;
public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification
@@ -65,7 +65,7 @@ public final class Config {
public static final boolean CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND = false;
public static final int AVATAR_SIZE = 192;
- public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP;
+ public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG;
public static final int IMAGE_SIZE = 1920;
public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
@@ -74,9 +74,12 @@ public final class Config {
public static final int MESSAGE_MERGE_WINDOW = 20;
+ public static final boolean UTF8_EMOTICONS = false;
+
public static final int PAGE_SIZE = 50;
public static final int MAX_NUM_PAGES = 3;
+ public static final int PROGRESS_UI_UPDATE_INTERVAL = 750;
public static final int REFRESH_UI_INTERVAL = 500;
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrService.java b/src/main/java/eu/siacs/conversations/crypto/OtrService.java
index 1804704e..4ddf51fb 100644
--- a/src/main/java/eu/siacs/conversations/crypto/OtrService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/OtrService.java
@@ -1,7 +1,5 @@
package eu.siacs.conversations.crypto;
-import android.util.Log;
-
import net.java.otr4j.OtrEngineHost;
import net.java.otr4j.OtrException;
import net.java.otr4j.OtrPolicy;
@@ -26,6 +24,8 @@ import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
@@ -110,7 +110,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
mXmppConnectionService.updateConversationUi();
}
} catch (InvalidJidException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
}
}
@@ -151,7 +151,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
this.saveKey();
mXmppConnectionService.databaseBackend.updateAccount(account);
} catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"error generating key pair " + e.getMessage());
}
}
@@ -185,7 +185,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
Jid jid = Jid.fromSessionID(session);
Conversation conversation = mXmppConnectionService.find(account,jid);
if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
- if (mXmppConnectionService.sendChatStates()) {
+ if (ConversationsPlusPreferences.chatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
}
}
@@ -217,7 +217,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void showError(SessionID arg0, String arg1) throws OtrException {
- Log.d(Config.LOGTAG,"show error");
+ Logging.d(Config.LOGTAG,"show error");
}
@Override
@@ -252,7 +252,8 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void unreadableMessageReceived(SessionID session) throws OtrException {
- Log.d(Config.LOGTAG,"unreadable message received");
+ Logging.d(Config.LOGTAG,"unreadable message received");
+ // Hier update des contents fuer FS#96
sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
}
@@ -266,8 +267,8 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
.generateOtrError(jid, id, errorText);
packet.setFrom(account.getJid());
mXmppConnectionService.sendMessagePacket(account,packet);
- Log.d(Config.LOGTAG,packet.toString());
- Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()
+ Logging.d(Config.LOGTAG,packet.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()
+": unreadable OTR message in "+conversation.getName());
}
} catch (InvalidJidException e) {
@@ -282,7 +283,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void verify(SessionID id, String fingerprint, boolean approved) {
- Log.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
+ Logging.d(Config.LOGTAG,"OtrService.verify("+id.toString()+","+fingerprint+","+String.valueOf(approved)+")");
try {
final Jid jid = Jid.fromSessionID(id);
Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
index ed67dc65..5afbe5c4 100644
--- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
@@ -2,15 +2,15 @@ package eu.siacs.conversations.crypto;
import android.app.PendingIntent;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.UiCallback;
-
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.UiCallback;
+
public class PgpDecryptionService {
private final XmppConnectionService xmppConnectionService;
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
index 0afcb9e1..0e79ea2c 100644
--- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
+++ b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
@@ -17,6 +17,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -60,7 +63,8 @@ public class PgpEngine {
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
if (message.trusted()
&& message.treatAsDownloadable() != Message.Decision.NEVER
- && manager.getAutoAcceptFileSize() > 0) {
+ && ConversationsPlusPreferences.autoDownloadFileLink()
+ && ConversationsPlusPreferences.autoAcceptFileSize() > 0) {
manager.createNewDownloadConnection(message);
}
mXmppConnectionService.updateMessage(message);
@@ -84,10 +88,8 @@ public class PgpEngine {
});
} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
try {
- final DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
- final DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
+ final DownloadableFile inputFile = FileBackend.getFile(message, false);
+ final DownloadableFile outputFile = FileBackend.getFile(message, true);
outputFile.getParentFile().mkdirs();
outputFile.createNewFile();
InputStream is = new FileInputStream(inputFile);
@@ -101,7 +103,7 @@ public class PgpEngine {
OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS:
URL url = message.getFileParams().url;
- mXmppConnectionService.getFileBackend().updateFileParams(message,url);
+ MessageUtil.updateFileParams(message, url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
PgpEngine.this.mXmppConnectionService
.updateMessage(message);
@@ -167,7 +169,7 @@ public class PgpEngine {
String[] lines = os.toString().split("\n");
for (int i = 2; i < lines.length - 1; ++i) {
if (!lines[i].contains("Version")) {
- encryptedMessageBody.append(lines[i].trim());
+ encryptedMessageBody.append(lines[i]);
}
}
message.setEncryptedBody(encryptedMessageBody
@@ -191,10 +193,8 @@ public class PgpEngine {
});
} else {
try {
- DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
- DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
+ DownloadableFile inputFile = FileBackend.getFile(message, true);
+ DownloadableFile outputFile = FileBackend.getFile(message, false);
outputFile.getParentFile().mkdirs();
outputFile.createNewFile();
final InputStream is = new FileInputStream(inputFile);
@@ -212,7 +212,7 @@ public class PgpEngine {
} catch (IOException ignored) {
//ignored
}
- FileBackend.close(os);
+ StreamUtil.close(os);
callback.success(message);
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
@@ -249,7 +249,7 @@ public class PgpEngine {
pgpSig.append("-----BEGIN PGP SIGNATURE-----");
pgpSig.append('\n');
pgpSig.append('\n');
- pgpSig.append(signature.replace("\n", "").trim());
+ pgpSig.append(signature.replace("\n", ""));
pgpSig.append('\n');
pgpSig.append("-----END PGP SIGNATURE-----");
Intent params = new Intent();
@@ -329,7 +329,7 @@ public class PgpEngine {
sig = false;
} else {
if (!line.contains("Version")) {
- signatureBuilder.append(line.trim());
+ signatureBuilder.append(line);
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index cc5c2491..d8287aff 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -1,211 +1,36 @@
package eu.siacs.conversations.crypto.axolotl;
-import android.os.Bundle;
-import android.security.KeyChain;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.util.Log;
-import android.util.Pair;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.IdentityKey;
-import org.whispersystems.libaxolotl.IdentityKeyPair;
-import org.whispersystems.libaxolotl.InvalidKeyException;
-import org.whispersystems.libaxolotl.InvalidKeyIdException;
-import org.whispersystems.libaxolotl.SessionBuilder;
-import org.whispersystems.libaxolotl.UntrustedIdentityException;
-import org.whispersystems.libaxolotl.ecc.ECPublicKey;
-import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
-import org.whispersystems.libaxolotl.util.KeyHelper;
-import java.security.PrivateKey;
-import java.security.Security;
-import java.security.Signature;
import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
import java.util.Set;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.parser.IqParser;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
-import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
-import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
+/**
+ * Created by tzur on 02.03.2016.
+ */
+public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded {
- public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
- public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
- public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
- public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
+ String LOGPREFIX = "AxolotlService";
- public static final String LOGPREFIX = "AxolotlService";
+ String PEP_PREFIX = "eu.siacs.conversations.axolotl";
+ String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
+ String PEP_BUNDLES = PEP_PREFIX + ".bundles";
+ String PEP_VERIFICATION = PEP_PREFIX + ".verification";
- public static final int NUM_KEYS_TO_PUBLISH = 100;
- public static final int publishTriesThreshold = 3;
-
- private final Account account;
- private final XmppConnectionService mXmppConnectionService;
- private final SQLiteAxolotlStore axolotlStore;
- private final SessionMap sessions;
- private final Map<Jid, Set<Integer>> deviceIds;
- private final Map<String, XmppAxolotlMessage> messageCache;
- private final FetchStatusMap fetchStatusMap;
- private final SerialSingleThreadExecutor executor;
- private int numPublishTriesOnEmptyPep = 0;
- private boolean pepBroken = false;
-
- @Override
- public void onAdvancedStreamFeaturesAvailable(Account account) {
- if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) {
- publishBundlesIfNeeded(true, false);
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
- }
- }
-
- public boolean fetchMapHasErrors(List<Jid> jids) {
- for(Jid jid : jids) {
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- private static class AxolotlAddressMap<T> {
- protected Map<String, Map<Integer, T>> map;
- protected final Object MAP_LOCK = new Object();
-
- public AxolotlAddressMap() {
- this.map = new HashMap<>();
- }
-
- public void put(AxolotlAddress address, T value) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- devices = new HashMap<>();
- map.put(address.getName(), devices);
- }
- devices.put(address.getDeviceId(), value);
- }
- }
-
- public T get(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- return null;
- }
- return devices.get(address.getDeviceId());
- }
- }
-
- public Map<Integer, T> getAll(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- if (devices == null) {
- return new HashMap<>();
- }
- return devices;
- }
- }
-
- public boolean hasAny(AxolotlAddress address) {
- synchronized (MAP_LOCK) {
- Map<Integer, T> devices = map.get(address.getName());
- return devices != null && !devices.isEmpty();
- }
- }
-
- public void clear() {
- map.clear();
- }
-
- }
-
- private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
- private final XmppConnectionService xmppConnectionService;
- private final Account account;
-
- public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
- super();
- this.xmppConnectionService = service;
- this.account = account;
- this.fillMap(store);
- }
-
- private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
- for (Integer deviceId : deviceIds) {
- AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
- IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
- if(Config.X509_VERIFICATION) {
- X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
- if (certificate != null) {
- Bundle information = CryptoHelper.extractCertificateInformation(certificate);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.fromString(bareJid);
- Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final InvalidJidException ignored) {
- //ignored
- }
- }
- }
- this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
- }
- }
-
- private void fillMap(SQLiteAxolotlStore store) {
- List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString());
- putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store);
- for (Contact contact : account.getRoster().getContacts()) {
- Jid bareJid = contact.getJid().toBareJid();
- String address = bareJid.toString();
- deviceIds = store.getSubDeviceSessions(address);
- putDevicesForJid(address, deviceIds, store);
- }
-
- }
-
- @Override
- public void put(AxolotlAddress address, XmppAxolotlSession value) {
- super.put(address, value);
- value.setNotFresh();
- xmppConnectionService.syncRosterToDisk(account);
- }
-
- public void put(XmppAxolotlSession session) {
- this.put(session.getRemoteAddress(), session);
- }
- }
-
- public enum FetchStatus {
+ enum FetchStatus {
PENDING,
SUCCESS,
SUCCESS_VERIFIED,
@@ -213,836 +38,73 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
ERROR
}
- private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
+ boolean fetchMapHasErrors(Contact contact);
- }
+ String getOwnFingerprint();
- public static String getLogprefix(Account account) {
- return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
- }
+ Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust);
- public AxolotlService(Account account, XmppConnectionService connectionService) {
- if (Security.getProvider("BC") == null) {
- Security.addProvider(new BouncyCastleProvider());
- }
- this.mXmppConnectionService = connectionService;
- this.account = account;
- this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
- this.deviceIds = new HashMap<>();
- this.messageCache = new HashMap<>();
- this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
- this.fetchStatusMap = new FetchStatusMap();
- this.executor = new SerialSingleThreadExecutor();
- }
+ Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact);
- public String getOwnFingerprint() {
- return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
- }
+ long getNumTrustedKeys(Contact contact);
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
- return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
- }
+ Set<String> getFingerprintsForOwnSessions();
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
- return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust);
- }
+ Set<String> getFingerprintsForContact(Contact contact);
- public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
- Set<IdentityKey> keys = new HashSet<>();
- for(Jid jid : jids) {
- keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust));
- }
- return keys;
- }
+ boolean isPepBroken();
- public long getNumTrustedKeys(Jid jid) {
- return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString());
- }
+ void regenerateKeys(boolean wipeOther);
- public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
- for(Jid jid : jids) {
- if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) {
- return true;
- }
- }
- return false;
- }
+ int getOwnDeviceId();
- private AxolotlAddress getAddressForJid(Jid jid) {
- return new AxolotlAddress(jid.toString(), 0);
- }
+ Set<Integer> getOwnDeviceIds();
- private Set<XmppAxolotlSession> findOwnSessions() {
- AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
- return new HashSet<>(this.sessions.getAll(ownAddress).values());
- }
+ void registerDevices(Jid jid, @NonNull Set<Integer> deviceIds);
- private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
- AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
- return new HashSet<>(this.sessions.getAll(contactAddress).values());
- }
+ void wipeOtherPepDevices();
- private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
- HashSet<XmppAxolotlSession> sessions = new HashSet<>();
- for(Jid jid : conversation.getAcceptedCryptoTargets()) {
- sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
- }
- return sessions;
- }
+ void purgeKey(String fingerprint);
- public Set<String> getFingerprintsForOwnSessions() {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findOwnSessions()) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
+ void publishOwnDeviceIdIfNeeded();
- public Set<String> getFingerprintsForContact(final Contact contact) {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findSessionsForContact(contact)) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
+ void publishOwnDeviceId(Set<Integer> deviceIds);
- private boolean hasAny(Jid jid) {
- return sessions.hasAny(getAddressForJid(jid));
- }
+ void publishDeviceVerificationAndBundle(SignedPreKeyRecord signedPreKeyRecord,
+ Set<PreKeyRecord> preKeyRecords,
+ boolean announceAfter,
+ boolean wipe);
- public boolean isPepBroken() {
- return this.pepBroken;
- }
+ void publishBundlesIfNeeded(boolean announce, boolean wipe);
- public void regenerateKeys(boolean wipeOther) {
- axolotlStore.regenerate();
- sessions.clear();
- fetchStatusMap.clear();
- publishBundlesIfNeeded(true, wipeOther);
- }
+ boolean isContactAxolotlCapable(Contact contact);
- public int getOwnDeviceId() {
- return axolotlStore.getLocalRegistrationId();
- }
+ XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint);
- public Set<Integer> getOwnDeviceIds() {
- return this.deviceIds.get(account.getJid().toBareJid());
- }
+ X509Certificate getFingerprintCertificate(String fingerprint);
- private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
- final XmppAxolotlSession.Trust from,
- final XmppAxolotlSession.Trust to) {
- for (Integer deviceId : deviceIds) {
- AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
- XmppAxolotlSession session = sessions.get(address);
- if (session != null && session.getFingerprint() != null
- && session.getTrust() == from) {
- session.setTrust(to);
- }
- }
- }
+ void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust);
- public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if (jid.toBareJid().equals(account.getJid().toBareJid())) {
- if (!deviceIds.isEmpty()) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status.");
- pepBroken = false;
- numPublishTriesOnEmptyPep = 0;
- }
- if (deviceIds.contains(getOwnDeviceId())) {
- deviceIds.remove(getOwnDeviceId());
- } else {
- publishOwnDeviceId(deviceIds);
- }
- for (Integer deviceId : deviceIds) {
- AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
- if (sessions.get(ownDeviceAddress) == null) {
- buildSessionFromPEP(ownDeviceAddress);
- }
- }
- }
- Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
- expiredDevices.removeAll(deviceIds);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED,
- XmppAxolotlSession.Trust.INACTIVE_TRUSTED);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509,
- XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED,
- XmppAxolotlSession.Trust.INACTIVE_UNDECIDED);
- setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED,
- XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED);
- Set<Integer> newDevices = new HashSet<>(deviceIds);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED,
- XmppAxolotlSession.Trust.TRUSTED);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509,
- XmppAxolotlSession.Trust.TRUSTED_X509);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED,
- XmppAxolotlSession.Trust.UNDECIDED);
- setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED,
- XmppAxolotlSession.Trust.UNTRUSTED);
- this.deviceIds.put(jid, deviceIds);
- mXmppConnectionService.keyStatusUpdated(null);
- }
+ Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation);
- public void wipeOtherPepDevices() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
- return;
- }
- Set<Integer> deviceIds = new HashSet<>();
- deviceIds.add(getOwnDeviceId());
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- // TODO: implement this!
- }
- });
- }
-
- public void purgeKey(final String fingerprint) {
- axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
- }
-
- public void publishOwnDeviceIdIfNeeded() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
- } else {
- Element item = mXmppConnectionService.getIqParser().getItem(packet);
- Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- if (!deviceIds.contains(getOwnDeviceId())) {
- publishOwnDeviceId(deviceIds);
- }
- }
- }
- });
- }
-
- public void publishOwnDeviceId(Set<Integer> deviceIds) {
- Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
- if (!deviceIdsCopy.contains(getOwnDeviceId())) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
- if (deviceIdsCopy.isEmpty()) {
- if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
- pepBroken = true;
- return;
- } else {
- numPublishTriesOnEmptyPep++;
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
- }
- } else {
- numPublishTriesOnEmptyPep = 0;
- }
- deviceIdsCopy.add(getOwnDeviceId());
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() != IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
- }
- }
- });
- }
- }
+ Set<AxolotlAddress> findDevicesWithoutSession(Jid contactJid);
- public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
- final Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- try {
- IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
- PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
- X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
- verifier.update(axolotlPublicKey.serialize());
- byte[] signature = verifier.sign();
- IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
-
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- return; //ignore timeout. do nothing
- }
-
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Element error = packet.findChild("error");
- if (error == null || !error.hasChild("item-not-found")) {
- pepBroken = true;
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
- return;
- }
- }
+ boolean createSessionsIfNeeded(Conversation conversation);
- PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
- Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
- boolean flush = false;
- if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
- bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
- flush = true;
- }
- if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
- }
- try {
- boolean changed = false;
- // Validate IdentityKey
- IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
- if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
- changed = true;
- }
+ boolean trustedSessionVerified(Conversation conversation);
- // Validate signedPreKeyRecord + ID
- SignedPreKeyRecord signedPreKeyRecord;
- int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
- try {
- signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if (flush
- || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
- || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
- } catch (InvalidKeyIdException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
-
- // Validate PreKeys
- Set<PreKeyRecord> preKeyRecords = new HashSet<>();
- if (keys != null) {
- for (Integer id : keys.keySet()) {
- try {
- PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
- if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
- preKeyRecords.add(preKeyRecord);
- }
- } catch (InvalidKeyIdException ignored) {
- }
- }
- }
- int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
- if (newKeys > 0) {
- List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId() + 1, newKeys);
- preKeyRecords.addAll(newRecords);
- for (PreKeyRecord record : newRecords) {
- axolotlStore.storePreKey(record.getId(), record);
- }
- changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
- }
-
-
- if (changed) {
- if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
- mXmppConnectionService.publishDisplayName(account);
- publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- } else {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announce) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- }
- } catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
- }
- }
- });
- }
-
- private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
- Set<PreKeyRecord> preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
- signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
- preKeyRecords, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announceAfter) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
- }
- }
- });
- }
-
- public boolean isConversationAxolotlCapable(Conversation conversation) {
- final List<Jid> jids = getCryptoTargets(conversation);
- for(Jid jid : jids) {
- if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
- return false;
- }
- }
- return jids.size() > 0;
- }
-
- public List<Jid> getCryptoTargets(Conversation conversation) {
- final List<Jid> jids;
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- jids = Arrays.asList(conversation.getJid().toBareJid());
- } else {
- jids = conversation.getMucOptions().getMembers();
- jids.remove(account.getJid().toBareJid());
- }
- return jids;
- }
-
- public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
- return axolotlStore.getFingerprintTrust(fingerprint);
- }
-
- public X509Certificate getFingerprintCertificate(String fingerprint) {
- return axolotlStore.getFingerprintCertificate(fingerprint);
- }
-
- public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
- axolotlStore.setFingerprintTrust(fingerprint, trust);
- }
-
- private void verifySessionWithPEP(final XmppAxolotlSession session) {
- Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
- final AxolotlAddress address = session.getRemoteAddress();
- final IdentityKey identityKey = session.getIdentityKey();
- try {
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
- if (verification != null) {
- try {
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initVerify(verification.first[0]);
- verifier.update(identityKey.serialize());
- if (verifier.verify(verification.second)) {
- try {
- mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
- String fingerprint = session.getFingerprint();
- Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
- setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
- axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
- fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
- Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.fromString(address.getName());
- Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final InvalidJidException ignored) {
- //ignored
- }
- finishBuildingSessionsFromPEP(address);
- return;
- } catch (Exception e) {
- Log.d(Config.LOGTAG,"could not verify certificate");
- }
- }
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
- }
- } else {
- Log.d(Config.LOGTAG,"no verification found");
- }
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- });
- } catch (InvalidJidException e) {
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- }
-
- private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
- if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
- && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
- FetchStatus report = null;
- if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
- | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
- report = FetchStatus.SUCCESS_VERIFIED;
- } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
- || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
- report = FetchStatus.ERROR;
- }
- mXmppConnectionService.keyStatusUpdated(report);
- }
- }
-
- private void buildSessionFromPEP(final AxolotlAddress address) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
- if (address.getDeviceId() == getOwnDeviceId()) {
- throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
- }
-
- try {
- IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
- Jid.fromString(address.getName()), address.getDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
- mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.TIMEOUT);
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
- final IqParser parser = mXmppConnectionService.getIqParser();
- final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
- final PreKeyBundle bundle = parser.bundle(packet);
- if (preKeyBundleList.isEmpty() || bundle == null) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- return;
- }
- Random random = new Random();
- final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
- if (preKey == null) {
- //should never happen
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- return;
- }
-
- final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
- preKey.getPreKeyId(), preKey.getPreKey(),
- bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
- bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
-
- try {
- SessionBuilder builder = new SessionBuilder(axolotlStore, address);
- builder.process(preKeyBundle);
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
- sessions.put(address, session);
- if (Config.X509_VERIFICATION) {
- verifySessionWithPEP(session);
- } else {
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- }
- } catch (UntrustedIdentityException | InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
- + e.getClass().getName() + ", " + e.getMessage());
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- }
- } else {
- fetchStatusMap.put(address, FetchStatus.ERROR);
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
- finishBuildingSessionsFromPEP(address);
- }
- }
- });
- } catch (InvalidJidException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
- }
- }
-
- public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
- Set<AxolotlAddress> addresses = new HashSet<>();
- for(Jid jid : getCryptoTargets(conversation)) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
- }
- }
- }
- }
- } else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
- }
- }
- if (deviceIds.get(account.getJid().toBareJid()) != null) {
- for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
- AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
- }
- }
- }
- }
- }
-
- return addresses;
- }
-
- public boolean createSessionsIfNeeded(final Conversation conversation) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
- boolean newSessions = false;
- Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
- for (AxolotlAddress address : addresses) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
- FetchStatus status = fetchStatusMap.get(address);
- if (status == null || status == FetchStatus.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.PENDING);
- this.buildSessionFromPEP(address);
- newSessions = true;
- } else if (status == FetchStatus.PENDING) {
- newSessions = true;
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
- }
- }
-
- return newSessions;
- }
-
- public boolean trustedSessionVerified(final Conversation conversation) {
- Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
- sessions.addAll(findOwnSessions());
- boolean verified = false;
- for(XmppAxolotlSession session : sessions) {
- if (session.getTrust().trusted()) {
- if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) {
- verified = true;
- } else {
- return false;
- }
- }
- }
- return verified;
- }
-
- public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
- if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- for(Jid jid : jids) {
- AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
- if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- }
- return false;
- }
+ boolean hasPendingKeyFetches(Account account, Contact contact);
@Nullable
- private XmppAxolotlMessage buildHeader(Conversation conversation) {
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
- account.getJid().toBareJid(), getOwnDeviceId());
+ XmppAxolotlMessage encrypt(Message message);
- Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
- Set<XmppAxolotlSession> ownSessions = findOwnSessions();
- if (remoteSessions.isEmpty()) {
- return null;
- }
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
- for (XmppAxolotlSession session : remoteSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
- axolotlMessage.addDevice(session);
- }
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
- for (XmppAxolotlSession session : ownSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
- axolotlMessage.addDevice(session);
- }
+ void preparePayloadMessage(Message message, boolean delay);
- return axolotlMessage;
- }
-
- @Nullable
- public XmppAxolotlMessage encrypt(Message message) {
- XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
+ void prepareKeyTransportMessage(Contact contact, OnMessageCreatedCallback onMessageCreatedCallback);
- if (axolotlMessage != null) {
- final String content;
- if (message.hasFileOnRemoteHost()) {
- content = message.getFileParams().url.toString();
- } else {
- content = message.getBody();
- }
- try {
- axolotlMessage.encrypt(content);
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
- return null;
- }
- }
+ XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message);
- return axolotlMessage;
- }
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message);
- public void preparePayloadMessage(final Message message, final boolean delay) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = encrypt(message);
- if (axolotlMessage == null) {
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
- //mXmppConnectionService.updateConversationUi();
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
- messageCache.put(message.getUuid(), axolotlMessage);
- mXmppConnectionService.resendMessage(message, delay);
- }
- }
- });
- }
-
- public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
- onMessageCreatedCallback.run(axolotlMessage);
- }
- });
- }
-
- public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
- XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
- if (axolotlMessage != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
- messageCache.remove(message.getUuid());
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
- }
- return axolotlMessage;
- }
-
- private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- return (identityKey != null)
- ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
- : null;
- }
-
- private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
- AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
- message.getSenderDeviceId());
- XmppAxolotlSession session = sessions.get(senderAddress);
- if (session == null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
- session = recreateUncachedSession(senderAddress);
- if (session == null) {
- session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
- }
- }
- return session;
- }
-
- public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
-
- XmppAxolotlSession session = getReceivingSession(message);
- try {
- plaintextMessage = message.decrypt(session, getOwnDeviceId());
- Integer preKeyId = session.getPreKeyId();
- if (preKeyId != null) {
- publishBundlesIfNeeded(false, false);
- session.resetPreKeyId();
- }
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
- }
-
- if (session.isFresh() && plaintextMessage != null) {
- putFreshSession(session);
- }
-
- return plaintextMessage;
- }
-
- public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
- XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
-
- XmppAxolotlSession session = getReceivingSession(message);
- keyTransportMessage = message.getParameters(session, getOwnDeviceId());
-
- if (session.isFresh() && keyTransportMessage != null) {
- putFreshSession(session);
- }
-
- return keyTransportMessage;
- }
-
- private void putFreshSession(XmppAxolotlSession session) {
- Log.d(Config.LOGTAG,"put fresh session");
- sessions.put(session);
- if (Config.X509_VERIFICATION) {
- if (session.getIdentityKey() != null) {
- verifySessionWithPEP(session);
- } else {
- Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
- }
- }
- }
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message);
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java
new file mode 100644
index 00000000..27d50dbb
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java
@@ -0,0 +1,1022 @@
+package eu.siacs.conversations.crypto.axolotl;
+
+import android.os.Bundle;
+import android.security.KeyChain;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.Pair;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.whispersystems.libaxolotl.AxolotlAddress;
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.IdentityKeyPair;
+import org.whispersystems.libaxolotl.InvalidKeyException;
+import org.whispersystems.libaxolotl.InvalidKeyIdException;
+import org.whispersystems.libaxolotl.SessionBuilder;
+import org.whispersystems.libaxolotl.UntrustedIdentityException;
+import org.whispersystems.libaxolotl.ecc.ECPublicKey;
+import org.whispersystems.libaxolotl.state.PreKeyBundle;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+import org.whispersystems.libaxolotl.util.KeyHelper;
+
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.parser.IqParser;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+
+public class AxolotlServiceImpl implements AxolotlService {
+
+ public static final int NUM_KEYS_TO_PUBLISH = 100;
+ public static final int publishTriesThreshold = 3;
+
+ private final Account account;
+ private final XmppConnectionService mXmppConnectionService;
+ private final SQLiteAxolotlStore axolotlStore;
+ private final SessionMap sessions;
+ private final Map<Jid, Set<Integer>> deviceIds;
+ private final Map<String, XmppAxolotlMessage> messageCache;
+ private final FetchStatusMap fetchStatusMap;
+ private final SerialSingleThreadExecutor executor;
+ private int numPublishTriesOnEmptyPep = 0;
+ private boolean pepBroken = false;
+
+ @Override
+ public void onAdvancedStreamFeaturesAvailable(Account account) {
+ if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) {
+ publishBundlesIfNeeded(true, false);
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
+ }
+ }
+
+ @Override
+ public boolean fetchMapHasErrors(Contact contact) {
+ Jid jid = contact.getJid().toBareJid();
+ if (deviceIds.get(jid) != null) {
+ for (Integer foreignId : this.deviceIds.get(jid)) {
+ AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static class AxolotlAddressMap<T> {
+ protected Map<String, Map<Integer, T>> map;
+ protected final Object MAP_LOCK = new Object();
+
+ public AxolotlAddressMap() {
+ this.map = new HashMap<>();
+ }
+
+ public void put(AxolotlAddress address, T value) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ devices = new HashMap<>();
+ map.put(address.getName(), devices);
+ }
+ devices.put(address.getDeviceId(), value);
+ }
+ }
+
+ public T get(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ return null;
+ }
+ return devices.get(address.getDeviceId());
+ }
+ }
+
+ public Map<Integer, T> getAll(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ if (devices == null) {
+ return new HashMap<>();
+ }
+ return devices;
+ }
+ }
+
+ public boolean hasAny(AxolotlAddress address) {
+ synchronized (MAP_LOCK) {
+ Map<Integer, T> devices = map.get(address.getName());
+ return devices != null && !devices.isEmpty();
+ }
+ }
+
+ public void clear() {
+ map.clear();
+ }
+
+ }
+
+ private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
+ private final XmppConnectionService xmppConnectionService;
+ private final Account account;
+
+ public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
+ super();
+ this.xmppConnectionService = service;
+ this.account = account;
+ this.fillMap(store);
+ }
+
+ private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
+ IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
+ if(Config.X509_VERIFICATION) {
+ X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
+ if (certificate != null) {
+ Bundle information = CryptoHelper.extractCertificateInformation(certificate);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(bareJid);
+ Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ }
+ }
+ this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
+ }
+ }
+
+ private void fillMap(SQLiteAxolotlStore store) {
+ List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString());
+ putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store);
+ for (Contact contact : account.getRoster().getContacts()) {
+ Jid bareJid = contact.getJid().toBareJid();
+ String address = bareJid.toString();
+ deviceIds = store.getSubDeviceSessions(address);
+ putDevicesForJid(address, deviceIds, store);
+ }
+
+ }
+
+ @Override
+ public void put(AxolotlAddress address, XmppAxolotlSession value) {
+ super.put(address, value);
+ value.setNotFresh();
+ xmppConnectionService.syncRosterToDisk(account);
+ }
+
+ public void put(XmppAxolotlSession session) {
+ this.put(session.getRemoteAddress(), session);
+ }
+ }
+
+ private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
+
+ }
+
+ public static String getLogprefix(Account account) {
+ return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
+ }
+
+ public AxolotlServiceImpl(Account account, XmppConnectionService connectionService) {
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ this.mXmppConnectionService = connectionService;
+ this.account = account;
+ this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
+ this.deviceIds = new HashMap<>();
+ this.messageCache = new HashMap<>();
+ this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
+ this.fetchStatusMap = new FetchStatusMap();
+ this.executor = new SerialSingleThreadExecutor();
+ }
+
+ @Override
+ public String getOwnFingerprint() {
+ return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
+ return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) {
+ return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust);
+ }
+
+ @Override
+ public long getNumTrustedKeys(Contact contact) {
+ return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString());
+ }
+
+ private AxolotlAddress getAddressForJid(Jid jid) {
+ return new AxolotlAddress(jid.toString(), 0);
+ }
+
+ private Set<XmppAxolotlSession> findOwnSessions() {
+ AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
+ return new HashSet<>(this.sessions.getAll(ownAddress).values());
+ }
+
+ private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) {
+ AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
+ return new HashSet<>(this.sessions.getAll(contactAddress).values());
+ }
+
+ @Override
+ public Set<String> getFingerprintsForOwnSessions() {
+ Set<String> fingerprints = new HashSet<>();
+ for (XmppAxolotlSession session : findOwnSessions()) {
+ fingerprints.add(session.getFingerprint());
+ }
+ return fingerprints;
+ }
+
+ @Override
+ public Set<String> getFingerprintsForContact(final Contact contact) {
+ Set<String> fingerprints = new HashSet<>();
+ for (XmppAxolotlSession session : findSessionsforContact(contact)) {
+ fingerprints.add(session.getFingerprint());
+ }
+ return fingerprints;
+ }
+
+ private boolean hasAny(Contact contact) {
+ AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
+ return sessions.hasAny(contactAddress);
+ }
+
+ @Override
+ public boolean isPepBroken() {
+ return this.pepBroken;
+ }
+
+ @Override
+ public void regenerateKeys(boolean wipeOther) {
+ axolotlStore.regenerate();
+ sessions.clear();
+ fetchStatusMap.clear();
+ publishBundlesIfNeeded(true, wipeOther);
+ }
+
+ @Override
+ public int getOwnDeviceId() {
+ return axolotlStore.getLocalRegistrationId();
+ }
+
+ @Override
+ public Set<Integer> getOwnDeviceIds() {
+ return this.deviceIds.get(account.getJid().toBareJid());
+ }
+
+ private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
+ final XmppAxolotlSession.Trust from,
+ final XmppAxolotlSession.Trust to) {
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+ XmppAxolotlSession session = sessions.get(address);
+ if (session != null && session.getFingerprint() != null
+ && session.getTrust() == from) {
+ session.setTrust(to);
+ }
+ }
+ }
+
+ @Override
+ public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
+ if (jid.toBareJid().equals(account.getJid().toBareJid())) {
+ if (!deviceIds.isEmpty()) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status.");
+ pepBroken = false;
+ numPublishTriesOnEmptyPep = 0;
+ }
+ if (deviceIds.contains(getOwnDeviceId())) {
+ deviceIds.remove(getOwnDeviceId());
+ } else {
+ publishOwnDeviceId(deviceIds);
+ }
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+ if (sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(ownDeviceAddress);
+ }
+ }
+ }
+ Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
+ expiredDevices.removeAll(deviceIds);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED,
+ XmppAxolotlSession.Trust.INACTIVE_TRUSTED);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509,
+ XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED,
+ XmppAxolotlSession.Trust.INACTIVE_UNDECIDED);
+ setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED,
+ XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED);
+ Set<Integer> newDevices = new HashSet<>(deviceIds);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED,
+ XmppAxolotlSession.Trust.TRUSTED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509,
+ XmppAxolotlSession.Trust.TRUSTED_X509);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED,
+ XmppAxolotlSession.Trust.UNDECIDED);
+ setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED,
+ XmppAxolotlSession.Trust.UNTRUSTED);
+ this.deviceIds.put(jid, deviceIds);
+ mXmppConnectionService.keyStatusUpdated(null);
+ }
+
+ @Override
+ public void wipeOtherPepDevices() {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ Set<Integer> deviceIds = new HashSet<>();
+ deviceIds.add(getOwnDeviceId());
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ // TODO: implement this!
+ }
+ });
+ }
+
+ @Override
+ public void purgeKey(final String fingerprint) {
+ axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
+ }
+
+ @Override
+ public void publishOwnDeviceIdIfNeeded() {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
+ } else {
+ Element item = mXmppConnectionService.getIqParser().getItem(packet);
+ Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
+ if (!deviceIds.contains(getOwnDeviceId())) {
+ publishOwnDeviceId(deviceIds);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void publishOwnDeviceId(Set<Integer> deviceIds) {
+ Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
+ if (!deviceIdsCopy.contains(getOwnDeviceId())) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
+ if (deviceIdsCopy.isEmpty()) {
+ if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
+ pepBroken = true;
+ return;
+ } else {
+ numPublishTriesOnEmptyPep++;
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
+ }
+ } else {
+ numPublishTriesOnEmptyPep = 0;
+ }
+ deviceIdsCopy.add(getOwnDeviceId());
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
+ final Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
+ try {
+ IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
+ PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
+ X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
+ verifier.update(axolotlPublicKey.serialize());
+ byte[] signature = verifier.sign();
+ IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
+ if (pepBroken) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
+ return;
+ }
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ return; //ignore timeout. do nothing
+ }
+
+ if (packet.getType() == IqPacket.TYPE.ERROR) {
+ Element error = packet.findChild("error");
+ if (error == null || !error.hasChild("item-not-found")) {
+ pepBroken = true;
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
+ return;
+ }
+ }
+
+ PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
+ Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
+ boolean flush = false;
+ if (bundle == null) {
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid bundle:" + packet);
+ bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
+ flush = true;
+ }
+ if (keys == null) {
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid prekeys:" + packet);
+ }
+ try {
+ boolean changed = false;
+ // Validate IdentityKey
+ IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
+ if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
+ changed = true;
+ }
+
+ // Validate signedPreKeyRecord + ID
+ SignedPreKeyRecord signedPreKeyRecord;
+ int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
+ try {
+ signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
+ if (flush
+ || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
+ || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
+ } catch (InvalidKeyIdException e) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
+ signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
+ axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
+ changed = true;
+ }
+
+ // Validate PreKeys
+ Set<PreKeyRecord> preKeyRecords = new HashSet<>();
+ if (keys != null) {
+ for (Integer id : keys.keySet()) {
+ try {
+ PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
+ if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
+ preKeyRecords.add(preKeyRecord);
+ }
+ } catch (InvalidKeyIdException ignored) {
+ }
+ }
+ }
+ int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
+ if (newKeys > 0) {
+ List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
+ axolotlStore.getCurrentPreKeyId() + 1, newKeys);
+ preKeyRecords.addAll(newRecords);
+ for (PreKeyRecord record : newRecords) {
+ axolotlStore.storePreKey(record.getId(), record);
+ }
+ changed = true;
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
+ }
+
+
+ if (changed) {
+ if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
+ mXmppConnectionService.publishDisplayName(account);
+ publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ } else {
+ publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
+ }
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announce) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ }
+ } catch (InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
+ }
+ }
+ });
+ }
+
+ private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
+ Set<PreKeyRecord> preKeyRecords,
+ final boolean announceAfter,
+ final boolean wipe) {
+ IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
+ signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
+ preKeyRecords, getOwnDeviceId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
+ mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Successfully published bundle. ");
+ if (wipe) {
+ wipeOtherPepDevices();
+ } else if (announceAfter) {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
+ publishOwnDeviceIdIfNeeded();
+ }
+ } else {
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean isContactAxolotlCapable(Contact contact) {
+ Jid jid = contact.getJid().toBareJid();
+ return hasAny(contact) ||
+ (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
+ }
+
+ @Override
+ public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
+ return axolotlStore.getFingerprintTrust(fingerprint);
+ }
+
+ @Override
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return axolotlStore.getFingerprintCertificate(fingerprint);
+ }
+
+ @Override
+ public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
+ axolotlStore.setFingerprintTrust(fingerprint, trust);
+ }
+
+ private void verifySessionWithPEP(final XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
+ final AxolotlAddress address = session.getRemoteAddress();
+ final IdentityKey identityKey = session.getIdentityKey();
+ try {
+ IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
+ mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
+ if (verification != null) {
+ try {
+ Signature verifier = Signature.getInstance("sha256WithRSA");
+ verifier.initVerify(verification.first[0]);
+ verifier.update(identityKey.serialize());
+ if (verifier.verify(verification.second)) {
+ try {
+ mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
+ String fingerprint = session.getFingerprint();
+ Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
+ setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
+ axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
+ fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
+ Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
+ try {
+ final String cn = information.getString("subject_cn");
+ final Jid jid = Jid.fromString(address.getName());
+ Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
+ account.getRoster().getContact(jid).setCommonName(cn);
+ } catch (final InvalidJidException ignored) {
+ //ignored
+ }
+ finishBuildingSessionsFromPEP(address);
+ return;
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG,"could not verify certificate");
+ }
+ }
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
+ }
+ } else {
+ Log.d(Config.LOGTAG,"no verification found");
+ }
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ });
+ } catch (InvalidJidException e) {
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ }
+
+ private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
+ if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
+ && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
+ FetchStatus report = null;
+ if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
+ | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
+ report = FetchStatus.SUCCESS_VERIFIED;
+ } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
+ || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
+ report = FetchStatus.ERROR;
+ }
+ mXmppConnectionService.keyStatusUpdated(report);
+ }
+ }
+
+ private void buildSessionFromPEP(final AxolotlAddress address) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building new sesstion for " + address.toString());
+ if (address.getDeviceId() == getOwnDeviceId()) {
+ throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
+ }
+
+ try {
+ IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
+ Jid.fromString(address.getName()), address.getDeviceId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
+ mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
+ fetchStatusMap.put(address, FetchStatus.TIMEOUT);
+ } else if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received preKey IQ packet, processing...");
+ final IqParser parser = mXmppConnectionService.getIqParser();
+ final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
+ final PreKeyBundle bundle = parser.bundle(packet);
+ if (preKeyBundleList.isEmpty() || bundle == null) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ return;
+ }
+ Random random = new Random();
+ final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
+ if (preKey == null) {
+ //should never happen
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ return;
+ }
+
+ final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
+ preKey.getPreKeyId(), preKey.getPreKey(),
+ bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
+ bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
+
+ try {
+ SessionBuilder builder = new SessionBuilder(axolotlStore, address);
+ builder.process(preKeyBundle);
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
+ sessions.put(address, session);
+ if (Config.X509_VERIFICATION) {
+ verifySessionWithPEP(session);
+ } else {
+ fetchStatusMap.put(address, FetchStatus.SUCCESS);
+ finishBuildingSessionsFromPEP(address);
+ }
+ } catch (UntrustedIdentityException | InvalidKeyException e) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error building session for " + address + ": "
+ + e.getClass().getName() + ", " + e.getMessage());
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ finishBuildingSessionsFromPEP(address);
+ }
+ } else {
+ fetchStatusMap.put(address, FetchStatus.ERROR);
+ Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
+ finishBuildingSessionsFromPEP(address);
+ }
+ }
+ });
+ } catch (InvalidJidException e) {
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
+ }
+ }
+
+ @Override
+ public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
+ return findDevicesWithoutSession(conversation.getContact().getJid().toBareJid());
+ }
+
+ @Override
+ public Set<AxolotlAddress> findDevicesWithoutSession(final Jid contactJid) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Finding devices without session for " + contactJid);
+ Set<AxolotlAddress> addresses = new HashSet<>();
+ if (deviceIds.get(contactJid) != null) {
+ for (Integer foreignId : this.deviceIds.get(contactJid)) {
+ AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + contactJid + ":" + foreignId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
+ }
+ }
+ }
+ }
+ } else {
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Have no target devices in PEP!");
+ }
+ if (deviceIds.get(account.getJid().toBareJid()) != null) {
+ for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
+ AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
+ if (sessions.get(address) == null) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ if (identityKey != null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
+ XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
+ sessions.put(address, session);
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
+ if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
+ addresses.add(address);
+ } else {
+ Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
+ }
+ }
+ }
+ }
+ }
+
+ return addresses;
+ }
+
+ @Override
+ public boolean createSessionsIfNeeded(final Conversation conversation) {
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Creating axolotl sessions if needed...");
+ boolean newSessions = false;
+ Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
+ for (AxolotlAddress address : addresses) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Processing device: " + address.toString());
+ FetchStatus status = fetchStatusMap.get(address);
+ if (status == null || status == FetchStatus.TIMEOUT) {
+ fetchStatusMap.put(address, FetchStatus.PENDING);
+ this.buildSessionFromPEP(address);
+ newSessions = true;
+ } else if (status == FetchStatus.PENDING) {
+ newSessions = true;
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Already fetching bundle for " + address.toString());
+ }
+ }
+
+ return newSessions;
+ }
+
+ @Override
+ public boolean trustedSessionVerified(final Conversation conversation) {
+ Set<XmppAxolotlSession> sessions = findSessionsforContact(conversation.getContact());
+ sessions.addAll(findOwnSessions());
+ boolean verified = false;
+ for(XmppAxolotlSession session : sessions) {
+ if (session.getTrust().trusted()) {
+ if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) {
+ verified = true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return verified;
+ }
+
+ @Override
+ public boolean hasPendingKeyFetches(Account account, Contact contact) {
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
+ AxolotlAddress foreignAddress = new AxolotlAddress(contact.getJid().toBareJid().toString(), 0);
+ return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
+ || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
+
+ }
+
+ @Nullable
+ private XmppAxolotlMessage buildHeader(Contact contact) {
+ final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
+ contact.getJid().toBareJid(), getOwnDeviceId());
+
+ Set<XmppAxolotlSession> contactSessions = findSessionsforContact(contact);
+ Set<XmppAxolotlSession> ownSessions = findOwnSessions();
+ if (contactSessions.isEmpty()) {
+ return null;
+ }
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl foreign keyElements...");
+ for (XmppAxolotlSession session : contactSessions) {
+ Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Building axolotl own keyElements...");
+ for (XmppAxolotlSession session : ownSessions) {
+ Log.v(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + session.getRemoteAddress().toString());
+ axolotlMessage.addDevice(session);
+ }
+
+ return axolotlMessage;
+ }
+
+ @Override
+ @Nullable
+ public XmppAxolotlMessage encrypt(Message message) {
+ XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact());
+
+ if (axolotlMessage != null) {
+ final String content;
+ if (message.hasFileOnRemoteHost()) {
+ content = message.getFileParams().url.toString();
+ } else {
+ content = message.getBody();
+ }
+ try {
+ axolotlMessage.encrypt(content);
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
+ return null;
+ }
+ }
+
+ return axolotlMessage;
+ }
+
+ @Override
+ public void preparePayloadMessage(final Message message, final boolean delay) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = encrypt(message);
+ if (axolotlMessage == null) {
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
+ //mXmppConnectionService.updateConversationUi();
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
+ messageCache.put(message.getUuid(), axolotlMessage);
+ mXmppConnectionService.resendMessage(message, delay);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ XmppAxolotlMessage axolotlMessage = buildHeader(contact);
+ onMessageCreatedCallback.run(axolotlMessage);
+ }
+ });
+ }
+
+ @Override
+ public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
+ XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
+ if (axolotlMessage != null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Cache hit: " + message.getUuid());
+ messageCache.remove(message.getUuid());
+ } else {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Cache miss: " + message.getUuid());
+ }
+ return axolotlMessage;
+ }
+
+ private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
+ IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
+ return (identityKey != null)
+ ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
+ : null;
+ }
+
+ private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
+ AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
+ message.getSenderDeviceId());
+ XmppAxolotlSession session = sessions.get(senderAddress);
+ if (session == null) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
+ session = recreateUncachedSession(senderAddress);
+ if (session == null) {
+ session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
+ }
+ }
+ return session;
+ }
+
+ @Override
+ public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ try {
+ plaintextMessage = message.decrypt(session, getOwnDeviceId());
+ Integer preKeyId = session.getPreKeyId();
+ if (preKeyId != null) {
+ publishBundlesIfNeeded(false, false);
+ session.resetPreKeyId();
+ }
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
+ }
+
+ if (session.isFresh() && plaintextMessage != null) {
+ putFreshSession(session);
+ }
+
+ return plaintextMessage;
+ }
+
+ @Override
+ public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
+ XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
+
+ XmppAxolotlSession session = getReceivingSession(message);
+ keyTransportMessage = message.getParameters(session, getOwnDeviceId());
+
+ if (session.isFresh() && keyTransportMessage != null) {
+ putFreshSession(session);
+ }
+
+ return keyTransportMessage;
+ }
+
+ private void putFreshSession(XmppAxolotlSession session) {
+ Log.d(Config.LOGTAG,"put fresh session");
+ sessions.put(session);
+ if (Config.X509_VERIFICATION) {
+ if (session.getIdentityKey() != null) {
+ verifySessionWithPEP(session);
+ } else {
+ Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
+ }
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java
new file mode 100644
index 00000000..42777b75
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java
@@ -0,0 +1,196 @@
+package eu.siacs.conversations.crypto.axolotl;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import org.whispersystems.libaxolotl.AxolotlAddress;
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Set;
+
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+/**
+ * Axolotl Service Stub implementation to avoid axolotl usage.
+ */
+public class AxolotlServiceStub implements AxolotlService {
+
+ @Override
+ public boolean fetchMapHasErrors(Contact contact) {
+ return false;
+ }
+
+ @Override
+ public String getOwnFingerprint() {
+ return null;
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public long getNumTrustedKeys(Contact contact) {
+ return 0;
+ }
+
+ @Override
+ public Set<String> getFingerprintsForOwnSessions() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<String> getFingerprintsForContact(Contact contact) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean isPepBroken() {
+ return true;
+ }
+
+ @Override
+ public void regenerateKeys(boolean wipeOther) {
+
+ }
+
+ @Override
+ public int getOwnDeviceId() {
+ return 0;
+ }
+
+ @Override
+ public Set<Integer> getOwnDeviceIds() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void registerDevices(Jid jid, @NonNull Set<Integer> deviceIds) {
+
+ }
+
+ @Override
+ public void wipeOtherPepDevices() {
+
+ }
+
+ @Override
+ public void purgeKey(String fingerprint) {
+
+ }
+
+ @Override
+ public void publishOwnDeviceIdIfNeeded() {
+
+ }
+
+ @Override
+ public void publishOwnDeviceId(Set<Integer> deviceIds) {
+
+ }
+
+ @Override
+ public void publishDeviceVerificationAndBundle(SignedPreKeyRecord signedPreKeyRecord, Set<PreKeyRecord> preKeyRecords, boolean announceAfter, boolean wipe) {
+
+ }
+
+ @Override
+ public void publishBundlesIfNeeded(boolean announce, boolean wipe) {
+
+ }
+
+ @Override
+ public boolean isContactAxolotlCapable(Contact contact) {
+ return false;
+ }
+
+ @Override
+ public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
+ return XmppAxolotlSession.Trust.TRUSTED;
+ }
+
+ @Override
+ public X509Certificate getFingerprintCertificate(String fingerprint) {
+ return null;
+ }
+
+ @Override
+ public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
+
+ }
+
+ @Override
+ public Set<AxolotlAddress> findDevicesWithoutSession(Conversation conversation) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set<AxolotlAddress> findDevicesWithoutSession(Jid contactJid) {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public boolean createSessionsIfNeeded(Conversation conversation) {
+ return false;
+ }
+
+ @Override
+ public boolean trustedSessionVerified(Conversation conversation) {
+ return false;
+ }
+
+ @Override
+ public boolean hasPendingKeyFetches(Account account, Contact contact) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public XmppAxolotlMessage encrypt(Message message) {
+ return null;
+ }
+
+ @Override
+ public void preparePayloadMessage(Message message, boolean delay) {
+
+ }
+
+ @Override
+ public void prepareKeyTransportMessage(Contact contact, OnMessageCreatedCallback onMessageCreatedCallback) {
+
+ }
+
+ @Override
+ public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
+ return null;
+ }
+
+ @Override
+ public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
+ return null;
+ }
+
+ @Override
+ public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
+ return null;
+ }
+
+ @Override
+ public void onAdvancedStreamFeaturesAvailable(Account account) {
+
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
index 3c8cb3c1..c634d877 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
@@ -77,7 +77,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
this.localRegistrationId = loadRegistrationId();
this.currentPreKeyId = loadCurrentPreKeyId();
for (SignedPreKeyRecord record : loadSignedPreKeys()) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
}
}
@@ -95,7 +95,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (ownKey != null) {
return ownKey;
} else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
ownKey = generateIdentityKeyPair();
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
}
@@ -112,13 +112,13 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (!regenerate && regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
reg_id = generateRegistrationId();
boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to write new key to the database!");
}
}
return reg_id;
@@ -130,7 +130,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (regIdString != null) {
reg_id = Integer.valueOf(regIdString);
} else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
reg_id = 0;
}
return reg_id;
@@ -344,7 +344,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
if (success) {
mXmppConnectionService.databaseBackend.updateAccount(account);
} else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Failed to write new prekey id to the database!");
}
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
index b713eb5f..93ed32a2 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -168,10 +168,10 @@ public class XmppAxolotlSession {
try {
try {
PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
IdentityKey msgIdentityKey = message.getIdentityKey();
if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
+ Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
} else {
this.identityKey = msgIdentityKey;
plaintext = cipher.decrypt(message);
@@ -180,14 +180,14 @@ public class XmppAxolotlSession {
}
}
} catch (InvalidMessageException | InvalidVersionException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
+ Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "WhisperMessage received");
WhisperMessage message = new WhisperMessage(encryptedKey);
plaintext = cipher.decrypt(message);
} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
}
} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
+ Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
}
if (plaintext != null) {
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
index df92898c..8fd91cf4 100644
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
+++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.crypto.sasl;
import android.util.Base64;
+
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index 356b34e5..7aee78b5 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -5,7 +5,6 @@ import android.database.Cursor;
import android.os.SystemClock;
import android.util.Pair;
-import eu.siacs.conversations.crypto.PgpDecryptionService;
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
import net.java.otr4j.crypto.OtrCryptoException;
@@ -20,10 +19,14 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.OtrService;
+import eu.siacs.conversations.crypto.PgpDecryptionService;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceStub;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -165,6 +168,10 @@ public class Account extends AbstractEntity {
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
+ public Account() {
+ this.uuid = "0";
+ }
+
public Account(final Jid jid, final String password) {
this(java.util.UUID.randomUUID().toString(), jid,
password, 0, null, "", null, null, null, 5222);
@@ -251,10 +258,6 @@ public class Account extends AbstractEntity {
return this.hostname == null ? "" : this.hostname;
}
- public boolean isOnion() {
- return getServer().toString().toLowerCase().endsWith(".onion");
- }
-
public void setPort(int port) {
this.port = port;
}
@@ -352,10 +355,15 @@ public class Account extends AbstractEntity {
public void initAccountServices(final XmppConnectionService context) {
this.mOtrService = new OtrService(context, this);
- this.axolotlService = new AxolotlService(this, context);
- if (xmppConnection != null) {
- xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
- }
+ if (ConversationsPlusPreferences.omemoEnabled()) {
+ this.axolotlService = new AxolotlServiceImpl(this, context);
+ if (xmppConnection != null) {
+ xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
+ }
+ } else {
+ this.axolotlService = new AxolotlServiceStub();
+ }
+
this.pgpDecryptionService = new PgpDecryptionService(context);
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
index 088dfd8a..fa30443d 100644
--- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java
+++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.entities;
+import android.graphics.Color;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -87,6 +89,11 @@ public class Bookmark extends Element implements ListItem {
return tags;
}
+ @Override
+ public int getStatusColor() {
+ return Color.parseColor("#259B23");
+ }
+
public String getNick() {
return this.findChildContent("nick");
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 691fc3e4..bdde12e3 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
+import android.graphics.Color;
import org.json.JSONArray;
import org.json.JSONException;
@@ -167,11 +168,16 @@ public class Contact implements ListItem, Blockable {
return tags;
}
+ @Override
+ public int getStatusColor() {
+ return Color.parseColor(UIHelper.getStatusColor(getMostAvailableStatus()));
+ }
+
public boolean match(String needle) {
if (needle == null || needle.isEmpty()) {
return true;
}
- needle = needle.toLowerCase(Locale.US).trim();
+ needle = needle.toLowerCase(Locale.US);
String[] parts = needle.split("\\s+");
if (parts.length > 1) {
for(int i = 0; i < parts.length; ++i) {
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 0252ea74..75477d3e 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -21,6 +21,8 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
@@ -308,14 +310,6 @@ public class Conversation extends AbstractEntity implements Blockable {
return this.mFirstMamReference;
}
- public void setLastClearHistory(long time) {
- setAttribute("last_clear_history",String.valueOf(time));
- }
-
- public long getLastClearHistory() {
- return getLongAttribute("last_clear_history", 0);
- }
-
public List<Jid> getAcceptedCryptoTargets() {
if (mode == MODE_SINGLE) {
return Arrays.asList(getJid().toBareJid());
@@ -781,10 +775,6 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public long getLastMessageTransmitted() {
- long last_clear = getLastClearHistory();
- if (last_clear != 0) {
- return last_clear;
- }
synchronized (this.messages) {
for(int i = this.messages.size() - 1; i >= 0; --i) {
Message message = this.messages.get(i);
@@ -941,13 +931,19 @@ public class Conversation extends AbstractEntity implements Blockable {
}
public int unreadCount() {
+ if (getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) {
+ return 0;
+ }
synchronized (this.messages) {
int count = 0;
for(int i = this.messages.size() - 1; i >= 0; --i) {
- if (this.messages.get(i).isRead()) {
+ Message message = this.messages.get(i);
+ if (message.isRead()) {
return count;
}
- ++count;
+ if (alwaysNotify() || MessageUtil.wasHighlightedOrPrivate(message)) {
+ ++count;
+ }
}
return count;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/ListItem.java b/src/main/java/eu/siacs/conversations/entities/ListItem.java
index 22aedd4b..56804fbf 100644
--- a/src/main/java/eu/siacs/conversations/entities/ListItem.java
+++ b/src/main/java/eu/siacs/conversations/entities/ListItem.java
@@ -11,6 +11,8 @@ public interface ListItem extends Comparable<ListItem> {
Jid getJid();
+ public int getStatusColor();
+
List<Tag> getTags();
final class Tag {
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index be9d3d92..599afc93 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -11,7 +11,6 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.MimeUtils;
-import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -189,14 +188,6 @@ public class Message extends AbstractEntity {
return message;
}
- public static Message createLoadMoreMessage(Conversation conversation) {
- final Message message = new Message();
- message.setType(Message.TYPE_STATUS);
- message.setConversation(conversation);
- message.setBody("LOAD_MORE");
- return message;
- }
-
@Override
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
@@ -380,16 +371,16 @@ public class Message extends AbstractEntity {
public boolean equals(Message message) {
if (this.serverMsgId != null && message.getServerMsgId() != null) {
return this.serverMsgId.equals(message.getServerMsgId());
- } else if (this.body == null || this.counterpart == null) {
+ } else if (this.getBody() == null || this.counterpart == null) {
return false;
} else {
String body, otherBody;
if (this.hasFileOnRemoteHost()) {
body = getFileParams().url.toString();
- otherBody = message.body == null ? null : message.body.trim();
+ otherBody = message.getBody() == null ? null : message.getBody();
} else {
- body = this.body;
- otherBody = message.body;
+ body = this.getBody();
+ otherBody = message.getBody();
}
if (message.getRemoteMsgId() != null) {
return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
@@ -465,13 +456,11 @@ public class Message extends AbstractEntity {
this.edited() == message.edited() &&
(message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
!GeoHelper.isGeoUri(message.getBody()) &&
- !GeoHelper.isGeoUri(this.body) &&
+ !GeoHelper.isGeoUri(this.getBody()) &&
message.treatAsDownloadable() == Decision.NEVER &&
this.treatAsDownloadable() == Decision.NEVER &&
!message.getBody().startsWith(ME_COMMAND) &&
!this.getBody().startsWith(ME_COMMAND) &&
- !this.bodyIsHeart() &&
- !message.bodyIsHeart() &&
this.isTrusted() == message.isTrusted()
);
}
@@ -488,12 +477,12 @@ public class Message extends AbstractEntity {
}
public String getMergedBody() {
- StringBuilder body = new StringBuilder(this.body.trim());
+ StringBuilder body = new StringBuilder(this.body);
Message current = this;
while(current.mergeable(current.next())) {
current = current.next();
body.append(MERGE_SEPARATOR);
- body.append(current.getBody().trim());
+ body.append(current.getBody());
}
return body.toString();
}
@@ -605,7 +594,7 @@ public class Message extends AbstractEntity {
}
} else {
try {
- return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(body.trim())));
+ return MimeUtils.guessMimeTypeFromExtension(extractRelevantExtension(new URL(this.getBody())));
} catch (MalformedURLException e) {
return null;
}
@@ -613,7 +602,12 @@ public class Message extends AbstractEntity {
}
public Decision treatAsDownloadable() {
- if (body.trim().contains(" ")) {
+ /**
+ * there are a few cases where spaces result in an unwanted behavior, e.g.
+ * "http://example.com/image.jpg" text that will not be shown /abc.png"
+ * or more than one image link in one message.
+ */
+ if (getBody().contains(" ")) {
return Decision.NEVER;
}
try {
@@ -648,10 +642,6 @@ public class Message extends AbstractEntity {
}
}
- public boolean bodyIsHeart() {
- return body != null && UIHelper.HEARTS.contains(body.trim());
- }
-
public FileParams getFileParams() {
FileParams params = getLegacyFileParams();
if (params != null) {
@@ -661,10 +651,10 @@ public class Message extends AbstractEntity {
if (this.transferable != null) {
params.size = this.transferable.getFileSize();
}
- if (body == null) {
+ if (this.getBody() == null) {
return params;
}
- String parts[] = body.split("\\|");
+ String parts[] = this.getBody().split("\\|");
switch (parts.length) {
case 1:
try {
@@ -723,10 +713,10 @@ public class Message extends AbstractEntity {
public FileParams getLegacyFileParams() {
FileParams params = new FileParams();
- if (body == null) {
+ if (this.getBody() == null) {
return params;
}
- String parts[] = body.split(",");
+ String parts[] = this.getBody().split(",");
if (parts.length == 3) {
try {
params.size = Long.parseLong(parts[0]);
diff --git a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
index 42f2d840..0e041454 100644
--- a/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
+++ b/src/main/java/eu/siacs/conversations/entities/ServiceDiscoveryResult.java
@@ -159,7 +159,7 @@ public class ServiceDiscoveryResult {
}
public String getVer() {
- return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
+ return new String(Base64.encode(this.ver, Base64.DEFAULT));
}
public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
index 05fa0b82..649f767d 100644
--- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java
@@ -12,11 +12,9 @@ import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.PhoneHelper;
-import eu.siacs.conversations.xmpp.jid.Jid;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public abstract class AbstractGenerator {
private final String[] FEATURES = {
@@ -38,35 +36,13 @@ public abstract class AbstractGenerator {
"urn:xmpp:chat-markers:0",
"urn:xmpp:receipts"
};
- private final String[] MESSAGE_CORRECTION_FEATURES = {
- "urn:xmpp:message-correct:0"
- };
- private String mVersion = null;
- protected final String IDENTITY_NAME = "Conversations";
protected final String IDENTITY_TYPE = "phone";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
- protected XmppConnectionService mXmppConnectionService;
-
- protected AbstractGenerator(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- protected String getIdentityVersion() {
- if (mVersion == null) {
- this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService);
- }
- return this.mVersion;
- }
-
- public String getIdentityName() {
- return IDENTITY_NAME + " " + getIdentityVersion();
- }
-
public String getCapHash() {
StringBuilder s = new StringBuilder();
- s.append("client/" + IDENTITY_TYPE + "//" + getIdentityName() + "<");
+ s.append("client/" + IDENTITY_TYPE + "//" + ConversationsPlusApplication.getNameAndVersion() + "<");
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-1");
@@ -78,7 +54,7 @@ public abstract class AbstractGenerator {
s.append(feature + "<");
}
byte[] sha1 = md.digest(s.toString().getBytes());
- return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
+ return new String(Base64.encode(sha1, Base64.DEFAULT));
}
public static String getTimestamp(long time) {
@@ -89,12 +65,9 @@ public abstract class AbstractGenerator {
public List<String> getFeatures() {
ArrayList<String> features = new ArrayList<>();
features.addAll(Arrays.asList(FEATURES));
- if (mXmppConnectionService.confirmMessages()) {
+ if (Settings.CONFIRM_MESSAGE_RECEIVED) {
features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES));
}
- if (mXmppConnectionService.allowMessageCorrection()) {
- features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES));
- }
Collections.sort(features);
return features;
}
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index a3b0f650..eff9d9c0 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -15,27 +15,22 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.services.MessageArchiveService;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.forms.Data;
-import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class IqGenerator extends AbstractGenerator {
- public IqGenerator(final XmppConnectionService service) {
- super(service);
- }
-
public IqPacket discoResponse(final IqPacket request) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
packet.setId(request.getId());
@@ -46,7 +41,7 @@ public class IqGenerator extends AbstractGenerator {
final Element identity = query.addChild("identity");
identity.setAttribute("category", "client");
identity.setAttribute("type", IDENTITY_TYPE);
- identity.setAttribute("name", getIdentityName());
+ identity.setAttribute("name", ConversationsPlusApplication.getNameAndVersion());
for (final String feature : getFeatures()) {
query.addChild("feature").setAttribute("var", feature);
}
@@ -56,8 +51,8 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket versionResponse(final IqPacket request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
Element query = packet.query("jabber:iq:version");
- query.addChild("name").setContent(IDENTITY_NAME);
- query.addChild("version").setContent(getIdentityVersion());
+ query.addChild("name").setContent(ConversationsPlusApplication.getName());
+ query.addChild("version").setContent(ConversationsPlusApplication.getVersion());
return packet;
}
@@ -89,51 +84,13 @@ public class IqGenerator extends AbstractGenerator {
return publish("http://jabber.org/protocol/nick", item);
}
- public IqPacket publishAvatar(Avatar avatar) {
- final Element item = new Element("item");
- item.setAttribute("id", avatar.sha1sum);
- final Element data = item.addChild("data", "urn:xmpp:avatar:data");
- data.setContent(avatar.image);
- return publish("urn:xmpp:avatar:data", item);
- }
-
- public IqPacket publishAvatarMetadata(final Avatar avatar) {
- final Element item = new Element("item");
- item.setAttribute("id", avatar.sha1sum);
- final Element metadata = item
- .addChild("metadata", "urn:xmpp:avatar:metadata");
- final Element info = metadata.addChild("info");
- info.setAttribute("bytes", avatar.size);
- info.setAttribute("id", avatar.sha1sum);
- info.setAttribute("height", avatar.height);
- info.setAttribute("width", avatar.height);
- info.setAttribute("type", avatar.type);
- return publish("urn:xmpp:avatar:metadata", item);
- }
-
- public IqPacket retrievePepAvatar(final Avatar avatar) {
- final Element item = new Element("item");
- item.setAttribute("id", avatar.sha1sum);
- final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
- packet.setTo(avatar.owner);
- return packet;
- }
-
- public IqPacket retrieveVcardAvatar(final Avatar avatar) {
+ public static IqPacket retrieveVcardAvatar(final Avatar avatar) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(avatar.owner);
packet.addChild("vCard", "vcard-temp");
return packet;
}
- public IqPacket retrieveAvatarMetaData(final Jid to) {
- final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
- if (to != null) {
- packet.setTo(to);
- }
- return packet;
- }
-
public IqPacket retrieveDeviceIds(final Jid to) {
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
if(to != null) {
diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
index 0e7a8ce6..2d7b66b5 100644
--- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java
@@ -9,20 +9,17 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class MessageGenerator extends AbstractGenerator {
- public MessageGenerator(XmppConnectionService service) {
- super(service);
- }
private MessagePacket preparePacket(Message message) {
Conversation conversation = message.getConversation();
@@ -32,13 +29,13 @@ public class MessageGenerator extends AbstractGenerator {
packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT);
packet.addChild("markable", "urn:xmpp:chat-markers:0");
- if (this.mXmppConnectionService.indicateReceived()) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
packet.addChild("request", "urn:xmpp:receipts");
}
} else if (message.getType() == Message.TYPE_PRIVATE) {
packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT);
- if (this.mXmppConnectionService.indicateReceived()) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
packet.addChild("request", "urn:xmpp:receipts");
}
} else {
@@ -72,6 +69,15 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
+ public static void addXhtmlImImage(MessagePacket packet, Message.FileParams params) {
+ Element html = packet.addChild("html", "http://jabber.org/protocol/xhtml-im");
+ Element body = html.addChild("body", "http://www.w3.org/1999/xhtml");
+ Element img = body.addChild("img");
+ img.setAttribute("src", params.url.toString());
+ img.setAttribute("height", params.height);
+ img.setAttribute("width", params.width);
+ }
+
public static void addMessageHints(MessagePacket packet) {
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
@@ -107,6 +113,9 @@ public class MessageGenerator extends AbstractGenerator {
Message.FileParams fileParams = message.getFileParams();
content = fileParams.url.toString();
packet.addChild("x","jabber:x:oob").addChild("url").setContent(content);
+ if (fileParams.width > 0 && fileParams.height > 0) {
+ addXhtmlImImage(packet,fileParams);
+ }
} else {
content = message.getBody();
}
@@ -142,9 +151,8 @@ public class MessageGenerator extends AbstractGenerator {
packet.setType(MessagePacket.TYPE_CHAT);
packet.setTo(to);
packet.setFrom(account.getJid());
- Element received = packet.addChild("displayed","urn:xmpp:chat-markers:0");
+ Element received = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
received.setAttribute("id", id);
- packet.addChild("store", "urn:xmpp:hints");
return packet;
}
diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
index 093a8963..9ac7d318 100644
--- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java
@@ -3,16 +3,11 @@ package eu.siacs.conversations.generator;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Presence;
-import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
public class PresenceGenerator extends AbstractGenerator {
- public PresenceGenerator(XmppConnectionService service) {
- super(service);
- }
-
private PresencePacket subscription(String type, Contact contact) {
PresencePacket packet = new PresencePacket();
packet.setAttribute("type", type);
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
index a8b31a7a..f105646f 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
@@ -1,7 +1,5 @@
package eu.siacs.conversations.http;
-import android.os.Build;
-
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import java.io.IOException;
diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
index 84a44715..cdc5d2e7 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
@@ -21,6 +21,11 @@ import java.util.concurrent.CancellationException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
@@ -43,13 +48,11 @@ public class HttpDownloadConnection implements Transferable {
private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
- private boolean mUseTor = false;
- private boolean canceled = false;
+ private boolean canceled = false;
public HttpDownloadConnection(HttpConnectionManager manager) {
this.mHttpConnectionManager = manager;
this.mXmppConnectionService = manager.getXmppConnectionService();
- this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@Override
@@ -95,7 +98,7 @@ public class HttpDownloadConnection implements Transferable {
extension = lastPart;
}
message.setRelativeFilePath(message.getUuid()+"."+extension);
- this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
String reference = mUrl.getRef();
if (reference != null && reference.length() == 96) {
this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
@@ -186,7 +189,10 @@ public class HttpDownloadConnection implements Transferable {
return;
}
file.setExpectedSize(size);
- if (mHttpConnectionManager.hasStoragePermission() && size <= mHttpConnectionManager.getAutoAcceptFileSize()) {
+ if (mHttpConnectionManager.hasStoragePermission()
+ && size != -1
+ && size <= ConversationsPlusPreferences.autoAcceptFileSize()
+ && mXmppConnectionService.isDownloadAllowedInConnection()) {
HttpDownloadConnection.this.acceptedAutomatically = true;
new Thread(new FileDownloader(interactive)).start();
} else {
@@ -197,34 +203,29 @@ public class HttpDownloadConnection implements Transferable {
}
private long retrieveFileSize() throws IOException {
- try {
- Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
- changeStatus(STATUS_CHECKING);
- HttpURLConnection connection;
- if (mUseTor) {
- connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
- } else {
- connection = (HttpURLConnection) mUrl.openConnection();
- }
- connection.setRequestMethod("HEAD");
- Log.d(Config.LOGTAG,"url: "+connection.getURL().toString());
- Log.d(Config.LOGTAG,"connection: "+connection.toString());
- connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
- if (connection instanceof HttpsURLConnection) {
- mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
- }
- connection.connect();
- String contentLength = connection.getHeaderField("Content-Length");
- connection.disconnect();
- if (contentLength == null) {
- throw new IOException();
- }
- return Long.parseLong(contentLength, 10);
- } catch (IOException e) {
- throw e;
- } catch (NumberFormatException e) {
- throw new IOException();
- }
+ try {
+ Logging.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
+ changeStatus(STATUS_CHECKING);
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ connection.setRequestMethod("HEAD");
+ Logging.d(Config.LOGTAG, "url: "+connection.getURL().toString());
+ Logging.d(Config.LOGTAG, "connection: "+connection.toString());
+ connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
+ if (connection instanceof HttpsURLConnection) {
+ mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
+ }
+ connection.connect();
+ String contentLength = connection.getHeaderField("Content-Length");
+ connection.disconnect();
+ if (contentLength == null) {
+ return -1;
+ }
+ return Long.parseLong(contentLength, 10);
+ } catch (IOException e) {
+ return -1;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
}
}
@@ -256,71 +257,66 @@ public class HttpDownloadConnection implements Transferable {
}
}
- private void download() throws Exception {
- InputStream is = null;
- PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
- try {
- wakeLock.acquire();
- HttpURLConnection connection;
- if (mUseTor) {
- connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
- } else {
- connection = (HttpURLConnection) mUrl.openConnection();
- }
- if (connection instanceof HttpsURLConnection) {
- mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
- }
- connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
- final boolean tryResume = file.exists() && file.getKey() == null;
- if (tryResume) {
- Log.d(Config.LOGTAG,"http download trying resume");
- long size = file.getSize();
- connection.setRequestProperty("Range", "bytes="+size+"-");
- }
- connection.connect();
- is = new BufferedInputStream(connection.getInputStream());
- boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges"));
- long transmitted = 0;
- long expected = file.getExpectedSize();
- if (tryResume && serverResumed) {
- Log.d(Config.LOGTAG,"server resumed");
- transmitted = file.getSize();
- updateProgress((int) ((((double) transmitted) / expected) * 100));
- os = AbstractConnectionManager.createAppendedOutputStream(file);
- } else {
- file.getParentFile().mkdirs();
- file.createNewFile();
- os = AbstractConnectionManager.createOutputStream(file, true);
- }
- int count = -1;
- byte[] buffer = new byte[1024];
- while ((count = is.read(buffer)) != -1) {
- transmitted += count;
- os.write(buffer, 0, count);
- updateProgress((int) ((((double) transmitted) / expected) * 100));
- if (canceled) {
- throw new CancellationException();
- }
- }
- } catch (CancellationException | IOException e) {
- throw e;
- } finally {
- if (os != null) {
- try {
- os.flush();
- } catch (final IOException ignored) {
-
- }
- }
- FileBackend.close(os);
- FileBackend.close(is);
- wakeLock.release();
- }
+ private void download() throws SSLHandshakeException, IOException {
+ InputStream is = null;
+ PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
+ try {
+ wakeLock.acquire();
+ HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
+ if (connection instanceof HttpsURLConnection) {
+ mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
+ }
+ connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
+ final boolean tryResume = file.exists() && file.getKey() == null;
+ if (tryResume) {
+ Logging.d(Config.LOGTAG, "http download trying resume");
+ long size = file.getSize();
+ connection.setRequestProperty("Range", "bytes="+size+"-");
+ }
+ connection.connect();
+ is = new BufferedInputStream(connection.getInputStream());
+ boolean serverResumed = "bytes".equals(connection.getHeaderField("Accept-Ranges"));
+ long transmitted = 0;
+ long expected = file.getExpectedSize();
+ if (tryResume && serverResumed) {
+ Logging.d(Config.LOGTAG, "server resumed");
+ transmitted = file.getSize();
+ updateProgress((int) ((((double) transmitted) / expected) * 100));
+ os = AbstractConnectionManager.createAppendedOutputStream(file);
+ } else {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ os = AbstractConnectionManager.createOutputStream(file, true);
+ }
+ int count = -1;
+ byte[] buffer = new byte[1024];
+ while ((count = is.read(buffer)) != -1) {
+ transmitted += count;
+ os.write(buffer, 0, count);
+ updateProgress((int) ((((double) transmitted) / expected) * 100));
+ if (canceled) {
+ throw new CancellationException();
+ }
+ }
+ } catch (CancellationException | IOException e) {
+ throw e;
+ } finally {
+ if (os != null) {
+ try {
+ os.flush();
+ } catch (final IOException ignored) {
+
+ }
+ }
+ StreamUtil.close(os);
+ StreamUtil.close(is);
+ wakeLock.release();
+ }
}
private void updateImageBounds() {
message.setType(Message.TYPE_FILE);
- mXmppConnectionService.getFileBackend().updateFileParams(message, mUrl);
+ MessageUtil.updateFileParams(message, mUrl);
mXmppConnectionService.updateMessage(message);
}
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
index 1e4b9102..4842a218 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
@@ -4,7 +4,6 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.PowerManager;
-import android.util.Log;
import android.util.Pair;
import java.io.FileNotFoundException;
@@ -12,14 +11,15 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
import java.net.MalformedURLException;
-import java.net.Proxy;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
@@ -49,7 +49,6 @@ public class HttpUploadConnection implements Transferable {
private String mime;
private URL mGetUrl;
private URL mPutUrl;
- private boolean mUseTor = false;
private byte[] key = null;
@@ -60,7 +59,6 @@ public class HttpUploadConnection implements Transferable {
public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
this.mHttpConnectionManager = httpConnectionManager;
this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
- this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@Override
@@ -95,13 +93,13 @@ public class HttpUploadConnection implements Transferable {
mHttpConnectionManager.finishUploadConnection(this);
message.setTransferable(null);
mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
- FileBackend.close(mFileInputStream);
+ StreamUtil.close(mFileInputStream);
}
public void init(Message message, boolean delay) {
this.message = message;
this.account = message.getConversation().getAccount();
- this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
this.mime = this.file.getMimeType();
this.delayed = delay;
if (Config.ENCRYPT_ON_HTTP_UPLOADED
@@ -162,19 +160,16 @@ public class HttpUploadConnection implements Transferable {
PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_"+message.getUuid());
try {
wakeLock.acquire();
- Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
- if (mUseTor) {
- connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy());
- } else {
- connection = (HttpURLConnection) mPutUrl.openConnection();
- }
- if (connection instanceof HttpsURLConnection) {
+ Logging.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
+ connection = (HttpURLConnection) mPutUrl.openConnection();
+
+ if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
}
connection.setRequestMethod("PUT");
connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime);
- connection.setRequestProperty("User-Agent",mXmppConnectionService.getIqGenerator().getIdentityName());
+ connection.setRequestProperty("User-Agent", ConversationsPlusApplication.getNameAndVersion());
connection.setDoOutput(true);
connection.connect();
os = connection.getOutputStream();
@@ -191,11 +186,11 @@ public class HttpUploadConnection implements Transferable {
mFileInputStream.close();
int code = connection.getResponseCode();
if (code == 200 || code == 201) {
- Log.d(Config.LOGTAG, "finished uploading file");
+ Logging.d(Config.LOGTAG, "finished uploading file");
if (key != null) {
mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
}
- mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
+ MessageUtil.updateFileParams(message, mGetUrl);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
mXmppConnectionService.sendBroadcast(intent);
@@ -225,12 +220,10 @@ public class HttpUploadConnection implements Transferable {
fail();
}
} catch (IOException e) {
- e.printStackTrace();
- Log.d(Config.LOGTAG,"http upload failed "+e.getMessage());
+ Logging.d(Config.LOGTAG, e.getMessage());
fail();
} finally {
- FileBackend.close(mFileInputStream);
- FileBackend.close(os);
+ StreamUtil.close(os);
if (connection != null) {
connection.disconnect();
}
diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
index 7d399073..ad368f11 100644
--- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.parser;
-
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -21,6 +20,15 @@ public abstract class AbstractParser {
this.mXmppConnectionService = service;
}
+ /**
+ * Gets the timestamp from the 'delay' element.
+ * Refer to XEP-0203: Delayed Delivery for details. @link{http://xmpp.org/extensions/xep-0203.html}
+ * @param element the element to find the child element 'delay' in.
+ * @return the time in milli seconds of the attribute 'stamp' of the
+ * element 'delay'. In case there is no 'delay' element or no 'stamp'
+ * attribute or the current time is less than the value of the 'stamp'
+ * attribute the current time is returned.
+ */
public static Long getTimestamp(Element element, Long defaultValue) {
Element delay = element.findChild("delay","urn:xmpp:delay");
if (delay != null) {
@@ -40,12 +48,27 @@ public abstract class AbstractParser {
return getTimestamp(packet,System.currentTimeMillis());
}
+ /**
+ * Parses the timestamp according to XEP-0082: XMPP Date and Time Profiles.
+ * @link{http://xmpp.org/extensions/xep-0082.html}
+ *
+ * @param timestamp the timestamp to parse
+ * @return Date
+ * @throws ParseException
+ */
public static Date parseTimestamp(String timestamp) throws ParseException {
- timestamp = timestamp.replace("Z", "+0000");
- SimpleDateFormat dateFormat;
- timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length());
- dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
- return dateFormat.parse(timestamp);
+ /*try {
+ Logging.d("TIMESTAMP", timestamp);
+ return DatatypeFactory.newInstance().newXMLGregorianCalendar(timestamp).toGregorianCalendar().getTime();
+ } catch (DatatypeConfigurationException e) {
+ Logging.d("TIMESTAMP", e.getMessage());
+ return new Date();
+ }*/
+ timestamp = timestamp.replace("Z", "+0000");
+ SimpleDateFormat dateFormat;
+ timestamp = timestamp.substring(0,19)+timestamp.substring(timestamp.length() -5,timestamp.length());
+ dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US);
+ return dateFormat.parse(timestamp);
}
protected void updateLastseen(final AbstractStanza packet, final Account account, final boolean presenceOverwrite) {
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 5903d511..c03ed42f 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -22,10 +22,12 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
@@ -69,7 +71,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
contact.parseSubscriptionFromElement(item);
}
}
- mXmppConnectionService.getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
}
}
mXmppConnectionService.updateConversationUi();
@@ -155,7 +157,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
try {
return Base64.decode(signedPreKeySignature.getContent(), Base64.DEFAULT);
} catch (Throwable e) {
- Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
+ Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : Invalid base64 in signedPreKeySignature");
return null;
}
}
@@ -169,7 +171,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
try {
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
} catch (Throwable e) {
- Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
+ Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
}
return identityKey;
}
@@ -209,7 +211,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
public Pair<X509Certificate[],byte[]> verification(final IqPacket packet) {
Element item = getItem(packet);
- Element verification = item != null ? item.findChild("verification",AxolotlService.PEP_PREFIX) : null;
+ Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null;
Element signature = verification != null ? verification.findChild("signature") : null;
if (chain != null && signature != null) {
@@ -280,7 +282,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} else if ((packet.hasChild("block", Xmlns.BLOCKING) || packet.hasChild("blocklist", Xmlns.BLOCKING)) &&
packet.fromServer(account)) {
// Block list or block push.
- Log.d(Config.LOGTAG, "Received blocklist update from server");
+ Logging.d(Config.LOGTAG, "Received blocklist update from server");
final Element blocklist = packet.findChild("blocklist", Xmlns.BLOCKING);
final Element block = packet.findChild("block", Xmlns.BLOCKING);
final Collection<Element> items = blocklist != null ? blocklist.getChildren() :
@@ -308,7 +310,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
} else if (packet.hasChild("unblock", Xmlns.BLOCKING) &&
packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
- Log.d(Config.LOGTAG, "Received unblock update from server");
+ Logging.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.findChild("unblock", Xmlns.BLOCKING).getChildren();
if (items.size() == 0) {
// No children to unblock == unblock all
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 9470dab8..f9d8f508 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -3,16 +3,22 @@ package eu.siacs.conversations.parser;
import android.util.Log;
import android.util.Pair;
+import de.tzur.conversations.Settings;
+
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionStatus;
import java.net.URL;
import java.util.ArrayList;
import java.util.Set;
-import java.util.UUID;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.AvatarUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
@@ -21,6 +27,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.http.HttpConnectionManager;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -112,7 +119,7 @@ public class MessageParser extends AbstractParser implements
if(plaintextMessage != null) {
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
+ Logging.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
}
return finishedMessage;
@@ -176,23 +183,23 @@ public class MessageParser extends AbstractParser implements
Avatar avatar = Avatar.parseMetadata(items);
if (avatar != null) {
avatar.owner = from.toBareJid();
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (AvatarUtil.isAvatarCached(avatar)) {
if (account.getJid().toBareJid().equals(from)) {
if (account.setAvatar(avatar.getFilename())) {
mXmppConnectionService.databaseBackend.updateAccount(account);
}
- mXmppConnectionService.getAvatarService().clear(account);
+ AvatarService.getInstance().clear(account);
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateAccountUi();
} else {
Contact contact = account.getRoster().getContact(from);
contact.setAvatar(avatar);
- mXmppConnectionService.getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateRosterUi();
}
} else {
- mXmppConnectionService.fetchAvatar(account, avatar);
+ AvatarService.getInstance().fetchAvatar(account, avatar);
}
}
} else if ("http://jabber.org/protocol/nick".equals(node)) {
@@ -201,12 +208,12 @@ public class MessageParser extends AbstractParser implements
if (nick != null && nick.getContent() != null) {
Contact contact = account.getRoster().getContact(from);
contact.setPresenceName(nick.getContent());
- mXmppConnectionService.getAvatarService().clear(account);
+ AvatarService.getInstance().clear(account);
mXmppConnectionService.updateConversationUi();
mXmppConnectionService.updateAccountUi();
}
- } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing...");
+ } else if (ConversationsPlusPreferences.omemoEnabled() && AxolotlService.PEP_DEVICE_LIST.equals(node)) {
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing...");
Element item = items.findChild("item");
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
AxolotlService axolotlService = account.getAxolotlService();
@@ -267,7 +274,7 @@ public class MessageParser extends AbstractParser implements
serverMsgId = result.getAttribute("id");
query.incrementMessageCount();
} else if (query != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
return;
} else if (original.fromServer(account)) {
Pair<MessagePacket, Long> f;
@@ -291,10 +298,8 @@ public class MessageParser extends AbstractParser implements
final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
- final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
final Element oob = packet.findChild("x", "jabber:x:oob");
final boolean isOob = oob!= null && body != null && body.equals(oob.findChildContent("url"));
- final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
int status;
final Jid counterpart;
@@ -354,7 +359,7 @@ public class MessageParser extends AbstractParser implements
return;
}
} else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed));
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed));
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
}
} else if (pgpEncrypted != null && Config.supportOpenPgp()) {
@@ -402,42 +407,6 @@ public class MessageParser extends AbstractParser implements
updateLastseen(timestamp, account, packet.getFrom(), true);
}
- if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
- Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
- counterpart,
- message.getStatus() == Message.STATUS_RECEIVED,
- message.isCarbon());
- if (replacedMessage != null) {
- final boolean fingerprintsMatch = replacedMessage.getAxolotlFingerprint() == null
- || replacedMessage.getAxolotlFingerprint().equals(message.getAxolotlFingerprint());
- final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
- && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
- if (fingerprintsMatch && (trueCountersMatch || conversation.getMode() == Conversation.MODE_SINGLE)) {
- Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
- final String uuid = replacedMessage.getUuid();
- replacedMessage.setUuid(UUID.randomUUID().toString());
- replacedMessage.setBody(message.getBody());
- replacedMessage.setEdited(replacedMessage.getRemoteMsgId());
- replacedMessage.setRemoteMsgId(remoteMsgId);
- replacedMessage.setEncryption(message.getEncryption());
- if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
- replacedMessage.markUnread();
- }
- mXmppConnectionService.updateMessage(replacedMessage, uuid);
- mXmppConnectionService.getNotificationService().updateNotification(false);
- if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
- sendMessageReceipts(account, packet);
- }
- if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
- conversation.getAccount().getPgpDecryptionService().add(replacedMessage);
- }
- return;
- } else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received message correction but verification didn't check out");
- }
- }
- }
-
boolean checkForDuplicates = query != null
|| (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay"))
|| message.getType() == Message.TYPE_PRIVATE;
@@ -471,7 +440,7 @@ public class MessageParser extends AbstractParser implements
mXmppConnectionService.updateConversationUi();
}
- if (mXmppConnectionService.confirmMessages() && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
+ if (Settings.CONFIRM_MESSAGE_READ && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
sendMessageReceipts(account, packet);
}
@@ -482,13 +451,16 @@ public class MessageParser extends AbstractParser implements
conversation.endOtrIfNeeded();
}
- if (message.getEncryption() == Message.ENCRYPTION_NONE || mXmppConnectionService.saveEncryptedMessages()) {
+ if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) {
mXmppConnectionService.databaseBackend.createMessage(message);
}
final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted() && message.treatAsDownloadable() != Message.Decision.NEVER && manager.getAutoAcceptFileSize() > 0) {
+ if (message.trusted()
+ && message.treatAsDownloadable() != Message.Decision.NEVER
+ && ConversationsPlusPreferences.autoAcceptFileSize() > 0
+ && ConversationsPlusPreferences.autoDownloadFileLink()) {
manager.createNewDownloadConnection(message);
- } else if (!message.isRead()) {
+ } else {
if (query == null) {
mXmppConnectionService.getNotificationService().push(message);
} else if (query.getWith() == null) { // mam catchup
diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
index c2782d23..76da5a31 100644
--- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java
@@ -5,6 +5,9 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.AvatarUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -14,8 +17,10 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
import eu.siacs.conversations.generator.PresenceGenerator;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -42,12 +47,13 @@ public class PresenceParser extends AbstractParser implements
processConferencePresence(packet, mucOptions);
final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
if (!tileUserAfter.equals(tileUserBefore)) {
- mXmppConnectionService.getAvatarService().clear(mucOptions);
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": update tiles for " + conversation.getName());
+ AvatarService.getInstance().clear(conversation);
}
if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) {
- mXmppConnectionService.updateConversationUi();
+ UiUpdateHelper.updateConversationUi();
} else if (mucOptions.online()) {
- mXmppConnectionService.updateMucRosterUi();
+ UiUpdateHelper.updateMucRosterUi();
}
}
}
@@ -97,12 +103,12 @@ public class PresenceParser extends AbstractParser implements
}
if (avatar != null) {
avatar.owner = from;
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (AvatarUtil.isAvatarCached(avatar)) {
if (user.setAvatar(avatar)) {
- mXmppConnectionService.getAvatarService().clear(user);
+ AvatarService.getInstance().clear(user);
}
} else {
- mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
+ AvatarService.getInstance().fetchAvatar(mucOptions.getAccount(), avatar);
}
}
}
@@ -129,7 +135,7 @@ public class PresenceParser extends AbstractParser implements
} else if (!from.isBareJid()){
MucOptions.User user = mucOptions.deleteUser(from.getResourcepart());
if (user != null) {
- mXmppConnectionService.getAvatarService().clear(user);
+ AvatarService.getInstance().clear(user);
}
}
} else if (type.equals("error")) {
@@ -182,14 +188,14 @@ public class PresenceParser extends AbstractParser implements
Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
if (avatar != null && !contact.isSelf()) {
avatar.owner = from.toBareJid();
- if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+ if (AvatarUtil.isAvatarCached(avatar)) {
if (contact.setAvatar(avatar)) {
- mXmppConnectionService.getAvatarService().clear(contact);
- mXmppConnectionService.updateConversationUi();
- mXmppConnectionService.updateRosterUi();
+ AvatarService.getInstance().clear(contact);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateRosterUi();
}
} else {
- mXmppConnectionService.fetchAvatar(account, avatar);
+ AvatarService.getInstance().fetchAvatar(account, avatar);
}
}
int sizeBefore = contact.getPresences().size();
@@ -240,7 +246,7 @@ public class PresenceParser extends AbstractParser implements
}
}
}
- mXmppConnectionService.updateRosterUi();
+ UiUpdateHelper.updateRosterUi();
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index b975a001..c376fa73 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -33,8 +33,9 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.json.JSONException;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
@@ -245,7 +246,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
).toString();
} catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
+ Logging.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
+ cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
+ ": " + ignored + ". Skipping...");
continue;
@@ -270,7 +271,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
cursor.getString(cursor.getColumnIndex(Contact.JID))
).toString();
} catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
+ Logging.e(Config.LOGTAG, "Failed to migrate Contact JID "
+ cursor.getString(cursor.getColumnIndex(Contact.JID))
+ ": " + ignored + ". Skipping...");
continue;
@@ -299,7 +300,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
"mobile"
).getDomainpart();
} catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
+ Logging.e(Config.LOGTAG, "Failed to migrate Account SERVER "
+ cursor.getString(cursor.getColumnIndex(Account.SERVER))
+ ": " + ignored + ". Skipping...");
continue;
@@ -365,7 +366,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
- if (oldVersion < 22 && newVersion >= 22) {
+ if (oldVersion < 22 && oldVersion >= 15 && newVersion >= 22) {
db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
}
@@ -944,7 +945,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
try {
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
} catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
}
cursor.close();
@@ -969,7 +970,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
try {
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT), 0));
} catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
}
}
cursor.close();
@@ -1099,7 +1100,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
public void recreateAxolotlDb(SQLiteDatabase db) {
- Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
db.execSQL(CREATE_SESSIONS_STATEMENT);
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
@@ -1112,7 +1113,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void wipeAxolotlDb(Account account) {
String accountName = account.getUuid();
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
SQLiteDatabase db = this.getWritableDatabase();
String[] deleteArgs = {
accountName
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 4bdf080c..2fc4f3ef 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -1,60 +1,39 @@
package eu.siacs.conversations.persistance;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.RectF;
import android.net.Uri;
import android.os.Environment;
-import android.util.Base64;
-import android.util.Base64OutputStream;
import android.util.Log;
import android.webkit.MimeTypeMap;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.Socket;
-import java.net.URL;
-import java.security.DigestOutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
-import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.exceptions.FileCopyException;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.entities.Transferable;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.ExifHelper;
-import eu.siacs.conversations.utils.FileUtils;
-import eu.siacs.conversations.xmpp.pep.Avatar;
public class FileBackend {
- private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
+ private static final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
- private XmppConnectionService mXmppConnectionService;
-
- public FileBackend(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- public DownloadableFile getFile(Message message) {
+ public static DownloadableFile getFile(Message message) {
return getFile(message, true);
}
- public DownloadableFile getFile(Message message, boolean decrypted) {
+ public static DownloadableFile getFile(Message message, boolean decrypted) {
final boolean encrypted = !decrypted
&& (message.getEncryption() == Message.ENCRYPTION_PGP
|| message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
@@ -81,88 +60,29 @@ public class FileBackend {
}
public static String getConversationsFileDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + ConversationsPlusPreferences.fileTransferFolder() + File.separator;
}
public static String getConversationsImageDirectory() {
- return Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES).getAbsolutePath()
- + "/Conversations/";
- }
-
- public Bitmap resize(Bitmap originalBitmap, int size) {
- int w = originalBitmap.getWidth();
- int h = originalBitmap.getHeight();
- if (Math.max(w, h) > size) {
- int scalledW;
- int scalledH;
- if (w <= h) {
- scalledW = (int) (w / ((double) h / size));
- scalledH = size;
- } else {
- scalledW = size;
- scalledH = (int) (h / ((double) w / size));
- }
- Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
- if (originalBitmap != null && !originalBitmap.isRecycled()) {
- originalBitmap.recycle();
- }
- return result;
- } else {
- return originalBitmap;
- }
- }
-
- public static Bitmap rotate(Bitmap bitmap, int degree) {
- if (degree == 0) {
- return bitmap;
- }
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
- Matrix mtx = new Matrix();
- mtx.postRotate(degree);
- Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
- if (bitmap != null && !bitmap.isRecycled()) {
- bitmap.recycle();
- }
- return result;
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + File.separator + ConversationsPlusPreferences.imgTransferFolder() + File.separator;
}
- public boolean useImageAsIs(Uri uri) {
- String path = getOriginalPath(uri);
- if (path == null) {
- return false;
- }
- File file = new File(path);
- long size = file.length();
- if (size == 0 || size >= Config.IMAGE_MAX_SIZE ) {
- return false;
- }
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- try {
- BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options);
- if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) {
- return false;
- }
- return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
- } catch (FileNotFoundException e) {
- return false;
- }
- }
+ private static String getPrivateFileDirectoryPath() {
+ return ConversationsPlusApplication.getPrivateFilesDir().getAbsolutePath();
+ }
- public String getOriginalPath(Uri uri) {
- return FileUtils.getPath(mXmppConnectionService,uri);
- }
+ private static String getPrivateImageDirectoryPath() {
+ return FileBackend.getPrivateFileDirectoryPath() + File.separator + "Images" + File.separator;
+ }
- public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
+ public static void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
file.getParentFile().mkdirs();
OutputStream os = null;
InputStream is = null;
try {
file.createNewFile();
os = new FileOutputStream(file);
- is = mXmppConnectionService.getContentResolver().openInputStream(uri);
+ is = StreamUtil.openInputStreamFromContentResolver(uri);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
@@ -175,420 +95,69 @@ public class FileBackend {
e.printStackTrace();
throw new FileCopyException(R.string.error_io_exception);
} finally {
- close(os);
- close(is);
+ StreamUtil.close(os);
+ StreamUtil.close(is);
}
- Log.d(Config.LOGTAG, "output file name " + file.getAbsolutePath());
+ Logging.d(Config.LOGTAG, "output file name " + file);
}
- public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ public static void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
- String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ String mime = ConversationsPlusApplication.getInstance().getContentResolver().getType(uri);
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
message.setRelativeFilePath(message.getUuid() + "." + extension);
- copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
- }
-
- private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException {
- file.getParentFile().mkdirs();
- InputStream is = null;
- OutputStream os = null;
- try {
- file.createNewFile();
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- Bitmap originalBitmap;
- BitmapFactory.Options options = new BitmapFactory.Options();
- int inSampleSize = (int) Math.pow(2, sampleSize);
- Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
- options.inSampleSize = inSampleSize;
- originalBitmap = BitmapFactory.decodeStream(is, null, options);
- is.close();
- if (originalBitmap == null) {
- throw new FileCopyException(R.string.error_not_an_image_file);
- }
- Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE);
- int rotation = getRotation(image);
- scaledBitmap = rotate(scaledBitmap, rotation);
- boolean targetSizeReached = false;
- int quality = Config.IMAGE_QUALITY;
- while(!targetSizeReached) {
- os = new FileOutputStream(file);
- boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os);
- if (!success) {
- throw new FileCopyException(R.string.error_compressing_image);
- }
- os.flush();
- targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50;
- quality -= 5;
- }
- scaledBitmap.recycle();
- return;
- } catch (FileNotFoundException e) {
- throw new FileCopyException(R.string.error_file_not_found);
- } catch (IOException e) {
- e.printStackTrace();
- throw new FileCopyException(R.string.error_io_exception);
- } catch (SecurityException e) {
- throw new FileCopyException(R.string.error_security_exception_during_image_copy);
- } catch (OutOfMemoryError e) {
- ++sampleSize;
- if (sampleSize <= 3) {
- copyImageToPrivateStorage(file, image, sampleSize);
- } else {
- throw new FileCopyException(R.string.error_out_of_memory);
- }
- } catch (NullPointerException e) {
- throw new FileCopyException(R.string.error_io_exception);
- } finally {
- close(os);
- close(is);
- }
- }
-
- public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException {
- copyImageToPrivateStorage(file, image, 0);
- }
-
- public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException {
- switch(Config.IMAGE_FORMAT) {
- case JPEG:
- message.setRelativeFilePath(message.getUuid()+".jpg");
- break;
- case PNG:
- message.setRelativeFilePath(message.getUuid()+".png");
- break;
- case WEBP:
- message.setRelativeFilePath(message.getUuid()+".webp");
- break;
- }
- copyImageToPrivateStorage(getFile(message), image);
- updateFileParams(message);
- }
-
- private int getRotation(File file) {
- return getRotation(Uri.parse("file://"+file.getAbsolutePath()));
- }
-
- private int getRotation(Uri image) {
- InputStream is = null;
- try {
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- return ExifHelper.getOrientation(is);
- } catch (FileNotFoundException e) {
- return 0;
- } finally {
- close(is);
- }
- }
-
- public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
- throws FileNotFoundException {
- Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(message.getUuid());
- if ((thumbnail == null) && (!cacheOnly)) {
- File file = getFile(message);
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(file, size);
- Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
- if (fullsize == null) {
- throw new FileNotFoundException();
- }
- thumbnail = resize(fullsize, size);
- thumbnail = rotate(thumbnail, getRotation(file));
- this.mXmppConnectionService.getBitmapCache().put(message.getUuid(),thumbnail);
- }
- return thumbnail;
- }
-
- public Uri getTakePhotoUri() {
+ copyFileToPrivateStorage(getFile(message), uri);
+ }
+
+ public static DownloadableFile compressImageAndCopyToPrivateStorage(Message message, Bitmap scaledBitmap) throws FileCopyException {
+ message.setRelativeFilePath(FileBackend.getPrivateImageDirectoryPath() + message.getUuid() + ".png");
+ DownloadableFile file = getFile(message);
+ file.getParentFile().mkdirs();
+ OutputStream os = null;
+ try {
+ file.createNewFile();
+ os = new FileOutputStream(file);
+
+ boolean success = scaledBitmap.compress(Bitmap.CompressFormat.PNG, 75, os);
+ if (!success) {
+ throw new FileCopyException(R.string.error_compressing_image);
+ }
+ os.flush();
+ } catch (IOException e) {
+ throw new FileCopyException(R.string.error_io_exception, e);
+ } catch (SecurityException e) {
+ throw new FileCopyException(R.string.error_security_exception_during_image_copy);
+ } catch (NullPointerException e) {
+ throw new FileCopyException(R.string.error_io_exception);
+ } finally {
+ StreamUtil.close(os);
+ }
+ return file;
+ }
+
+ public static Uri getTakePhotoUri() {
StringBuilder pathBuilder = new StringBuilder();
pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
pathBuilder.append('/');
pathBuilder.append("Camera");
pathBuilder.append('/');
- pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg");
+ pathBuilder.append("IMG_" + imageDateFormat.format(new Date()) + ".jpg");
Uri uri = Uri.parse("file://" + pathBuilder.toString());
File file = new File(uri.toString());
file.getParentFile().mkdirs();
return uri;
}
- public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
- try {
- Avatar avatar = new Avatar();
- Bitmap bm = cropCenterSquare(image, size);
- if (bm == null) {
- return null;
- }
- ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
- Base64OutputStream mBase64OutputSttream = new Base64OutputStream(
- mByteArrayOutputStream, Base64.DEFAULT);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(
- mBase64OutputSttream, digest);
- if (!bm.compress(format, 75, mDigestOutputStream)) {
- return null;
- }
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
- avatar.image = new String(mByteArrayOutputStream.toByteArray());
- return avatar;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } catch (IOException e) {
- return null;
- }
- }
-
- public boolean isAvatarCached(Avatar avatar) {
- File file = new File(getAvatarPath(avatar.getFilename()));
- return file.exists();
- }
-
- public boolean save(Avatar avatar) {
- File file;
- if (isAvatarCached(avatar)) {
- file = new File(getAvatarPath(avatar.getFilename()));
- } else {
- String filename = getAvatarPath(avatar.getFilename());
- file = new File(filename + ".tmp");
- file.getParentFile().mkdirs();
- OutputStream os = null;
- try {
- file.createNewFile();
- os = new FileOutputStream(file);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- digest.reset();
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
- mDigestOutputStream.write(avatar.getImageAsBytes());
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- String sha1sum = CryptoHelper.bytesToHex(digest.digest());
- if (sha1sum.equals(avatar.sha1sum)) {
- file.renameTo(new File(filename));
- } else {
- Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
- file.delete();
- return false;
- }
- } catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) {
- return false;
- } finally {
- close(os);
- }
- }
- avatar.size = file.length();
- return true;
- }
-
- public String getAvatarPath(String avatar) {
- return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
- }
-
- public Uri getAvatarUri(String avatar) {
- return Uri.parse("file:" + getAvatarPath(avatar));
- }
-
- public Bitmap cropCenterSquare(Uri image, int size) {
- if (image == null) {
- return null;
- }
- InputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(image, size);
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- return null;
- }
- Bitmap input = BitmapFactory.decodeStream(is, null, options);
- if (input == null) {
- return null;
- } else {
- input = rotate(input, getRotation(image));
- return cropCenterSquare(input, size);
- }
- } catch (SecurityException e) {
- return null; // happens for example on Android 6.0 if contacts permissions get revoked
- } catch (FileNotFoundException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
- if (image == null) {
- return null;
- }
- InputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(image, Math.max(newHeight, newWidth));
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- return null;
- }
- Bitmap source = BitmapFactory.decodeStream(is, null, options);
- if (source == null) {
- return null;
- }
- int sourceWidth = source.getWidth();
- int sourceHeight = source.getHeight();
- float xScale = (float) newWidth / sourceWidth;
- float yScale = (float) newHeight / sourceHeight;
- float scale = Math.max(xScale, yScale);
- float scaledWidth = scale * sourceWidth;
- float scaledHeight = scale * sourceHeight;
- float left = (newWidth - scaledWidth) / 2;
- float top = (newHeight - scaledHeight) / 2;
-
- RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
- Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(dest);
- canvas.drawBitmap(source, null, targetRect, null);
- if (source != null && !source.isRecycled()) {
- source.recycle();
- }
- return dest;
- } catch (SecurityException e) {
- return null; //android 6.0 with revoked permissions for example
- } catch (FileNotFoundException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public Bitmap cropCenterSquare(Bitmap input, int size) {
- int w = input.getWidth();
- int h = input.getHeight();
-
- float scale = Math.max((float) size / h, (float) size / w);
-
- float outWidth = scale * w;
- float outHeight = scale * h;
- float left = (size - outWidth) / 2;
- float top = (size - outHeight) / 2;
- RectF target = new RectF(left, top, left + outWidth, top + outHeight);
-
- Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- canvas.drawBitmap(input, null, target, null);
- if (input != null && !input.isRecycled()) {
- input.recycle();
- }
- return output;
- }
-
- private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options);
- return calcSampleSize(options, size);
- }
-
- private static int calcSampleSize(File image, int size) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(image.getAbsolutePath(), options);
- return calcSampleSize(options, size);
- }
-
- public static int calcSampleSize(BitmapFactory.Options options, int size) {
- int height = options.outHeight;
- int width = options.outWidth;
- int inSampleSize = 1;
-
- if (height > size || width > size) {
- int halfHeight = height / 2;
- int halfWidth = width / 2;
-
- while ((halfHeight / inSampleSize) > size
- && (halfWidth / inSampleSize) > size) {
- inSampleSize *= 2;
- }
- }
- return inSampleSize;
- }
-
- public Uri getJingleFileUri(Message message) {
+ public static Uri getJingleFileUri(Message message) {
File file = getFile(message);
return Uri.parse("file://" + file.getAbsolutePath());
}
- public void updateFileParams(Message message) {
- updateFileParams(message,null);
- }
-
- public void updateFileParams(Message message, URL url) {
- DownloadableFile file = getFile(message);
- if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- int rotation = getRotation(file);
- boolean rotated = rotation == 90 || rotation == 270;
- int imageHeight = rotated ? options.outWidth : options.outHeight;
- int imageWidth = rotated ? options.outHeight : options.outWidth;
- if (url == null) {
- message.setBody(Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
- } else {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
- }
- } else {
- if (url != null) {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
- } else {
- message.setBody(Long.toString(file.getSize()));
- }
- }
-
- }
-
- public class FileCopyException extends Exception {
- private static final long serialVersionUID = -1010013599132881427L;
- private int resId;
-
- public FileCopyException(int resId) {
- this.resId = resId;
- }
-
- public int getResId() {
- return resId;
- }
- }
-
- public Bitmap getAvatar(String avatar, int size) {
- if (avatar == null) {
- return null;
- }
- Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
- if (bm == null) {
- return null;
- }
- return bm;
- }
-
- public boolean isFileAvailable(Message message) {
+ public static boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
- public static void close(Closeable stream) {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- }
- }
- }
-
- public static void close(Socket socket) {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException e) {
- }
- }
- }
+ private FileBackend() {
+ // Static helper class
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
index 8d02f975..7728c38a 100644
--- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
@@ -44,16 +44,6 @@ public class AbstractConnectionManager {
return this.mXmppConnectionService;
}
- public long getAutoAcceptFileSize() {
- String config = this.mXmppConnectionService.getPreferences().getString(
- "auto_accept_file_size", "524288");
- try {
- return Long.parseLong(config);
- } catch (NumberFormatException e) {
- return 524288;
- }
- }
-
public boolean hasStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java
index 276be10d..93eb0889 100644
--- a/src/main/java/eu/siacs/conversations/services/AvatarService.java
+++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java
@@ -8,9 +8,20 @@ import android.graphics.Typeface;
import android.net.Uri;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.utils.AvatarUtil;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketGenerator;
+import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketParser;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
@@ -18,7 +29,14 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
+import eu.siacs.conversations.generator.IqGenerator;
+import eu.siacs.conversations.persistance.DatabaseBackend;
+import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.xml.Element;
+import eu.siacs.conversations.xmpp.OnIqPacketReceived;
+import eu.siacs.conversations.xmpp.pep.Avatar;
+import eu.siacs.conversations.xmpp.stanzas.IqPacket;
public class AvatarService {
@@ -30,31 +48,31 @@ public class AvatarService {
private static final String PREFIX_CONVERSATION = "conversation";
private static final String PREFIX_ACCOUNT = "account";
private static final String PREFIX_GENERIC = "generic";
+ private static final AvatarService INSTANCE = new AvatarService();
- final private ArrayList<Integer> sizes = new ArrayList<>();
-
- protected XmppConnectionService mXmppConnectionService = null;
-
- public AvatarService(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
+ final private ArrayList<Integer> sizes = new ArrayList<>();
+ private final List<String> mInProgressAvatarFetches = new ArrayList<>();
+
+ public static AvatarService getInstance() {
+ return INSTANCE;
+ }
private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
final String KEY = key(contact, size);
- Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap avatar = ImageUtil.getBitmapFromCache(KEY);
if (avatar != null || cachedOnly) {
return avatar;
}
if (contact.getProfilePhoto() != null) {
- avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
+ avatar = ImageUtil.cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
}
if (avatar == null && contact.getAvatar() != null) {
- avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size);
+ avatar = AvatarUtil.getAvatar(contact.getAvatar(), size);
}
if (avatar == null) {
avatar = get(contact.getDisplayName(), size, cachedOnly);
}
- this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+ ImageUtil.addBitmapToCache(KEY, avatar);
return avatar;
}
@@ -69,12 +87,12 @@ public class AvatarService {
private Bitmap getImpl(final MucOptions.User user, final int size, boolean cachedOnly) {
final String KEY = key(user, size);
- Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap avatar = ImageUtil.getBitmapFromCache(KEY);
if (avatar != null || cachedOnly) {
return avatar;
}
if (user.getAvatar() != null) {
- avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size);
+ avatar = AvatarUtil.getAvatar(user.getAvatar(), size);
}
if (avatar == null) {
Contact contact = user.getContact();
@@ -84,15 +102,14 @@ public class AvatarService {
avatar = get(user.getName(), size, cachedOnly);
}
}
- this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+ ImageUtil.addBitmapToCache(KEY, avatar);
return avatar;
}
public void clear(Contact contact) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(
- key(contact, size));
+ ImageUtil.removeBitmapFromCache(key(contact, size));
}
}
}
@@ -158,7 +175,7 @@ public class AvatarService {
private Bitmap get(MucOptions mucOptions, int size, boolean cachedOnly) {
final String KEY = key(mucOptions, size);
- Bitmap bitmap = this.mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap bitmap = ImageUtil.getBitmapFromCache(KEY);
if (bitmap != null || cachedOnly) {
return bitmap;
}
@@ -195,14 +212,14 @@ public class AvatarService {
drawTile(canvas, "\u2026", PLACEHOLDER_COLOR, size / 2 + 1, size / 2 + 1,
size, size);
}
- this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
+ ImageUtil.addBitmapToCache(KEY, bitmap);
return bitmap;
}
public void clear(MucOptions options) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(key(options, size));
+ ImageUtil.removeBitmapFromCache(key(options, size));
}
}
}
@@ -223,16 +240,15 @@ public class AvatarService {
public Bitmap get(Account account, int size, boolean cachedOnly) {
final String KEY = key(account, size);
- Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap avatar = ImageUtil.getBitmapFromCache(KEY);
if (avatar != null || cachedOnly) {
return avatar;
}
- avatar = mXmppConnectionService.getFileBackend().getAvatar(
- account.getAvatar(), size);
+ avatar = AvatarUtil.getAvatar(account.getAvatar(), size);
if (avatar == null) {
avatar = get(account.getJid().toBareJid().toString(), size,false);
}
- mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+ ImageUtil.addBitmapToCache(KEY, avatar);
return avatar;
}
@@ -257,7 +273,7 @@ public class AvatarService {
public void clear(Account account) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(key(account, size));
+ ImageUtil.removeBitmapFromCache(key(account, size));
}
}
}
@@ -265,7 +281,7 @@ public class AvatarService {
public void clear(MucOptions.User user) {
synchronized (this.sizes) {
for (Integer size : sizes) {
- this.mXmppConnectionService.getBitmapCache().remove(key(user, size));
+ ImageUtil.removeBitmapFromCache(key(user, size));
}
}
}
@@ -286,7 +302,7 @@ public class AvatarService {
public Bitmap get(final String name, final int size, boolean cachedOnly) {
final String KEY = key(name, size);
- Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
+ Bitmap bitmap = ImageUtil.getBitmapFromCache(KEY);
if (bitmap != null || cachedOnly) {
return bitmap;
}
@@ -294,7 +310,7 @@ public class AvatarService {
Canvas canvas = new Canvas(bitmap);
final String trimmedName = name.trim();
drawTile(canvas, trimmedName, 0, 0, size, size);
- mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
+ ImageUtil.addBitmapToCache(KEY, bitmap);
return bitmap;
}
@@ -335,14 +351,13 @@ public class AvatarService {
if (contact.getProfilePhoto() != null) {
uri = Uri.parse(contact.getProfilePhoto());
} else if (contact.getAvatar() != null) {
- uri = mXmppConnectionService.getFileBackend().getAvatarUri(
- contact.getAvatar());
+ uri = AvatarUtil.getAvatarUri(contact.getAvatar());
}
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
}
} else if (user.getAvatar() != null) {
- Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar());
+ Uri uri = AvatarUtil.getAvatarUri(user.getAvatar());
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
}
@@ -355,7 +370,7 @@ public class AvatarService {
private boolean drawTile(Canvas canvas, Account account, int left, int top, int right, int bottom) {
String avatar = account.getAvatar();
if (avatar != null) {
- Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(avatar);
+ Uri uri = AvatarUtil.getAvatarUri(avatar);
if (uri != null) {
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
@@ -377,8 +392,7 @@ public class AvatarService {
private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) {
if (uri != null) {
- Bitmap bitmap = mXmppConnectionService.getFileBackend()
- .cropCenter(uri, bottom - top, right - left);
+ Bitmap bitmap = ImageUtil.cropCenter(uri, bottom - top, right - left);
if (bitmap != null) {
drawTile(canvas, bitmap, left, top, right, bottom);
return true;
@@ -393,4 +407,221 @@ public class AvatarService {
canvas.drawBitmap(bm, null, dst, null);
return true;
}
+
+ public void publishAvatar(final Account account,
+ final Uri image,
+ final UiCallback<Avatar> callback) {
+ final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
+ final int size = Config.AVATAR_SIZE;
+ final Avatar avatar = AvatarUtil.getPepAvatar(image, size, format);
+ if (avatar != null) {
+ avatar.height = size;
+ avatar.width = size;
+ if (format.equals(Bitmap.CompressFormat.WEBP)) {
+ avatar.type = "image/webp";
+ } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
+ avatar.type = "image/jpeg";
+ } else if (format.equals(Bitmap.CompressFormat.PNG)) {
+ avatar.type = "image/png";
+ }
+ if (!AvatarUtil.save(avatar)) {
+ callback.error(R.string.error_saving_avatar, avatar);
+ return;
+ }
+ final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarPacket(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket result) {
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarMetadataPacket(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account,
+ IqPacket result) {
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ if (account.setAvatar(avatar.getFilename())) {
+ AvatarService.getInstance().clear(account);
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account);
+ }
+ callback.success(avatar);
+ } else {
+ callback.error(
+ R.string.error_publish_avatar_server_reject,
+ avatar);
+ }
+ }
+ });
+ } else {
+ callback.error(
+ R.string.error_publish_avatar_server_reject,
+ avatar);
+ }
+ }
+ });
+ } else {
+ callback.error(R.string.error_publish_avatar_converting, null);
+ }
+ }
+
+ private static String generateFetchKey(Account account, final Avatar avatar) {
+ return account.getJid().toBareJid()+"_"+avatar.owner+"_"+avatar.sha1sum;
+ }
+
+ public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ final String KEY = generateFetchKey(account, avatar);
+ synchronized(this.mInProgressAvatarFetches) {
+ if (this.mInProgressAvatarFetches.contains(KEY)) {
+ return;
+ } else {
+ switch (avatar.origin) {
+ case PEP:
+ this.mInProgressAvatarFetches.add(KEY);
+ fetchAvatarPep(account, avatar, callback);
+ break;
+ case VCARD:
+ this.mInProgressAvatarFetches.add(KEY);
+ fetchAvatarVcard(account, avatar, callback);
+ break;
+ }
+ }
+ }
+ }
+
+ private void fetchAvatarPep(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ IqPacket packet = AvatarPacketGenerator.generateRetrieveAvatarPacket(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket result) {
+ synchronized (mInProgressAvatarFetches) {
+ mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
+ }
+ final String ERROR = account.getJid().toBareJid()
+ + ": fetching avatar for " + avatar.owner + " failed ";
+ if (result.getType() == IqPacket.TYPE.RESULT) {
+ avatar.image = AvatarPacketParser.parseAvatarData(result);
+ if (avatar.image != null) {
+ if (AvatarUtil.save(avatar)) {
+ if (account.getJid().toBareJid().equals(avatar.owner)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account);
+ }
+ AvatarService.this.clear(account);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateAccountUi();
+ } else {
+ Contact contact = account.getRoster()
+ .getContact(avatar.owner);
+ contact.setAvatar(avatar);
+ AvatarService.this.clear(contact);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateRosterUi();
+ }
+ if (callback != null) {
+ callback.success(avatar);
+ }
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": succesfuly fetched pep avatar for " + avatar.owner);
+ return;
+ }
+ } else {
+
+ Logging.d(Config.LOGTAG, ERROR + "(parsing error)");
+ }
+ } else {
+ Element error = result.findChild("error");
+ if (error == null) {
+ Logging.d(Config.LOGTAG, ERROR + "(server error)");
+ } else {
+ Logging.d(Config.LOGTAG, ERROR + error.toString());
+ }
+ }
+ if (callback != null) {
+ callback.error(0, null);
+ }
+
+ }
+ });
+ }
+
+ private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+ IqPacket packet = IqGenerator.retrieveVcardAvatar(avatar);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ synchronized (mInProgressAvatarFetches) {
+ mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
+ }
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element vCard = packet.findChild("vCard", "vcard-temp");
+ Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
+ String image = photo != null ? photo.findChildContent("BINVAL") : null;
+ if (image != null) {
+ avatar.image = image;
+ if (AvatarUtil.save(avatar)) {
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": successfully fetched vCard avatar for " + avatar.owner);
+ Contact contact = account.getRoster()
+ .getContact(avatar.owner);
+ contact.setAvatar(avatar);
+ AvatarService.this.clear(contact);
+ UiUpdateHelper.updateConversationUi();
+ UiUpdateHelper.updateRosterUi();
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public void checkForAvatar(Account account, final UiCallback<Avatar> callback) {
+ IqPacket packet = AvatarPacketGenerator.generateRetrieveAvatarMetadataPacket(null);
+ XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ Element pubsub = packet.findChild("pubsub",
+ "http://jabber.org/protocol/pubsub");
+ if (pubsub != null) {
+ Element items = pubsub.findChild("items");
+ if (items != null) {
+ Avatar avatar = Avatar.parseMetadata(items);
+ if (avatar != null) {
+ avatar.owner = account.getJid().toBareJid();
+ if (AvatarUtil.isAvatarCached(avatar)) {
+ if (account.setAvatar(avatar.getFilename())) {
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account);
+ }
+ AvatarService.this.clear(account);
+ callback.success(avatar);
+ } else {
+ fetchAvatarPep(account, avatar, callback);
+ }
+ return;
+ }
+ }
+ }
+ }
+ callback.error(0, null);
+ }
+ });
+ }
+
+ public void fetchAvatar(Account account, Avatar avatar) {
+ fetchAvatar(account, avatar, null);
+ }
+
+ public void clearFetchInProgress(Account account) {
+ synchronized (this.mInProgressAvatarFetches) {
+ for(Iterator<String> iterator = this.mInProgressAvatarFetches.iterator(); iterator.hasNext();) {
+ final String KEY = iterator.next();
+ if (KEY.startsWith(account.getJid().toBareJid()+"_")) {
+ iterator.remove();
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
index c2a45bf2..52894f07 100644
--- a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
+++ b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java
@@ -1,7 +1,6 @@
package eu.siacs.conversations.services;
import android.annotation.TargetApi;
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -11,11 +10,8 @@ import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.SystemClock;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
-import android.util.DisplayMetrics;
-import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -51,7 +47,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements
for(int i = 0; i < Math.min(conversations.size(),MAX_TARGETS); ++i) {
final Conversation conversation = conversations.get(i);
final String name = conversation.getName();
- final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel));
+ final Icon icon = Icon.createWithBitmap(AvatarService.getInstance().get(conversation, pixel));
final float score = (1.0f / MAX_TARGETS) * i;
final Bundle extras = new Bundle();
extras.putString("uuid", conversation.getUuid());
diff --git a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
index 87e65931..53d0caaf 100644
--- a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
+++ b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
+
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
index e8616bd8..f3a1a7f9 100644
--- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
+++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.services;
-import android.util.Log;
import android.util.Pair;
import java.math.BigInteger;
@@ -9,6 +8,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -30,7 +30,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
public enum PagingOrder {
NORMAL,
REVERSE
- }
+ };
public MessageArchiveService(final XmppConnectionService service) {
this.mXmppConnectionService = service;
@@ -128,7 +128,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
private void execute(final Query query) {
final Account account= query.getAccount();
if (account.getStatus() == Account.State.ONLINE) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString());
IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
@Override
@@ -141,7 +141,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
} else if (packet.getType() != IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString());
finalizeQuery(query, true);
}
}
@@ -171,6 +171,9 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
if (query.hasCallback()) {
query.callback(done);
} else {
+ if (null != conversation) {
+ conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0);
+ }
this.mXmppConnectionService.updateConversationUi();
}
}
@@ -213,7 +216,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
if (complete || relevant == null || abort) {
final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0;
this.finalizeQuery(query, done);
- Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done));
+ Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done));
if (query.getWith() == null && query.getMessageCount() > 0) {
mXmppConnectionService.getNotificationService().finishBacklog(true);
}
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index cec9a3ef..0f40e8d7 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -5,7 +5,6 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
@@ -26,9 +25,12 @@ import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -36,7 +38,6 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
-import eu.siacs.conversations.ui.TimePreference;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
@@ -60,10 +61,11 @@ public class NotificationService {
public boolean notify(final Message message) {
return (message.getStatus() == Message.STATUS_RECEIVED)
- && notificationsEnabled()
- && !message.getConversation().isMuted()
- && (message.getConversation().alwaysNotify() || wasHighlightedOrPrivate(message)
- );
+ && !message.isRead()
+ && ConversationsPlusPreferences.showNotification()
+ && !message.getConversation().isMuted()
+ && (message.getConversation().alwaysNotify() || wasHighlightedOrPrivate(message)
+ );
}
public void notifyPebble(final Message message) {
@@ -77,7 +79,7 @@ public class NotificationService {
final String notificationData = new JSONArray().put(jsonData).toString();
i.putExtra("messageType", "PEBBLE_ALERT");
- i.putExtra("sender", "Conversations"); /* XXX: Shouldn't be hardcoded, e.g., AbstractGenerator.APP_NAME); */
+ i.putExtra("sender", ConversationsPlusApplication.getName());
i.putExtra("notificationData", notificationData);
// notify Pebble App
i.setPackage("com.getpebble.android");
@@ -87,17 +89,12 @@ public class NotificationService {
mXmppConnectionService.sendBroadcast(i);
}
-
- public boolean notificationsEnabled() {
- return mXmppConnectionService.getPreferences().getBoolean("show_notification", true);
- }
-
public boolean isQuietHours() {
- if (!mXmppConnectionService.getPreferences().getBoolean("enable_quiet_hours", false)) {
+ if (!ConversationsPlusPreferences.enableQuietHours()) {
return false;
}
- final long startTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
- final long endTime = mXmppConnectionService.getPreferences().getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY;
+ final long startTime = ConversationsPlusPreferences.quietHoursStart() % Config.MILLISECONDS_IN_DAY;
+ final long endTime = ConversationsPlusPreferences.quietHoursEnd() % Config.MILLISECONDS_IN_DAY;
final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY;
if (endTime < startTime) {
@@ -134,10 +131,10 @@ public class NotificationService {
}
public void push(final Message message) {
- mXmppConnectionService.updateUnreadCountBadge();
if (!notify(message)) {
return;
}
+ mXmppConnectionService.updateUnreadCountBadge();
final boolean isScreenOn = mXmppConnectionService.isInteractive();
if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
return;
@@ -174,12 +171,10 @@ public class NotificationService {
}
public void updateNotification(final boolean notify) {
- final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService
- .getSystemService(Context.NOTIFICATION_SERVICE);
- final SharedPreferences preferences = mXmppConnectionService.getPreferences();
+ final NotificationManager notificationManager = (NotificationManager) ConversationsPlusApplication.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
- final String ringtone = preferences.getString("notification_ringtone", null);
- final boolean vibrate = preferences.getBoolean("vibrate_on_notification", true);
+ final String ringtone = ConversationsPlusPreferences.notificationRingtone();
+ final boolean vibrate = ConversationsPlusPreferences.vibrateOnNotification();
if (notifications.size() == 0) {
notificationManager.cancel(NOTIFICATION_ID);
@@ -210,7 +205,7 @@ public class NotificationService {
mBuilder.setDefaults(0);
mBuilder.setSmallIcon(R.drawable.ic_notification);
mBuilder.setDeleteIntent(createDeleteIntent());
- mBuilder.setLights(0xff00FF00, 2000, 3000);
+ mBuilder.setLights(Settings.LED_COLOR, 2000, 4000);
final Notification notification = mBuilder.build();
notificationManager.notify(NOTIFICATION_ID, notification);
}
@@ -262,8 +257,7 @@ public class NotificationService {
final ArrayList<Message> messages = notifications.values().iterator().next();
if (messages.size() >= 1) {
final Conversation conversation = messages.get(0).getConversation();
- mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
- .get(conversation, getPixel(64)));
+ mBuilder.setLargeIcon(AvatarService.getInstance().get(conversation, getPixel(64)));
mBuilder.setContentTitle(conversation.getName());
if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
int count = messages.size();
@@ -300,8 +294,7 @@ public class NotificationService {
private void modifyForImage(final Builder builder, final Message message,
final ArrayList<Message> messages, final boolean notify) {
try {
- final Bitmap bitmap = mXmppConnectionService.getFileBackend()
- .getThumbnail(message, getPixel(288), false);
+ final Bitmap bitmap = ImageUtil.getThumbnail(message, getPixel(288), false);
final ArrayList<Message> tmp = new ArrayList<>();
for (final Message msg : messages) {
if (msg.getType() == Message.TYPE_TEXT
@@ -361,7 +354,7 @@ public class NotificationService {
&& message.getEncryption() != Message.ENCRYPTION_PGP
&& message.getFileParams().height > 0) {
return message;
- }
+ }
}
return null;
}
@@ -467,23 +460,7 @@ public class NotificationService {
}
private boolean wasHighlightedOrPrivate(final Message message) {
- final String nick = message.getConversation().getMucOptions().getActualNick();
- final Pattern highlight = generateNickHighlightPattern(nick);
- if (message.getBody() == null || nick == null) {
- return false;
- }
- final Matcher m = highlight.matcher(message.getBody());
- return (m.find() || message.getType() == Message.TYPE_PRIVATE);
- }
-
- private static Pattern generateNickHighlightPattern(final String nick) {
- // We expect a word boundary, i.e. space or start of string, followed by
- // the
- // nick (matched in case-insensitive manner), followed by optional
- // punctuation (for example "bob: i disagree" or "how are you alice?"),
- // followed by another word boundary.
- return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
- Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
+ return MessageUtil.wasHighlightedOrPrivate(message);
}
public void setOpenConversation(final Conversation conversation) {
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index ecf5504b..c814e454 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -59,6 +59,16 @@ import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.exceptions.FileCopyException;
+import de.thedevstack.conversationsplus.utils.FileHelper;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -92,7 +102,6 @@ import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener;
import eu.siacs.conversations.utils.PRNGFixes;
import eu.siacs.conversations.utils.PhoneHelper;
-import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnBindListener;
@@ -113,7 +122,6 @@ import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
-import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
@@ -128,11 +136,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private static final String ACTION_MERGE_PHONE_CONTACTS = "merge_phone_contacts";
public static final String ACTION_GCM_TOKEN_REFRESH = "gcm_token_refresh";
public static final String ACTION_GCM_MESSAGE_RECEIVED = "gcm_message_received";
- private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor();
- private final SerialSingleThreadExecutor mDatabaseExecutor = new SerialSingleThreadExecutor();
private final IBinder mBinder = new XmppConnectionBinder();
private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
- private final IqGenerator mIqGenerator = new IqGenerator(this);
+ private final IqGenerator mIqGenerator = new IqGenerator();
private final List<String> mInProgressAvatarFetches = new ArrayList<>();
public DatabaseBackend databaseBackend;
private ContentObserver contactObserver = new ContentObserver(null) {
@@ -145,7 +151,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
startService(intent);
}
};
- private FileBackend fileBackend = new FileBackend(this);
private MemorizingTrustManager mMemorizingTrustManager;
private NotificationService mNotificationService = new NotificationService(
this);
@@ -159,13 +164,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Element error = packet.findChild("error");
String text = error != null ? error.findChildContent("text") : null;
if (text != null) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text);
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": received iq error - " + text);
}
}
}
};
- private MessageGenerator mMessageGenerator = new MessageGenerator(this);
- private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
+ private MessageGenerator mMessageGenerator = new MessageGenerator();
+ private PresenceGenerator mPresenceGenerator = new PresenceGenerator();
private List<Account> accounts;
private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(
this);
@@ -197,7 +202,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
};
private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(
this);
- private AvatarService mAvatarService = new AvatarService(this);
private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this);
private PushManagementService mPushManagementService = new PushManagementService(this);
private OnConversationUpdate mOnConversationUpdate = null;
@@ -286,10 +290,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mMessageArchiveService.executePendingQueries(account);
if (connection != null && connection.getFeatures().csi()) {
if (checkListeners()) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//inactive");
connection.sendInactive();
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " sending csi//active");
connection.sendActive();
}
}
@@ -298,7 +302,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getAccount() == account
&& !account.pendingConferenceJoins.contains(conversation)) {
if (!conversation.startOtrIfNeeded()) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": couldn't start OTR with "+conversation.getContact().getJid()+" when needed");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": couldn't start OTR with "+conversation.getContact().getJid()+" when needed");
}
sendUnsentMessages(conversation);
}
@@ -318,7 +322,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
final boolean pushMode = Config.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND
&& mPushManagementService.available(account)
&& checkListeners();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode));
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": push mode "+Boolean.toString(pushMode));
if (!disabled && !pushMode) {
int timeToReconnect = mRandom.nextInt(20) + 10;
scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
@@ -330,7 +334,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
&& (account.getStatus() != Account.State.NO_INTERNET)) {
if (connection != null) {
int next = connection.getTimeToNextAttempt();
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": error connecting account. try again in "
+ next + "s for the "
+ (connection.getAttempt() + 1) + " time");
@@ -350,10 +354,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private boolean mRestoredFromDatabase = false;
- private static String generateFetchKey(Account account, final Avatar avatar) {
- return account.getJid().toBareJid() + "_" + avatar.owner + "_" + avatar.sha1sum;
- }
-
public boolean areMessagesInitialized() {
return this.mRestoredFromDatabase;
}
@@ -371,15 +371,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
} else {
return null;
}
-
- }
-
- public FileBackend getFileBackend() {
- return this.fileBackend;
- }
-
- public AvatarService getAvatarService() {
- return this.mAvatarService;
}
public void attachLocationToConversation(final Conversation conversation,
@@ -411,67 +402,33 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_FILE);
- String path = getFileBackend().getOriginalPath(uri);
+ String path = FileHelper.getRealPathFromUri(uri);
if (path != null) {
message.setRelativeFilePath(path);
- getFileBackend().updateFileParams(message);
+ MessageUtil.updateFileParams(message);
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
getPgpEngine().encrypt(message, callback);
} else {
callback.success(message);
}
} else {
- mFileAddingExecutor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- getFileBackend().copyFileToPrivateStorage(message, uri);
- getFileBackend().updateFileParams(message);
- if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- getPgpEngine().encrypt(message, callback);
- } else {
- callback.success(message);
- }
- } catch (FileBackend.FileCopyException e) {
- callback.error(e.getResId(), message);
- }
- }
- });
- }
- }
-
- public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
- final String compressPictures = getCompressPicturesPreference();
- if ("never".equals(compressPictures)
- || ("auto".equals(compressPictures) && getFileBackend().useImageAsIs(uri))) {
- Log.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+ ": not compressing picture. sending as file");
- attachFileToConversation(conversation, uri, callback);
- return;
- }
- final Message message;
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
- } else {
- message = new Message(conversation, "", conversation.getNextEncryption());
+ ConversationsPlusApplication.executeFileAdding(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FileBackend.copyFileToPrivateStorage(message, uri);
+ MessageUtil.updateFileParams(message);
+ if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ getPgpEngine().encrypt(message, callback);
+ } else {
+ callback.success(message);
+ }
+ } catch (FileCopyException e) {
+ callback.error(e.getResId(), message);
+ }
+ }
+ });
}
- message.setCounterpart(conversation.getNextCounterpart());
- message.setType(Message.TYPE_IMAGE);
- mFileAddingExecutor.execute(new Runnable() {
-
- @Override
- public void run() {
- try {
- getFileBackend().copyImageToPrivateStorage(message, uri);
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- getPgpEngine().encrypt(message, callback);
- } else {
- callback.success(message);
- }
- } catch (final FileBackend.FileCopyException e) {
- callback.error(e.getResId(), message);
- }
- }
- });
}
public Conversation find(Bookmark bookmark) {
@@ -505,7 +462,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mNotificationService.clear();
break;
case ACTION_DISABLE_FOREGROUND:
- getPreferences().edit().putBoolean("keep_foreground_service", false).commit();
+ ConversationsPlusPreferences.commitKeepForegroundService(false);
toggleForegroundService();
break;
case ACTION_TRY_AGAIN:
@@ -566,7 +523,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
long pingTimeoutIn = (lastSent + Config.PING_TIMEOUT * 1000) - SystemClock.elapsedRealtime();
if (lastSent > lastReceived) {
if (pingTimeoutIn < 0) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
this.reconnectAccount(account, true, interactive);
} else {
int secs = (int) (pingTimeoutIn / 1000);
@@ -574,7 +531,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
} else if (msToNextPing <= 0) {
account.getXmppConnection().sendPing();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping");
this.scheduleWakeUpCall(Config.PING_TIMEOUT, account.getUuid().hashCode());
} else {
this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
@@ -587,7 +544,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
if (timeout < 0) {
- Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
+ Logging.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
reconnectAccount(account, true, interactive);
} else if (discoTimeout < 0) {
account.getXmppConnection().sendDiscoTimeout();
@@ -600,6 +557,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
reconnectAccount(account, true, interactive);
}
}
+
}
if (mOnAccountUpdate != null) {
mOnAccountUpdate.onAccountUpdate();
@@ -627,10 +585,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("away_when_screen_off", false);
}
- private String getCompressPicturesPreference() {
- return getPreferences().getString("picture_compression", "auto");
- }
-
private Presence.Status getTargetPresence() {
if (xaOnSilentMode() && isPhoneSilenced()) {
return Presence.Status.XA;
@@ -665,7 +619,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
private void resetAllAttemptCounts(boolean reallyAll) {
- Log.d(Config.LOGTAG, "resetting all attepmt counts");
+ Logging.d(Config.LOGTAG, "resetting all attepmt counts");
for (Account account : accounts) {
if (account.hasErrorStatus() || reallyAll) {
final XmppConnection connection = account.getXmppConnection();
@@ -683,6 +637,25 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return activeNetwork != null && activeNetwork.isConnected();
}
+ /**
+ * check whether we are allowed to download at the moment
+ */
+ public boolean isDownloadAllowedInConnection() {
+ if (ConversationsPlusPreferences.autoDownloadFileWLAN()) {
+ return isWifiConnected();
+ }
+ return true;
+ }
+
+ /**
+ * check whether wifi is connected
+ */
+ public boolean isWifiConnected() {
+ ConnectivityManager cm = (ConnectivityManager) ConversationsPlusApplication.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo niWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ return niWifi.isConnected();
+ }
+
@SuppressLint("TrulyRandom")
@Override
public void onCreate() {
@@ -729,6 +702,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService");
toggleForegroundService();
updateUnreadCountBadge();
+ UiUpdateHelper.initXmppConnectionService(this);
toggleScreenEventReceiver();
}
@@ -766,7 +740,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void toggleForegroundService() {
- if (getPreferences().getBoolean("keep_foreground_service", false)) {
+ if (ConversationsPlusPreferences.keepForegroundService()) {
startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification());
} else {
stopForeground(true);
@@ -776,7 +750,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- if (!getPreferences().getBoolean("keep_foreground_service", false)) {
+ if (!ConversationsPlusPreferences.keepForegroundService()) {
this.logoutAndSave();
}
}
@@ -794,7 +768,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
cancelWakeUpCall(account.getUuid().hashCode());
}
}
- Log.d(Config.LOGTAG, "good bye");
+ Logging.d(Config.LOGTAG, "good bye");
stopSelf();
}
@@ -818,9 +792,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public XmppConnection createConnection(final Account account) {
- final SharedPreferences sharedPref = getPreferences();
- account.setResource(sharedPref.getString("resource", "mobile")
- .toLowerCase(Locale.getDefault()));
+ account.setResource(ConversationsPlusPreferences.resource().toLowerCase(Locale.getDefault()));
final XmppConnection connection = new XmppConnection(account, this);
connection.setOnMessagePacketReceivedListener(this.mMessageParser);
connection.setOnStatusChangedListener(this.statusListener);
@@ -838,14 +810,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void sendChatState(Conversation conversation) {
- if (sendChatStates()) {
+ if (ConversationsPlusPreferences.chatStates()) {
MessagePacket packet = mMessageGenerator.generateChatState(conversation);
sendMessagePacket(conversation.getAccount(), packet);
}
}
private void sendFileMessage(final Message message, final boolean delay) {
- Log.d(Config.LOGTAG, "send file message");
+ Logging.d(Config.LOGTAG, "send file message");
final Account account = message.getConversation().getAccount();
final XmppConnection connection = account.getXmppConnection();
if (connection != null && connection.getFeatures().httpUpload()) {
@@ -923,11 +895,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (message.fixCounterpart()) {
conversation.startOtrSession(message.getCounterpart().getResourcepart(), true);
} else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not fix counterpart for OTR message to contact "+message.getContact().getJid());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not fix counterpart for OTR message to contact "+message.getContact().getJid());
break;
}
} else {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+" OTR session with "+message.getContact()+" is in wrong state: "+otrSession.getSessionStatus().toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+" OTR session with "+message.getContact()+" is in wrong state: "+otrSession.getSessionStatus().toString());
}
break;
case Message.ENCRYPTION_AXOLOTL:
@@ -972,7 +944,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
break;
case Message.ENCRYPTION_OTR:
if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": create otr session without starting for "+message.getContact().getJid());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": create otr session without starting for "+message.getContact().getJid());
conversation.startOtrSession(message.getCounterpart().getResourcepart(), false);
}
break;
@@ -1008,7 +980,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mMessageGenerator.addDelay(packet, message.getTimeSent());
}
if (conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
- if (this.sendChatStates()) {
+ if (ConversationsPlusPreferences.chatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
}
}
@@ -1033,10 +1005,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void fetchRosterFromServer(final Account account) {
final IqPacket iqPacket = new IqPacket(IqPacket.TYPE.GET);
if (!"".equals(account.getRosterVersion())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": fetching roster version " + account.getRosterVersion());
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster");
}
iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion());
sendIqPacket(account, iqPacket, mIqParser);
@@ -1077,7 +1049,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
account.setBookmarks(new ArrayList<>(bookmarks.values()));
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not fetch bookmarks");
}
}
};
@@ -1085,7 +1057,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void pushBookmarks(Account account) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()+": pushing bookmarks");
IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
Element query = iqPacket.query("jabber:iq:private");
Element storage = query.addChild("storage", "storage:bookmarks");
@@ -1102,12 +1074,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
mPhoneContactMergerThread = new Thread(new Runnable() {
@Override
public void run() {
- Log.d(Config.LOGTAG, "start merging phone contacts with roster");
+ Logging.d(Config.LOGTAG, "start merging phone contacts with roster");
for (Account account : accounts) {
List<Contact> withSystemAccounts = account.getRoster().getWithSystemAccounts();
for (Bundle phoneContact : phoneContacts) {
if (Thread.interrupted()) {
- Log.d(Config.LOGTAG, "interrupted merging phone contacts");
+ Logging.d(Config.LOGTAG,"interrupted merging phone contacts");
return;
}
Jid jid;
@@ -1122,7 +1094,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
+ phoneContact.getString("lookup");
contact.setSystemAccount(systemAccount);
if (contact.setPhotoUri(phoneContact.getString("photouri"))) {
- getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
}
contact.setSystemName(phoneContact.getString("displayname"));
withSystemAccounts.remove(contact);
@@ -1131,11 +1103,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
contact.setSystemAccount(null);
contact.setSystemName(null);
if (contact.setPhotoUri(null)) {
- getAvatarService().clear(contact);
+ AvatarService.getInstance().clear(contact);
}
}
}
- Log.d(Config.LOGTAG, "finished merging phone contacts");
+ Logging.d(Config.LOGTAG,"finished merging phone contacts");
updateAccountUi();
}
});
@@ -1156,15 +1128,15 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
Runnable runnable = new Runnable() {
@Override
public void run() {
- Log.d(Config.LOGTAG, "restoring roster");
+ Logging.d(Config.LOGTAG, "restoring roster");
for (Account account : accounts) {
databaseBackend.readRoster(account.getRoster());
account.initAccountServices(XmppConnectionService.this); //roster needs to be loaded at this stage
}
- getBitmapCache().evictAll();
+ ImageUtil.evictBitmapCache();
Looper.prepare();
loadPhoneContacts();
- Log.d(Config.LOGTAG, "restoring messages");
+ Logging.d(Config.LOGTAG, "restoring messages");
for (Conversation conversation : conversations) {
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
checkDeletedFiles(conversation);
@@ -1177,11 +1149,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
mNotificationService.finishBacklog(false);
mRestoredFromDatabase = true;
- Log.d(Config.LOGTAG, "restored all messages");
+ Logging.d(Config.LOGTAG,"restored all messages");
updateConversationUi();
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
}
@@ -1200,7 +1172,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onMessageFound(Message message) {
- if (!getFileBackend().isFileAvailable(message)) {
+ if (!FileBackend.isFileAvailable(message)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
final int s = message.getStatus();
if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
@@ -1215,7 +1187,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Conversation conversation : getConversations()) {
Message message = conversation.findMessageWithFileAndUuid(uuid);
if (message != null) {
- if (!getFileBackend().isFileAvailable(message)) {
+ if (!FileBackend.isFileAvailable(message)) {
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
final int s = message.getStatus();
if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
@@ -1263,35 +1235,49 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) {
if (XmppConnectionService.this.getMessageArchiveService().queryInProgress(conversation, callback)) {
+ Logging.d("mam", "Query in progress");
return;
} else if (timestamp == 0) {
+ Logging.d("mam", "Query stopped due to timestamp");
return;
}
- Log.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp));
+ //TODO Create a separate class for this runnable to store if messages are getting loaded or not. Not really a good idea to do this in the callback.
+ Logging.d(Config.LOGTAG, "load more messages for " + conversation.getName() + " prior to " + MessageGenerator.getTimestamp(timestamp));
Runnable runnable = new Runnable() {
@Override
public void run() {
+ if (null == callback || !callback.isLoadingInProgress()) { // if a callback is set, ensure that there is no loading in progress
+ if (null != callback) {
+ callback.setLoadingInProgress(); // Tell the callback that the loading is in progress
+ }
final Account account = conversation.getAccount();
List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp);
+ Logging.d("mam", "runnable load more messages");
if (messages.size() > 0) {
+ Logging.d("mam", "At least one message");
conversation.addAll(0, messages);
checkDeletedFiles(conversation);
callback.onMoreMessagesLoaded(messages.size(), conversation);
} else if (conversation.hasMessagesLeftOnServer()
- && account.isOnlineAndConnected()
- && conversation.getLastClearHistory() == 0) {
+ && account.isOnlineAndConnected()) {
+ Logging.d("mam", "mam activate, account online and connected and messages left on server");
if ((conversation.getMode() == Conversation.MODE_SINGLE && account.getXmppConnection().getFeatures().mam())
|| (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().mamSupport())) {
- MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp);
+ MessageArchiveService.Query query = getMessageArchiveService().query(conversation, 0, timestamp - 1);
if (query != null) {
query.setCallback(callback);
}
callback.informUser(R.string.fetching_history_from_server);
}
- }
+ } else {
+ Logging.d("mam", ((!conversation.hasMessagesLeftOnServer()) ? "no" : "") + " more messages left on server, mam " + ((account.getXmppConnection().getFeatures().mam()) ? "" : "not") + " activated, account is " + ((account.isOnlineAndConnected()) ? "" : "not") + " online or connected)");
+ callback.onMoreMessagesLoaded(0, conversation);
+ callback.informUser(R.string.no_more_history_on_server);
+ }
}
+ }
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public List<Account> getAccounts() {
@@ -1520,7 +1506,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.deleteAccount(account);
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
this.accounts.remove(account);
updateAccountUi();
getNotificationService().updateErrorNotification();
@@ -1752,7 +1738,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
- Log.d(Config.LOGTAG, "app switched into foreground");
+ Logging.d(Config.LOGTAG, "app switched into foreground");
}
private void switchToBackground() {
@@ -1771,7 +1757,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
this.mNotificationService.setIsInForeground(false);
- Log.d(Config.LOGTAG, "app switched into background");
+ Logging.d(Config.LOGTAG, "app switched into background");
}
private void connectMultiModeConversations(Account account) {
@@ -1968,7 +1954,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendPresencePacket(conversation.getAccount(), packet);
conversation.getMucOptions().setOffline();
conversation.deregisterWithBookmark();
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
+ Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid()
+ ": leaving muc " + conversation.getJid());
} else {
account.pendingConferenceLeaves.add(conversation);
@@ -1995,7 +1981,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void createAdhocConference(final Account account, final Iterable<Jid> jids, final UiCallback<Conversation> callback) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": creating adhoc conference with " + jids.toString());
if (account.getStatus() == Account.State.ONLINE) {
try {
String server = findConferenceServer(account);
@@ -2171,11 +2157,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) {
IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString());
- Log.d(Config.LOGTAG, request.toString());
+ Logging.d(Config.LOGTAG, request.toString());
sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
- Log.d(Config.LOGTAG, packet.toString());
+ Logging.d(Config.LOGTAG, packet.toString());
if (packet.getType() == IqPacket.TYPE.RESULT) {
callback.onRoleChangedSuccessful(nick);
} else {
@@ -2196,7 +2182,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
leaveMuc(conversation, true);
} else {
if (conversation.endOtrIfNeeded()) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": ended otr session with "
+ conversation.getJid());
}
@@ -2236,8 +2222,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void createContact(Contact contact) {
- boolean autoGrant = getPreferences().getBoolean("grant_new_contacts", true);
- if (autoGrant) {
+ if (ConversationsPlusPreferences.grantNewContacts()) {
contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
contact.setOption(Contact.Options.ASKING);
}
@@ -2247,7 +2232,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onOtrSessionEstablished(Conversation conversation) {
final Account account = conversation.getAccount();
final Session otrSession = conversation.getOtrSession();
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
account.getJid().toBareJid() + " otr session established with "
+ conversation.getJid() + "/"
+ otrSession.getSessionID().getUserID());
@@ -2326,221 +2311,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
- public void publishAvatar(final Account account,
- final Uri image,
- final UiCallback<Avatar> callback) {
- final Bitmap.CompressFormat format = Config.AVATAR_FORMAT;
- final int size = Config.AVATAR_SIZE;
- final Avatar avatar = getFileBackend().getPepAvatar(image, size, format);
- if (avatar != null) {
- avatar.height = size;
- avatar.width = size;
- if (format.equals(Bitmap.CompressFormat.WEBP)) {
- avatar.type = "image/webp";
- } else if (format.equals(Bitmap.CompressFormat.JPEG)) {
- avatar.type = "image/jpeg";
- } else if (format.equals(Bitmap.CompressFormat.PNG)) {
- avatar.type = "image/png";
- }
- if (!getFileBackend().save(avatar)) {
- callback.error(R.string.error_saving_avatar, avatar);
- return;
- }
- final IqPacket packet = this.mIqGenerator.publishAvatar(avatar);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- final IqPacket packet = XmppConnectionService.this.mIqGenerator
- .publishAvatarMetadata(avatar);
- sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- if (result.getType() == IqPacket.TYPE.RESULT) {
- if (account.setAvatar(avatar.getFilename())) {
- getAvatarService().clear(account);
- databaseBackend.updateAccount(account);
- }
- callback.success(avatar);
- } else {
- callback.error(
- R.string.error_publish_avatar_server_reject,
- avatar);
- }
- }
- });
- } else {
- callback.error(
- R.string.error_publish_avatar_server_reject,
- avatar);
- }
- }
- });
- } else {
- callback.error(R.string.error_publish_avatar_converting, null);
- }
- }
-
- public void fetchAvatar(Account account, Avatar avatar) {
- fetchAvatar(account, avatar, null);
- }
-
- public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- final String KEY = generateFetchKey(account, avatar);
- synchronized (this.mInProgressAvatarFetches) {
- if (this.mInProgressAvatarFetches.contains(KEY)) {
- return;
- } else {
- switch (avatar.origin) {
- case PEP:
- this.mInProgressAvatarFetches.add(KEY);
- fetchAvatarPep(account, avatar, callback);
- break;
- case VCARD:
- this.mInProgressAvatarFetches.add(KEY);
- fetchAvatarVcard(account, avatar, callback);
- break;
- }
- }
- }
- }
-
- private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar);
- sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket result) {
- synchronized (mInProgressAvatarFetches) {
- mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
- }
- final String ERROR = account.getJid().toBareJid()
- + ": fetching avatar for " + avatar.owner + " failed ";
- if (result.getType() == IqPacket.TYPE.RESULT) {
- avatar.image = mIqParser.avatarData(result);
- if (avatar.image != null) {
- if (getFileBackend().save(avatar)) {
- if (account.getJid().toBareJid().equals(avatar.owner)) {
- if (account.setAvatar(avatar.getFilename())) {
- databaseBackend.updateAccount(account);
- }
- getAvatarService().clear(account);
- updateConversationUi();
- updateAccountUi();
- } else {
- Contact contact = account.getRoster()
- .getContact(avatar.owner);
- contact.setAvatar(avatar);
- getAvatarService().clear(contact);
- updateConversationUi();
- updateRosterUi();
- }
- if (callback != null) {
- callback.success(avatar);
- }
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": succesfuly fetched pep avatar for " + avatar.owner);
- return;
- }
- } else {
-
- Log.d(Config.LOGTAG, ERROR + "(parsing error)");
- }
- } else {
- Element error = result.findChild("error");
- if (error == null) {
- Log.d(Config.LOGTAG, ERROR + "(server error)");
- } else {
- Log.d(Config.LOGTAG, ERROR + error.toString());
- }
- }
- if (callback != null) {
- callback.error(0, null);
- }
-
- }
- });
- }
-
- private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- synchronized (mInProgressAvatarFetches) {
- mInProgressAvatarFetches.remove(generateFetchKey(account, avatar));
- }
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element vCard = packet.findChild("vCard", "vcard-temp");
- Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
- String image = photo != null ? photo.findChildContent("BINVAL") : null;
- if (image != null) {
- avatar.image = image;
- if (getFileBackend().save(avatar)) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": successfully fetched vCard avatar for " + avatar.owner);
- if (avatar.owner.isBareJid()) {
- Contact contact = account.getRoster()
- .getContact(avatar.owner);
- contact.setAvatar(avatar);
- getAvatarService().clear(contact);
- updateConversationUi();
- updateRosterUi();
- } else {
- Conversation conversation = find(account, avatar.owner.toBareJid());
- if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
- MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart());
- if (user != null) {
- if (user.setAvatar(avatar)) {
- getAvatarService().clear(user);
- updateConversationUi();
- updateMucRosterUi();
- }
- }
- }
- }
- }
- }
- }
- }
- });
- }
-
- public void checkForAvatar(Account account, final UiCallback<Avatar> callback) {
- IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
- this.sendIqPacket(account, packet, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- Element pubsub = packet.findChild("pubsub",
- "http://jabber.org/protocol/pubsub");
- if (pubsub != null) {
- Element items = pubsub.findChild("items");
- if (items != null) {
- Avatar avatar = Avatar.parseMetadata(items);
- if (avatar != null) {
- avatar.owner = account.getJid().toBareJid();
- if (fileBackend.isAvatarCached(avatar)) {
- if (account.setAvatar(avatar.getFilename())) {
- databaseBackend.updateAccount(account);
- }
- getAvatarService().clear(account);
- callback.success(avatar);
- } else {
- fetchAvatarPep(account, avatar, callback);
- }
- return;
- }
- }
- }
- }
- callback.error(0, null);
- }
- });
- }
-
public void deleteContactOnServer(Contact contact) {
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
contact.resetOption(Contact.Options.DIRTY_PUSH);
@@ -2570,7 +2340,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (!force) {
disconnect(account, false);
try {
- Log.d(Config.LOGTAG, "wait for disconnect");
+ Logging.d(Config.LOGTAG, "wait for disconnect");
Thread.sleep(500); //sleep wait for disconnect
} catch (InterruptedException e) {
//ignored
@@ -2598,7 +2368,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void invite(Conversation conversation, Jid contact) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": inviting " + contact + " to " + conversation.getJid().toBareJid());
+ Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": inviting " + contact + " to " + conversation.getJid().toBareJid());
MessagePacket packet = mMessageGenerator.invite(conversation, contact);
sendMessagePacket(conversation.getAccount(), packet);
}
@@ -2668,18 +2438,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
.getDefaultSharedPreferences(getApplicationContext());
}
- public boolean confirmMessages() {
- return getPreferences().getBoolean("confirm_messages", true);
- }
-
- public boolean allowMessageCorrection() {
- return getPreferences().getBoolean("allow_message_correction", false);
- }
-
- public boolean sendChatStates() {
- return getPreferences().getBoolean("chat_states", false);
- }
-
public boolean saveEncryptedMessages() {
return !getPreferences().getBoolean("dont_save_encrypted", false);
}
@@ -2688,14 +2446,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("autojoin", true);
}
- public boolean indicateReceived() {
- return getPreferences().getBoolean("indicate_received", false);
- }
-
- public boolean useTorToConnect() {
- return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false);
- }
-
public boolean showExtendedConnectionOptions() {
return getPreferences().getBoolean("show_connection_options", false);
}
@@ -2795,7 +2545,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
updateUnreadCountBadge();
return true;
} else {
@@ -2806,7 +2556,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public synchronized void updateUnreadCountBadge() {
int count = unreadCount();
if (unreadCount != count) {
- Log.d(Config.LOGTAG, "update unread count to " + count);
+ Logging.d(Config.LOGTAG, "update unread count to " + count);
if (count > 0) {
ShortcutBadger.with(getApplicationContext()).count(count);
} else {
@@ -2821,8 +2571,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (this.markRead(conversation)) {
updateConversationUi();
}
- if (confirmMessages() && markable != null && markable.getRemoteMsgId() != null) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString());
+ if (Settings.CONFIRM_MESSAGE_READ && markable != null && markable.getRemoteMsgId() != null) {
+ Logging.d(Config.LOGTAG, conversation.getAccount().getJid().toBareJid() + ": sending read marker to " + markable.getCounterpart().toString());
Account account = conversation.getAccount();
final Jid to = markable.getCounterpart();
MessagePacket packet = mMessageGenerator.confirm(account, to, markable.getRemoteMsgId());
@@ -2844,9 +2594,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void updateMemorizingTrustmanager() {
final MemorizingTrustManager tm;
- final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", false);
- if (dontTrustSystemCAs) {
- tm = new MemorizingTrustManager(getApplicationContext(), null);
+ if (ConversationsPlusPreferences.dontTrustSystemCAs()) {
+ tm = new MemorizingTrustManager(getApplicationContext(), null);
} else {
tm = new MemorizingTrustManager(getApplicationContext());
}
@@ -2869,8 +2618,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.writeRoster(account.getRoster());
}
};
- mDatabaseExecutor.execute(runnable);
-
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public List<String> getKnownHosts() {
@@ -2904,18 +2652,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return mucServers;
}
+ @Deprecated
public void sendMessagePacket(Account account, MessagePacket packet) {
- XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.sendMessagePacket(packet);
- }
+ XmppSendUtil.sendMessagePacket(account, packet);
}
+ @Deprecated
public void sendPresencePacket(Account account, PresencePacket packet) {
- XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.sendPresencePacket(packet);
- }
+ XmppSendUtil.sendPresencePacket(account, packet);
}
public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) {
@@ -2925,15 +2669,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ @Deprecated
public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) {
- final XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.sendIqPacket(packet, callback);
- }
+ XmppSendUtil.sendIqPacket(account, packet, callback);
}
public void sendPresence(final Account account) {
- sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence()));
+ XmppSendUtil.sendPresencePacket(account, mPresenceGenerator.selfPresence(account, getTargetPresence()));
}
public void refreshAllPresences() {
@@ -2953,7 +2695,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void sendOfflinePresence(final Account account) {
- sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
+ XmppSendUtil.sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
}
public MessageGenerator getMessageGenerator() {
@@ -3021,15 +2763,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void clearConversationHistory(final Conversation conversation) {
conversation.clearMessages();
- conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam
- conversation.setLastClearHistory(System.currentTimeMillis());
+ /*
+ * In case the history was loaded completely before.
+ * The flag "hasMessagesLeftOnServer" is set to false and no messages will be loaded anymore
+ * Therefore set this flag to true and try to get messages from server
+ */
+ conversation.setHasMessagesLeftOnServer(true);
Runnable runnable = new Runnable() {
@Override
public void run() {
databaseBackend.deleteMessagesInConversation(conversation);
}
};
- mDatabaseExecutor.execute(runnable);
+ ConversationsPlusApplication.executeDatabaseOperation(runnable);
}
public void sendBlockRequest(final Blockable blockable) {
@@ -3071,7 +2817,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.ERROR) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not publish nick");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not publish nick");
}
}
});
@@ -3112,7 +2858,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
databaseBackend.insertDiscoveryResult(disco);
injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco);
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + disco.getVer());
}
}
account.inProgressDiscoFetches.remove(key);
@@ -3173,6 +2919,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
void onMoreMessagesLoaded(int count, Conversation conversation);
void informUser(int r);
+
+ void setLoadingInProgress();
+
+ boolean isLoadingInProgress();
}
public interface OnAccountPasswordChanged {
diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
index bd2042fb..b9e5c367 100644
--- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
+++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
@@ -5,7 +5,7 @@ import android.content.Intent;
import android.preference.Preference;
import android.util.AttributeSet;
-import eu.siacs.conversations.utils.PhoneHelper;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
public class AboutPreference extends Preference {
public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
@@ -26,7 +26,7 @@ public class AboutPreference extends Preference {
}
private void setSummary() {
- setSummary("Conversations " + PhoneHelper.getVersionName(getContext()));
+ setSummary(ConversationsPlusApplication.getNameAndVersion());
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
index ccb3a22e..1cc8fd29 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
@@ -9,8 +9,6 @@ import android.widget.Toast;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.xmpp.jid.InvalidJidException;
-import eu.siacs.conversations.xmpp.jid.Jid;
public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged {
@@ -26,11 +24,14 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti
mCurrentPassword.requestFocus();
mCurrentPassword.setError(getString(R.string.account_status_unauthorized));
} else if (!newPassword.equals(newPasswordConfirm)) {
- mNewPasswordConfirm.requestFocus();
- mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
+ mNewPasswordConfirm.requestFocus();
+ mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
+ } else if (newPassword.isEmpty()) {
+ mNewPassword.requestFocus();
+ mNewPassword.setError(getString(R.string.password_should_not_be_empty));
} else if (newPassword.trim().isEmpty()) {
mNewPassword.requestFocus();
- mNewPassword.setError(getString(R.string.password_should_not_be_empty));
+ mNewPassword.setError(getString(R.string.password_should_not_contain_only_spaces));
} else {
mCurrentPassword.setError(null);
mNewPassword.setError(null);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index adbb0953..dec387b6 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -38,6 +38,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.MucOptions.User;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnMucRosterUpdate;
@@ -509,7 +510,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
account = mConversation.getAccount().getJid().toBareJid().toString();
}
mAccountJid.setText(getString(R.string.using_account, account));
- mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48)));
+ mYourPhoto.setImageBitmap(AvatarService.getInstance().get(mConversation.getAccount(), getPixel(48)));
setTitle(mConversation.getName());
if (Config.LOCK_DOMAINS_IN_CONVERSATIONS && mConversation.getJid().getDomainpart().equals(Config.CONFERENCE_DOMAIN_LOCK)) {
mFullJid.setText(mConversation.getJid().getLocalpart());
@@ -603,7 +604,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
ImageView iv = (ImageView) view.findViewById(R.id.contact_photo);
- iv.setImageBitmap(avatarService().get(user, getPixel(48), false));
+ iv.setImageBitmap(AvatarService.getInstance().get(user, getPixel(48), false));
membersView.addView(view);
if (mConversation.getMucOptions().canInvite()) {
mInviteButton.setVisibility(View.VISIBLE);
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index 4165b4be..5bedc24d 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -6,10 +6,8 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
-import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
@@ -34,6 +32,8 @@ import org.openintents.openpgp.util.OpenPgpUtils;
import java.security.cert.X509Certificate;
import java.util.List;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -42,6 +42,7 @@ import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -220,8 +221,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
getActionBar().setDisplayHomeAsUpEnabled(true);
}
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
+ this.showDynamicTags = ConversationsPlusPreferences.showDynamicTags();
}
@Override
@@ -369,8 +369,9 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
} else {
account = contact.getAccount().getJid().toBareJid().toString();
}
+ contactJidTv.setOnClickListener(new ShowResourcesListDialogListener(ContactDetailsActivity.this, contact));
accountJidTv.setText(getString(R.string.using_account, account));
- badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
+ badge.setImageBitmap(AvatarService.getInstance().get(contact, getPixel(72)));
badge.setOnClickListener(this.onBadgeClick);
keys.removeAllViews();
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index aee923f6..e991c2d2 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -43,10 +43,14 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog;
+import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener;
import de.timroes.android.listview.EnhancedListView;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.AxolotlServiceImpl;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
@@ -54,6 +58,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
@@ -273,6 +278,7 @@ public class ConversationActivity extends XmppActivity
}
});
listView.enableSwipeToDismiss();
+ listView.setSwipeDirection(EnhancedListView.SwipeDirection.START);
listView.setSwipingLayout(R.id.swipeable_item);
listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
listView.setUndoHideDelay(5000);
@@ -284,9 +290,10 @@ public class ConversationActivity extends XmppActivity
}
if (mContentView instanceof SlidingPaneLayout) {
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ // Move the conversation list when sliding the selected conversation
mSlidingPaneLayout.setParallaxDistance(150);
- mSlidingPaneLayout
- .setShadowResource(R.drawable.es_slidingpane_shadow);
+ // The shadow between conversation list and selected conversation
+ mSlidingPaneLayout.setShadowResourceLeft(R.drawable.es_slidingpane_shadow);
mSlidingPaneLayout.setSliderFadeColor(0);
mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
@@ -340,7 +347,7 @@ public class ConversationActivity extends XmppActivity
if (titleShouldBeName && conversation != null) {
ab.setDisplayHomeAsUpEnabled(true);
ab.setHomeButtonEnabled(true);
- if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) {
+ if (conversation.getMode() == Conversation.MODE_SINGLE || ConversationsPlusPreferences.useSubject()) {
ab.setTitle(conversation.getName());
} else {
ab.setTitle(conversation.getJid().toBareJid().toString());
@@ -443,7 +450,7 @@ public class ConversationActivity extends XmppActivity
chooser = true;
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
- Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
+ Uri uri = FileBackend.getTakePhotoUri();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
mPendingImageUris.clear();
@@ -504,16 +511,16 @@ public class ConversationActivity extends XmppActivity
}
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_LOCATION:
- getPreferences().edit().putString("recently_used_quick_action", "location").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("location");
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
- getPreferences().edit().putString("recently_used_quick_action", "voice").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("voice");
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
- getPreferences().edit().putString("recently_used_quick_action", "photo").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("photo");
break;
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
- getPreferences().edit().putString("recently_used_quick_action", "picture").apply();
+ ConversationsPlusPreferences.applyRecentlyUsedQuickAction("picture");
break;
}
final Conversation conversation = getSelectedConversation();
@@ -829,7 +836,7 @@ public class ConversationActivity extends XmppActivity
}
break;
case R.id.encryption_choice_axolotl:
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
+ Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(conversation.getAccount())
+ "Enabled axolotl for Contact " + conversation.getContact().getJid());
conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
item.setChecked(true);
@@ -1070,7 +1077,7 @@ public class ConversationActivity extends XmppActivity
public void onResume() {
super.onResume();
final int theme = findTheme();
- final boolean usingEnterKey = usingEnterKey();
+ final boolean usingEnterKey = ConversationsPlusPreferences.displayEnterKey();
if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) {
recreate();
}
@@ -1155,7 +1162,6 @@ public class ConversationActivity extends XmppActivity
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
} else {
- this.mConversationFragment.messageListAdapter.updatePreferences();
this.mConversationFragment.messagesView.invalidateViews();
this.mConversationFragment.setupIme();
}
@@ -1443,28 +1449,9 @@ public class ConversationActivity extends XmppActivity
if (conversation == null) {
return;
}
- final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG);
- prepareFileToast.show();
- xmppConnectionService.attachImageToConversation(conversation, uri,
- new UiCallback<Message>() {
-
- @Override
- public void userInputRequried(PendingIntent pi, Message object) {
- hidePrepareFileToast(prepareFileToast);
- }
-
- @Override
- public void success(Message message) {
- hidePrepareFileToast(prepareFileToast);
- xmppConnectionService.sendMessage(message);
- }
-
- @Override
- public void error(int error, Message message) {
- hidePrepareFileToast(prepareFileToast);
- displayErrorDialog(error);
- }
- });
+ ResizePictureUserDecisionListener userDecisionListener = new ResizePictureUserDecisionListener(this, conversation, uri, xmppConnectionService);
+ UserDecisionDialog userDecisionDialog = new UserDecisionDialog(this, R.string.userdecision_question_resize_picture, userDecisionListener);
+ userDecisionDialog.decide(ConversationsPlusPreferences.resizePicture());
}
private void hidePrepareFileToast(final Toast prepareFileToast) {
@@ -1524,18 +1511,6 @@ public class ConversationActivity extends XmppActivity
});
}
- public boolean useSendButtonToIndicateStatus() {
- return getPreferences().getBoolean("send_button_status", false);
- }
-
- public boolean indicateReceived() {
- return getPreferences().getBoolean("indicate_received", false);
- }
-
- public boolean useWhiteBackground() {
- return getPreferences().getBoolean("use_white_background",false);
- }
-
protected boolean trustKeysIfNeeded(int requestCode) {
return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
}
@@ -1600,10 +1575,6 @@ public class ConversationActivity extends XmppActivity
xmppConnectionService.sendUnblockRequest(conversation);
}
- public boolean enterIsSend() {
- return getPreferences().getBoolean("enter_is_send",false);
- }
-
@Override
public void onShowErrorToast(final int resId) {
runOnUiThread(new Runnable() {
@@ -1613,15 +1584,4 @@ public class ConversationActivity extends XmppActivity
}
});
}
-
- public boolean highlightSelectedConversations() {
- return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard;
- }
-
- public void setMessagesLoaded() {
- if (mConversationFragment != null) {
- mConversationFragment.setMessagesLoaded();
- mConversationFragment.updateMessages();
- }
- }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 1665090c..369de781 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.annotation.Nullable;
@@ -28,19 +29,27 @@ import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ImageButton;
+import android.widget.ImageView;
import android.widget.ListView;
+import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout;
+
import net.java.otr4j.session.SessionStatus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.UUID;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -51,9 +60,11 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.http.HttpDownloadConnection;
+import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
@@ -61,11 +72,15 @@ import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
import eu.siacs.conversations.ui.adapter.MessageAdapter;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
+import eu.siacs.conversations.ui.listeners.ConversationSwipeRefreshListener;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.Jid;
+import github.ankushsachdeva.emojicon.EmojiconGridView;
+import github.ankushsachdeva.emojicon.EmojiconsPopup;
+import github.ankushsachdeva.emojicon.emoji.Emojicon;
public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener {
@@ -104,113 +119,19 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
};
protected ListView messagesView;
+ protected SwipyRefreshLayout swipeLayout;
final protected List<Message> messageList = new ArrayList<>();
protected MessageAdapter messageListAdapter;
private EditMessage mEditMessage;
private ImageButton mSendButton;
+ private ImageView mEmojButton;
+ private View mRootView;
+ private EmojiconsPopup mEmojPopup;
private RelativeLayout snackbar;
private TextView snackbarMessage;
private TextView snackbarAction;
private boolean messagesLoaded = true;
private Toast messageLoaderToast;
-
- private OnScrollListener mOnScrollListener = new OnScrollListener() {
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- // TODO Auto-generated method stub
-
- }
-
- private int getIndexOf(String uuid, List<Message> messages) {
- if (uuid == null) {
- return messages.size() - 1;
- }
- for(int i = 0; i < messages.size(); ++i) {
- if (uuid.equals(messages.get(i).getUuid())) {
- return i;
- } else {
- Message next = messages.get(i);
- while(next != null && next.wasMergedIntoPrevious()) {
- if (uuid.equals(next.getUuid())) {
- return i;
- }
- next = next.next();
- }
-
- }
- }
- return 0;
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- synchronized (ConversationFragment.this.messageList) {
- if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) {
- long timestamp;
- if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) {
- timestamp = messageList.get(1).getTimeSent();
- } else {
- timestamp = messageList.get(0).getTimeSent();
- }
- messagesLoaded = false;
- activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() {
- @Override
- public void onMoreMessagesLoaded(final int c, Conversation conversation) {
- if (ConversationFragment.this.conversation != conversation) {
- return;
- }
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- final int oldPosition = messagesView.getFirstVisiblePosition();
- final Message message;
- if (oldPosition < messageList.size()) {
- message = messageList.get(oldPosition);
- } else {
- message = null;
- }
- String uuid = message != null ? message.getUuid() : null;
- View v = messagesView.getChildAt(0);
- final int pxOffset = (v == null) ? 0 : v.getTop();
- ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
- updateStatusMessages();
- messageListAdapter.notifyDataSetChanged();
- int pos = getIndexOf(uuid,messageList);
- messagesView.setSelectionFromTop(pos, pxOffset);
- messagesLoaded = true;
- if (messageLoaderToast != null) {
- messageLoaderToast.cancel();
- }
- }
- });
- }
-
- @Override
- public void informUser(final int resId) {
-
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (messageLoaderToast != null) {
- messageLoaderToast.cancel();
- }
- if (ConversationFragment.this.conversation != conversation) {
- return;
- }
- messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG);
- messageLoaderToast.show();
- }
- });
-
- }
- });
-
- }
- }
- }
- };
private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0;
private final int KEYCHAIN_UNLOCK_REQUIRED = 1;
private final int KEYCHAIN_UNLOCK_PENDING = 2;
@@ -300,14 +221,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE);
break;
case CANCEL:
- if (conversation != null) {
- if (conversation.getCorrectingMessage() != null) {
- conversation.setCorrectingMessage(null);
- mEditMessage.getEditableText().clear();
- }
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- conversation.setNextCounterpart(null);
- }
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setNextCounterpart(null);
updateChatMsgHint();
updateSendButton();
}
@@ -342,21 +257,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (body.length() == 0 || this.conversation == null) {
return;
}
- final Message message;
- if (conversation.getCorrectingMessage() == null) {
- message = new Message(conversation, body, conversation.getNextEncryption());
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- if (conversation.getNextCounterpart() != null) {
- message.setCounterpart(conversation.getNextCounterpart());
- message.setType(Message.TYPE_PRIVATE);
- }
+ Message message = new Message(conversation, body, conversation.getNextEncryption());
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ if (conversation.getNextCounterpart() != null) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ message.setType(Message.TYPE_PRIVATE);
}
- } else {
- message = conversation.getCorrectingMessage();
- message.setBody(body);
- message.setEdited(message.getUuid());
- message.setUuid(UUID.randomUUID().toString());
- conversation.setCorrectingMessage(null);
}
switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_OTR:
@@ -377,9 +283,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
public void updateChatMsgHint() {
final boolean multi = conversation.getMode() == Conversation.MODE_MULTI;
- if (conversation.getCorrectingMessage() != null) {
- this.mEditMessage.setHint(R.string.send_corrected_message);
- } else if (multi && conversation.getNextCounterpart() != null) {
+ if (multi && conversation.getNextCounterpart() != null) {
this.mEditMessage.setHint(getString(
R.string.send_private_message_to,
conversation.getNextCounterpart().getResourcepart()));
@@ -415,7 +319,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
public void setupIme() {
if (activity == null) {
return;
- } else if (activity.usingEnterKey() && activity.enterIsSend()) {
+ } else if (activity.usingEnterKey() && ConversationsPlusPreferences.enterIsSend()) {
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
} else if (activity.usingEnterKey()) {
@@ -443,6 +347,107 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
});
mEditMessage.setOnEditorActionListener(mEditorActionListener);
+ // Start of emojicon
+ mEmojButton = (ImageView) view.findViewById(R.id.emoji_btn);
+ mRootView = view.findViewById(R.id.textsend);
+
+ // Give the topmost view of your activity layout hierarchy. This will be used to measure soft keyboard height
+ mEmojPopup = new EmojiconsPopup(mRootView, this.getActivity());
+
+ //Will automatically set size according to the soft keyboard size
+ mEmojPopup.setSizeForSoftKeyboard();
+
+ //If the emoji popup is dismissed, change emojiButton to smiley icon
+ mEmojPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+
+ @Override
+ public void onDismiss() {
+ changeEmojiKeyboardIcon(mEmojButton, R.drawable.smiley);
+ }
+ });
+
+ //If the text keyboard closes, also dismiss the emoji popup
+ mEmojPopup.setOnSoftKeyboardOpenCloseListener(new EmojiconsPopup.OnSoftKeyboardOpenCloseListener() {
+
+ @Override
+ public void onKeyboardOpen(int keyBoardHeight) {
+
+ }
+
+ @Override
+ public void onKeyboardClose() {
+ if (mEmojPopup.isShowing())
+ mEmojPopup.dismiss();
+ }
+ });
+
+ //On emoji clicked, add it to edittext
+ mEmojPopup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() {
+
+ @Override
+ public void onEmojiconClicked(Emojicon emojicon) {
+ if (mEditMessage == null || emojicon == null) {
+ return;
+ }
+
+ int start = mEditMessage.getSelectionStart();
+ int end = mEditMessage.getSelectionEnd();
+ if (start < 0) {
+ mEditMessage.append(emojicon.getEmoji());
+ } else {
+ mEditMessage.getText().replace(Math.min(start, end),
+ Math.max(start, end), emojicon.getEmoji(), 0,
+ emojicon.getEmoji().length());
+ }
+ }
+ });
+
+ //On backspace clicked, emulate the KEYCODE_DEL key event
+ mEmojPopup.setOnEmojiconBackspaceClickedListener(new EmojiconsPopup.OnEmojiconBackspaceClickedListener() {
+
+ @Override
+ public void onEmojiconBackspaceClicked(View v) {
+ KeyEvent event = new KeyEvent(
+ 0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
+ mEditMessage.dispatchKeyEvent(event);
+ }
+ });
+
+ // To toggle between text keyboard and emoji keyboard keyboard(Popup)
+ mEmojButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+
+ //If popup is not showing => emoji keyboard is not visible, we need to show it
+ if(!mEmojPopup.isShowing()){
+
+ //If keyboard is visible, simply show the emoji popup
+ if(mEmojPopup.isKeyBoardOpen()){
+ mEmojPopup.showAtBottom();
+ changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard);
+ }
+
+ //else, open the text keyboard first and immediately after that show the emoji popup
+ else{
+ mEditMessage.setFocusableInTouchMode(true);
+ mEditMessage.requestFocus();
+ mEmojPopup.showAtBottomPending();
+ final InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT);
+ changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard);
+ }
+ }
+
+ //If popup is showing, simply dismiss it to show the undelying text keyboard
+ else{
+ mEmojPopup.dismiss();
+ }
+ }
+ });
+
+ // End of emojicon
+
mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
mSendButton.setOnClickListener(this.mSendButtonListener);
@@ -451,7 +456,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
messagesView = (ListView) view.findViewById(R.id.messages_view);
- messagesView.setOnScrollListener(mOnScrollListener);
messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList);
messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() {
@@ -506,6 +510,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
registerForContextMenu(messagesView);
+ // Start of swipe refresh
+ // New Swipe refresh
+ swipeLayout = (SwipyRefreshLayout) view.findViewById(R.id.swipe_refresh_container);
+ swipeLayout.setOnRefreshListener(new ConversationSwipeRefreshListener(messageList, swipeLayout, this, messagesView, messageListAdapter));
+ // End of swipe refresh
+
return view;
}
@@ -522,16 +532,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private void populateContextMenu(ContextMenu menu) {
final Message m = this.selectedMessage;
final Transferable t = m.getTransferable();
- Message relevantForCorrection = m;
- while(relevantForCorrection.mergeable(relevantForCorrection.next())) {
- relevantForCorrection = relevantForCorrection.next();
- }
if (m.getType() != Message.TYPE_STATUS) {
activity.getMenuInflater().inflate(R.menu.message_context, menu);
menu.setHeaderTitle(R.string.message_options);
MenuItem copyText = menu.findItem(R.id.copy_text);
MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
- MenuItem correctMessage = menu.findItem(R.id.correct_message);
MenuItem shareWith = menu.findItem(R.id.share_with);
MenuItem sendAgain = menu.findItem(R.id.send_again);
MenuItem copyUrl = menu.findItem(R.id.copy_url);
@@ -546,10 +551,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
retryDecryption.setVisible(true);
}
- if (relevantForCorrection.getType() == Message.TYPE_TEXT
- && relevantForCorrection.isLastCorrectableMessage()) {
- correctMessage.setVisible(true);
- }
+
if ((m.getType() != Message.TYPE_TEXT
&& m.getType() != Message.TYPE_PRIVATE
&& t == null)
@@ -581,15 +583,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case R.id.msg_ctx_mnu_details:
+ new MessageDetailsDialog(getActivity(), selectedMessage).show();
+ return true;
case R.id.share_with:
shareWith(selectedMessage);
return true;
case R.id.copy_text:
copyText(selectedMessage);
return true;
- case R.id.correct_message:
- correctMessage(selectedMessage);
- return true;
case R.id.send_again:
resendMessage(selectedMessage);
return true;
@@ -618,8 +620,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
shareIntent.setType("text/plain");
} else {
shareIntent.putExtra(Intent.EXTRA_STREAM,
- activity.xmppConnectionService.getFileBackend()
- .getJingleFileUri(message));
+ FileBackend.getJingleFileUri(message));
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String mime = message.getMimeType();
if (mime == null) {
@@ -645,7 +646,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private void resendMessage(Message message) {
if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
- DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ DownloadableFile file = FileBackend.getFile(message);
if (!file.exists()) {
Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
@@ -665,7 +666,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
resId = R.string.file_url;
url = message.getFileParams().url.toString();
} else {
- url = message.getBody().trim();
+ url = message.getBody();
resId = R.string.file_url;
}
if (activity.copyTextToClipboard(url, resId)) {
@@ -701,18 +702,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateSendButton();
}
- private void correctMessage(Message message) {
- while(message.mergeable(message.next())) {
- message = message.next();
- }
- this.conversation.setCorrectingMessage(message);
- this.mEditMessage.getEditableText().clear();
- this.mEditMessage.getEditableText().append(message.getBody());
-
- }
-
protected void highlightInConference(String nick) {
- String oldString = mEditMessage.getText().toString().trim();
+ String oldString = mEditMessage.getText().toString();
if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) {
mEditMessage.getText().insert(0, nick + ": ");
} else {
@@ -767,7 +758,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mEditMessage.setText("");
this.mEditMessage.append(this.conversation.getNextMessage());
this.mEditMessage.setKeyboardListener(this);
- messageListAdapter.updatePreferences();
this.messagesView.setAdapter(messageListAdapter);
updateMessages();
this.messagesLoaded = true;
@@ -775,6 +765,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (size > 0) {
messagesView.setSelection(size - 1);
}
+ swipeLayout.setRefreshing(false);
}
private OnClickListener mEnableAccountListener = new OnClickListener() {
@@ -1033,9 +1024,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString();
final boolean empty = text.length() == 0;
final boolean conference = c.getMode() == Conversation.MODE_MULTI;
- if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) {
- action = SendButtonAction.CANCEL;
- } else if (conference && !c.getAccount().httpUploadAvailable()) {
+ if (conference && !c.getAccount().httpUploadAvailable()) {
if (empty && c.getNextCounterpart() != null) {
action = SendButtonAction.CANCEL;
} else {
@@ -1046,11 +1035,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (conference && c.getNextCounterpart() != null) {
action = SendButtonAction.CANCEL;
} else {
- String setting = activity.getPreferences().getString("quick_action", "recent");
+ String setting = ConversationsPlusPreferences.quickAction();
if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) {
setting = "location";
} else if (setting.equals("recent")) {
- setting = activity.getPreferences().getString("recently_used_quick_action", "text");
+ setting = ConversationsPlusPreferences.recentlyUsedQuickAction();
}
switch (setting) {
case "photo":
@@ -1074,7 +1063,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
action = SendButtonAction.TEXT;
}
}
- if (activity.useSendButtonToIndicateStatus() && c != null
+ if (ConversationsPlusPreferences.sendButtonStatus() && c != null
&& c.getAccount().getStatus() == Account.State.ONLINE) {
if (c.getMode() == Conversation.MODE_SINGLE) {
status = c.getContact().getMostAvailableStatus();
@@ -1088,11 +1077,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mSendButton.setImageResource(getSendButtonImageResource(action, status));
}
- protected void updateStatusMessages() {
+ public void updateStatusMessages() {
synchronized (this.messageList) {
- if (showLoadMoreMessages(conversation)) {
- this.messageList.add(0, Message.createLoadMoreMessage(conversation));
- }
if (conversation.getMode() == Conversation.MODE_SINGLE) {
ChatState state = conversation.getIncomingChatState();
if (state == ChatState.COMPOSING) {
@@ -1116,21 +1102,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
}
- private boolean showLoadMoreMessages(final Conversation c) {
- final boolean mam = hasMamSupport(c);
- final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService();
- return mam && (c.getLastClearHistory() != 0 || (c.countMessages() == 0 && c.hasMessagesLeftOnServer() && !service.queryInProgress(c)));
- }
-
- private boolean hasMamSupport(final Conversation c) {
- if (c.getMode() == Conversation.MODE_SINGLE) {
- final XmppConnection connection = c.getAccount().getXmppConnection();
- return connection != null && connection.getFeatures().mam();
- } else {
- return c.getMucOptions().mamSupport();
- }
- }
-
protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) {
snackbar.setVisibility(View.VISIBLE);
snackbar.setOnClickListener(null);
@@ -1291,7 +1262,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public boolean onEnterPressed() {
- if (activity.enterIsSend()) {
+ if (ConversationsPlusPreferences.enterIsSend()) {
sendMessage();
return true;
} else {
@@ -1326,13 +1297,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updateSendButton();
}
- @Override
- public void onTextChanged() {
- if (conversation != null && conversation.getCorrectingMessage() != null) {
- updateSendButton();
- }
- }
-
private int completionIndex = 0;
private int lastCompletionLength = 0;
private String incomplete;
@@ -1399,4 +1363,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
}
+ private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){
+ iconToBeChanged.setImageResource(drawableResourceId);
+ }
+
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index ed80a509..720a7454 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -38,13 +38,15 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
+import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.UIHelper;
@@ -239,7 +241,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
if (!mFetchingAvatar) {
mFetchingAvatar = true;
- xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
+ AvatarService.getInstance().checkForAvatar(mAccount, mAvatarFetchCallback);
}
} else {
updateSaveButton();
@@ -526,9 +528,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
}
SharedPreferences preferences = getPreferences();
- boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
- this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false);
- mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example);
+ this.mShowOptions = preferences.getBoolean("show_connection_options", false);
this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
}
@@ -630,7 +630,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
if (!mInitMode) {
this.mAvatar.setVisibility(View.VISIBLE);
- this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(72)));
+ this.mAvatar.setImageBitmap(AvatarService.getInstance().get(this.mAccount, getPixel(72)));
}
if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
this.mRegisterNew.setVisibility(View.VISIBLE);
@@ -641,6 +641,15 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mRegisterNew.setChecked(false);
}
if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
+ this.findViewById(R.id.editAccountBoxes).setVisibility(View.GONE);
+ this.findViewById(R.id.displayAccountFrame).setVisibility(View.VISIBLE);
+ TextView detailsAccountJid = (TextView)this.findViewById(R.id.detailsAccountJid);
+ if (this.mAccount.countPresences() > 0) {
+ detailsAccountJid.setText(this.mAccount.getJid().toBareJid().toString() + " (" + this.mAccount.countPresences() + ")");
+ detailsAccountJid.setOnClickListener(new ShowResourcesListDialogListener(EditAccountActivity.this, this.mAccount.getRoster().getContact(this.mAccount.getJid().toBareJid())));
+ } else {
+ detailsAccountJid.setText(this.mAccount.getJid().toBareJid().toString());
+ }
Features features = this.mAccount.getXmppConnection().getFeatures();
this.mStats.setVisibility(View.VISIBLE);
boolean showOptimizingWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
diff --git a/src/main/java/eu/siacs/conversations/ui/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
index e3841d1d..06868a98 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditMessage.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditMessage.java
@@ -4,11 +4,11 @@ import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
-import android.widget.EditText;
import eu.siacs.conversations.Config;
+import github.ankushsachdeva.emojicon.EmojiconEditText;
-public class EditMessage extends EditText {
+public class EditMessage extends EmojiconEditText {
public EditMessage(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -69,7 +69,6 @@ public class EditMessage extends EditText {
this.isUserTyping = false;
this.keyboardListener.onTextDeleted();
}
- this.keyboardListener.onTextChanged();
}
}
@@ -85,7 +84,6 @@ public class EditMessage extends EditText {
void onTypingStarted();
void onTypingStopped();
void onTextDeleted();
- void onTextChanged();
boolean onTabPressed(boolean repeated);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
index feac2c62..c83a0275 100644
--- a/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ManageAccountActivity.java
@@ -21,6 +21,8 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
+import org.openintents.openpgp.util.OpenPgpApi;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -34,8 +36,6 @@ import eu.siacs.conversations.ui.adapter.AccountAdapter;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
-import org.openintents.openpgp.util.OpenPgpApi;
-
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated {
private final String STATE_SELECTED_ACCOUNT = "selected_account";
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index ee70ee43..d36a4316 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -22,10 +22,11 @@ import com.soundcloud.android.crop.Crop;
import java.io.File;
import java.io.FileNotFoundException;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.utils.ExifHelper;
import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.PhoneHelper;
@@ -114,7 +115,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (avatarUri != null) {
publishButton.setText(R.string.publishing);
disablePublishButton();
- xmppConnectionService.publishAvatar(account, avatarUri,
+ AvatarService.getInstance().publishAvatar(account, avatarUri,
avatarPublication);
}
}
@@ -235,7 +236,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (this.avatarUri == null) {
if (this.account.getAvatar() != null
|| this.defaultUri == null) {
- this.avatar.setImageBitmap(avatarService().get(account, getPixel(192)));
+ this.avatar.setImageBitmap(AvatarService.getInstance().get(account, getPixel(192)));
if (this.defaultUri != null) {
this.avatar
.setOnLongClickListener(this.backToDefaultListener);
@@ -280,7 +281,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
protected void loadImageIntoPreview(Uri uri) {
Bitmap bm = null;
try {
- bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, getPixel(192));
+ bm = FileBackend.cropCenterSquare(uri, getPixel(192));
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 750a7421..c0bd162e 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -9,6 +9,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
+import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
@@ -25,11 +26,13 @@ import java.util.List;
import java.util.Locale;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.ExportLogsService;
import eu.siacs.conversations.xmpp.XmppConnection;
+import github.ankushsachdeva.emojicon.EmojiconHandler;
public class SettingsActivity extends XmppActivity implements
OnSharedPreferenceChangeListener {
@@ -67,14 +70,6 @@ public class SettingsActivity extends XmppActivity implements
}
}
- if (Config.FORCE_ORBOT) {
- PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options");
- PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert");
- if (connectionOptions != null) {
- expert.removePreference(connectionOptions);
- }
- }
-
final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@@ -106,45 +101,42 @@ public class SettingsActivity extends XmppActivity implements
}
});
- dialogBuilder.setPositiveButton(
- getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- int count = selectedItems.size();
- if (count > 0) {
- for (int i = 0; i < count; i++) {
- try {
- Integer item = Integer.valueOf(selectedItems.get(i).toString());
- String alias = aliases.get(item);
- mtm.deleteCertificate(alias);
- } catch (KeyStoreException e) {
- e.printStackTrace();
- displayToast("Error: " + e.getLocalizedMessage());
- }
- }
- if (xmppConnectionServiceBound) {
- reconnectAccounts();
- }
- displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
- }
- }
- });
- dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
- AlertDialog removeCertsDialog = dialogBuilder.create();
- removeCertsDialog.show();
- removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
- return true;
- }
- });
-
- final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
- exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- hasStoragePermission(REQUEST_WRITE_LOGS);
- return true;
- }
- });
+ dialogBuilder.setPositiveButton(
+ getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int count = selectedItems.size();
+ if (count > 0) {
+ for (int i = 0; i < count; i++) {
+ try {
+ Integer item = Integer.valueOf(selectedItems.get(i).toString());
+ String alias = aliases.get(item);
+ mtm.deleteCertificate(alias);
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ displayToast("Error: " + e.getLocalizedMessage());
+ }
+ }
+ if (xmppConnectionServiceBound) {
+ reconnectAccounts();
+ }
+ displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
+ }
+ }
+ });
+ dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
+ AlertDialog removeCertsDialog = dialogBuilder.create();
+ removeCertsDialog.show();
+ removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ return true;
+ }
+ });
+ // Avoid appearence of setting to enable or disable omemo in screen
+ Preference omemoEnabledPreference = this.mSettingsFragment.findPreference("omemo_enabled");
+ PreferenceCategory otherExpertSettingsGroup = (PreferenceCategory) this.mSettingsFragment.findPreference("other_expert_settings");
+ if (null != omemoEnabledPreference && null != otherExpertSettingsGroup) {
+ otherExpertSettingsGroup.removePreference(omemoEnabledPreference);
+ }
}
@Override
@@ -157,11 +149,12 @@ public class SettingsActivity extends XmppActivity implements
@Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String name) {
final List<String> resendPresence = Arrays.asList(
- "confirm_messages",
+ "confirm_messages_list",
"xa_on_silent_mode",
"away_when_screen_off",
- "allow_message_correction",
"treat_vibrate_as_silent");
+ // need to synchronize the settings class first
+ Settings.synchronizeSettingsClassWithPreferences(preferences, name);
if (name.equals("resource")) {
String resource = preferences.getString("resource", "mobile")
.toLowerCase(Locale.US);
@@ -190,9 +183,9 @@ public class SettingsActivity extends XmppActivity implements
} else if (name.equals("dont_trust_system_cas")) {
xmppConnectionService.updateMemorizingTrustmanager();
reconnectAccounts();
- } else if (name.equals("use_tor")) {
- reconnectAccounts();
- }
+ } else if ("parse_emoticons".equals(name)) {
+ EmojiconHandler.setParseEmoticons(Settings.PARSE_EMOTICONS);
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
index 09bbe0df..2aa1e89d 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -15,9 +15,12 @@ import android.widget.Toast;
import java.net.URLConnection;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.ui.dialogs.UserDecisionDialog;
+import de.thedevstack.conversationsplus.ui.listeners.ResizePictureUserDecisionListener;
+import de.thedevstack.conversationsplus.ui.listeners.ShareWithResizePictureUserDecisionListener;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -234,34 +237,35 @@ public class ShareWithActivity extends XmppActivity {
return;
}
if (share.uris.size() != 0) {
- OnPresenceSelected callback = new OnPresenceSelected() {
- @Override
- public void onPresenceSelected() {
- if (share.image) {
- mToast = Toast.makeText(getApplicationContext(),
- getText(R.string.preparing_image),
- Toast.LENGTH_LONG);
- mToast.show();
- for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
- ShareWithActivity.this.xmppConnectionService
- .attachImageToConversation(conversation, i.next(),
- attachFileCallback);
- }
- } else {
- mToast = Toast.makeText(getApplicationContext(),
- getText(R.string.preparing_file),
- Toast.LENGTH_LONG);
- mToast.show();
- ShareWithActivity.this.xmppConnectionService
- .attachFileToConversation(conversation, share.uris.get(0),
- attachFileCallback);
- }
+ OnPresenceSelected callback;
+ if (this.share.image) {
+ callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ ResizePictureUserDecisionListener userDecisionListener = new ShareWithResizePictureUserDecisionListener(ShareWithActivity.this, conversation, xmppConnectionService, share.uris);
+ UserDecisionDialog userDecisionDialog = new UserDecisionDialog(ShareWithActivity.this, R.string.userdecision_question_resize_picture, userDecisionListener);
+ userDecisionDialog.decide(ConversationsPlusPreferences.resizePicture());
+ }
+ };
+ } else {
+ callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ mToast = Toast.makeText(getApplicationContext(),
+ getText(R.string.preparing_file),
+ Toast.LENGTH_LONG);
+ mToast.show();
+ ShareWithActivity.this.xmppConnectionService
+ .attachFileToConversation(conversation, share.uris.get(0),
+ attachFileCallback);
if (share.uuid == null) {
switchToConversation(conversation, null, true);
}
- finish();
- }
- };
+ finish();
+ }
+ };
+ }
+
if (conversation.getAccount().httpUploadAvailable()) {
callback.onPresenceSelected();
} else {
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 7d650e5b..4e751799 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -26,7 +26,6 @@ import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.Editable;
import android.text.TextWatcher;
-import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
@@ -56,6 +55,8 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -192,6 +193,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_conversation);
+ this.mHideOfflineContacts = ConversationsPlusPreferences.hideOffline();
mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
@@ -248,7 +250,6 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
}
});
- this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false);
}
@@ -549,12 +550,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
new IntentIntegrator(this).initiateScan();
return true;
case R.id.action_hide_offline:
- mHideOfflineContacts = !item.isChecked();
- getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
+ mHideOfflineContacts = !item.isChecked(); // the item is the menu item which is displayed, the inversion here calculates the new value
+ ConversationsPlusPreferences.commitHideOffline(mHideOfflineContacts);
if (mSearchEditText != null) {
filter(mSearchEditText.getText().toString());
}
- invalidateOptionsMenu();
+ invalidateOptionsMenu(); // Since the selection of this item changed the checked value, the options menu is now invalid
+ return true;
}
return super.onOptionsItemSelected(item);
}
@@ -668,12 +670,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
switch (intent.getAction()) {
case Intent.ACTION_SENDTO:
case Intent.ACTION_VIEW:
- Log.d(Config.LOGTAG, "received uri=" + intent.getData());
+ Logging.d(Config.LOGTAG, "received uri=" + intent.getData());
return new Invite(intent.getData()).invite();
case NfcAdapter.ACTION_NDEF_DISCOVERED:
for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
if (message instanceof NdefMessage) {
- Log.d(Config.LOGTAG, "received message=" + message);
+ Logging.d(Config.LOGTAG, "received message=" + message);
for (NdefRecord record : ((NdefMessage) message).getRecords()) {
switch (record.getTnf()) {
case NdefRecord.TNF_WELL_KNOWN:
@@ -705,7 +707,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
Contact contact = contacts.get(0);
if (invite.getFingerprint() != null) {
if (contact.addOtrFingerprint(invite.getFingerprint())) {
- Log.d(Config.LOGTAG,"added new fingerprint");
+ Logging.d(Config.LOGTAG,"added new fingerprint");
xmppConnectionService.syncRosterToDisk(contact.getAccount());
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index baf40b22..c709d842 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -41,7 +41,6 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -69,6 +68,9 @@ import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.ImageUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
@@ -78,7 +80,6 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presences;
-import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
import eu.siacs.conversations.ui.widget.Switch;
@@ -359,18 +360,18 @@ public abstract class XmppActivity extends Activity {
super.onCreate(savedInstanceState);
metrics = getResources().getDisplayMetrics();
ExceptionHelper.init(getApplicationContext());
- mPrimaryTextColor = getResources().getColor(R.color.black87);
- mSecondaryTextColor = getResources().getColor(R.color.black54);
+ mPrimaryTextColor = getResources().getColor(R.color.primaryText);
+ mSecondaryTextColor = getResources().getColor(R.color.secondaryText);
mTertiaryTextColor = getResources().getColor(R.color.black12);
- mColorRed = getResources().getColor(R.color.red800);
+ mColorRed = getResources().getColor(R.color.warning);
mColorOrange = getResources().getColor(R.color.orange500);
- mColorGreen = getResources().getColor(R.color.green500);
+ mColorGreen = getResources().getColor(R.color.online);
mPrimaryColor = getResources().getColor(R.color.primary);
- mPrimaryBackgroundColor = getResources().getColor(R.color.grey50);
- mSecondaryBackgroundColor = getResources().getColor(R.color.grey200);
+ mPrimaryBackgroundColor = getResources().getColor(R.color.primaryBackground);
+ mSecondaryBackgroundColor = getResources().getColor(R.color.secondaryBackground);
this.mTheme = findTheme();
setTheme(this.mTheme);
- this.mUsingEnterKey = usingEnterKey();
+ this.mUsingEnterKey = ConversationsPlusPreferences.displayEnterKey();
mUseSubject = getPreferences().getBoolean("use_subject", true);
final ActionBar ab = getActionBar();
if (ab!=null) {
@@ -558,7 +559,7 @@ public abstract class XmppActivity extends Activity {
});
}
- protected void displayErrorDialog(final int errorCode) {
+ public void displayErrorDialog(final int errorCode) {
runOnUiThread(new Runnable() {
@Override
@@ -1021,7 +1022,7 @@ public abstract class XmppActivity extends Activity {
}
protected int findTheme() {
- if (getPreferences().getBoolean("use_larger_font", false)) {
+ if (ConversationsPlusPreferences.useLargerFont()) {
return R.style.ConversationsTheme_LargerText;
} else {
return R.style.ConversationsTheme;
@@ -1050,7 +1051,7 @@ public abstract class XmppActivity extends Activity {
}
protected Bitmap createQrCodeBitmap(String input, int size) {
- Log.d(Config.LOGTAG,"qr code requested size: "+size);
+ Logging.d(Config.LOGTAG,"qr code requested size: "+size);
try {
final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
@@ -1066,7 +1067,7 @@ public abstract class XmppActivity extends Activity {
}
}
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
+ Logging.d(Config.LOGTAG,"output size: "+width+"x"+height);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
} catch (final WriterException e) {
@@ -1125,24 +1126,21 @@ public abstract class XmppActivity extends Activity {
}
}
- public AvatarService avatarService() {
- return xmppConnectionService.getAvatarService();
- }
-
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
- private Message message = null;
+ private final boolean setSize;
+ private Message message = null;
- public BitmapWorkerTask(ImageView imageView) {
+ public BitmapWorkerTask(ImageView imageView, boolean setSize) {
imageViewReference = new WeakReference<>(imageView);
+ this.setSize = setSize;
}
@Override
protected Bitmap doInBackground(Message... params) {
message = params[0];
try {
- return xmppConnectionService.getFileBackend().getThumbnail(
- message, (int) (metrics.density * 288), false);
+ return ImageUtil.getThumbnail(message, (int) (metrics.density * 288), false);
} catch (FileNotFoundException e) {
return null;
}
@@ -1155,26 +1153,35 @@ public abstract class XmppActivity extends Activity {
if (imageView != null) {
imageView.setImageBitmap(bitmap);
imageView.setBackgroundColor(0x00000000);
+ if (setSize) {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(
+ bitmap.getWidth(), bitmap.getHeight()));
+ }
}
}
}
}
- public void loadBitmap(Message message, ImageView imageView) {
+ public void loadBitmap(Message message, ImageView imageView, boolean setSize) {
Bitmap bm;
try {
- bm = xmppConnectionService.getFileBackend().getThumbnail(message,
- (int) (metrics.density * 288), true);
+ bm = ImageUtil.getThumbnail(message,(int) (metrics.density * 288), true);
} catch (FileNotFoundException e) {
bm = null;
}
+
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
+ if (setSize) {
+ imageView.setLayoutParams(new LinearLayout.LayoutParams(
+ bm.getWidth(), bm.getHeight()));
+ }
} else {
if (cancelPotentialWork(message, imageView)) {
imageView.setBackgroundColor(0xff333333);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView, setSize);
final AsyncDrawable asyncDrawable = new AsyncDrawable(
getResources(), null, task);
imageView.setImageDrawable(asyncDrawable);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
index 98250af9..4ab47b37 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
@@ -14,6 +14,7 @@ import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.widget.Switch;
@@ -43,7 +44,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
}
TextView statusView = (TextView) view.findViewById(R.id.account_status);
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
- imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
+ imageView.setImageBitmap(AvatarService.getInstance().get(account, activity.getPixel(48)));
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
index f5f48a26..2e40717a 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -3,6 +3,7 @@ package eu.siacs.conversations.ui.adapter;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -19,10 +20,15 @@ import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.utils.UIHelper;
@@ -44,11 +50,12 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
view = inflater.inflate(R.layout.conversation_list_row,parent, false);
}
Conversation conversation = getItem(position);
+ // Highlight the currently selected conversation
if (this.activity instanceof ConversationActivity) {
- View swipeableItem = view.findViewById(R.id.swipeable_item);
ConversationActivity a = (ConversationActivity) this.activity;
- int c = a.highlightSelectedConversations() && conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
- swipeableItem.setBackgroundColor(c);
+ int c = conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
+ view.findViewById(R.id.conversationListRowContent).setBackgroundColor(c);
+ view.findViewById(R.id.conversationListRowFrame).setBackgroundColor(c);
}
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
@@ -61,6 +68,30 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage);
ImageView notificationStatus = (ImageView) view.findViewById(R.id.notification_status);
+ if (Settings.SHOW_ONLINE_STATUS && conversation.getAccount().getStatus() == Account.State.ONLINE) {
+ TextView status = (TextView) view.findViewById(R.id.status);
+
+ String color = "#000000";
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ switch (conversation.getContact().getMostAvailableStatus()) {
+ case ONLINE:
+ case CHAT:
+ color = "#259B23";
+ break;
+ case AWAY:
+ case XA:
+ color = "#FF9800";
+ break;
+ case DND:
+ color = "#E51C23";
+ break;
+ }
+ } else if (conversation.getMode() == Conversation.MODE_MULTI && conversation.getMucOptions().online()) {
+ color = "#259B23";
+ }
+ status.setBackgroundColor(Color.parseColor(color));
+ }
+
Message message = conversation.getLatestMessage();
if (!conversation.isRead()) {
@@ -74,12 +105,23 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|| message.getTransferable().getStatus() != Transferable.STATUS_DELETED)) {
mLastMessage.setVisibility(View.GONE);
imagePreview.setVisibility(View.VISIBLE);
- activity.loadBitmap(message, imagePreview);
+ activity.loadBitmap(message, imagePreview, false);
} else {
Pair<String,Boolean> preview = UIHelper.getMessagePreview(activity,message);
mLastMessage.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
- mLastMessage.setText(preview.first);
+ CharSequence msgText = preview.first;
+ String msgPrefix = null;
+ if (message.getStatus() == Message.STATUS_SEND
+ || message.getStatus() == Message.STATUS_SEND_DISPLAYED
+ || message.getStatus() == Message.STATUS_SEND_FAILED
+ || message.getStatus() == Message.STATUS_SEND_RECEIVED) {
+ msgPrefix = activity.getString(R.string.cplus_me);
+ } else if (conversation.getMode() == Conversation.MODE_MULTI) {
+ msgPrefix = UIHelper.getMessageDisplayName(message);
+ }
+ String lastMessagePreview = ((null == msgPrefix || msgPrefix.isEmpty()) ? "" : (msgPrefix + ": ")) + msgText;
+ mLastMessage.setText(lastMessagePreview);
if (preview.second) {
if (conversation.isRead()) {
mLastMessage.setTypeface(null, Typeface.ITALIC);
@@ -109,9 +151,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
notificationStatus.setImageResource(R.drawable.ic_notifications_none_grey600_24dp);
}
- mTimestamp.setText(UIHelper.readableTimeDifference(activity,conversation.getLatestMessage().getTimeSent()));
+ mTimestamp.setText(UIHelper.readableTimeDifference(activity, message.getTimeSent()));
ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image);
- loadAvatar(conversation,profilePicture);
+ profilePicture.setOnLongClickListener(new ShowResourcesListDialogListener(activity, conversation.getContact()));
+ loadAvatar(conversation, profilePicture);
return view;
}
@@ -126,7 +169,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
@Override
protected Bitmap doInBackground(Conversation... params) {
- return activity.avatarService().get(params[0], activity.getPixel(56));
+ return AvatarService.getInstance().get(params[0], activity.getPixel(56));
}
@Override
@@ -143,7 +186,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
public void loadAvatar(Conversation conversation, ImageView imageView) {
if (cancelPotentialWork(conversation, imageView)) {
- final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
+ final Bitmap bm = AvatarService.getInstance().get(conversation, activity.getPixel(56), true);
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index da8e3910..29d706c7 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -1,13 +1,11 @@
package eu.siacs.conversations.ui.adapter;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -20,8 +18,11 @@ import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.tzur.conversations.Settings;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.ListItem;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -45,8 +46,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
public ListItemAdapter(XmppActivity activity, List<ListItem> objects) {
super(activity, 0, objects);
this.activity = activity;
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
- this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
+ this.showDynamicTags = ConversationsPlusPreferences.showDynamicTags();
}
@Override
@@ -57,6 +57,12 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
if (view == null) {
view = inflater.inflate(R.layout.contact, parent, false);
}
+
+ if (Settings.SHOW_ONLINE_STATUS) {
+ TextView tvStatus = (TextView) view.findViewById(R.id.contact_status);
+ tvStatus.setBackgroundColor(item.getStatusColor());
+ }
+
TextView tvName = (TextView) view.findViewById(R.id.contact_display_name);
TextView tvJid = (TextView) view.findViewById(R.id.contact_jid);
ImageView picture = (ImageView) view.findViewById(R.id.contact_photo);
@@ -106,7 +112,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
@Override
protected Bitmap doInBackground(ListItem... params) {
- return activity.avatarService().get(params[0], activity.getPixel(48));
+ return AvatarService.getInstance().get(params[0], activity.getPixel(48));
}
@Override
@@ -123,7 +129,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
public void loadAvatar(ListItem item, ImageView imageView) {
if (cancelPotentialWork(item, imageView)) {
- final Bitmap bm = activity.avatarService().get(item,activity.getPixel(48),true);
+ final Bitmap bm = AvatarService.getInstance().get(item,activity.getPixel(48),true);
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
index ab9f74a3..53611d37 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -38,6 +38,7 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
@@ -46,6 +47,8 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Message.FileParams;
import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.GeoHelper;
@@ -56,6 +59,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
private static final int SENT = 0;
private static final int RECEIVED = 1;
private static final int STATUS = 2;
+ private static final int NULL = 3;
private static final Pattern XMPP_PATTERN = Pattern
.compile("xmpp\\:(?:(?:["
+ Patterns.GOOD_IRI_CHAR
@@ -77,14 +81,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return true;
}
};
- private boolean mIndicateReceived = false;
- private boolean mUseWhiteBackground = false;
public MessageAdapter(ConversationActivity activity, List<Message> messages) {
super(activity, 0, messages);
this.activity = activity;
metrics = getContext().getResources().getDisplayMetrics();
- updatePreferences();
}
public void setOnContactPictureClicked(OnContactPictureClicked listener) {
@@ -132,15 +133,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.indicatorReceived.setVisibility(View.GONE);
}
- if (viewHolder.edit_indicator != null) {
- if (message.edited()) {
- viewHolder.edit_indicator.setVisibility(View.VISIBLE);
- viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp);
- viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f);
- } else {
- viewHolder.edit_indicator.setVisibility(View.GONE);
- }
- }
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
@@ -170,12 +162,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
info = getContext().getString(R.string.offering);
break;
case Message.STATUS_SEND_RECEIVED:
- if (mIndicateReceived) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
case Message.STATUS_SEND_DISPLAYED:
- if (mIndicateReceived) {
+ if (ConversationsPlusPreferences.indicateReceived()) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
@@ -279,19 +271,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
- if (viewHolder.download_button != null) {
- viewHolder.download_button.setVisibility(View.GONE);
- }
- viewHolder.image.setVisibility(View.GONE);
- viewHolder.messageBody.setVisibility(View.VISIBLE);
- viewHolder.messageBody.setIncludeFontPadding(false);
- Spannable span = new SpannableString(body);
- span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- viewHolder.messageBody.setText(span);
- }
-
private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) {
if (viewHolder.download_button != null) {
viewHolder.download_button.setVisibility(View.GONE);
@@ -425,7 +404,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setVisibility(View.GONE);
viewHolder.image.setVisibility(View.VISIBLE);
FileParams params = message.getFileParams();
- double target = metrics.density * 288;
+ //TODO: Check what value add the following lines have (compared with setting height/width in XmppActivity.loadBitmap from thumbnail after thumbnail is created)
+ /*double target = metrics.density * 288;
int scalledW;
int scalledH;
if (params.width <= params.height) {
@@ -437,8 +417,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH);
layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
- viewHolder.image.setLayoutParams(layoutParams);
- activity.loadBitmap(message, viewHolder.image);
+ viewHolder.image.setLayoutParams(layoutParams);*/
+ //TODO Why should this be calculated by hand???
+ activity.loadBitmap(message, viewHolder.image, true);
viewHolder.image.setOnClickListener(new OnClickListener() {
@Override
@@ -449,19 +430,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.image.setOnLongClickListener(openContextMenu);
}
- private void loadMoreMessages(Conversation conversation) {
- conversation.setLastClearHistory(0);
- conversation.setHasMessagesLeftOnServer(true);
- conversation.setFirstMamReference(null);
- long timestamp = conversation.getLastMessageTransmitted();
- if (timestamp == 0) {
- timestamp = System.currentTimeMillis();
- }
- activity.setMessagesLoaded();
- activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp);
- Toast.makeText(activity, R.string.fetching_history_from_server,Toast.LENGTH_LONG).show();
- }
-
@Override
public View getView(int position, View view, ViewGroup parent) {
final Message message = getItem(position);
@@ -484,7 +452,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
.findViewById(R.id.download_button);
viewHolder.indicator = (ImageView) view
.findViewById(R.id.security_indicator);
- viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
viewHolder.image = (ImageView) view
.findViewById(R.id.message_image);
viewHolder.messageBody = (TextView) view
@@ -505,7 +472,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
.findViewById(R.id.download_button);
viewHolder.indicator = (ImageView) view
.findViewById(R.id.security_indicator);
- viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
viewHolder.image = (ImageView) view
.findViewById(R.id.message_image);
viewHolder.messageBody = (TextView) view
@@ -520,7 +486,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
- viewHolder.load_more_messages = (Button) view.findViewById(R.id.load_more_messages);
break;
default:
viewHolder = null;
@@ -534,31 +499,17 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- boolean darkBackground = (type == RECEIVED && (!isInValidSession || !mUseWhiteBackground));
+ boolean darkBackground = (type == RECEIVED && !isInValidSession);
if (type == STATUS) {
- if ("LOAD_MORE".equals(message.getBody())) {
- viewHolder.status_message.setVisibility(View.GONE);
- viewHolder.contact_picture.setVisibility(View.GONE);
- viewHolder.load_more_messages.setVisibility(View.VISIBLE);
- viewHolder.load_more_messages.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- loadMoreMessages(message.getConversation());
- }
- });
- } else {
- viewHolder.status_message.setVisibility(View.VISIBLE);
- viewHolder.contact_picture.setVisibility(View.VISIBLE);
- viewHolder.load_more_messages.setVisibility(View.GONE);
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- viewHolder.contact_picture.setImageBitmap(activity
- .avatarService().get(conversation.getContact(),
- activity.getPixel(32)));
- viewHolder.contact_picture.setAlpha(0.5f);
- }
- viewHolder.status_message.setText(message.getBody());
+ viewHolder.status_message.setVisibility(View.VISIBLE);
+ viewHolder.contact_picture.setVisibility(View.VISIBLE);
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ viewHolder.contact_picture.setImageBitmap(AvatarService.getInstance().get(conversation.getContact(),
+ activity.getPixel(32)));
+ viewHolder.contact_picture.setAlpha(0.5f);
}
+ viewHolder.status_message.setText(message.getBody());
return view;
} else {
loadAvatar(message, viewHolder.contact_picture);
@@ -633,8 +584,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else {
if (GeoHelper.isGeoUri(message.getBody())) {
displayLocationMessage(viewHolder,message);
- } else if (message.bodyIsHeart()) {
- displayHeartMessage(viewHolder, message.getBody().trim());
} else if (message.treatAsDownloadable() == Message.Decision.MUST) {
try {
URL url = new URL(message.getBody());
@@ -656,11 +605,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (type == RECEIVED) {
if(isInValidSession) {
- if (mUseWhiteBackground) {
- viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_white);
- } else {
- viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received);
- }
viewHolder.encryption.setVisibility(View.GONE);
} else {
viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
@@ -675,7 +619,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
public void openDownloadable(Message message) {
- DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ DownloadableFile file = FileBackend.getFile(message);
if (!file.exists()) {
Toast.makeText(activity,R.string.file_deleted,Toast.LENGTH_SHORT).show();
return;
@@ -710,11 +654,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
}
- public void updatePreferences() {
- this.mIndicateReceived = activity.indicateReceived();
- this.mUseWhiteBackground = activity.useWhiteBackground();
- }
-
public interface OnContactPictureClicked {
void onContactPictureClicked(Message message);
}
@@ -735,8 +674,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
protected ImageView contact_picture;
protected TextView status_message;
protected TextView encryption;
- public Button load_more_messages;
- public ImageView edit_indicator;
}
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
@@ -749,7 +686,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
@Override
protected Bitmap doInBackground(Message... params) {
- return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled());
+ return AvatarService.getInstance().get(params[0], activity.getPixel(48), isCancelled());
}
@Override
@@ -766,7 +703,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
public void loadAvatar(Message message, ImageView imageView) {
if (cancelPotentialWork(message, imageView)) {
- final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true);
+ final Bitmap bm = AvatarService.getInstance().get(message, activity.getPixel(48), true);
if (bm != null) {
imageView.setImageBitmap(bm);
imageView.setBackgroundColor(0x00000000);
diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
index 3a21ade3..3b051191 100644
--- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
+++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java
@@ -4,13 +4,11 @@ import android.content.Context;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import java.util.List;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.xmpp.forms.Field;
diff --git a/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java
new file mode 100644
index 00000000..a5c5e2aa
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationMoreMessagesLoadedListener.java
@@ -0,0 +1,152 @@
+package eu.siacs.conversations.ui.listeners;
+
+import android.view.View;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout;
+
+import java.util.List;
+
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.ui.ConversationFragment;
+import eu.siacs.conversations.ui.adapter.MessageAdapter;
+
+/**
+ * This listener updates the UI when messages are loaded from the server.
+ */
+public class ConversationMoreMessagesLoadedListener implements XmppConnectionService.OnMoreMessagesLoaded {
+ private SwipyRefreshLayout swipeLayout;
+ private List<Message> messageList;
+ private ConversationFragment fragment;
+ private ListView messagesView;
+ private MessageAdapter messageListAdapter;
+ private Toast messageLoaderToast;
+ /*
+ The current loading status
+ */
+ private boolean loadingMessages = false;
+ /**
+ * Whether the user is loading only history messages or not.
+ * History messages are messages which are older than the oldest in the database.
+ */
+ private boolean loadHistory = true;
+
+ public ConversationMoreMessagesLoadedListener(SwipyRefreshLayout swipeLayout, List<Message> messageList, ConversationFragment fragment, ListView messagesView, MessageAdapter messageListAdapter) {
+ this.swipeLayout = swipeLayout;
+ this.messageList = messageList;
+ this.fragment = fragment;
+ this.messagesView = messagesView;
+ this.messageListAdapter = messageListAdapter;
+ }
+
+ public void setLoadHistory(boolean value) {
+ this.loadHistory = value;
+ }
+
+ public void setLoadingInProgress() {
+ this.loadingMessages = true;
+ }
+
+ public boolean isLoadingInProgress() {
+ return this.loadingMessages;
+ }
+
+ @Override
+ public void onMoreMessagesLoaded(final int c, final Conversation conversation) {
+ ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+ // Current selected conversation is not the same the messages are loaded - skip updating message view and hide loading graphic
+ if (activity.getSelectedConversation() != conversation) {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ swipeLayout.setRefreshing(false);
+ }
+ });
+ return;
+ }
+ // No new messages are loaded
+ if (0 == c) {
+ if (this.loadHistory) {
+ conversation.setHasMessagesLeftOnServer(false);
+ }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ swipeLayout.setRefreshing(false);
+ }
+ });
+ }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int oldPosition = messagesView.getFirstVisiblePosition(); // Always 0 - because loading starts always when hitting the top
+ String uuid = null;
+ boolean oldMessageListWasEmpty = messageList.isEmpty();
+ if (-1 < oldPosition && messageList.size() > oldPosition) {
+ Message message = messageList.get(oldPosition);
+ uuid = message != null ? message.getUuid() : null;
+ }
+ View v = messagesView.getChildAt(0);
+ final int pxOffset = (v == null) ? 0 : v.getTop();
+
+ conversation.populateWithMessages(messageList); // This overrides the old message list
+ fragment.updateStatusMessages(); // This adds "messages" to the list for the status
+ messageListAdapter.notifyDataSetChanged();
+ loadingMessages = false; // Loading of messages is finished - next query can be loaded
+
+ int pos = getIndexOf(uuid, messageList);
+
+ if (!oldMessageListWasEmpty) {
+ messagesView.setSelectionFromTop(pos, pxOffset);
+ }
+
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ swipeLayout.setRefreshing(false);
+ }
+ });
+ }
+
+ @Override
+ public void informUser(final int resId) {
+ final ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG);
+ messageLoaderToast.show();
+ }
+ });
+
+ }
+
+ private int getIndexOf(String uuid, List<Message> messages) {
+ if (uuid == null) {
+ return 0;
+ }
+ for (int i = 0; i < messages.size(); ++i) {
+ if (uuid.equals(messages.get(i).getUuid())) {
+ return i;
+ } else {
+ Message next = messages.get(i);
+ while(next != null && next.wasMergedIntoPrevious()) {
+ if (uuid.equals(next.getUuid())) {
+ return i;
+ }
+ next = next.next();
+ }
+
+ }
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java
new file mode 100644
index 00000000..9d508357
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/listeners/ConversationSwipeRefreshListener.java
@@ -0,0 +1,92 @@
+package eu.siacs.conversations.ui.listeners;
+
+import android.widget.ListView;
+
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout;
+import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayoutDirection;
+
+import java.util.List;
+
+import de.thedevstack.android.logcat.Logging;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.services.MessageArchiveService;
+import eu.siacs.conversations.ui.ConversationActivity;
+import eu.siacs.conversations.ui.ConversationFragment;
+import eu.siacs.conversations.ui.adapter.MessageAdapter;
+
+/**
+ * This listener starts loading messages from the server.
+ */
+public class ConversationSwipeRefreshListener implements SwipyRefreshLayout.OnRefreshListener {
+ private List<Message> messageList;
+ private ConversationFragment fragment;
+ private ConversationMoreMessagesLoadedListener listener;
+
+ public ConversationSwipeRefreshListener(List<Message> messageList, SwipyRefreshLayout swipeLayout, ConversationFragment fragment, ListView messagesView, MessageAdapter messageListAdapter) {
+ this.messageList = messageList;
+ this.fragment = fragment;
+ this.listener = new ConversationMoreMessagesLoadedListener(swipeLayout, messageList, fragment, messagesView, messageListAdapter);
+ }
+
+ @Override
+ public void onRefresh(SwipyRefreshLayoutDirection direction) {
+ Logging.d(Config.LOGTAG, "Refresh swipe container");
+ Logging.d(Config.LOGTAG, "Refresh direction " + direction);
+ synchronized (this.messageList) {
+ long timestamp;
+ if (SwipyRefreshLayoutDirection.TOP == direction) { // Load history -> messages sent/received before first message in database
+ if (messageList.isEmpty()) {
+ timestamp = System.currentTimeMillis();
+ } else {
+ timestamp = this.messageList.get(0).getTimeSent(); // works only because of the ordering (last msg = first msg in list)
+ }
+ ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+ this.listener.setLoadHistory(true);
+ activity.xmppConnectionService.loadMoreMessages(activity.getSelectedConversation(), timestamp, this.listener);
+ } else if (SwipyRefreshLayoutDirection.BOTTOM == direction) { // load messages sent/received between last received or last session establishment and now
+ Logging.d("mam", "loading missing messages from mam (last session establishing or last received message)");
+ final ConversationActivity activity = (ConversationActivity) fragment.getActivity();
+ long lastSessionEstablished = this.getTimestampOfLastSessionEstablished(activity.getSelectedConversation());
+ long lastReceivedMessage = this.getTimestampOfLastReceivedOrTransmittedMessage();
+ long startTimestamp = Math.min(lastSessionEstablished, lastReceivedMessage);
+ MessageArchiveService.Query query = activity.xmppConnectionService.getMessageArchiveService().query(activity.getSelectedConversation(), startTimestamp, System.currentTimeMillis());
+ if (query != null) {
+ this.listener.setLoadHistory(false);
+ query.setCallback(this.listener);
+ } else {
+ Logging.d("mam", "no query built - no messages loaded");
+ this.listener.onMoreMessagesLoaded(0, activity.getSelectedConversation());
+ this.listener.informUser(R.string.no_more_history_on_server);
+ }
+ this.listener.informUser(R.string.fetching_history_from_server);
+ }
+ }
+ Logging.d(Config.LOGTAG, "End Refresh swipe container");
+ }
+
+ private long getTimestampOfLastReceivedOrTransmittedMessage() {
+ long lastReceivedOrTransmittedMessage = Long.MAX_VALUE;
+ if (null != this.messageList
+ && !this.messageList.isEmpty()) {
+ int lastMessageIndex = this.messageList.size() - 1;
+ if (0 <= lastMessageIndex && this.messageList.size() > lastMessageIndex) {
+ lastReceivedOrTransmittedMessage = this.messageList.get(lastMessageIndex).getTimeSent();
+ }
+ }
+
+ return lastReceivedOrTransmittedMessage;
+ }
+
+ private long getTimestampOfLastSessionEstablished(Conversation conversation) {
+ long lastSessionEstablished = Long.MAX_VALUE;
+ if (null != conversation
+ && null != conversation.getAccount()
+ && null != conversation.getAccount().getXmppConnection()) {
+ lastSessionEstablished = conversation.getAccount().getXmppConnection().getLastSessionEstablished();
+ }
+ return lastSessionEstablished;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index 306d50c2..1568eb8c 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -7,20 +7,13 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.RouteInfo;
import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
-import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
-import java.util.Random;
-import java.util.TreeMap;
-import java.util.Map;
+import java.util.TreeSet;
import java.util.regex.Pattern;
import de.measite.minidns.Client;
@@ -28,47 +21,58 @@ import de.measite.minidns.DNSMessage;
import de.measite.minidns.Record;
import de.measite.minidns.Record.CLASS;
import de.measite.minidns.Record.TYPE;
-import de.measite.minidns.record.A;
-import de.measite.minidns.record.AAAA;
import de.measite.minidns.record.Data;
import de.measite.minidns.record.SRV;
import de.measite.minidns.util.NameUtil;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusApplication;
+import de.thedevstack.conversationsplus.dto.SrvRecord;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.xmpp.jid.Jid;
public class DNSHelper {
-
- public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
- public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+ private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp.";
+ private static final String SECURE_CLIENT_SRV_PREFIX = "_xmpps-client._tcp.";
+ private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
protected static Client client = new Client();
- public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException {
- final String host = jid.getDomainpart();
- final List<InetAddress> servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop();
- Bundle b = new Bundle();
- for(InetAddress server : servers) {
- b = queryDNS(host, server);
- if (b.containsKey("values")) {
- return b;
- }
- }
- if (!b.containsKey("values")) {
- Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record");
- ArrayList<Parcelable> values = new ArrayList<>();
- values.add(createNamePortBundle(host, 5222, false));
- b.putParcelableArrayList("values",values);
- }
- return b;
- }
+ static {
+ client.setTimeout(Config.PING_TIMEOUT * 1000);
+ }
+
+ /**
+ * Queries the SRV record for the server JID.
+ * This method uses all available Domain Name Servers.
+ * @param jid the server JID
+ * @return TreeSet with SrvRecords. If no SRV record is found for JID an empty TreeSet is returned.
+ */
+ public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) {
+ String host = jid.getDomainpart();
+ TreeSet<SrvRecord> result = new TreeSet<>();
+
+ final List<InetAddress> dnsServers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers() : getDnsServersPreLollipop();
+
+ if (dnsServers != null) {
+ for (InetAddress dnsServer : dnsServers) {
+ result = querySrvRecord(host, dnsServer);
+ if (!result.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
@TargetApi(21)
- private static List<InetAddress> getDnsServers(Context context) {
+ private static List<InetAddress> getDnsServers() {
List<InetAddress> servers = new ArrayList<>();
- ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ ConnectivityManager connectivityManager = (ConnectivityManager) ConversationsPlusApplication.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE);
Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
if (networks == null) {
return getDnsServersPreLollipop();
@@ -84,7 +88,7 @@ public class DNSHelper {
}
}
if (servers.size() > 0) {
- Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks");
+ Logging.d("dns", "used lollipop variant to discover dns servers in " + networks.length + " networks");
}
return servers.size() > 0 ? servers : getDnsServersPreLollipop();
}
@@ -112,155 +116,38 @@ public class DNSHelper {
return servers;
}
- private static class TlsSrv {
- private final SRV srv;
- private final boolean tls;
-
- public TlsSrv(SRV srv, boolean tls) {
- this.srv = srv;
- this.tls = tls;
- }
- }
-
- private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException {
- final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
- for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
- for (Record rr : rrset) {
- Data d = rr.getPayload();
- if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
- SRV srv = (SRV) d;
- if (!priorities.containsKey(srv.getPriority())) {
- priorities.put(srv.getPriority(),new ArrayList<TlsSrv>());
- }
- priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls));
- }
- if (d instanceof A) {
- A a = (A) d;
- if (!ips4.containsKey(rr.getName())) {
- ips4.put(rr.getName(), new ArrayList<String>());
- }
- ips4.get(rr.getName()).add(a.toString());
- }
- if (d instanceof AAAA) {
- AAAA aaaa = (AAAA) d;
- if (!ips6.containsKey(rr.getName())) {
- ips6.put(rr.getName(), new ArrayList<String>());
- }
- ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
- }
- }
- }
- }
-
- public static Bundle queryDNS(String host, InetAddress dnsServer) {
- Bundle bundle = new Bundle();
- try {
- client.setTimeout(Config.PING_TIMEOUT * 1000);
- final String qname = "_xmpp-client._tcp." + host;
- final String tlsQname = "_xmpps-client._tcp." + host;
- Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
-
- final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>();
- final Map<String, List<String>> ips4 = new TreeMap<>();
- final Map<String, List<String>> ips6 = new TreeMap<>();
-
- fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false);
- fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true);
-
- final List<TlsSrv> result = new ArrayList<>();
- for (final List<TlsSrv> s : priorities.values()) {
- result.addAll(s);
- }
-
- final ArrayList<Bundle> values = new ArrayList<>();
- if (result.size() == 0) {
- DNSMessage response;
- try {
- response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
- }
- try {
- response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
- }
- values.add(createNamePortBundle(host, 5222, false));
- bundle.putParcelableArrayList("values", values);
- return bundle;
- }
- for (final TlsSrv tlsSrv : result) {
- final SRV srv = tlsSrv.srv;
- if (ips6.containsKey(srv.getName())) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls));
- } else {
- try {
- DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
- for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls));
- }
- } catch (SocketTimeoutException e) {
- Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
- }
- }
- if (ips4.containsKey(srv.getName())) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls));
- } else {
- DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
- for(int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls));
- }
- }
- values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls));
- }
- bundle.putParcelableArrayList("values", values);
- } catch (SocketTimeoutException e) {
- bundle.putString("error", "timeout");
- } catch (Exception e) {
- bundle.putString("error", "unhandled");
- }
- return bundle;
- }
-
- private static Bundle createNamePortBundle(String name, int port, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- return namePort;
- }
-
- private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- if (ips!=null) {
- List<String> ip = ips.get(name);
- Collections.shuffle(ip, new Random());
- namePort.putString("ip", ip.get(0));
- }
- return namePort;
- }
-
- private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) {
- Bundle namePort = new Bundle();
- namePort.putString("name", name);
- namePort.putBoolean("tls", tls);
- namePort.putInt("port", port);
- if (data instanceof A) {
- namePort.putString("ip", data.toString());
- } else if (data instanceof AAAA) {
- namePort.putString("ip","["+data.toString()+"]");
- }
- return namePort;
- }
+ /**
+ * Queries the SRV record for an host from the given Domain Name Server.
+ * @param host the host to query for
+ * @param dnsServerAddress the DNS to query on
+ * @return TreeSet with SrvRecords.
+ */
+ private static final TreeSet<SrvRecord> querySrvRecord(String host, InetAddress dnsServerAddress) {
+ TreeSet<SrvRecord> result = new TreeSet<>();
+ querySrvRecord(host, dnsServerAddress, false, result);
+ querySrvRecord(host, dnsServerAddress, true, result);
+ return result;
+ }
+
+ private static final void querySrvRecord(String host, InetAddress dnsServerAddress, boolean tlsSrvRecord, TreeSet<SrvRecord> result) {
+ String qname = (tlsSrvRecord ? SECURE_CLIENT_SRV_PREFIX : CLIENT_SRV_PREFIX) + host;
+ String dnsServerHostAddress = dnsServerAddress.getHostAddress();
+ Logging.d("dns", "using dns server: " + dnsServerHostAddress + " to look up " + qname);
+ try {
+ DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerHostAddress);
+ Record[] rrset = message.getAnswers();
+ for (Record rr : rrset) {
+ Data d = rr.getPayload();
+ if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
+ SRV srv = (SRV) d;
+ SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort(), tlsSrvRecord);
+ result.add(srvRecord);
+ }
+ }
+ } catch (IOException e) {
+ Logging.d("dns", "Error while retrieving SRV record '" + qname + "' for '" + host + "' from DNS '" + dnsServerHostAddress + "': " + e.getMessage());
+ }
+ }
public static boolean isIp(final String server) {
return server != null && (
diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
index 8799b4a5..9c8db210 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
@@ -4,13 +4,10 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
-import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.preference.PreferenceManager;
import android.text.format.DateUtils;
-import android.util.Log;
import java.io.BufferedReader;
import java.io.FileInputStream;
@@ -18,6 +15,8 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -36,11 +35,9 @@ public class ExceptionHelper {
}
}
- public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) {
+ public static boolean checkForCrash(final ConversationActivity activity, final XmppConnectionService service) {
try {
- final SharedPreferences preferences = PreferenceManager
- .getDefaultSharedPreferences(activity);
- boolean neverSend = preferences.getBoolean("never_send", false);
+ boolean neverSend = ConversationsPlusPreferences.neverSend();
if (neverSend) {
return false;
}
@@ -89,13 +86,13 @@ public class ExceptionHelper {
@Override
public void onClick(DialogInterface dialog, int which) {
- Log.d(Config.LOGTAG, "using account="
+ Logging.d(Config.LOGTAG, "using account="
+ finalAccount.getJid().toBareJid()
+ " to send in stack trace");
Conversation conversation = null;
try {
conversation = service.findOrCreateConversation(finalAccount,
- Jid.fromString("bugs@siacs.eu"), false);
+ Jid.fromString(activity.getString(R.string.cplus_bugreport_jabberid)), false);
} catch (final InvalidJidException ignored) {
}
Message message = new Message(conversation, report
@@ -108,8 +105,7 @@ public class ExceptionHelper {
@Override
public void onClick(DialogInterface dialog, int which) {
- preferences.edit().putBoolean("never_send", true)
- .apply();
+ ConversationsPlusPreferences.applyNeverSend(true);
}
});
builder.create().show();
diff --git a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java b/src/main/java/eu/siacs/conversations/utils/ExifHelper.java
index ceda7293..5e465e94 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExifHelper.java
@@ -16,11 +16,11 @@
package eu.siacs.conversations.utils;
-import android.util.Log;
-
import java.io.IOException;
import java.io.InputStream;
+import de.thedevstack.android.logcat.Logging;
+
public class ExifHelper {
private static final String TAG = "CameraExif";
@@ -56,7 +56,7 @@ public class ExifHelper {
}
length = pack(buf, 0, 2, false);
if (length < 2) {
- Log.e(TAG, "Invalid length");
+ Logging.e(TAG, "Invalid length");
return 0;
}
length -= 2;
@@ -91,7 +91,7 @@ public class ExifHelper {
// Identify the byte order.
int tag = pack(jpeg, offset, 4, false);
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
- Log.e(TAG, "Invalid byte order");
+ Logging.e(TAG, "Invalid byte order");
return 0;
}
boolean littleEndian = (tag == 0x49492A00);
@@ -99,7 +99,7 @@ public class ExifHelper {
// Get the offset and check if it is reasonable.
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
if (count < 10 || count > length) {
- Log.e(TAG, "Invalid offset");
+ Logging.e(TAG, "Invalid offset");
return 0;
}
offset += count;
@@ -123,7 +123,7 @@ public class ExifHelper {
case 8:
return 270;
}
- Log.i(TAG, "Unsupported orientation");
+ Logging.i(TAG, "Unsupported orientation");
return 0;
}
offset += 12;
@@ -131,7 +131,7 @@ public class ExifHelper {
}
}
- Log.i(TAG, "Orientation not found");
+ Logging.i(TAG, "Orientation not found");
return 0;
}
diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java
index ad8b8640..c5d0c3b0 100644
--- a/src/main/java/eu/siacs/conversations/utils/FileUtils.java
+++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java
@@ -1,6 +1,7 @@
package eu.siacs.conversations.utils;
import android.annotation.SuppressLint;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
@@ -76,11 +77,11 @@ public class FileUtils {
}
}
// MediaStore (and general)
- else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
- else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ else if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return uri.getPath();
}
diff --git a/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java b/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java
index 8fe67234..5faa1fa7 100644
--- a/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java
+++ b/src/main/java/eu/siacs/conversations/utils/PRNGFixes.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.utils;
import android.os.Build;
import android.os.Process;
-import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -19,6 +18,8 @@ import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.security.Security;
+import de.thedevstack.android.logcat.Logging;
+
/**
* Fixes for the output of the default PRNG having low entropy.
*
@@ -209,7 +210,7 @@ public final class PRNGFixes {
} catch (IOException e) {
// On a small fraction of devices /dev/urandom is not writable.
// Log and ignore.
- Log.w(PRNGFixes.class.getSimpleName(),
+ Logging.w(PRNGFixes.class.getSimpleName(),
"Failed to mix seed into " + URANDOM_FILE);
} finally {
mSeeded = true;
diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
index 768e9f17..04cfa2eb 100644
--- a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
+++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
@@ -43,10 +43,6 @@ public class SocksSocketFactory {
return socket;
}
- public static Socket createSocketOverTor(String destination, int port) throws IOException {
- return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 9050), destination, port);
- }
-
static class SocksConnectionException extends IOException {
}
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 8cae8117..3d091e39 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -5,6 +5,7 @@ import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Pair;
+import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -15,17 +16,13 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Presence;
+import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.xmpp.jid.Jid;
public class UIHelper {
- private static String BLACK_HEART_SUIT = "\u2665";
- private static String HEAVY_BLACK_HEART_SUIT = "\u2764";
- private static String WHITE_HEART_SUIT = "\u2661";
-
- public static final ArrayList<String> HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT));
-
private static final ArrayList<String> LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList(
"where are you", //en
"where are you now", //en
@@ -195,7 +192,7 @@ public class UIHelper {
return new Pair<>(context.getString(R.string.x_file_offered_for_download,
getFileDescriptionString(context,message)),true);
} else{
- return new Pair<>(message.getBody().trim(), false);
+ return new Pair<>(message.getBody(), false);
}
}
}
@@ -246,13 +243,27 @@ public class UIHelper {
}
}
+ public static String getStatusColor(Presence.Status status) {
+ switch (status) {
+ case ONLINE:
+ case CHAT:
+ return "#259B23";
+ case AWAY:
+ case XA:
+ return "#FF9800";
+ case DND:
+ return "#E51C23";
+ }
+ return "#CCCCCC";
+ }
+
private static String getDisplayedMucCounterpart(final Jid counterpart) {
if (counterpart==null) {
return "";
} else if (!counterpart.isBareJid()) {
- return counterpart.getResourcepart().trim();
+ return counterpart.getResourcepart();
} else {
- return counterpart.toString().trim();
+ return counterpart.toString();
}
}
@@ -262,8 +273,24 @@ public class UIHelper {
|| message.getType() != Message.TYPE_TEXT) {
return false;
}
- String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault());
+ String body = message.getBody() == null ? null : message.getBody().toLowerCase(Locale.getDefault());
body = body.replace("?","").replace("¿","");
return LOCATION_QUESTIONS.contains(body);
}
+
+ public static String getHumanReadableFileSize(long filesize) {
+ if (0 > filesize) {
+ return "?";
+ }
+ double size = Double.valueOf(filesize);
+ String[] sizes = {" bytes", " Kb", " Mb", " Gb", " Tb"};
+ int i = 0;
+ while (1023 < size) {
+ size /= 1024d;
+ ++i;
+ }
+ BigDecimal readableSize = new BigDecimal(size);
+ readableSize = readableSize.setScale(2, BigDecimal.ROUND_HALF_UP);
+ return readableSize.doubleValue() + sizes[i];
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java
index 34794be1..9152c679 100644
--- a/src/main/java/eu/siacs/conversations/xml/Element.java
+++ b/src/main/java/eu/siacs/conversations/xml/Element.java
@@ -1,11 +1,10 @@
package eu.siacs.conversations.xml;
-import android.util.Log;
-
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -128,7 +127,7 @@ public class Element {
try {
return Jid.fromString(jid);
} catch (final InvalidJidException e) {
- Log.e(Config.LOGTAG, "could not parse jid " + jid);
+ Logging.e(Config.LOGTAG, "could not parse jid " + jid);
return null;
}
}
diff --git a/src/main/java/eu/siacs/conversations/xml/XmlReader.java b/src/main/java/eu/siacs/conversations/xml/XmlReader.java
index aeaaa593..74e65fcd 100644
--- a/src/main/java/eu/siacs/conversations/xml/XmlReader.java
+++ b/src/main/java/eu/siacs/conversations/xml/XmlReader.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.xml;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
-import android.util.Log;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -12,6 +11,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
public class XmlReader {
@@ -25,7 +25,7 @@ public class XmlReader {
this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
true);
} catch (XmlPullParserException e) {
- Log.d(Config.LOGTAG, "error setting namespace feature on parser");
+ Logging.d(Config.LOGTAG, "error setting namespace feature on parser");
}
this.wakeLock = wakeLock;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index 6be220c7..fa381668 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -39,6 +39,7 @@ import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
+import java.util.TreeSet;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
@@ -49,6 +50,8 @@ import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import de.duenndns.ssl.MemorizingTrustManager;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.dto.SrvRecord;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.crypto.sasl.DigestMd5;
@@ -88,7 +91,7 @@ import eu.siacs.conversations.xmpp.stanzas.streammgmt.RequestPacket;
import eu.siacs.conversations.xmpp.stanzas.streammgmt.ResumePacket;
public class XmppConnection implements Runnable {
-
+ private static final int DEFAULT_PORT = 5222;
private static final int PACKET_IQ = 0;
private static final int PACKET_MESSAGE = 1;
private static final int PACKET_PRESENCE = 2;
@@ -220,7 +223,7 @@ public class XmppConnection implements Runnable {
}
protected void connect() {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": connecting");
features.encryptionEnabled = false;
lastConnect = SystemClock.elapsedRealtime();
lastPingSent = SystemClock.elapsedRealtime();
@@ -242,19 +245,8 @@ public class XmppConnection implements Runnable {
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
this.changeStatus(Account.State.CONNECTING);
- final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion();
final boolean extended = mXmppConnectionService.showExtendedConnectionOptions();
- if (useTor) {
- String destination;
- if (account.getHostname() == null || account.getHostname().isEmpty()) {
- destination = account.getServer().toString();
- } else {
- destination = account.getHostname();
- }
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR");
- socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
- startXmpp();
- } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
+ if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
socket = new Socket();
try {
socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000);
@@ -265,75 +257,60 @@ public class XmppConnection implements Runnable {
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
try {
- socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
+ socket.connect(new InetSocketAddress(account.getServer().toString(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
startXmpp();
} else {
- final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
- final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
- for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) {
- final Bundle namePort = (Bundle) iterator.next();
- try {
- String srvRecordServer;
- try {
- srvRecordServer = IDN.toASCII(namePort.getString("name"));
- } catch (final IllegalArgumentException e) {
- // TODO: Handle me?`
- srvRecordServer = "";
- }
- final int srvRecordPort = namePort.getInt("port");
- final String srvIpServer = namePort.getString("ip");
- // if tls is true, encryption is implied and must not be started
- features.encryptionEnabled = namePort.getBoolean("tls");
- final InetSocketAddress addr;
- if (srvIpServer != null) {
- addr = new InetSocketAddress(srvIpServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns " + srvRecordServer
- + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
- } else {
- addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
- + ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
- }
-
- if (!features.encryptionEnabled) {
- socket = new Socket();
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
- } else {
- final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
- socket = tlsFactoryVerifier.factory.createSocket();
-
- if (socket == null) {
- throw new IOException("could not initialize ssl socket");
- }
-
- SSLSocketHelper.setSecurity((SSLSocket) socket);
- SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
- SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
-
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
-
- if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
- throw new SecurityException();
- }
- }
-
- if (startXmpp())
- break; // successfully connected to server that speaks xmpp
- } catch(final SecurityException e) {
- throw e;
- } catch (final Throwable e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
- if (!iterator.hasNext()) {
- throw new UnknownHostException();
- }
- }
- }
+ final TreeSet<SrvRecord> srvRecords = DNSHelper.querySrvRecord(account.getServer());
+ if (srvRecords.isEmpty()) {
+ socket = new Socket();
+ try {
+ socket.connect(new InetSocketAddress(account.getServer().getDomainpart(), DEFAULT_PORT), Config.SOCKET_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new UnknownHostException();
+ }
+ startXmpp();
+ } else {
+ for (SrvRecord srvRecord : srvRecords) {
+ // if tls is true, encryption is implied and must not be started
+ features.encryptionEnabled = srvRecord.isUseTls();
+ TlsFactoryVerifier tlsFactoryVerifier = null;
+ if (features.encryptionEnabled) {
+ try {
+ tlsFactoryVerifier = getTlsFactoryVerifier();
+ socket = tlsFactoryVerifier.factory.createSocket();
+
+ if (socket == null) {
+ throw new IOException("could not initialize ssl socket");
+ }
+
+ SSLSocketHelper.setSecurity((SSLSocket) socket);
+ SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
+ SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+ } catch (SecurityException e) {
+ throw e;
+ } catch (KeyManagementException e) {
+ Logging.e("connection-init", "Error while creating TLS verifier factory: " + e.getMessage(), e);
+ throw new SecurityException();
+ }
+ } else {
+ socket = new Socket();
+ }
+
+ socket.connect(new InetSocketAddress(srvRecord.getName(), srvRecord.getPort()), Config.SOCKET_TIMEOUT * 1000);
+
+ if (null != tlsFactoryVerifier && !tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
+ throw new SecurityException();
+ }
+
+ if (startXmpp()) {
+ break; // successfully connected to server that speaks xmpp
+ }
+ }
+ }
}
processStream();
} catch (final IncompatibleServerException e) {
@@ -344,10 +321,8 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.UNAUTHORIZED);
} catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
- } catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
- this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
this.changeStatus(Account.State.OFFLINE);
this.attempt--; //don't count attempt when reconnecting instantly anyway
} finally {
@@ -647,7 +622,7 @@ public class XmppConnection implements Runnable {
callback = packetCallbackDuple.second;
packetCallbacks.remove(packet.getId());
} else {
- Log.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
+ Logging.e(Config.LOGTAG, account.getJid().toBareJid().toString() + ": ignoring spoofed iq packet");
}
}
} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
@@ -754,16 +729,16 @@ public class XmppConnection implements Runnable {
try {
if (keys.has(Account.PINNED_MECHANISM_KEY) &&
keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) {
- Log.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
+ Logging.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() +
" has lower priority (" + String.valueOf(saslMechanism.getPriority()) +
") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) +
"). Possible downgrade attack?");
throw new SecurityException();
}
} catch (final JSONException e) {
- Log.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
+ Logging.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism");
}
- Log.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
+ Logging.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism());
auth.setAttribute("mechanism", saslMechanism.getMechanism());
if (!saslMechanism.getClientFirstMessage().isEmpty()) {
auth.setContent(saslMechanism.getClientFirstMessage());
@@ -774,7 +749,7 @@ public class XmppConnection implements Runnable {
}
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) {
if (Config.EXTENDED_SM_LOGGING) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
}
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
@@ -846,7 +821,7 @@ public class XmppConnection implements Runnable {
URL uri = new URL(urlString);
captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
} catch (IOException e) {
- Log.e(Config.LOGTAG, e.toString());
+ Logging.e(Config.LOGTAG, e.toString());
}
}
@@ -915,11 +890,11 @@ public class XmppConnection implements Runnable {
sendPostBindInitialization();
}
} else {
- Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure");
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure");
disconnect(true);
}
} else {
- Log.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure");
+ Logging.d(Config.LOGTAG, account.getJid() + ": disconnecting because of bind failure");
disconnect(true);
}
}
@@ -979,7 +954,7 @@ public class XmppConnection implements Runnable {
if (packet.getType() == IqPacket.TYPE.RESULT) {
sendPostBindInitialization();
} else if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not init sessions");
disconnect(true);
}
}
@@ -1070,13 +1045,13 @@ public class XmppConnection implements Runnable {
enableAdvancedStreamFeatures();
}
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString());
}
if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
mPendingServiceDiscoveries--;
if (mPendingServiceDiscoveries == 0) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery");
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource());
if (bindListener != null) {
bindListener.onBind(account);
}
@@ -1136,11 +1111,11 @@ public class XmppConnection implements Runnable {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
if (!packet.hasChild("error")) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": successfully enabled carbons");
features.carbonsEnabled = true;
} else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid()
+ ": error enableing carbons " + packet.toString());
}
}
@@ -1153,11 +1128,11 @@ public class XmppConnection implements Runnable {
if (streamError != null && streamError.hasChild("conflict")) {
final String resource = account.getResource().split("\\.")[0];
account.setResource(resource + "." + nextRandomId());
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
account.getJid().toBareJid() + ": switching resource due to conflict ("
+ account.getResource() + ")");
} else if (streamError != null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": stream error "+streamError.toString());
}
}
@@ -1281,7 +1256,7 @@ public class XmppConnection implements Runnable {
Thread.sleep(10);
}
socket.close();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
@@ -1291,7 +1266,7 @@ public class XmppConnection implements Runnable {
}).start();
} else {
forceCloseSocket();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
}
}
@@ -1300,13 +1275,13 @@ public class XmppConnection implements Runnable {
try {
socket.close();
} catch (IOException e) {
- e.printStackTrace();
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": exception during force close ("+e.getMessage()+")");
}
}
}
public void disconnect(final boolean force) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting force="+Boolean.valueOf(force));
if (force) {
forceCloseSocket();
return;
@@ -1400,7 +1375,12 @@ public class XmppConnection implements Runnable {
}
public long getLastSessionEstablished() {
- final long diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
+ final long diff;
+ if (this.lastSessionStarted == 0) {
+ diff = SystemClock.elapsedRealtime() - this.lastConnect;
+ } else {
+ diff = SystemClock.elapsedRealtime() - this.lastSessionStarted;
+ }
return System.currentTimeMillis() - diff;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
index dcb13c23..8ab0fe8a 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.xmpp.jingle;
import android.content.Intent;
import android.net.Uri;
-import android.util.Log;
import android.util.Pair;
import java.io.FileNotFoundException;
@@ -16,6 +15,10 @@ import java.util.Locale;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback;
@@ -97,7 +100,7 @@ public class JingleConnection implements Transferable {
public void onFileTransmitted(DownloadableFile file) {
if (responder.equals(account.getJid())) {
sendSuccess();
- mXmppConnectionService.getFileBackend().updateFileParams(message);
+ MessageUtil.updateFileParams(message);
mXmppConnectionService.databaseBackend.createMessage(message);
mXmppConnectionService.markMessage(message,Message.STATUS_RECEIVED);
if (acceptedAutomatically) {
@@ -109,7 +112,7 @@ public class JingleConnection implements Transferable {
file.delete();
}
}
- Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")");
+ Logging.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")");
if (message.getEncryption() != Message.ENCRYPTION_PGP) {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
@@ -139,17 +142,17 @@ public class JingleConnection implements Transferable {
@Override
public void success() {
if (initiator.equals(account.getJid())) {
- Log.d(Config.LOGTAG, "we were initiating. sending file");
+ Logging.d(Config.LOGTAG, "we were initiating. sending file");
transport.send(file, onFileTransmissionSatusChanged);
} else {
transport.receive(file, onFileTransmissionSatusChanged);
- Log.d(Config.LOGTAG, "we were responding. receiving file");
+ Logging.d(Config.LOGTAG, "we were responding. receiving file");
}
}
@Override
public void failed() {
- Log.d(Config.LOGTAG, "proxy activation failed");
+ Logging.d(Config.LOGTAG, "proxy activation failed");
}
};
@@ -195,13 +198,13 @@ public class JingleConnection implements Transferable {
returnResult = this.receiveFallbackToIbb(packet);
} else {
returnResult = false;
- Log.d(Config.LOGTAG, "trying to fallback to something unknown"
+ Logging.d(Config.LOGTAG, "trying to fallback to something unknown"
+ packet.toString());
}
} else if (packet.isAction("transport-accept")) {
returnResult = this.receiveTransportAccept(packet);
} else {
- Log.d(Config.LOGTAG, "packet arrived in connection. action was "
+ Logging.d(Config.LOGTAG, "packet arrived in connection. action was "
+ packet.getAction());
returnResult = false;
}
@@ -263,14 +266,14 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"connection to our own primary candidete failed");
sendInitRequest();
}
@Override
public void established() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"succesfully connected to our own primary candidate");
mergeCandidate(candidate);
sendInitRequest();
@@ -278,7 +281,8 @@ public class JingleConnection implements Transferable {
});
mergeCandidate(candidate);
} else {
- Log.d(Config.LOGTAG, "no primary candidate of our own was found");
+ Logging.d(Config.LOGTAG,
+ "no primary candidate of our own was found");
sendInitRequest();
}
}
@@ -362,21 +366,21 @@ public class JingleConnection implements Transferable {
conversation.add(message);
mXmppConnectionService.updateConversationUi();
if (mJingleConnectionManager.hasStoragePermission()
- && size < this.mJingleConnectionManager.getAutoAcceptFileSize()) {
- Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
+ && size <= ConversationsPlusPreferences.autoAcceptFileSize()
+ && mXmppConnectionService.isDownloadAllowedInConnection()) {
+ Logging.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
this.acceptedAutomatically = true;
this.sendAccept();
} else {
message.markUnread();
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"not auto accepting new file offer with size: "
+ size
+ " allowed size:"
- + this.mJingleConnectionManager
- .getAutoAcceptFileSize());
+ + ConversationsPlusPreferences.autoAcceptFileSize());
this.mXmppConnectionService.getNotificationService().push(message);
}
- this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
if (mXmppAxolotlMessage != null) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage);
if (transportMessage != null) {
@@ -385,7 +389,7 @@ public class JingleConnection implements Transferable {
this.file.setIv(transportMessage.getIv());
message.setAxolotlFingerprint(transportMessage.getFingerprint());
} else {
- Log.d(Config.LOGTAG,"could not process KeyTransportMessage");
+ Logging.d(Config.LOGTAG,"could not process KeyTransportMessage");
}
} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
byte[] key = conversation.getSymmetricKey();
@@ -403,7 +407,7 @@ public class JingleConnection implements Transferable {
} else {
this.file.setExpectedSize(size);
}
- Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
+ Logging.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
} else {
this.sendCancel();
this.fail();
@@ -419,13 +423,13 @@ public class JingleConnection implements Transferable {
Content content = new Content(this.contentCreator, this.contentName);
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
content.setTransportId(this.transportId);
- this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
+ this.file = FileBackend.getFile(message, false);
Pair<InputStream,Integer> pair;
try {
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
Conversation conversation = this.message.getConversation();
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key");
+ Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not set symmetric key");
cancel();
}
this.file.setKeyAndIv(conversation.getSymmetricKey());
@@ -457,7 +461,7 @@ public class JingleConnection implements Transferable {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
mJingleStatus = JINGLE_STATUS_INITIATED;
mXmppConnectionService.markMessage(message, Message.STATUS_OFFERED);
} else {
@@ -499,7 +503,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG,"connection to our own primary candidate failed");
+ Logging.d(Config.LOGTAG,"connection to our own primary candidate failed");
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
sendJinglePacket(packet);
@@ -508,7 +512,7 @@ public class JingleConnection implements Transferable {
@Override
public void established() {
- Log.d(Config.LOGTAG, "connected to primary candidate");
+ Logging.d(Config.LOGTAG, "connected to primary candidate");
mergeCandidate(candidate);
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
@@ -517,7 +521,7 @@ public class JingleConnection implements Transferable {
}
});
} else {
- Log.d(Config.LOGTAG,"did not find a primary candidate for ourself");
+ Logging.d(Config.LOGTAG,"did not find a primary candidate for ourself");
content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content);
sendJinglePacket(packet);
@@ -563,13 +567,13 @@ public class JingleConnection implements Transferable {
onProxyActivated.success();
} else {
String cid = content.socks5transport().findChild("activated").getAttribute("cid");
- Log.d(Config.LOGTAG, "received proxy activated (" + cid
+ Logging.d(Config.LOGTAG, "received proxy activated (" + cid
+ ")prior to choosing our own transport");
JingleSocks5Transport connection = this.connections.get(cid);
if (connection != null) {
connection.setActivated(true);
} else {
- Log.d(Config.LOGTAG, "activated connection not found");
+ Logging.d(Config.LOGTAG, "activated connection not found");
this.sendCancel();
this.fail();
}
@@ -579,7 +583,7 @@ public class JingleConnection implements Transferable {
onProxyActivated.failed();
return true;
} else if (content.socks5transport().hasChild("candidate-error")) {
- Log.d(Config.LOGTAG, "received candidate error");
+ Logging.d(Config.LOGTAG, "received candidate error");
this.receivedCandidate = true;
if ((mJingleStatus == JINGLE_STATUS_ACCEPTED)
&& (this.sentCandidate)) {
@@ -590,7 +594,7 @@ public class JingleConnection implements Transferable {
String cid = content.socks5transport()
.findChild("candidate-used").getAttribute("cid");
if (cid != null) {
- Log.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
+ Logging.d(Config.LOGTAG, "candidate used by counterpart:" + cid);
JingleCandidate candidate = getCandidate(cid);
candidate.flagAsUsedByCounterpart();
this.receivedCandidate = true;
@@ -598,7 +602,7 @@ public class JingleConnection implements Transferable {
&& (this.sentCandidate)) {
this.connect();
} else {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"ignoring because file is already in transmission or we havent sent our candidate yet");
}
return true;
@@ -617,7 +621,7 @@ public class JingleConnection implements Transferable {
final JingleSocks5Transport connection = chooseConnection();
this.transport = connection;
if (connection == null) {
- Log.d(Config.LOGTAG, "could not find suitable candidate");
+ Logging.d(Config.LOGTAG, "could not find suitable candidate");
this.disconnectSocks5Connections();
if (this.initiator.equals(account.getJid())) {
this.sendFallbackToIbb();
@@ -626,7 +630,7 @@ public class JingleConnection implements Transferable {
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
if (connection.needsActivation()) {
if (connection.getCandidate().isOurs()) {
- Log.d(Config.LOGTAG, "candidate "
+ Logging.d(Config.LOGTAG, "candidate "
+ connection.getCandidate().getCid()
+ " was our proxy. going to activate");
IqPacket activation = new IqPacket(IqPacket.TYPE.SET);
@@ -650,17 +654,17 @@ public class JingleConnection implements Transferable {
}
});
} else {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"candidate "
+ connection.getCandidate().getCid()
+ " was a proxy. waiting for other party to activate");
}
} else {
if (initiator.equals(account.getJid())) {
- Log.d(Config.LOGTAG, "we were initiating. sending file");
+ Logging.d(Config.LOGTAG, "we were initiating. sending file");
connection.send(file, onFileTransmissionSatusChanged);
} else {
- Log.d(Config.LOGTAG, "we were responding. receiving file");
+ Logging.d(Config.LOGTAG, "we were responding. receiving file");
connection.receive(file, onFileTransmissionSatusChanged);
}
}
@@ -672,11 +676,11 @@ public class JingleConnection implements Transferable {
for (Entry<String, JingleSocks5Transport> cursor : connections
.entrySet()) {
JingleSocks5Transport currentConnection = cursor.getValue();
- // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
+ // Logging.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
if (currentConnection.isEstablished()
&& (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection
.getCandidate().isOurs()))) {
- // Log.d(Config.LOGTAG,"is usable");
+ // Logging.d(Config.LOGTAG,"is usable");
if (connection == null) {
connection = currentConnection;
} else {
@@ -685,7 +689,7 @@ public class JingleConnection implements Transferable {
connection = currentConnection;
} else if (connection.getCandidate().getPriority() == currentConnection
.getCandidate().getPriority()) {
- // Log.d(Config.LOGTAG,"found two candidates with same priority");
+ // Logging.d(Config.LOGTAG,"found two candidates with same priority");
if (initiator.equals(account.getJid())) {
if (currentConnection.getCandidate().isOurs()) {
connection = currentConnection;
@@ -717,7 +721,7 @@ public class JingleConnection implements Transferable {
}
private void sendFallbackToIbb() {
- Log.d(Config.LOGTAG, "sending fallback to ibb");
+ Logging.d(Config.LOGTAG, "sending fallback to ibb");
JinglePacket packet = this.bootstrapPacket("transport-replace");
Content content = new Content(this.contentCreator, this.contentName);
this.transportId = this.mJingleConnectionManager.nextRandomId();
@@ -729,7 +733,7 @@ public class JingleConnection implements Transferable {
}
private boolean receiveFallbackToIbb(JinglePacket packet) {
- Log.d(Config.LOGTAG, "receiving fallack to ibb");
+ Logging.d(Config.LOGTAG, "receiving fallack to ibb");
String receivedBlockSize = packet.getJingleContent().ibbTransport()
.getAttribute("block-size");
if (receivedBlockSize != null) {
@@ -765,7 +769,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG, "ibb open failed");
+ Logging.d(Config.LOGTAG, "ibb open failed");
}
@Override
@@ -817,8 +821,8 @@ public class JingleConnection implements Transferable {
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
this.transport.disconnect();
}
- FileBackend.close(mFileInputStream);
- FileBackend.close(mFileOutputStream);
+ StreamUtil.close(mFileInputStream);
+ StreamUtil.close(mFileOutputStream);
if (this.message != null) {
if (this.responder.equals(account.getJid())) {
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
@@ -862,7 +866,7 @@ public class JingleConnection implements Transferable {
@Override
public void failed() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"connection failed with " + candidate.getHost() + ":"
+ candidate.getPort());
connectNextCandidate();
@@ -870,7 +874,7 @@ public class JingleConnection implements Transferable {
@Override
public void established() {
- Log.d(Config.LOGTAG,
+ Logging.d(Config.LOGTAG,
"established connection with " + candidate.getHost()
+ ":" + candidate.getPort());
sendCandidateUsed(candidate.getCid());
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
index 0f0361cd..f4a069bc 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -1,7 +1,6 @@
package eu.siacs.conversations.xmpp.jingle;
import android.annotation.SuppressLint;
-import android.util.Log;
import java.math.BigInteger;
import java.security.SecureRandom;
@@ -9,6 +8,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import de.thedevstack.android.logcat.Logging;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message;
@@ -155,9 +155,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
}
- Log.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString());
+ Logging.d(Config.LOGTAG,"couldn't deliver payload: " + payload.toString());
} else {
- Log.d(Config.LOGTAG, "no sid found in incoming ibb packet");
+ Logging.d(Config.LOGTAG, "no sid found in incoming ibb packet");
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
index 0b0cb408..3800b94f 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java
@@ -1,7 +1,6 @@
package eu.siacs.conversations.xmpp.jingle;
import android.util.Base64;
-import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
@@ -10,10 +9,11 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -95,13 +95,13 @@ public class JingleInbandTransport extends JingleTransport {
file.createNewFile();
this.fileOutputStream = connection.getFileOutputStream();
if (this.fileOutputStream == null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
callback.onFileTransferAborted();
return;
}
this.remainingSize = this.fileSize = file.getExpectedSize();
} catch (final NoSuchAlgorithmException | IOException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage());
callback.onFileTransferAborted();
}
}
@@ -118,7 +118,7 @@ public class JingleInbandTransport extends JingleTransport {
this.digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
callback.onFileTransferAborted();
return;
}
@@ -127,7 +127,7 @@ public class JingleInbandTransport extends JingleTransport {
}
} catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
}
}
@@ -186,8 +186,8 @@ public class JingleInbandTransport extends JingleTransport {
fileInputStream.close();
}
} catch (IOException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
- FileBackend.close(fileInputStream);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ StreamUtil.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
@@ -210,8 +210,8 @@ public class JingleInbandTransport extends JingleTransport {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
}
} catch (IOException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
- FileBackend.close(fileOutputStream);
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
+ StreamUtil.close(fileOutputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
index 9240bd2c..76cd0c87 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java
@@ -1,25 +1,22 @@
package eu.siacs.conversations.xmpp.jingle;
import android.os.PowerManager;
-import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.InetAddress;
import java.net.InetSocketAddress;
-import java.net.Proxy;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.utils.StreamUtil;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SocksSocketFactory;
@@ -62,14 +59,10 @@ public class JingleSocks5Transport extends JingleTransport {
@Override
public void run() {
try {
- final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
- if (useTor) {
- socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(),candidate.getPort());
- } else {
- socket = new Socket();
- SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
- socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
- }
+ socket = new Socket();
+ SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
+ socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
+
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
SocksSocketFactory.createSocksConnection(socket,destination,0);
@@ -98,7 +91,7 @@ public class JingleSocks5Transport extends JingleTransport {
digest.reset();
fileInputStream = connection.getFileInputStream();
if (fileInputStream == null) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
callback.onFileTransferAborted();
return;
}
@@ -118,16 +111,16 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransmitted(file);
}
} catch (FileNotFoundException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (IOException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} finally {
- FileBackend.close(fileInputStream);
+ StreamUtil.close(fileInputStream);
wakeLock.release();
}
}
@@ -153,7 +146,7 @@ public class JingleSocks5Transport extends JingleTransport {
fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
return;
}
double size = file.getExpectedSize();
@@ -164,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
count = inputStream.read(buffer);
if (count == -1) {
callback.onFileTransferAborted();
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": file ended prematurely with "+remainingSize+" bytes remaining");
return;
} else {
fileOutputStream.write(buffer, 0, count);
@@ -178,18 +171,18 @@ public class JingleSocks5Transport extends JingleTransport {
file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
callback.onFileTransmitted(file);
} catch (FileNotFoundException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (IOException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
+ Logging.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());
callback.onFileTransferAborted();
} finally {
wakeLock.release();
- FileBackend.close(fileOutputStream);
- FileBackend.close(inputStream);
+ StreamUtil.close(fileOutputStream);
+ StreamUtil.close(inputStream);
}
}
}).start();
@@ -204,9 +197,9 @@ public class JingleSocks5Transport extends JingleTransport {
}
public void disconnect() {
- FileBackend.close(inputStream);
- FileBackend.close(outputStream);
- FileBackend.close(socket);
+ StreamUtil.close(inputStream);
+ StreamUtil.close(outputStream);
+ StreamUtil.close(socket);
}
public boolean isEstablished() {
diff --git a/src/main/project.properties b/src/main/project.properties
new file mode 100644
index 00000000..4ab12569
--- /dev/null
+++ b/src/main/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
diff --git a/src/main/res/drawable-hdpi/ic_action_keyboard.png b/src/main/res/drawable-hdpi/ic_action_keyboard.png
new file mode 100644
index 00000000..37c04af2
--- /dev/null
+++ b/src/main/res/drawable-hdpi/ic_action_keyboard.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_launcher.png b/src/main/res/drawable-hdpi/ic_launcher.png
index 690400eb..1bea2f74 100644
--- a/src/main/res/drawable-hdpi/ic_launcher.png
+++ b/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png
deleted file mode 100644
index 00e7d074..00000000
--- a/src/main/res/drawable-hdpi/ic_mode_edit_black_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png
deleted file mode 100644
index 558f0ea3..00000000
--- a/src/main/res/drawable-hdpi/ic_mode_edit_white_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-hdpi/ic_notification.png b/src/main/res/drawable-hdpi/ic_notification.png
index 31c0ee1a..fd5d1d8b 100644
--- a/src/main/res/drawable-hdpi/ic_notification.png
+++ b/src/main/res/drawable-hdpi/ic_notification.png
Binary files differ
diff --git a/src/main/res/drawable-hdpi/smiley.png b/src/main/res/drawable-hdpi/smiley.png
new file mode 100644
index 00000000..c841c4c0
--- /dev/null
+++ b/src/main/res/drawable-hdpi/smiley.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_action_keyboard.png b/src/main/res/drawable-mdpi/ic_action_keyboard.png
new file mode 100644
index 00000000..481e4222
--- /dev/null
+++ b/src/main/res/drawable-mdpi/ic_action_keyboard.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_launcher.png b/src/main/res/drawable-mdpi/ic_launcher.png
index 9e076434..f2df046e 100644
--- a/src/main/res/drawable-mdpi/ic_launcher.png
+++ b/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png
deleted file mode 100644
index ebd96073..00000000
--- a/src/main/res/drawable-mdpi/ic_mode_edit_black_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png
deleted file mode 100644
index e23c42db..00000000
--- a/src/main/res/drawable-mdpi/ic_mode_edit_white_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-mdpi/ic_notification.png b/src/main/res/drawable-mdpi/ic_notification.png
index aafc54f5..5cf7593b 100644
--- a/src/main/res/drawable-mdpi/ic_notification.png
+++ b/src/main/res/drawable-mdpi/ic_notification.png
Binary files differ
diff --git a/src/main/res/drawable-mdpi/smiley.png b/src/main/res/drawable-mdpi/smiley.png
new file mode 100644
index 00000000..9f6d6511
--- /dev/null
+++ b/src/main/res/drawable-mdpi/smiley.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_action_keyboard.png b/src/main/res/drawable-xhdpi/ic_action_keyboard.png
new file mode 100644
index 00000000..c1af1a2f
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/ic_action_keyboard.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_launcher.png b/src/main/res/drawable-xhdpi/ic_launcher.png
index c49b2cb1..2c0f1647 100644
--- a/src/main/res/drawable-xhdpi/ic_launcher.png
+++ b/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png
deleted file mode 100644
index b33c964d..00000000
--- a/src/main/res/drawable-xhdpi/ic_mode_edit_black_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png
deleted file mode 100644
index 3ee3e172..00000000
--- a/src/main/res/drawable-xhdpi/ic_mode_edit_white_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/ic_notification.png b/src/main/res/drawable-xhdpi/ic_notification.png
index 042d2cda..76a9414a 100644
--- a/src/main/res/drawable-xhdpi/ic_notification.png
+++ b/src/main/res/drawable-xhdpi/ic_notification.png
Binary files differ
diff --git a/src/main/res/drawable-xhdpi/smiley.png b/src/main/res/drawable-xhdpi/smiley.png
new file mode 100644
index 00000000..b06e0073
--- /dev/null
+++ b/src/main/res/drawable-xhdpi/smiley.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_action_keyboard.png b/src/main/res/drawable-xxhdpi/ic_action_keyboard.png
new file mode 100644
index 00000000..a4668c41
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/ic_action_keyboard.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_launcher.png b/src/main/res/drawable-xxhdpi/ic_launcher.png
index 873d4b14..f8edda90 100644
--- a/src/main/res/drawable-xxhdpi/ic_launcher.png
+++ b/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png
deleted file mode 100644
index 66d25296..00000000
--- a/src/main/res/drawable-xxhdpi/ic_mode_edit_black_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png
deleted file mode 100644
index 9d7f2ff9..00000000
--- a/src/main/res/drawable-xxhdpi/ic_mode_edit_white_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/ic_notification.png b/src/main/res/drawable-xxhdpi/ic_notification.png
index 42c62d32..68f051b3 100644
--- a/src/main/res/drawable-xxhdpi/ic_notification.png
+++ b/src/main/res/drawable-xxhdpi/ic_notification.png
Binary files differ
diff --git a/src/main/res/drawable-xxhdpi/smiley.png b/src/main/res/drawable-xxhdpi/smiley.png
new file mode 100644
index 00000000..2f11d408
--- /dev/null
+++ b/src/main/res/drawable-xxhdpi/smiley.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_launcher.png b/src/main/res/drawable-xxxhdpi/ic_launcher.png
index baadfa78..d9aa6c0e 100644
--- a/src/main/res/drawable-xxxhdpi/ic_launcher.png
+++ b/src/main/res/drawable-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png b/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png
deleted file mode 100644
index 827b6848..00000000
--- a/src/main/res/drawable-xxxhdpi/ic_mode_edit_black_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png b/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png
deleted file mode 100644
index 34ec7092..00000000
--- a/src/main/res/drawable-xxxhdpi/ic_mode_edit_white_18dp.png
+++ /dev/null
Binary files differ
diff --git a/src/main/res/drawable-xxxhdpi/ic_notification.png b/src/main/res/drawable-xxxhdpi/ic_notification.png
index c3439f1a..ba656860 100644
--- a/src/main/res/drawable-xxxhdpi/ic_notification.png
+++ b/src/main/res/drawable-xxxhdpi/ic_notification.png
Binary files differ
diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml
index cac9a9fa..c6569d5a 100644
--- a/src/main/res/layout/account_row.xml
+++ b/src/main/res/layout/account_row.xml
@@ -33,7 +33,7 @@
android:layout_height="wrap_content"
android:scrollHorizontally="false"
android:singleLine="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline" />
<TextView
diff --git a/src/main/res/layout/activity_about.xml b/src/main/res/layout/activity_about.xml
index 247e96e5..8f1113ad 100644
--- a/src/main/res/layout/activity_about.xml
+++ b/src/main/res/layout/activity_about.xml
@@ -15,7 +15,7 @@
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"
android:fontFamily="monospace"/>
diff --git a/src/main/res/layout/activity_change_password.xml b/src/main/res/layout/activity_change_password.xml
index 1a4d00d8..df75e61f 100644
--- a/src/main/res/layout/activity_change_password.xml
+++ b/src/main/res/layout/activity_change_password.xml
@@ -22,7 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/current_password"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
@@ -32,15 +32,15 @@
android:layout_marginBottom="8dp"
android:hint="@string/password"
android:inputType="textPassword"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/new_password"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
@@ -50,15 +50,15 @@
android:layout_marginBottom="8dp"
android:hint="@string/password"
android:inputType="textPassword"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_confirm_password"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
@@ -67,8 +67,8 @@
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout>
</ScrollView>
diff --git a/src/main/res/layout/activity_contact_details.xml b/src/main/res/layout/activity_contact_details.xml
index c35f26bb..598003e3 100644
--- a/src/main/res/layout/activity_contact_details.xml
+++ b/src/main/res/layout/activity_contact_details.xml
@@ -5,7 +5,6 @@
android:background="@color/grey200" >
<LinearLayout
- android:id="@+id/details_main_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
@@ -40,7 +39,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_example_jabber_id"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold" />
@@ -62,7 +61,7 @@
android:id="@+id/details_lastseen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeBody" />
</LinearLayout>
@@ -79,7 +78,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/send_presence_updates"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
<CheckBox
@@ -87,7 +86,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/receive_presence_updates"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
</LinearLayout>
@@ -99,7 +98,7 @@
android:layout_below="@+id/details_jidbox"
android:layout_marginTop="32dp"
android:text="@string/using_account"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo" />
</RelativeLayout>
diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml
index d308b4ce..58e168f3 100644
--- a/src/main/res/layout/activity_edit_account.xml
+++ b/src/main/res/layout/activity_edit_account.xml
@@ -41,13 +41,14 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/avater"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:id="@+id/editAccountBoxes">
<TextView
android:id="@+id/account_jid_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_jabber_id"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<AutoCompleteTextView
@@ -56,8 +57,8 @@
android:layout_height="wrap_content"
android:hint="@string/account_settings_example_jabber_id"
android:inputType="textEmailAddress"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -65,7 +66,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/account_settings_password"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
@@ -74,8 +75,8 @@
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
<LinearLayout
@@ -95,16 +96,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_hostname"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
android:id="@+id/hostname"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
- android:hint="@string/hostname_or_onion"
+ android:hint="@string/hostname_example"
android:inputType="textNoSuggestions"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout>
<LinearLayout
@@ -117,7 +118,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_port"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
android:id="@+id/port"
@@ -125,8 +126,8 @@
android:layout_height="match_parent"
android:inputType="number"
android:maxLength="5"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout>
</LinearLayout>
@@ -136,7 +137,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/register_account"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -144,7 +145,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_confirm_password"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:visibility="gone"/>
@@ -155,11 +156,27 @@
android:layout_marginTop="8dp"
android:hint="@string/confirm_password"
android:inputType="textPassword"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"
android:visibility="gone"/>
</LinearLayout>
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_toRightOf="@+id/avater"
+ android:id="@+id/displayAccountFrame"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/detailsAccountJid"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/account_settings_example_jabber_id"
+ android:textColor="@color/primaryText"
+ android:textSize="?attr/TextSizeHeadline"
+ android:textStyle="bold" />
+ </LinearLayout>
</RelativeLayout>
<RelativeLayout
@@ -179,7 +196,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/battery_optimizations_enabled"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold"/>
<TextView
@@ -190,7 +207,7 @@
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="@string/battery_optimizations_enabled_explained"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<Button
android:id="@+id/batt_op_disable"
@@ -232,7 +249,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_session_established"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -240,7 +257,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -261,7 +278,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_pep"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -269,7 +286,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -282,7 +299,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_blocking"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -290,7 +307,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -303,7 +320,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_stream_management"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -311,7 +328,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -324,7 +341,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_roster_version"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -332,7 +349,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -345,7 +362,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_carbon_messages"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -353,7 +370,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -366,7 +383,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_mam"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -374,7 +391,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -387,7 +404,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_csi"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -395,7 +412,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
tools:ignore="RtlHardcoded"/>
</TableRow>
@@ -408,7 +425,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_push"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -416,7 +433,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
</TableRow>
<TableRow
@@ -427,7 +444,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_http_upload"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<TextView
@@ -435,8 +452,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
- android:textSize="?attr/TextSizeBody"/>
+ android:textColor="@color/primaryText"
+ android:textSize="?attr/TextSizeBody"
+ tools:ignore="RtlHardcoded"/>
</TableRow>
</TableLayout>
@@ -458,7 +476,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"/>
@@ -466,7 +484,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/otr_fingerprint"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
</LinearLayout>
@@ -486,7 +504,8 @@
android:id="@+id/axolotl_fingerprint_box"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginTop="32dp">
+ android:layout_marginTop="32dp"
+ android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
@@ -500,7 +519,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="monospace"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"/>
@@ -508,7 +527,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/this_device_omemo_fingerprint"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
</LinearLayout>
@@ -560,7 +579,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/other_devices"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold"/>
@@ -593,7 +612,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel"
- android:textColor="@color/black87"/>
+ android:textColor="@color/primaryText"/>
<View
android:layout_width="1dp"
@@ -610,7 +629,7 @@
android:layout_weight="1"
android:enabled="false"
android:text="@string/save"
- android:textColor="@color/black54"/>
+ android:textColor="@color/secondaryText"/>
</LinearLayout>
</RelativeLayout>
diff --git a/src/main/res/layout/activity_logcatoutput.xml b/src/main/res/layout/activity_logcatoutput.xml
new file mode 100644
index 00000000..302991b1
--- /dev/null
+++ b/src/main/res/layout/activity_logcatoutput.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/cplus_copy_to_clipboard"
+ android:id="@+id/actLogOutputCopyButton" />
+
+ <ListView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/actLogInfoOutput"
+ android:scrollbars="vertical"
+ android:textSize="12sp"
+ android:textIsSelectable="true"/>
+</LinearLayout> \ 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 e7b11d9f..4c6b0301 100644
--- a/src/main/res/layout/activity_muc_details.xml
+++ b/src/main/res/layout/activity_muc_details.xml
@@ -28,7 +28,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/account_settings_example_jabber_id"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold"/>
@@ -58,7 +58,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"/>
<TextView
@@ -66,7 +66,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout>
@@ -90,7 +90,7 @@
android:layout_height="wrap_content"
android:text="@string/private_conference"
android:layout_centerVertical="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/change_conference_button"
@@ -104,7 +104,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="?android:selectableItemBackground"
- android:padding="@dimen/image_button_padding"
+ android:padding="8dp"
android:src="?attr/icon_settings"/>
</RelativeLayout>
@@ -117,7 +117,7 @@
android:layout_height="wrap_content"
android:text="@string/notify_on_all_messages"
android:layout_centerVertical="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/notification_status_button"
@@ -150,7 +150,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_info_mam"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
<TextView
@@ -158,7 +158,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
</TableRow>
@@ -171,7 +171,7 @@
android:layout_gravity="right"
android:layout_marginTop="32dp"
android:text="@string/using_account"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
</LinearLayout>
diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml
index 8c7c359b..1520b330 100644
--- a/src/main/res/layout/activity_publish_profile_picture.xml
+++ b/src/main/res/layout/activity_publish_profile_picture.xml
@@ -27,7 +27,7 @@
android:layout_below="@id/account_image_wrapper"
android:layout_centerHorizontal="true"
android:text="@string/touch_to_choose_picture"
- android:textColor="@color/black54" />
+ android:textColor="@color/secondaryText" />
<TextView
android:id="@+id/secondary_hint"
@@ -36,7 +36,7 @@
android:layout_below="@id/hint"
android:layout_centerHorizontal="true"
android:text="@string/or_long_press_for_default"
- android:textColor="@color/black54" />
+ android:textColor="@color/secondaryText" />
<LinearLayout
android:id="@+id/button_bar"
@@ -53,7 +53,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel"
- android:textColor="@color/black87" />
+ android:textColor="@color/primaryText" />
<View
android:layout_width="1dp"
@@ -70,7 +70,7 @@
android:layout_weight="1"
android:enabled="false"
android:text="@string/publish"
- android:textColor="@color/black54" />
+ android:textColor="@color/secondaryText" />
</LinearLayout>
<LinearLayout
@@ -89,7 +89,7 @@
android:id="@+id/account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline" />
<TextView
@@ -99,7 +99,7 @@
android:layout_marginTop="8dp"
android:minLines="3"
android:text="@string/publish_avatar_explanation"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
</LinearLayout>
diff --git a/src/main/res/layout/activity_verify_otr.xml b/src/main/res/layout/activity_verify_otr.xml
index c15f19d5..2d5821a4 100644
--- a/src/main/res/layout/activity_verify_otr.xml
+++ b/src/main/res/layout/activity_verify_otr.xml
@@ -34,7 +34,7 @@
android:id="@+id/your_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"
android:fontFamily="monospace"/>
@@ -43,7 +43,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/your_fingerprint"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
<TextView
@@ -51,7 +51,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:typeface="monospace"
android:fontFamily="monospace"/>
@@ -61,7 +61,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="@string/remote_fingerprint"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
</LinearLayout>
@@ -79,7 +79,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/verified"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:textStyle="bold"
android:visibility="gone"/>
@@ -89,7 +89,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"
android:textStyle="bold"
android:visibility="gone"/>
@@ -101,8 +101,8 @@
android:layout_marginBottom="8dp"
android:hint="@string/shared_secret_hint"
android:inputType="textAutoComplete"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
<EditText
@@ -112,8 +112,8 @@
android:layout_marginTop="8dp"
android:hint="@string/shared_secret_secret"
android:inputType="textPassword"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout>
</LinearLayout>
diff --git a/src/main/res/layout/contact.xml b/src/main/res/layout/contact.xml
index 56443c34..faf017d0 100644
--- a/src/main/res/layout/contact.xml
+++ b/src/main/res/layout/contact.xml
@@ -14,6 +14,14 @@
android:scaleType="centerCrop"
android:src="@drawable/ic_profile"
app:riv_corner_radius="2dp" />
+ <TextView
+ android:layout_width="48dp"
+ android:layout_height="4dp"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:id="@+id/contact_status"
+ android:layout_below="@+id/contact_photo"
+ android:paddingTop="8dp"
+ android:paddingRight="8dp"/>
<LinearLayout
android:layout_width="wrap_content"
@@ -28,7 +36,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline" />
<TextView
@@ -36,7 +44,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
<LinearLayout
android:id="@+id/tags"
@@ -49,7 +57,7 @@
android:id="@+id/key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:typeface="monospace"
android:fontFamily="monospace"
diff --git a/src/main/res/layout/contact_key.xml b/src/main/res/layout/contact_key.xml
index a4fd29e9..6d61026d 100644
--- a/src/main/res/layout/contact_key.xml
+++ b/src/main/res/layout/contact_key.xml
@@ -16,7 +16,7 @@
android:id="@+id/key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/tgl_trust"
android:textSize="?attr/TextSizeBody"
@@ -27,7 +27,7 @@
android:id="@+id/key_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:layout_alignParentLeft="true"
android:layout_below="@+id/key"
android:maxLines="1"
@@ -40,7 +40,7 @@
android:layout_alignParentRight="true"
android:layout_below="@+id/key"
android:visibility="gone"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
<ImageButton
diff --git a/src/main/res/layout/conversation_list_row.xml b/src/main/res/layout/conversation_list_row.xml
index cd3f9266..9f040676 100644
--- a/src/main/res/layout/conversation_list_row.xml
+++ b/src/main/res/layout/conversation_list_row.xml
@@ -1,13 +1,18 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:descendantFocusability="blocksDescendants">
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="8dp"
+ android:descendantFocusability="blocksDescendants"
+ android:id="@+id/conversationListRowFrame"
+ android:background="@color/primaryBackground">
<View
android:layout_width="fill_parent"
android:layout_height="match_parent"
- android:background="@color/primary"/>
+ android:background="@color/red500"/>
<FrameLayout
android:id="@+id/swipeable_item"
@@ -20,7 +25,8 @@
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
- android:padding="8dp">
+ android:padding="8dp"
+ android:id="@+id/conversationListRowContent">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/conversation_image"
@@ -30,6 +36,15 @@
android:scaleType="centerCrop"
app:riv_corner_radius="2dp"/>
+ <TextView
+ android:layout_width="56dp"
+ android:layout_height="4dp"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:id="@+id/status"
+ android:layout_below="@+id/conversation_image"
+ android:paddingTop="8dp"
+ android:paddingRight="8dp"/>
+
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@@ -46,7 +61,7 @@
android:paddingRight="4dp"
android:singleLine="true"
android:text="Awesome groupchat"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeHeadline"
android:typeface="sans"/>
@@ -63,21 +78,23 @@
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/notification_status"
android:orientation="vertical">
- <TextView
- android:id="@+id/conversation_lastmsg"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:scrollHorizontally="false"
- android:singleLine="true"
+
+ <github.ankushsachdeva.emojicon.EmojiconTextView
+ android:id="@+id/conversation_lastmsg"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollHorizontally="false"
+ android:singleLine="true"
android:text="This is a placeholder text to show the last messages"
- android:textColor="@color/black87"
- android:textSize="?attr/TextSizeBody"/>
+ android:textColor="@color/primaryText"
+ android:textSize="?attr/TextSizeBody"
+ app:emojiconSize="20sp" />
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/conversation_lastimage"
android:layout_width="fill_parent"
android:layout_height="36dp"
- android:background="@color/black87"
+ android:background="@color/primaryText"
android:scaleType="centerCrop"
android:visibility="gone"
app:riv_corner_radius="2dp"/>
@@ -101,7 +118,7 @@
android:layout_alignParentRight="true"
android:gravity="right"
android:text="23:42"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"/>
</RelativeLayout>
</RelativeLayout>
diff --git a/src/main/res/layout/dialog_message_details.xml b/src/main/res/layout/dialog_message_details.xml
new file mode 100644
index 00000000..84159f44
--- /dev/null
+++ b/src/main/res/layout/dialog_message_details.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp">
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_time_sent"
+ android:id="@+id/dlgMsgDetLblTimeSent" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/dlgMsgDetTimeSent"
+ android:width="200dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:textAlignment="viewEnd"
+ android:gravity="end" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_msg_type"
+ android:id="@+id/dlgMsgDetLblMsgType"
+ android:layout_below="@+id/dlgMsgDetLblTimeSent" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/dlgMsgDetMsgType"
+ android:layout_below="@+id/dlgMsgDetTimeSent"
+ android:layout_alignRight="@+id/dlgMsgDetTimeSent"
+ android:layout_alignEnd="@+id/dlgMsgDetTimeSent"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_msg_status"
+ android:id="@+id/dlgMsgDetLblMsgStatus"
+ android:layout_below="@+id/dlgMsgDetLblMsgType" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/dlgMsgDetMsgStatus"
+ android:layout_below="@+id/dlgMsgDetMsgType"
+ android:layout_alignRight="@+id/dlgMsgDetMsgType"
+ android:layout_alignEnd="@+id/dlgMsgDetMsgType"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_sender_resource"
+ android:id="@+id/dlgMsgDetLblSender"
+ android:layout_below="@+id/dlgMsgDetLblMsgStatus" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/dlgMsgDetSender"
+ android:layout_below="@+id/dlgMsgDetMsgStatus"
+ android:layout_alignRight="@+id/dlgMsgDetMsgStatus"
+ android:layout_alignEnd="@+id/dlgMsgDetMsgStatus"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_receipient_resource"
+ android:id="@+id/dlgMsgDetLblReceipient"
+ android:layout_below="@+id/dlgMsgDetLblSender" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/dlgMsgDetReceipient"
+ android:layout_below="@+id/dlgMsgDetSender"
+ android:layout_alignRight="@+id/dlgMsgDetSender"
+ android:layout_alignEnd="@+id/dlgMsgDetSender" />
+
+ <TableLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/dlgMsgDetLblReceipient"
+ android:layout_marginTop="11dp"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:stretchColumns="1"
+ android:background="@color/primaryText"
+ android:id="@+id/dlgMsgDetFileTable"
+ android:visibility="gone">
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/white"
+ android:layout_marginTop="0.3dp"
+ android:layout_marginLeft="0.3dp"
+ android:layout_marginRight="0.3dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_file_details"
+ android:layout_column="0"/>
+ </TableRow>
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/white"
+ android:layout_marginTop="0.3dp"
+ android:layout_marginLeft="0.3dp"
+ android:layout_marginRight="0.3dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_file_mime" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:textAlignment="textEnd"
+ android:gravity="end"
+ android:id="@+id/dlgMsgDetFileMimeType" />
+ </TableRow>
+
+ <TableRow
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/white"
+ android:layout_marginLeft="0.3dp"
+ android:layout_marginTop="0.3dp"
+ android:layout_marginRight="0.3dp">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dlg_msg_details_file_size" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:textAlignment="textEnd"
+ android:gravity="end"
+ android:id="@+id/dlgMsgDetFileSize"/>
+ </TableRow>
+ </TableLayout>
+ </RelativeLayout>
+
+
+
+</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/layout/dialog_resources_status.xml b/src/main/res/layout/dialog_resources_status.xml
new file mode 100644
index 00000000..4dd511f5
--- /dev/null
+++ b/src/main/res/layout/dialog_resources_status.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="Medium Text"
+ android:id="@+id/dlg_res_stat_resource_name"
+ android:layout_marginLeft="10dp" />
+</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/layout/dialog_userdecision.xml b/src/main/res/layout/dialog_userdecision.xml
new file mode 100644
index 00000000..edeff812
--- /dev/null
+++ b/src/main/res/layout/dialog_userdecision.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:text="Medium Text"
+ android:id="@+id/dlgUserDecQuestion" />
+
+ <CheckBox
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/cplus_remember_userdecision"
+ android:id="@+id/dlgUserDecRemember"
+ android:checked="false" />
+</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/layout/enter_jid_dialog.xml b/src/main/res/layout/enter_jid_dialog.xml
index d4af0dfc..f75f2674 100644
--- a/src/main/res/layout/enter_jid_dialog.xml
+++ b/src/main/res/layout/enter_jid_dialog.xml
@@ -13,7 +13,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/your_account"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<Spinner
@@ -27,7 +27,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/account_settings_jabber_id"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
<AutoCompleteTextView
@@ -36,8 +36,8 @@
android:layout_height="wrap_content"
android:hint="@string/account_settings_example_jabber_id"
android:inputType="textEmailAddress"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody" />
</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml
index db92c05f..d14091fd 100644
--- a/src/main/res/layout/fragment_conversation.xml
+++ b/src/main/res/layout/fragment_conversation.xml
@@ -1,10 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/grey200" >
+ <com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_above="@+id/snackbar"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentTop="true"
+ android:id="@+id/swipe_refresh_container"
+ app:direction="both">
<ListView
android:id="@+id/messages_view"
android:layout_width="fill_parent"
@@ -20,6 +29,7 @@
android:transcriptMode="normal"
tools:listitem="@layout/message_sent">
</ListView>
+ </com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout>
<RelativeLayout
android:id="@+id/textsend"
@@ -33,8 +43,8 @@
android:id="@+id/textinput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/textSendButton"
+ android:layout_toRightOf="@+id/emoji_btn"
android:background="@color/grey50"
android:ems="10"
android:imeOptions="flagNoExtractUi|actionSend"
@@ -45,7 +55,7 @@
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="12dp"
- android:textColor="@color/black87" >
+ android:textColor="@color/primaryText" >
<requestFocus />
</eu.siacs.conversations.ui.EditMessage>
@@ -58,6 +68,15 @@
android:layout_centerVertical="true"
android:background="?android:selectableItemBackground"
android:src="@drawable/ic_send_text_offline" />
+
+ <ImageView
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:id="@+id/emoji_btn"
+ android:layout_centerVertical="true"
+ android:layout_alignParentLeft="true"
+ android:padding="4dp"
+ android:src="@drawable/smiley" />
</RelativeLayout>
<RelativeLayout
diff --git a/src/main/res/layout/join_conference_dialog.xml b/src/main/res/layout/join_conference_dialog.xml
index f7aa3c46..d172142a 100644
--- a/src/main/res/layout/join_conference_dialog.xml
+++ b/src/main/res/layout/join_conference_dialog.xml
@@ -13,7 +13,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/your_account"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
<Spinner
@@ -27,7 +27,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/conference_address"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody" />
<AutoCompleteTextView
@@ -36,8 +36,8 @@
android:layout_height="wrap_content"
android:hint="@string/conference_address_example"
android:inputType="textEmailAddress"
- android:textColor="@color/black87"
- android:textColorHint="@color/black54"
+ android:textColor="@color/primaryText"
+ android:textColorHint="@color/secondaryText"
android:textSize="?attr/TextSizeBody"/>
<CheckBox
@@ -47,7 +47,7 @@
android:layout_marginTop="8dp"
android:checked="true"
android:text="@string/save_as_bookmark"
- android:textColor="@color/black87"
+ android:textColor="@color/primaryText"
android:textSize="?attr/TextSizeBody"/>
</LinearLayout> \ No newline at end of file
diff --git a/src/main/res/layout/list_item_logcatoutput.xml b/src/main/res/layout/list_item_logcatoutput.xml
new file mode 100644
index 00000000..9a31be17
--- /dev/null
+++ b/src/main/res/layout/list_item_logcatoutput.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/listItemLogcatoutputText"
+ android:scrollbars="vertical"
+ android:textSize="12sp"/> \ No newline at end of file
diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml
index 466dd045..93ab095b 100644
--- a/src/main/res/layout/message_received.xml
+++ b/src/main/res/layout/message_received.xml
@@ -25,7 +25,7 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@+id/message_photo"
- android:background="@drawable/message_bubble_received"
+ android:background="@drawable/message_bubble_received_white"
android:minHeight="53dp"
android:layout_marginTop="-2dp"
android:layout_marginRight="-4dp"
@@ -34,6 +34,7 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
+ android:background="@color/primaryBackground"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="2dp">
@@ -48,15 +49,16 @@
android:background="@color/black87"
android:scaleType="centerCrop" />
- <TextView
+ <github.ankushsachdeva.emojicon.EmojiconTextView
android:id="@+id/message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:autoLink="web"
- android:textColorLink="@color/white"
- android:textColor="@color/white"
- android:textColorHighlight="@color/grey800"
- android:textSize="?attr/TextSizeBody" />
+ android:autoLink="web|phone|email"
+ android:textColorLink="@color/primaryText"
+ android:textColor="@color/primaryText"
+ android:textColorHighlight="@color/grey500"
+ android:textSize="?attr/TextSizeBody"
+ app:emojiconSize="28sp" />
<Button
android:id="@+id/download_button"
@@ -93,16 +95,6 @@
android:gravity="center_vertical"
android:src="@drawable/ic_lock_white_18dp" />
- <ImageView
- android:id="@+id/edit_indicator"
- android:layout_width="?attr/TextSizeInfo"
- android:layout_height="?attr/TextSizeInfo"
- android:layout_gravity="center_vertical"
- android:layout_marginRight="4sp"
- android:alpha="0.70"
- android:gravity="center_vertical"
- android:src="@drawable/ic_mode_edit_white_18dp" />
-
<TextView
android:id="@+id/message_time"
android:layout_width="wrap_content"
diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml
index fc68c389..909ae9c7 100644
--- a/src/main/res/layout/message_sent.xml
+++ b/src/main/res/layout/message_sent.xml
@@ -46,18 +46,19 @@
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:adjustViewBounds="true"
- android:background="@color/black87"
+ android:background="@color/primaryText"
android:scaleType="centerCrop" />
- <TextView
+ <github.ankushsachdeva.emojicon.EmojiconTextView
android:id="@+id/message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:autoLink="web"
- android:textColorLink="@color/black87"
- android:textColor="@color/black87"
+ android:autoLink="web|phone|email"
+ android:textColorLink="@color/primaryText"
+ android:textColor="@color/primaryText"
android:textColorHighlight="@color/grey500"
- android:textSize="?attr/TextSizeBody" />
+ android:textSize="?attr/TextSizeBody"
+ app:emojiconSize="28sp"/>
<Button
android:id="@+id/download_button"
@@ -80,7 +81,7 @@
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:text="@string/sending"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo" />
<ImageView
@@ -94,16 +95,6 @@
android:src="@drawable/ic_lock_black_18dp" />
<ImageView
- android:id="@+id/edit_indicator"
- android:layout_width="?attr/TextSizeInfo"
- android:layout_height="?attr/TextSizeInfo"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="4sp"
- android:alpha="0.54"
- android:gravity="center_vertical"
- android:src="@drawable/ic_mode_edit_black_18dp" />
-
- <ImageView
android:id="@+id/indicator_received"
android:layout_width="?attr/TextSizeInfo"
android:layout_height="?attr/TextSizeInfo"
diff --git a/src/main/res/layout/message_status.xml b/src/main/res/layout/message_status.xml
index 93a80d84..aa02e154 100644
--- a/src/main/res/layout/message_status.xml
+++ b/src/main/res/layout/message_status.xml
@@ -9,15 +9,6 @@
android:paddingRight="8dp"
android:paddingTop="5dp">
- <Button
- android:id="@+id/load_more_messages"
- style="?android:attr/borderlessButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/load_more_messages"
- android:textColor="@color/accent" android:layout_centerVertical="true"
- android:layout_centerHorizontal="true"/>
-
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/message_photo"
android:layout_width="32dp"
@@ -39,7 +30,7 @@
android:layout_toEndOf="@+id/message_photo"
android:layout_toRightOf="@+id/message_photo"
android:text="@string/contact_has_read_up_to_this_point"
- android:textColor="@color/black54"
+ android:textColor="@color/secondaryText"
android:textSize="?attr/TextSizeInfo"
android:textStyle="italic"/>
diff --git a/src/main/res/layout/quickedit.xml b/src/main/res/layout/quickedit.xml
index ff6b0413..74e8dcf8 100644
--- a/src/main/res/layout/quickedit.xml
+++ b/src/main/res/layout/quickedit.xml
@@ -11,7 +11,7 @@
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
- android:textColor="@color/black87" >
+ android:textColor="@color/primaryText" >
<requestFocus />
</EditText>
diff --git a/src/main/res/menu/encryption_choices.xml b/src/main/res/menu/encryption_choices.xml
index ab42a206..445c4114 100644
--- a/src/main/res/menu/encryption_choices.xml
+++ b/src/main/res/menu/encryption_choices.xml
@@ -7,7 +7,8 @@
android:title="@string/encryption_choice_unencrypted"/>
<item
android:id="@+id/encryption_choice_axolotl"
- android:title="@string/encryption_choice_omemo"/>
+ android:title="@string/encryption_choice_omemo"
+ android:visible="@bool/omemo_enabled"/>
<item
android:id="@+id/encryption_choice_otr"
android:title="@string/encryption_choice_otr"/>
diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml
index 4096297c..292a4938 100644
--- a/src/main/res/menu/message_context.xml
+++ b/src/main/res/menu/message_context.xml
@@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
-
+ <item
+ android:id="@+id/msg_ctx_mnu_details"
+ android:title="@string/msg_ctx_mnu_details"
+ android:visible="true"/>
<item
android:id="@+id/copy_text"
android:title="@string/copy_text"
android:visible="false"/>
<item
android:id="@+id/retry_decryption"
- android:title="Retry decryption"
- android:visible="false"/>
- <item
- android:id="@+id/correct_message"
- android:title="@string/correct_message"
+ android:title="@string/retry_decryption"
android:visible="false"/>
<item
android:id="@+id/share_with"
diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml
index b7589244..0ff4b8af 100644
--- a/src/main/res/values-bg/strings.xml
+++ b/src/main/res/values-bg/strings.xml
@@ -496,8 +496,6 @@
<string name="download_failed_server_not_found">Неуспешно сваляне: Сървърът не е открит</string>
<string name="download_failed_file_not_found">Неуспешно сваляне: Файлът не е открит</string>
<string name="download_failed_could_not_connect">Неуспешно сваляне: Неуспешна връзка със сървъра</string>
- <string name="pref_use_white_background">Използване на бял фон</string>
- <string name="pref_use_white_background_summary">Показване на получените съобщения с черен текст на бял фон</string>
<string name="account_status_tor_unavailable">Мрежата на Тор е недостъпна</string>
<string name="server_info_broken">Повредено</string>
<string name="pref_presence_settings">Присъствие</string>
@@ -527,11 +525,8 @@
<string name="verified_omemo_key_with_certificate">Ключът за OMEMO беше потвърден със сертификат!</string>
<string name="device_does_not_support_certificates">Устройството Ви не поддържа избраните клиентски сертификати!</string>
<string name="pref_connection_options">Връзка</string>
- <string name="pref_use_tor">Свързване през Тор</string>
- <string name="pref_use_tor_summary">Всички връзки да минават през мрежата на Тор. Изисква Орбот</string>
<string name="account_settings_hostname">Име на сървър</string>
<string name="account_settings_port">Порт</string>
- <string name="hostname_or_onion">Адрес на сървър или .onion</string>
<string name="not_a_valid_port">Това не е правилен номер на порт</string>
<string name="not_valid_hostname">Това не е правилно име на сървър</string>
<string name="connected_accounts">%1$d от %2$d свързани профила</string>
@@ -539,7 +534,6 @@
<item quantity="one">%d съобщение</item>
<item quantity="other">%d съобщения</item>
</plurals>
- <string name="load_more_messages">Зареждане на още съобщения</string>
<string name="shared_file_with_x">Файлът е споделен с %s</string>
<string name="shared_image_with_x">Изображението е споделено с %s</string>
<string name="no_storage_permission">Conversations се нуждае от достъп до външно място за съхранение</string>
@@ -568,8 +562,6 @@
<string name="selection_too_large">Избраната област е твърде голяма</string>
<string name="no_accounts">(Няма активирани профили)</string>
<string name="this_field_is_required">Това поле е задължително</string>
- <string name="correct_message">Поправяне на съобщението</string>
- <string name="send_corrected_message">Изпращане на поправеното съобщение</string>
<string name="no_keys_just_confirm">Вие вече имате доверие на този контакт. Избирайки „готово“, Вие просто потвърждавате, че %s е част от тази беседа.</string>
<string name="select_image_and_crop">Изберете изображение и изрежете</string>
<string name="this_account_is_disabled">Вие сте деактивирали този профил</string>
diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml
index 8979f86f..88d0b049 100644
--- a/src/main/res/values-cs/strings.xml
+++ b/src/main/res/values-cs/strings.xml
@@ -497,8 +497,6 @@
<string name="download_failed_server_not_found">Stahování selhalo: Server nenalezen</string>
<string name="download_failed_file_not_found">Stahování selhalo: Soubor nenalezen</string>
<string name="download_failed_could_not_connect">Stahování selhalo: Nelze se připojit k hostu</string>
- <string name="pref_use_white_background">Použít bílé pozadí</string>
- <string name="pref_use_white_background_summary">Zobrazovat přijaté zprávy jako černý text na bílém pozadí</string>
<string name="account_status_tor_unavailable">Tor síť není dostupná</string>
<string name="server_info_broken">Rozbité</string>
<string name="pref_presence_settings">Stav</string>
@@ -526,11 +524,8 @@
<string name="verified_omemo_key_with_certificate">OMEMO klíč ověřen certifikátem!</string>
<string name="device_does_not_support_certificates">Tento přístroj nepodporuje výběr klientského certifikátu!</string>
<string name="pref_connection_options">Připojení</string>
- <string name="pref_use_tor">Připojit přes Tor</string>
- <string name="pref_use_tor_summary">Vedení všech připojení po Tor síti vyžaduje aplikaci Orbot</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Server- nebo .onion-adresa</string>
<string name="not_a_valid_port">Toto není platné číslo portu</string>
<string name="not_valid_hostname">Toto není platné hostname</string>
<string name="connected_accounts">%1$d z %2$d účtů připojeno</string>
@@ -539,7 +534,6 @@
<item quantity="few">%d zprávy</item>
<item quantity="other">%d zpráv</item>
</plurals>
- <string name="load_more_messages">Načíst více zpráv</string>
<string name="shared_file_with_x">Soubor sdílen s %s</string>
<string name="shared_image_with_x">Obrázek sdílen s %s</string>
<string name="no_storage_permission">Conversations vyžaduje přístup k externímu úložišti</string>
@@ -568,6 +562,4 @@
<string name="selection_too_large">Vybraný obsah je příliš dlouhý</string>
<string name="no_accounts">(Žádné aktivované účty)</string>
<string name="this_field_is_required">Toto pole je vyžadováno</string>
- <string name="correct_message">Opravit zprávu</string>
- <string name="send_corrected_message">Odeslat opravenou zprávu</string>
</resources>
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 454c8791..aff8affd 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -77,7 +77,7 @@
<string name="delete_messages">Nachrichten löschen</string>
<string name="also_end_conversation">Diese Unterhaltung danach beenden</string>
<string name="choose_presence">Ressource des Kontakts auswählen</string>
- <string name="send_unencrypted_message">Unverschlüsselt schreiben…</string>
+ <string name="send_unencrypted_message">Normal schreiben…</string>
<string name="send_otr_message">OTR-verschlüsselt schreiben…</string>
<string name="send_omemo_message">OMEMO-verschlüsselt schreiben…</string>
<string name="send_omemo_x509_message">v\\OMEMO-verschlüsselte Nachricht senden</string>
@@ -99,9 +99,15 @@
<string name="pref_general">Allgemeines</string>
<string name="pref_xmpp_resource">XMPP-Ressource</string>
<string name="pref_xmpp_resource_summary">Der Name, mit dem sich der Client selbst identifiziert</string>
- <string name="pref_accept_files">Dateien annehmen</string>
- <string name="pref_accept_files_summary">Dateien automatisch annehmen, die kleiner sind als …</string>
- <string name="pref_notification_settings">Benachrichtigung</string>
+ <string name="pref_accept_files">Dateiannahme</string>
+ <string name="pref_accept_files_summary">Einstellungen für Dateiannahme und automatischen Download</string>
+ <string name="pref_accept_files_size">Größe</string>
+ <string name="pref_accept_files_size_summary">Dateien, die kleiner sind als …, automatisch annehmen</string>
+ <string name="pref_accept_files_download">nur WLAN</string>
+ <string name="pref_accept_files_download_summary">Automatisches Herunterladen und Akzeptieren von Dateien nur im WLAN</string>
+ <string name="pref_accept_files_download_link">Bilder-Links</string>
+ <string name="pref_accept_files_download_link_summary">Bilder-Links automatisch herunterladen</string>
+ <string name="pref_notification_settings">Benachrichtigungen</string>
<string name="pref_notifications">Benachrichtigungen</string>
<string name="pref_notifications_summary">Benachrichtigen bei Erhalt einer neuen Nachricht</string>
<string name="pref_vibrate">Vibrieren</string>
@@ -115,7 +121,12 @@
<string name="pref_never_send_crash_summary">Wenn du Absturzberichte einschickst, hilfst du Conversations stetig zu verbessern</string>
<string name="pref_confirm_messages">Lese- und Empfangsbestätigung senden</string>
<string name="pref_confirm_messages_summary">Informiere deine Kontakte, wenn du eine Nachricht empfangen und gelesen hast</string>
+ <string name="pref_confirm_messages_none">Keine Bestätigungen</string>
+ <string name="pref_confirm_messages_received">Nur Empfangsbestätigung</string>
+ <string name="pref_confirm_messages_read_and_received">Lese- und Empfangsbestätigung</string>
<string name="pref_ui_options">Benutzeroberfläche</string>
+ <string name="pref_parse_emoticons">Smilies ersetzen</string>
+ <string name="pref_parse_emoticons_summary">Zeige Smilie-Bilder anstelle von Emoticons.</string>
<string name="openpgp_error">Fehler mit OpenKeychain</string>
<string name="error_decrypting_file">Fehler beim Entschlüsseln der Datei</string>
<string name="accept">Annehmen</string>
@@ -401,7 +412,8 @@
<string name="current_password">Aktuelles Passwort</string>
<string name="new_password">Neues Passwort</string>
<string name="password_should_not_be_empty">Das Passwort darf nicht leer sein</string>
- <string name="enable_all_accounts">Alle Konten aktiveren</string>
+ <string name="password_should_not_contain_only_spaces">Das Passwort darf nicht nur aus Leerzeichen bestehen</string>
+ <string name="enable_all_accounts">Alle Konten aktivieren</string>
<string name="disable_all_accounts">Alle Konten abschalten</string>
<string name="perform_action_with">Aktion durchführen mit</string>
<string name="no_affiliation">Keine Zugehörigkeit</string>
@@ -486,6 +498,54 @@
<string name="recently_used">zuletzt verwendet</string>
<string name="choose_quick_action">wähle Schnell-Taste</string>
<string name="search_for_contacts_or_groups">Nach Kontakten oder Konferenzen suchen</string>
+ <string name="pref_led_notification_color">LED-Benachrichtigung Farbe</string>
+ <string name="pref_led_notification_color_summary">Setze die Farbe der LED-Benachrichtigung</string>
+ <string name="msg_ctx_mnu_details">Nachrichtendetails</string>
+ <string name="dlg_msg_details_title">Nachrichtendetails</string>
+ <string name="cplus_ok">Ok</string>
+ <string name="dlg_resources_title">%1$s (%2$d)</string><!-- %s = bare jid, %d = count of online resources -->
+ <string name="dlg_msg_details_receipient_resource">Empfänger Resource:</string>
+ <string name="dlg_msg_details_receipient_nick">Empfänger Nickname:</string>
+ <string name="dlg_msg_details_sender_resource">Absender Resource:</string>
+ <string name="dlg_msg_details_sender_nick">Absender Nickname:</string>
+ <string name="dlg_msg_details_time_sent">Gesendet:</string>
+ <string name="dlg_msg_details_msg_type">Nachrichtentyp:</string>
+ <string name="dlg_msg_details_msg_status">Nachrichtenstatus:</string>
+ <string name="dlg_msg_details_file_details">Dateidetails</string>
+ <string name="dlg_msg_details_file_mime">Mime Type</string>
+ <string name="dlg_msg_details_file_size">Größe</string>
+ <string name="dlg_msg_details_msg_type_text">Text</string>
+ <string name="dlg_msg_details_msg_type_image">Bild</string>
+ <string name="dlg_msg_details_msg_type_file">Datei</string>
+ <string name="dlg_msg_details_msg_type_status">Status</string>
+ <string name="dlg_msg_details_msg_type_private">Privat</string>
+ <string name="dlg_msg_details_msg_status_sent">Gesendet</string>
+ <string name="dlg_msg_details_msg_status_received">Empfangen</string>
+ <string name="dlg_msg_details_msg_status_waiting">Wartend</string>
+ <string name="dlg_msg_details_msg_status_unsend">In Übertragung</string>
+ <string name="dlg_msg_details_msg_status_offered">Angeboten</string>
+ <string name="dlg_msg_details_msg_status_failed">Fehlgeschlagen</string>
+ <string name="pref_resize_picture_ask">nachfragen</string>
+ <string name="pref_resize_picture_always">immer</string>
+ <string name="pref_resize_picture_never">nie</string>
+ <string name="pref_resize_picture_summary">Sollen Bilder vor dem Senden verkleinert werden oder nicht?</string>
+ <string name="pref_resize_picture">Bilder verkleinern</string>
+ <string name="cplus_yes">Ja</string>
+ <string name="cplus_no">Nein</string>
+ <string name="cplus_remember_userdecision">Diese Entscheidung merken</string>
+ <string name="userdecision_question_resize_picture">Soll das Bild verkleinert werden?</string>
+ <string name="title_activity_loginformation">Logausgaben</string>
+ <string name="cplus_copy_to_clipboard">Kopieren</string>
+ <string name="cplus_copied_to_clipboard">In Zwischenablage kopiert</string>
+ <string name="pref_show_logcat_title">Zeige logcat Ausgabe</string>
+ <string name="pref_show_logcat_summary">Zeigt die Ausgabe von logcat an. Hilfreich für die Fehlersuche.</string>
+ <string name="pref_file_transfer">Ordnername, um eingehnde Datein zu speichern</string>
+ <string name="pref_file_transfer_folder_summary">Unterordner des globalen Dateiordners, um eingehende Dateien zu speichern.</string>
+ <string name="pref_img_file_transfer">Ordnername, um eingehende Bilder zu speichern</string>
+ <string name="pref_img_file_transfer_summary">Unterordner des globalen Bilderordners, um eingehende Bilder zu speichern.</string>
+ <string name="pref_file_transfer_category">Dateiübertragung</string>
+ <string name="cplus_not_copied_to_clipboard_empty">Nichts zu kopieren.</string>
+ <string name="cplus_me">Ich</string>
<string name="send_private_message">Private Nachricht senden</string>
<string name="user_has_left_conference">%s hat die Konferenz verlassen!</string>
<string name="username">Benutzername</string>
@@ -496,8 +556,6 @@
<string name="download_failed_server_not_found">Download fehlgeschlagen: Server nicht gefunden</string>
<string name="download_failed_file_not_found">Download fehlgeschlagen: Datei nicht gefunden</string>
<string name="download_failed_could_not_connect">Download fehlgeschlagen: keine Verbindung zum Host</string>
- <string name="pref_use_white_background">Weißen Hintergrund benutzen</string>
- <string name="pref_use_white_background_summary">Empfangene Nachrichten als schwarzen Text auf weißem Hintergrund anzeigen</string>
<string name="account_status_tor_unavailable">Tor-Netzwerk nicht verfügbar</string>
<string name="server_info_broken">Fehlerhaft</string>
<string name="pref_presence_settings">Status</string>
@@ -527,11 +585,8 @@
<string name="verified_omemo_key_with_certificate">OMEMO Schlüssel mit Zertifikat bestätigt!</string>
<string name="device_does_not_support_certificates">Dein Gerät unterstützt das Auswählen von Client-Zertifikaten nicht!</string>
<string name="pref_connection_options">Verbindung</string>
- <string name="pref_use_tor">Über TOR verbinden</string>
- <string name="pref_use_tor_summary">Alle Verbindungen über das Tor-Netzwerk tunneln. Benötigt Orbot</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Server- oder .onion-Adresse</string>
<string name="not_a_valid_port">Dies ist keine gültige Port-Nummer</string>
<string name="not_valid_hostname">Dies ist kein gültiger Hostname</string>
<string name="connected_accounts">%1$d von %2$d Konten verbunden</string>
@@ -539,7 +594,6 @@
<item quantity="one">%d Nachricht</item>
<item quantity="other">%d Nachrichten</item>
</plurals>
- <string name="load_more_messages">Weitere Nachrichten laden</string>
<string name="shared_file_with_x">Datei mit %s geteilt</string>
<string name="shared_image_with_x">Bild mit %s geteilt</string>
<string name="no_storage_permission">Conversations benötigt Zugriff auf externen Speicher</string>
@@ -557,8 +611,6 @@
<string name="notify_only_when_highlighted">Nur benachrichtigen, wenn ich angesprochen werde</string>
<string name="notify_never">Benachrichtigungen deaktiviert</string>
<string name="notify_paused">Benachrichtigungen pausiert</string>
- <string name="pref_picture_compression">Bilder komprimieren</string>
- <string name="pref_picture_compression_summary">Bildgröße anpassen und komprimieren</string>
<string name="always">Immer</string>
<string name="automatically">Automatisch</string>
<string name="battery_optimizations_enabled">Batterieoptimierung aktiv</string>
@@ -568,8 +620,7 @@
<string name="selection_too_large">Der ausgewählte Bereich ist zu groß</string>
<string name="no_accounts">(Keine aktivierten Konten)</string>
<string name="this_field_is_required">Dieses Feld ist erforderlich</string>
- <string name="correct_message">Nachricht korrigieren</string>
- <string name="send_corrected_message">Korrigierte Nachricht senden</string>
+ <string name="retry_decryption">Entschlüsselung nochmal versuchen</string>
<string name="no_keys_just_confirm">Du vertraust diesem Kontakt bereits. In dem du \'Fertig\' auswählst bestätigst Du, dass %s Teil dieser Konferenz ist.</string>
<string name="select_image_and_crop">Bild auswählen und zuschneiden</string>
<string name="this_account_is_disabled">Du hast diesen Account deaktiviert</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index f630f7ca..fb088efc 100644
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -496,8 +496,6 @@
<string name="download_failed_server_not_found">Error al descargar: Servidor no encontrado</string>
<string name="download_failed_file_not_found">Error al descargar: Archivo no encontrado</string>
<string name="download_failed_could_not_connect">Error al descargar: No se ha podido conectar con el servidor</string>
- <string name="pref_use_white_background">Usar fondo blanco</string>
- <string name="pref_use_white_background_summary">Mostrar mensajes recibidos en texto negro con fondo blanco</string>
<string name="account_status_tor_unavailable">Red Tor no disponible.</string>
<string name="server_info_broken">Error</string>
<string name="pref_presence_settings">Presencia</string>
@@ -527,11 +525,8 @@
<string name="verified_omemo_key_with_certificate">¡Clave OMEMO con certificado verificada!</string>
<string name="device_does_not_support_certificates">¡Tu dispositivo no soporta la elección de certificados de cliente!</string>
<string name="pref_connection_options">Conexión</string>
- <string name="pref_use_tor">Conectar via Tor</string>
- <string name="pref_use_tor_summary">Todas las conexiones se realizan a través de la red TOR. Requiere Orbot</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_port">Puerto</string>
- <string name="hostname_or_onion">Server- or .onion-Address</string>
<string name="not_a_valid_port">Éste no es un número de puerto válido</string>
<string name="not_valid_hostname">Éste no es un hostame válido</string>
<string name="connected_accounts">%1$d de %2$d cuentas conectadas</string>
@@ -539,7 +534,6 @@
<item quantity="one">%d mensaje</item>
<item quantity="other">%d mensajes</item>
</plurals>
- <string name="load_more_messages">Cargar más mensajes</string>
<string name="shared_file_with_x">Archivo compartido con %s</string>
<string name="shared_image_with_x">Imagen compartida con %s</string>
<string name="no_storage_permission">Conversations necesita acceder al almacenamiento externo</string>
@@ -557,8 +551,6 @@
<string name="notify_only_when_highlighted">Notificar solo cuando se recibe un mensjae resaltado</string>
<string name="notify_never">Notificaciones deshabilitadas</string>
<string name="notify_paused">Notificaciones pausadas</string>
- <string name="pref_picture_compression">Comprimir imágenes</string>
- <string name="pref_picture_compression_summary">Redimensionar y comprimir las imágenes</string>
<string name="always">Siempre</string>
<string name="automatically">Automáticamente</string>
<string name="battery_optimizations_enabled">Optimizaciones de uso de batería habilitadas</string>
@@ -568,8 +560,6 @@
<string name="selection_too_large">El área seleccionada es demasiado grande</string>
<string name="no_accounts">(No hay cuentas activas)</string>
<string name="this_field_is_required">Este campo es requerido</string>
- <string name="correct_message">Corregir mensaje</string>
- <string name="send_corrected_message">Enviar mensaje corregido</string>
<string name="no_keys_just_confirm">Ya confías en este contacto. Seleccionando \'hecho\', estás confirmando que %s es parte de esta conversación.</string>
<string name="select_image_and_crop">Seleccionar imagen y recortar</string>
<string name="this_account_is_disabled">Has deshabilitado esta cuenta</string>
diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml
index 27bbf42f..f04fd284 100644
--- a/src/main/res/values-eu/strings.xml
+++ b/src/main/res/values-eu/strings.xml
@@ -495,8 +495,6 @@
<string name="download_failed_server_not_found">Deskargak huts egin du: zerbitzaria ez da aurkitu</string>
<string name="download_failed_file_not_found">Deskargak huts egin du: fitxategia ez da aurkitu</string>
<string name="download_failed_could_not_connect">Deskargak huts egin du: ezin izan da ostalarira konektatu</string>
- <string name="pref_use_white_background">Atzeko-planoan kolore zuria erabili</string>
- <string name="pref_use_white_background_summary">Jasotako mezuak testu beltza atzeko-plano zuri baten gainean bezala erakutsi</string>
<string name="account_status_tor_unavailable">Tor sarea ez dago eskuragarri</string>
<string name="server_info_broken">Hondatuta</string>
<string name="pref_presence_settings">Presentzia</string>
@@ -524,11 +522,8 @@
<string name="verified_omemo_key_with_certificate">OMEMO gakoa ziurtagiriarekin egiaztatuta!</string>
<string name="device_does_not_support_certificates">Zure gailuak ez du bezero ziurtagiriak aukeratzea onartzen!</string>
<string name="pref_connection_options">Konexioa</string>
- <string name="pref_use_tor">Tor bidez konektatu</string>
- <string name="pref_use_tor_summary">Konexio guztiak Tor sarean zehar igaro. Orbot behar du</string>
<string name="account_settings_hostname">Ostalariaren izena</string>
<string name="account_settings_port">Ataka</string>
- <string name="hostname_or_onion">Zerbitzari- edo .onion-helbidea</string>
<string name="not_a_valid_port">Hau ez da ataka zenbaki balioduna</string>
<string name="not_valid_hostname">Hau ez da ostalari izen balioduna</string>
<string name="connected_accounts">%2$dtik %1$d kontu konektatuta</string>
@@ -536,7 +531,6 @@
<item quantity="one">mezu %d</item>
<item quantity="other">%d mezu</item>
</plurals>
- <string name="load_more_messages">Mezu gehiago kargatu</string>
<string name="shared_file_with_x">Fitxategia %s(r)ekin partekatu da</string>
<string name="shared_image_with_x">Irudia %s(r)ekin partekatu da</string>
<string name="no_storage_permission">Conversationsek kanpoko biltegirako sarbidea behar du</string>
@@ -554,8 +548,6 @@
<string name="notify_only_when_highlighted">Jakinarazi nabarmentzerakoan soilik</string>
<string name="notify_never">Jakinarazpenak ezgaituta</string>
<string name="notify_paused">Jakinarazpenak gelditu dira</string>
- <string name="pref_picture_compression">Irudiak konprimitu</string>
- <string name="pref_picture_compression_summary">Irudiak konprimitu eta neurriz aldatu</string>
<string name="always">Beti</string>
<string name="automatically">Automatikoki</string>
<string name="battery_optimizations_enabled">Bateriaren optimizazioak gaituta</string>
@@ -565,6 +557,4 @@
<string name="selection_too_large">Hautatutako zatia handiegia da</string>
<string name="no_accounts">(Ez dago kontu aktiborik)</string>
<string name="this_field_is_required">Datu hau beharrezkoa da</string>
- <string name="correct_message">Mezua zuzendu</string>
- <string name="send_corrected_message">Mezu zuzendua bidali</string>
</resources>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index 488a4b58..d93a0224 100644
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -496,8 +496,6 @@
<string name="download_failed_server_not_found">Échec du téléchargement : impossible de trouver le serveur</string>
<string name="download_failed_file_not_found">Échec du téléchargement : impossible de trouver le fichier</string>
<string name="download_failed_could_not_connect">Échec du téléchargement : impossible de se connecter à l\'hôte</string>
- <string name="pref_use_white_background">Utiliser un fond blanc</string>
- <string name="pref_use_white_background_summary">Afficher les messages reçus en texte noir sur fond blanc.</string>
<string name="account_status_tor_unavailable">Réseau Tor inaccessible</string>
<string name="server_info_broken">Détraqué</string>
<string name="pref_presence_settings">Présence</string>
@@ -527,11 +525,8 @@
<string name="verified_omemo_key_with_certificate">Clef OMEMO vérifiée avec un certificat !</string>
<string name="device_does_not_support_certificates">Votre appareil ne supporte pas la sélection de certificats client !</string>
<string name="pref_connection_options">Connexion</string>
- <string name="pref_use_tor">Connection via Tor</string>
- <string name="pref_use_tor_summary">Rediriger toutes les connexions via le réseau Tor. Nécessite Orbot.</string>
<string name="account_settings_hostname">Nom d\'hôte</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Adresse du serveur ou .onion</string>
<string name="not_a_valid_port">Ce numéro de port n\'est pas valide</string>
<string name="not_valid_hostname">Ce nom d\'hôte n\'est pas valide</string>
<string name="connected_accounts">%1$d compte(s) sur %2$d connecté(s)</string>
@@ -539,7 +534,6 @@
<item quantity="one">%d message</item>
<item quantity="other">%d messages</item>
</plurals>
- <string name="load_more_messages">Charger plus de messages</string>
<string name="shared_file_with_x">Fichier partagé avec %s</string>
<string name="shared_image_with_x">Image partagée avec %s</string>
<string name="no_storage_permission">Conversations a besoin d\'accéder au stockage externe</string>
@@ -557,8 +551,6 @@
<string name="notify_only_when_highlighted">Notifier uniquement quand surligné</string>
<string name="notify_never">Notifications désactivées</string>
<string name="notify_paused">Notifications en pause</string>
- <string name="pref_picture_compression">Compresser images</string>
- <string name="pref_picture_compression_summary">Redimensionner et compresser les images</string>
<string name="always">Toujours</string>
<string name="automatically">Automatiquement</string>
<string name="battery_optimizations_enabled">Optimisations de batterie activées</string>
@@ -568,8 +560,6 @@
<string name="selection_too_large">La zone sélectionnée est trop grande</string>
<string name="no_accounts">(Aucun compte activé)</string>
<string name="this_field_is_required">Ce champ est requis</string>
- <string name="correct_message">Corriger le message</string>
- <string name="send_corrected_message">Envoyer le message corrigé</string>
<string name="no_keys_just_confirm">Vous faites déjà confiance à ce contact. En sélectionnant « Terminé » vous confirmerez seulement que %s est membre de cette conférence.</string>
<string name="select_image_and_crop">Sélectionner et découper une image</string>
<string name="this_account_is_disabled">Vous avez désactivé ce compte</string>
diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml
index 0a414b9e..3abda855 100644
--- a/src/main/res/values-gl/strings.xml
+++ b/src/main/res/values-gl/strings.xml
@@ -57,6 +57,8 @@
<string name="pref_xmpp_resource">Recurso</string>
<string name="pref_xmpp_resource_summary">O nome que identifica o cliente que estás a empregar</string>
<string name="pref_accept_files">Aceptar arquivos</string>
+ <string name="pref_accept_files_size_summary">De forma automática aceptar arquivos menores de…</string>
+ <string name="pref_notification_settings">Axustes de notificación</string>
<string name="pref_accept_files_summary">De forma automática aceptar arquivos menores de…</string>
<string name="pref_notifications">Notificacións</string>
<string name="pref_notifications_summary">Notifica cuando chega unha nova mensaxe</string>
diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml
index a512575b..b7b5f160 100644
--- a/src/main/res/values-it/strings.xml
+++ b/src/main/res/values-it/strings.xml
@@ -277,8 +277,6 @@
<string name="pref_security_settings">Sicurezza</string>
<string name="pref_force_encryption">Forza cifratura end-to-end</string>
<string name="pref_force_encryption_summary">Manda sempre messaggi cifrati (ad eccezione delle conferenze)</string>
- <string name="pref_allow_message_correction">Permetti correzione del messaggio</string>
- <string name="pref_allow_message_correction_summary">Consenti ai tuoi contatti di modificare retroattivamente i loro messaggi</string>
<string name="pref_dont_save_encrypted">Non salvare i messaggi cifrati</string>
<string name="pref_dont_save_encrypted_summary">Attenzione: Questo potrebbe comportare la perdita di messaggi</string>
<string name="pref_expert_options">Impostazioni esperto</string>
@@ -495,8 +493,6 @@
<string name="download_failed_server_not_found">Download fallito: Server non trovato</string>
<string name="download_failed_file_not_found">Download fallito: File non trovato</string>
<string name="download_failed_could_not_connect">Download fallito: Impossibile connettersi all\'host</string>
- <string name="pref_use_white_background">Usa sfondo bianco</string>
- <string name="pref_use_white_background_summary">Mostra i messaggi ricevuti con testo nero su sfondo bianco</string>
<string name="account_status_tor_unavailable">Rete Tor non disponibile</string>
<string name="server_info_broken">Rotto</string>
<string name="pref_presence_settings">Presenza</string>
@@ -524,11 +520,8 @@
<string name="verified_omemo_key_with_certificate">Chiave OMEMO verificata con certificato!</string>
<string name="device_does_not_support_certificates">Il tuo dispositivo non supporta la selezione di certificati utente!</string>
<string name="pref_connection_options">Connessione</string>
- <string name="pref_use_tor">Connettiti via Tor</string>
- <string name="pref_use_tor_summary">Indirizza tutte le connessioni attraverso la rete Tor. Richiede Orbot</string>
<string name="account_settings_hostname">Nome host</string>
<string name="account_settings_port">Porta</string>
- <string name="hostname_or_onion">Indirizzo server o .onion</string>
<string name="not_a_valid_port">Questo non è un numero di porta valido</string>
<string name="not_valid_hostname">Questo non è un nome host valido</string>
<string name="connected_accounts">%1$d su %2$d account connessi</string>
@@ -536,7 +529,6 @@
<item quantity="one">%d messaggio</item>
<item quantity="other">%d messaggi</item>
</plurals>
- <string name="load_more_messages">Carica altri messaggi</string>
<string name="shared_file_with_x">Condividi file con %s</string>
<string name="shared_image_with_x">Immagine condivisa con %s</string>
<string name="no_storage_permission">Conversations necessita di accesso alla memoria esterna</string>
@@ -554,8 +546,6 @@
<string name="notify_only_when_highlighted">Notifica solo quando evidenziato</string>
<string name="notify_never">Notifiche disabilitate</string>
<string name="notify_paused">Notifiche in pausa</string>
- <string name="pref_picture_compression">Comprimi immagini</string>
- <string name="pref_picture_compression_summary">Ridimensiona e comprimi immagini</string>
<string name="always">Sempre</string>
<string name="automatically">Automaticamente</string>
<string name="battery_optimizations_enabled">Ottimizzazioni batteria abilitate</string>
@@ -565,6 +555,4 @@
<string name="selection_too_large">L\'area selezionata è troppo grande</string>
<string name="no_accounts">(Nessun account attivo)</string>
<string name="this_field_is_required">Questo campo è obbligatorio</string>
- <string name="correct_message">Correggi messaggio</string>
- <string name="send_corrected_message">Invia messaggio corretto</string>
</resources>
diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml
index 5b640ff2..e3732fc7 100644
--- a/src/main/res/values-iw/strings.xml
+++ b/src/main/res/values-iw/strings.xml
@@ -466,8 +466,6 @@
<string name="download_failed_server_not_found">ההורדה נכשלה: שרת לא נמצא</string>
<string name="download_failed_file_not_found">ההורדה נכשלה: הקובץ לא נמצא</string>
<string name="download_failed_could_not_connect">ההורדה נכשלה: נכשל ביצוע חיבור לשרת</string>
- <string name="pref_use_white_background">השתמש ברקע לבן</string>
- <string name="pref_use_white_background_summary">הראה הודעות שהתקבלו בטקסט שחור על גבי רקע לבען</string>
<string name="server_info_broken">לא עובד</string>
<string name="pref_away_when_screen_off">העבר למצב \"לא נמצא\" כאשר המסך כבוי</string>
<string name="pref_away_when_screen_off_summary">מעביר את המכשיר לסטטוס \"לא נמצא\" כאשר המסך כבוי</string>
@@ -482,10 +480,8 @@
<string name="jid_does_not_match_certificate">אין התאמה בין מזהה Jabber לבין תעודה</string>
<string name="action_renew_certificate">חידוש תעודה</string>
<string name="error_fetching_omemo_key">שגיאה בתפיסת OMEMO!</string>
- <string name="pref_use_tor">התחבר דרך Tor</string>
<string name="account_settings_hostname">שם מארח</string>
<string name="account_settings_port">פורט</string>
- <string name="hostname_or_onion">שרת- או כתובת onion.</string>
<string name="not_a_valid_port">זהו אינו מספר פורט תקין</string>
<string name="not_valid_hostname">זהו אינו שם מארח תקין</string>
<string name="connected_accounts">%1$d מתוך %2$d חשבונות מחוברים</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index 54611dc5..c20bd099 100644
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -493,8 +493,6 @@
<string name="download_failed_server_not_found">ダウンロードに失敗しました: サーバーが見つかりません</string>
<string name="download_failed_file_not_found">ダウンロードに失敗しました: ファイルが見つかりません</string>
<string name="download_failed_could_not_connect">ダウンロードに失敗しました: ホストに接続できませんでした</string>
- <string name="pref_use_white_background">白い背景を使用する</string>
- <string name="pref_use_white_background_summary">白地に黒の文字で、受け取ったメッセージを表示します</string>
<string name="account_status_tor_unavailable">Tor ネットワークが利用できません</string>
<string name="server_info_broken">壊れています</string>
<string name="pref_presence_settings">参加</string>
@@ -522,18 +520,14 @@
<string name="verified_omemo_key_with_certificate">OMEMO 鍵の取得中にエラー!</string>
<string name="device_does_not_support_certificates">お使いのデバイスはクライアント証明書の選択をサポートしていません!</string>
<string name="pref_connection_options">接続</string>
- <string name="pref_use_tor">Tor 経由で接続</string>
- <string name="pref_use_tor_summary">Tor ネットワークを介してすべての接続をトンネルします。 Orbot が必要です</string>
<string name="account_settings_hostname">ホスト名</string>
<string name="account_settings_port">ポート</string>
- <string name="hostname_or_onion">サーバーまたは .onion アドレス</string>
<string name="not_a_valid_port">これは有効なポート番号ではありません</string>
<string name="not_valid_hostname">これは有効なホスト名ではありません</string>
<string name="connected_accounts">%1$d / %2$d アカウントが接続しました</string>
<plurals name="x_messages">
<item quantity="other">%d メッセージ</item>
</plurals>
- <string name="load_more_messages">さらにメッセージをロード</string>
<string name="shared_file_with_x">%s でファイルを共有</string>
<string name="shared_image_with_x">%s で画像を共有</string>
<string name="no_storage_permission">Conversations は外部ストレージにアクセスが必要です</string>
@@ -551,8 +545,6 @@
<string name="notify_only_when_highlighted">ハイライトされたときにのみ通知</string>
<string name="notify_never">通知は無効</string>
<string name="notify_paused">通知は一時停止</string>
- <string name="pref_picture_compression">写真を圧縮</string>
- <string name="pref_picture_compression_summary">写真のサイズ変更と圧縮</string>
<string name="always">常に</string>
<string name="automatically">自動</string>
<string name="battery_optimizations_enabled">バッテリー最適化が有効</string>
@@ -562,6 +554,4 @@
<string name="selection_too_large">選択した範囲が大きすぎます</string>
<string name="no_accounts">(アクティベートしたアカウントはありません)</string>
<string name="this_field_is_required">このフィールドは必須です</string>
- <string name="correct_message">メッセージを修正</string>
- <string name="send_corrected_message">修正したメッセージを送信</string>
</resources>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index 311d3fdd..14f4493d 100644
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -496,11 +496,8 @@
<string name="error_fetching_omemo_key">OMEMO key를 가져오는 도중 오류가 발생했습니다</string>
<string name="verified_omemo_key_with_certificate">OMEMO 키와 인증서 검증됨</string>
<string name="device_does_not_support_certificates">기기가 선택된 클라이언트 인증서를 지원하지 않습니다</string>
- <string name="pref_use_tor">Tor를 통해 접속</string>
- <string name="pref_use_tor_summary">모든 연결을 Tor 네트워크를 통하도록 유도함. Orbot이 필요합니다</string>
<string name="account_settings_hostname">호스트 이름</string>
<string name="account_settings_port">포트</string>
- <string name="hostname_or_onion">서버 또는 .onion 주소</string>
<string name="not_a_valid_port">올바른 포트 번호가 아닙니다</string>
<string name="not_valid_hostname">올바른 호스트 이름이 아닙니다</string>
<string name="connected_accounts">%2$d 중 %1$d 계정이 연결되었습니다</string>
diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml
index b78c1fba..623013e1 100644
--- a/src/main/res/values-nb-rNO/strings.xml
+++ b/src/main/res/values-nb-rNO/strings.xml
@@ -101,6 +101,7 @@
<string name="pref_xmpp_resource_summary">Navnet denne klienten identifiserer seg med</string>
<string name="pref_accept_files">Godta filer</string>
<string name="pref_accept_files_summary">Automatisk godkjenning av filer mindre enn...</string>
+ <string name="pref_notification_settings">Varslingsinnstillinger</string>
<string name="pref_notifications">Varslinger</string>
<string name="pref_notifications_summary">Varsle når en ny melding ankommer</string>
<string name="pref_vibrate">Vibrer</string>
@@ -109,10 +110,12 @@
<string name="pref_sound_summary">Spill av ringetone ved varsel</string>
<string name="pref_notification_grace_period">Stilleperiode</string>
<string name="pref_notification_grace_period_summary">Deaktiver varslinger for en kort periode etter at en kopi er mottatt</string>
+ <string name="pref_advanced_options">Avanserte valg</string>
<string name="pref_never_send_crash">Aldri send feilrettingsrapporter</string>
<string name="pref_never_send_crash_summary">Ved å sende inn stabelsporinger hjelper du den pågående utviklingen av Conversations</string>
<string name="pref_confirm_messages">Bekreft meldinger</string>
<string name="pref_confirm_messages_summary">La din kontakt få vite når du har mottatt og lest en melding</string>
+ <string name="pref_ui_options">Valg for grensesnitt</string>
<string name="openpgp_error">Feilmelding fra OpenKeychain</string>
<string name="error_decrypting_file">I/O-feil ved dekryptering av fil</string>
<string name="accept">Godta</string>
@@ -269,10 +272,12 @@
<string name="sure_delete_fingerprint">Bekreft fjerning av fingeravtrykk.</string>
<string name="ignore">Ignorer</string>
<string name="without_mutual_presence_updates"><b>Advarsel:</b> Å sende dette uten at tilstandsoppdateringer er i overenstemmelse kan forårsake uventede problemer.\n\n<small>Gå til kontaktdetaljer for å bekrefte dine tilstedeværelsesabonnementer.</small></string>
+ <string name="pref_encryption_settings">Krypteringsinnstillinger</string>
<string name="pref_force_encryption">Krev ende-til-ende-kryptering</string>
<string name="pref_force_encryption_summary">Alltid send meldinger kryptert (bortsett fra konferanser)</string>
<string name="pref_dont_save_encrypted">Ikke lagre krypterte meldinger</string>
<string name="pref_dont_save_encrypted_summary">Advarsel: Dette kan føre til at meldinger går tapt</string>
+ <string name="pref_expert_options">Ekspertinnstillinger</string>
<string name="pref_expert_options_summary">Vær forsiktig med disse</string>
<string name="title_activity_about">Om Conversations</string>
<string name="pref_about_conversations_summary">Utgave og lisensinformasjon</string>
@@ -420,6 +425,7 @@
<string name="two_hours">2 timer</string>
<string name="eight_hours">8 timer</string>
<string name="until_further_notice">Til videre beskjed</string>
+ <string name="pref_input_options">Inndata-valg</string>
<string name="pref_enter_is_send">Enter er forsendelsesknapp</string>
<string name="pref_enter_is_send_summary">Bruk enter for å sende en melding</string>
<string name="pref_display_enter_key">Vis enter-tast</string>
@@ -479,10 +485,9 @@
<string name="download_failed_server_not_found">Nedlasting feilet: Fant ikke tjener</string>
<string name="download_failed_file_not_found">Nedlasting feilet: Fant ikke fila</string>
<string name="download_failed_could_not_connect">Nedlasting feilet: Kunne ikke koble til tjeneren</string>
- <string name="pref_use_white_background">Bruk hvit bakgrunn</string>
- <string name="pref_use_white_background_summary">Vis mottatte meldinger som svart tekst på hvit bakgrunn</string>
<string name="account_status_tor_unavailable">Tor-nettverk utilgjengelig</string>
<string name="server_info_broken">Knekt</string>
+ <string name="pref_presence_settings">Tilstedeværelse-innstillinger</string>
<string name="pref_away_when_screen_off">Borte når skjermen er av</string>
<string name="pref_away_when_screen_off_summary">Markerer din ressurs som borte når skjermen er avskrudd</string>
<string name="pref_xa_on_silent_mode">Ikke tilgjengelig i stille-modus</string>
@@ -499,11 +504,9 @@
<string name="error_fetching_omemo_key">Feil ved innhenting av OMEMO-nøkkel!</string>
<string name="verified_omemo_key_with_certificate">Bekreftet OMEMO-nøkkel med sertifikat!</string>
<string name="device_does_not_support_certificates">Din enhet støtter ikke valg av klientsertifikat!</string>
- <string name="pref_use_tor">Koble til via Tor</string>
- <string name="pref_use_tor_summary">Send alle tilkoblinger i tunnel gjennom Tor-nettverket. Krever Orbot</string>
+ <string name="pref_connection_options">Tilkoblingsalternativ</string>
<string name="account_settings_hostname">Tjenernavn</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Tjener- eller .onion-adresse</string>
<string name="not_a_valid_port">Dette er ikke et gyldig portnummer</string>
<string name="not_valid_hostname">Dette er ikke et gyldig tjenernavn</string>
<string name="connected_accounts">%1$d av %2$d kontoer tilkoblet</string>
@@ -521,13 +524,13 @@
<string name="certificate_issuer">Utsteder</string>
<string name="certificate_cn">Vanlig navn</string>
<string name="certificate_o">Organisasjon</string>
+ <string name="certificate_sha1">SHA1</string>
<string name="certicate_info_not_available">(Ikke tilgjengelig)</string>
<string name="certificate_not_found">Fant ikke noe sertifikat</string>
<string name="notify_on_all_messages">Varsle ved alle meldinger</string>
<string name="notify_only_when_highlighted">Varsle bare når fremhevet</string>
<string name="notify_never">Varslinger deaktivert</string>
<string name="notify_paused">Varslinger pauset</string>
- <string name="pref_picture_compression">Komprimer bilder</string>
<string name="always">Alltid</string>
<string name="automatically">Automatisk</string>
<string name="battery_optimizations_enabled">Batterioptimaliseringer aktivert</string>
diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml
index 9431e8cf..fb154115 100644
--- a/src/main/res/values-nl/strings.xml
+++ b/src/main/res/values-nl/strings.xml
@@ -496,8 +496,6 @@
<string name="download_failed_server_not_found">Downloaden mislukt: server niet gevonden</string>
<string name="download_failed_file_not_found">Downloaden mislukt: bestand niet gevonden</string>
<string name="download_failed_could_not_connect">Downloaden mislukt: kon geen verbinding maken met host</string>
- <string name="pref_use_white_background">Gebruik witte achtergrond</string>
- <string name="pref_use_white_background_summary">Toon ontvangen berichten als zwarte tekst op een witte achtergrond</string>
<string name="account_status_tor_unavailable">Tor-netwerk niet beschikbaar</string>
<string name="server_info_broken">Gebroken</string>
<string name="pref_presence_settings">Aanwezigheid</string>
@@ -527,11 +525,8 @@
<string name="verified_omemo_key_with_certificate">OMEMO-sleutel geverifieerd met certificaat!</string>
<string name="device_does_not_support_certificates">Je apparaat ondersteunt de selectie van cliënt-certificaten niet!</string>
<string name="pref_connection_options">Verbinding</string>
- <string name="pref_use_tor">Verbinden via Tor</string>
- <string name="pref_use_tor_summary">Tunnel alle verbindingen door het Tor-netwerk. Vereist Orbot</string>
<string name="account_settings_hostname">Hostnaam</string>
<string name="account_settings_port">Poort</string>
- <string name="hostname_or_onion">Server- of .onion-adres</string>
<string name="not_a_valid_port">Dit is geen geldig poortnummer</string>
<string name="not_valid_hostname">Dit is geen geldige hostnaam</string>
<string name="connected_accounts">%1$d van %2$d accounts verbonden</string>
@@ -539,7 +534,6 @@
<item quantity="one">%d bericht</item>
<item quantity="other">%d berichten</item>
</plurals>
- <string name="load_more_messages">Laad meer berichten</string>
<string name="shared_file_with_x">Bestand gedeeld met %s</string>
<string name="shared_image_with_x">Afbeelding gedeeld met %s</string>
<string name="no_storage_permission">Conversations heeft toegang nodig tot de externe opslag</string>
@@ -557,8 +551,6 @@
<string name="notify_only_when_highlighted">Melding enkel wanneer vermeld</string>
<string name="notify_never">Meldingen uitgeschakeld</string>
<string name="notify_paused">Meldingen gepauzeerd</string>
- <string name="pref_picture_compression">Afbeeldingen comprimeren</string>
- <string name="pref_picture_compression_summary">Verklein en comprimeer afbeeldingen</string>
<string name="always">Altijd</string>
<string name="automatically">Automatisch</string>
<string name="battery_optimizations_enabled">Batterij-optimalisaties ingeschakeld</string>
@@ -568,8 +560,6 @@
<string name="selection_too_large">Het gekozen vlak is te groot</string>
<string name="no_accounts">(Geen actieve accounts)</string>
<string name="this_field_is_required">Dit veld is vereist</string>
- <string name="correct_message">Bericht verbeteren</string>
- <string name="send_corrected_message">Verbeterd bericht sturen</string>
<string name="no_keys_just_confirm">Je vertrouwt dit contact al. Door \'klaar\' te kiezen bevestig je enkel dat %s deel uitmaakt van dit groepsgesprek.</string>
<string name="select_image_and_crop">Afbeelding kiezen en bijsnijden</string>
<string name="this_account_is_disabled">Je hebt deze account uitgeschakeld</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 15bd0955..c5c25605 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -277,8 +277,6 @@
<string name="pref_security_settings">Bezpieczeństwo</string>
<string name="pref_force_encryption">Wymuszaj szyfrowanie typu end-to-end</string>
<string name="pref_force_encryption_summary">Szyfruj wszystkie wiadomości (poza konferencjami)</string>
- <string name="pref_allow_message_correction">Pozwól na poprawianie wiadomości</string>
- <string name="pref_allow_message_correction_summary">Pozwól swoim kontaktom poprawiać wiadomości</string>
<string name="pref_dont_save_encrypted">Nie zapisuj zaszyfrowanych wiadomości</string>
<string name="pref_dont_save_encrypted_summary">Uwaga: Może powodować utratę wiadomości</string>
<string name="pref_expert_options">Ustawienia zaawansowane</string>
@@ -496,8 +494,6 @@
<string name="download_failed_server_not_found">Pobieranie nieudane: Nie odnaleziono serwera</string>
<string name="download_failed_file_not_found">Pobieranie nieudane: Nie odnaleziono pliku</string>
<string name="download_failed_could_not_connect">Pobieranie nieudane: Nie można połączyć z hostem</string>
- <string name="pref_use_white_background">Białe tło</string>
- <string name="pref_use_white_background_summary">Pokazuj otrzymane wiadomości jako czarny tekst na białym tle</string>
<string name="account_status_tor_unavailable">Sieć TOR jest niedostepna</string>
<string name="server_info_broken">Zepsute</string>
<string name="pref_presence_settings">Obecność</string>
@@ -525,11 +521,8 @@
<string name="verified_omemo_key_with_certificate">Zweryfikowano klucz OMEMO z certyfikatem</string>
<string name="device_does_not_support_certificates">Twoje urządzenie nie wspiera wyboru certyfikatów klienckich</string>
<string name="pref_connection_options">Połączenie</string>
- <string name="pref_use_tor">Połącz przez sieć TOR</string>
- <string name="pref_use_tor_summary">Tuneluj wszystkie połączenia przez sieć TOR. Wymaga zainstalowania aplikacji \"Orbot\"</string>
<string name="account_settings_hostname">Nazwa hosta</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Adres serwera lub \".onion\" adres</string>
<string name="not_a_valid_port">To nie jest prawidłowy numer portu</string>
<string name="not_valid_hostname">To nie jest prawidłowa nazwa hosta</string>
<string name="connected_accounts">%1$d z %2$d kont połączonych</string>
@@ -538,7 +531,6 @@
<item quantity="few">%d wiadomości</item>
<item quantity="other">%d wiadomości</item>
</plurals>
- <string name="load_more_messages">Załaduj wiecej wiadomości</string>
<string name="shared_file_with_x">Dzielony plik z %s</string>
<string name="shared_image_with_x">Dzielony obraz z %s</string>
<string name="no_storage_permission">Conversations potrzebuje dostęp do zewnętrznego magazynu</string>
@@ -567,6 +559,4 @@
<string name="selection_too_large">Zaznaczony obszar jest zbyt duży</string>
<string name="no_accounts">(Brak aktywynych kont)</string>
<string name="this_field_is_required">To pole jest wymagane</string>
- <string name="correct_message">Popraw wiadomość</string>
- <string name="send_corrected_message">Wyślij poprawioną wiadomość</string>
</resources>
diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml
index 19d25d5d..7c4d40b1 100644
--- a/src/main/res/values-pt-rBR/strings.xml
+++ b/src/main/res/values-pt-rBR/strings.xml
@@ -524,11 +524,8 @@
<string name="verified_omemo_key_with_certificate">A chave OMEMO foi verificada com o certificado.</string>
<string name="device_does_not_support_certificates">O seu dispositivo não suporta a seleção de certificados de clientes.</string>
<string name="pref_connection_options">Conexão</string>
- <string name="pref_use_tor">Conectar via Tor</string>
- <string name="pref_use_tor_summary">Faça o tunelamento de todas as conexões através de redes Tor. Necessita do Orbot.</string>
<string name="account_settings_hostname">Nome do host</string>
<string name="account_settings_port">Porta</string>
- <string name="hostname_or_onion">Server- ou .onion-Address</string>
<string name="not_a_valid_port">Esse número de porta não é válido</string>
<string name="not_valid_hostname">Esse nome de host não é válido</string>
<string name="connected_accounts">%1$d de %2$d contas conectadas</string>
@@ -536,7 +533,6 @@
<item quantity="one">%d mensagem</item>
<item quantity="other">%d mensagens</item>
</plurals>
- <string name="load_more_messages">Carregar mais mensagens</string>
<string name="shared_file_with_x">Arquivo compartilhado com %s</string>
<string name="shared_image_with_x">Imagem compartilhada com %s</string>
<string name="no_storage_permission">O Conversations necessita de acesso ao armazenamento externo</string>
@@ -565,6 +561,4 @@
<string name="selection_too_large">A área selecionada é muito grande</string>
<string name="no_accounts">(Nenhuma conta ativa)</string>
<string name="this_field_is_required">Este campo é necessário</string>
- <string name="correct_message">Corrigir a mensagem</string>
- <string name="send_corrected_message">Enviar a mensagem corrigida</string>
</resources>
diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml
index 35a60872..202151fe 100644
--- a/src/main/res/values-ro-rRO/strings.xml
+++ b/src/main/res/values-ro-rRO/strings.xml
@@ -498,8 +498,6 @@
<string name="download_failed_server_not_found">Descarcarea a esuat: Serverul nu a fost gasit</string>
<string name="download_failed_file_not_found">Descarcare esuata: Fisierul nu a fost gasit</string>
<string name="download_failed_could_not_connect">Descarcarea a esuat. Nu s-a putut realiza conexiunea cu gazda.</string>
- <string name="pref_use_white_background">Foloseste un fundal alb</string>
- <string name="pref_use_white_background_summary">Arata mesajele primite cu negru pe fond alb</string>
<string name="account_status_tor_unavailable">Reteaua Tor nu este disponibila</string>
<string name="server_info_broken">Deteriorat</string>
<string name="pref_presence_settings">Setari de prezenta</string>
@@ -529,11 +527,8 @@
<string name="verified_omemo_key_with_certificate">Verifica cheia OMEMO cu un certificat</string>
<string name="device_does_not_support_certificates">Dispozitivul nu permite selectia unui certificat pentru client!</string>
<string name="pref_connection_options">Optiuni conexiune</string>
- <string name="pref_use_tor">Conectare prin TOR</string>
- <string name="pref_use_tor_summary">Trimite toate conexiunile prin reteaua Tor. Necesita OrBot</string>
<string name="account_settings_hostname">Nume gazda</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Adresa server- sau .onion</string>
<string name="not_a_valid_port">Acesta nu este un numar de port valabil</string>
<string name="not_valid_hostname">Acesta nu este un nume de gazda valabila</string>
<string name="connected_accounts">%1$d din %2$d conturi conectate</string>
@@ -542,7 +537,6 @@
<item quantity="few">%d mesaje</item>
<item quantity="other">%d mesaje</item>
</plurals>
- <string name="load_more_messages">Incarca mai multe mesaje</string>
<string name="shared_file_with_x">Partajeaza fisierul cu %s...</string>
<string name="shared_image_with_x">Partajeaza imaginea cu %s.</string>
<string name="no_storage_permission">Conversations are nevoie de acces la stocarea externa</string>
@@ -561,8 +555,6 @@ Emitent</string>
<string name="notify_only_when_highlighted">Notifica numai la evidentiere</string>
<string name="notify_never">Notificari dezactivate</string>
<string name="notify_paused">Notificari suspendate</string>
- <string name="pref_picture_compression">Comprima imaginile</string>
- <string name="pref_picture_compression_summary">Redimensioneaza si comprima imaginile</string>
<string name="always">Mereu</string>
<string name="automatically">Automat</string>
<string name="battery_optimizations_enabled">Optimizare baterie activata</string>
@@ -572,8 +564,6 @@ Emitent</string>
<string name="selection_too_large">Zona selectata este prea mare</string>
<string name="no_accounts">(Nici un cont activat)</string>
<string name="this_field_is_required">Acest camp este obligatoriu</string>
- <string name="correct_message">Corectie mesaj</string>
- <string name="send_corrected_message">Trimite text corectat</string>
<string name="no_keys_just_confirm">Deja ai incredere in acest contact. Selectand \'gata\' doar confirmi ca %s ia parte la conferinta.</string>
<string name="select_image_and_crop">Selecteaza imaginea si decupeaza</string>
<string name="this_account_is_disabled">Ai dezactivat acest cont</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 5ed84b7b..1640e051 100644
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -482,8 +482,6 @@
<string name="download_failed_server_not_found">Загрузка не удалась: сервер не найден</string>
<string name="download_failed_file_not_found">Загрузка не удалась: файл не найден</string>
<string name="download_failed_could_not_connect">Загрузка не удалась: не удалось подключиться к серверу</string>
- <string name="pref_use_white_background">Использовать белый фон</string>
- <string name="pref_use_white_background_summary">Показывать принятые сообщения черным текстом на белом фоне</string>
<string name="account_status_tor_unavailable">Сеть Tor недоступна</string>
<string name="server_info_broken">Повреждено</string>
<string name="pref_away_when_screen_off">Вышел когда экран выключен</string>
@@ -501,11 +499,8 @@
<string name="error_fetching_omemo_key">Ошибка при получении OMEMO ключа!</string>
<string name="verified_omemo_key_with_certificate">Проверен OMEMO ключ с сертификатом!</string>
<string name="device_does_not_support_certificates">Ваше устройство не поддерживает выбор клиентских сертификатов!</string>
- <string name="pref_use_tor">Соединение через Tor</string>
- <string name="pref_use_tor_summary">Направить все соединения через сеть TOR. Требуется Orbot</string>
<string name="account_settings_hostname">Имя сервера</string>
<string name="account_settings_port">Порт</string>
- <string name="hostname_or_onion">Сервер- или .onion-Адрес</string>
<string name="not_a_valid_port">Это недопустимый номер порта</string>
<string name="not_valid_hostname">Это недопустимое имя сервера</string>
<string name="connected_accounts">%1$d из %2$d аккаунтов соединены</string>
@@ -523,7 +518,6 @@
<string name="notify_on_all_messages">Уведомлять о всех сообщениях</string>
<string name="notify_never">Уведомления запрещены</string>
<string name="notify_paused">Уведомления приостановлены</string>
- <string name="pref_picture_compression">Сжать изображения</string>
<string name="always">Всегда</string>
<string name="automatically">Автоматически</string>
<string name="battery_optimizations_enabled">Оптимизации энергопотребления разрешены</string>
diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml
index bf816ba2..ac056472 100644
--- a/src/main/res/values-sr/strings.xml
+++ b/src/main/res/values-sr/strings.xml
@@ -277,8 +277,6 @@
<string name="pref_security_settings">Безбедност</string>
<string name="pref_force_encryption">Присили крај-на-крај шифровање</string>
<string name="pref_force_encryption_summary">Увек шифруј поруке (осим за групна ћаскања)</string>
- <string name="pref_allow_message_correction">Дозволи исправљање порука</string>
- <string name="pref_allow_message_correction_summary">Дозвољава вашим контактима да ретроактивно уређују њихове поруке</string>
<string name="pref_dont_save_encrypted">Не успремај шифроване поруке</string>
<string name="pref_dont_save_encrypted_summary">Упозорење: Ово може да доведе до губитка порука</string>
<string name="pref_expert_options">Поставке за стручњаке</string>
@@ -498,8 +496,6 @@
<string name="download_failed_server_not_found">Преузимање није успело: сервер није нађен</string>
<string name="download_failed_file_not_found">Преузимање није успело: фајл није нађен</string>
<string name="download_failed_could_not_connect">Преузимање није успело: не могу да се повежем са домаћином</string>
- <string name="pref_use_white_background">Користи белу позадину</string>
- <string name="pref_use_white_background_summary">Приказ примљених порука црним текстом на белој позадини</string>
<string name="account_status_tor_unavailable">Тор мрежа недоступна</string>
<string name="server_info_broken">Оштећен</string>
<string name="pref_presence_settings">Присутност</string>
@@ -513,7 +509,7 @@
<string name="pref_show_connection_options_summary">Приказ домаћина и порта у поставкама налога</string>
<string name="hostname_example">xmpp.primer.com</string>
<string name="action_add_account_with_certificate">Додај налог сертификатом</string>
- <string name="unable_to_parse_certificate">Не могох да рашчланим сертификат</string>
+ <string name="unable_to_parse_certificate">Не могу да рашчланим сертификат</string>
<string name="authenticate_with_certificate">Оставите празно за аутентификацију сертификатом</string>
<string name="mam_prefs">Поставке архивисања</string>
<string name="server_side_mam_prefs">Серверске поставке архивисања</string>
@@ -529,11 +525,8 @@
<string name="verified_omemo_key_with_certificate">Оверен ОМЕМО кључ помоћу сертификата!</string>
<string name="device_does_not_support_certificates">Ваш уређај не подржава избор сертификата клијента!</string>
<string name="pref_connection_options">Повезивање</string>
- <string name="pref_use_tor">Повежи се преко Тора</string>
- <string name="pref_use_tor_summary">Тунеловање свих веза кроз Тор мрежу. Захтева Орбот</string>
<string name="account_settings_hostname">Име домаћина</string>
<string name="account_settings_port">Порт</string>
- <string name="hostname_or_onion">Сервер или .onion адреса</string>
<string name="not_a_valid_port">Ово није исправан број порта</string>
<string name="not_valid_hostname">Ово није исправно име домаћина</string>
<string name="connected_accounts">%1$d од %2$d налога повезано</string>
@@ -542,7 +535,6 @@
<item quantity="few">%d поруке</item>
<item quantity="other">%d порука</item>
</plurals>
- <string name="load_more_messages">Учитај још порука</string>
<string name="shared_file_with_x">Подељен фајл са %s</string>
<string name="shared_image_with_x">Подељена слика са %s</string>
<string name="no_storage_permission">Конверзацији је потребан приступ спољашњем складишту</string>
@@ -560,8 +552,6 @@
<string name="notify_only_when_highlighted">Обавештења само за означене поруке</string>
<string name="notify_never">Обавештења искључена</string>
<string name="notify_paused">Обавештења паузирана</string>
- <string name="pref_picture_compression">Компресуј слике</string>
- <string name="pref_picture_compression_summary">Промена величине и компресовање слика</string>
<string name="always">увек</string>
<string name="automatically">аутоматски</string>
<string name="battery_optimizations_enabled">Оптимизација батерије је укључена</string>
@@ -571,8 +561,6 @@
<string name="selection_too_large">Назначена површина је превелика</string>
<string name="no_accounts">(Нема активираних налога)</string>
<string name="this_field_is_required">Ово поље је захтевано</string>
- <string name="correct_message">Исправи поруку</string>
- <string name="send_corrected_message">Пошаљи исправљену поруку</string>
<string name="no_keys_just_confirm">Већ се поуздате у овог контакта. Избором „Готово“ само потврђујете да је %s део овог групног ћаскања.</string>
<string name="select_image_and_crop">Изабери слику и опсеци</string>
<string name="this_account_is_disabled">Искључили сте овај налог</string>
diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml
index ee6a46ae..4610e300 100644
--- a/src/main/res/values-sv/strings.xml
+++ b/src/main/res/values-sv/strings.xml
@@ -496,8 +496,6 @@
<string name="download_failed_server_not_found">Nerladdning gick fel: Server hittades inte</string>
<string name="download_failed_file_not_found">Nerladdning gick fel: Filen hittades inte</string>
<string name="download_failed_could_not_connect">Nerladdningen gick fel: Kunder inte ansluta till server</string>
- <string name="pref_use_white_background">Använd vit bakgrund</string>
- <string name="pref_use_white_background_summary">Visa mottagna meddelanden som svart text på vit bakgrund</string>
<string name="account_status_tor_unavailable">Tor-nätverk ej tillgängligt</string>
<string name="server_info_broken">Sönder</string>
<string name="pref_presence_settings">Tillgänglighet</string>
@@ -527,11 +525,8 @@
<string name="verified_omemo_key_with_certificate">Verifierade OMEMO-nyckel med certifikat!</string>
<string name="device_does_not_support_certificates">Din enhet stödjer inte val av klientcertifikat!</string>
<string name="pref_connection_options">Anslutning</string>
- <string name="pref_use_tor">Ansluten via Tor</string>
- <string name="pref_use_tor_summary">Tunnla alla anslutningar genom Tor-nätverket. Kräver Orbot</string>
<string name="account_settings_hostname">Servernamn</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Server- eller .onion-adress</string>
<string name="not_a_valid_port">Inte ett giltigt portnummer</string>
<string name="not_valid_hostname">Inte ett giltigt servernamn</string>
<string name="connected_accounts">%1$d av %2$d konton anslutna</string>
@@ -539,7 +534,6 @@
<item quantity="one">%d meddelande</item>
<item quantity="other">%d meddelanden</item>
</plurals>
- <string name="load_more_messages">Ladda fler meddelanden</string>
<string name="shared_file_with_x">Delade fil med %s</string>
<string name="shared_image_with_x">Delade bild med %s</string>
<string name="no_storage_permission">Conversations behöver access till extern lagring</string>
@@ -557,8 +551,6 @@
<string name="notify_only_when_highlighted">Notifiera endast vid highlight</string>
<string name="notify_never">Notifieringar deaktiverade</string>
<string name="notify_paused">Notifieringar pausade</string>
- <string name="pref_picture_compression">Komprimera bilder</string>
- <string name="pref_picture_compression_summary">Storleksjustera och komprimera bilder</string>
<string name="always">Alltid</string>
<string name="automatically">Automatiskt</string>
<string name="battery_optimizations_enabled">Batterioptimeringar aktiverade</string>
@@ -568,8 +560,6 @@
<string name="selection_too_large">The valda området är för stort</string>
<string name="no_accounts">(Inget konto aktiverat)</string>
<string name="this_field_is_required">Detta fält måste fyllas i</string>
- <string name="correct_message">Korrigera meddelanden</string>
- <string name="send_corrected_message">Skicka korrigerat meddelande</string>
<string name="no_keys_just_confirm">Du litar redan på denna kontakt. Genom att välja \'klar\' bekräftar du att %s är med i denna konferens.</string>
<string name="select_image_and_crop">Välj bild och beskär</string>
<string name="this_account_is_disabled">Du har deaktiverat detta konto</string>
diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml
index 2d434f03..e11e56e3 100644
--- a/src/main/res/values-tr-rTR/strings.xml
+++ b/src/main/res/values-tr-rTR/strings.xml
@@ -494,8 +494,6 @@
<string name="download_failed_server_not_found">İndirme başarısız: Sunucu bulunamadı</string>
<string name="download_failed_file_not_found">İndirme başarısız: Dosya bulunamadı</string>
<string name="download_failed_could_not_connect">İndirme başarısız: Sunucuya bağlanılamadı</string>
- <string name="pref_use_white_background">Beyaz arka plan kullan</string>
- <string name="pref_use_white_background_summary">Alınan iletileri beyaz arka plan üzerinde siyah yazıyla göster</string>
<string name="account_status_tor_unavailable">Tor ağına erişilemiyor</string>
<string name="server_info_broken">Bozuk</string>
<string name="pref_presence_settings">Durum</string>
@@ -525,18 +523,14 @@
<string name="verified_omemo_key_with_certificate">Sertifikalı OMEMO anahtarı onaylandı!</string>
<string name="device_does_not_support_certificates">Cihazınız seçilen istemci sertifikalarını desteklemiyor!</string>
<string name="pref_connection_options">Bağlantı</string>
- <string name="pref_use_tor">Tor üzerinden bağlan</string>
- <string name="pref_use_tor_summary">Bütün bağlantıları Tor ağı üzerinden aktar. Orbot gerekir.</string>
<string name="account_settings_hostname">Sunucu adı</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Sunucu- veya .onion-Address</string>
<string name="not_a_valid_port">Bu port numarası geçerli değil</string>
<string name="not_valid_hostname">Bu sunucu geçerli değil</string>
<string name="connected_accounts"> %2$d hesabın %1$ kadarı bağlandı</string>
<plurals name="x_messages">
<item quantity="other">%d ileti</item>
</plurals>
- <string name="load_more_messages">Daha fazla ileti yükle</string>
<string name="shared_file_with_x">%s ile paylaşılan dosyalar</string>
<string name="shared_image_with_x">%s ile paylaşılan resim</string>
<string name="no_storage_permission">Conversations’ın harici depolama alanına erişmesi gerek </string>
@@ -554,8 +548,6 @@
<string name="notify_only_when_highlighted">Sadece işaretliyse uyar</string>
<string name="notify_never">Uyarılar devre dışı</string>
<string name="notify_paused">Uyarılar geçici olarak durduruldu</string>
- <string name="pref_picture_compression">Resimleri sıkıştır</string>
- <string name="pref_picture_compression_summary">Resimleri yeniden boyutlandır ve sıkıştır</string>
<string name="always">Her zaman</string>
<string name="automatically">Otomatik olarak</string>
<string name="battery_optimizations_enabled">Pil optimizasyonu devrede</string>
@@ -565,8 +557,6 @@
<string name="selection_too_large">Seçilen alan çok büyük</string>
<string name="no_accounts">(Aktif hesap bulunmuyor)</string>
<string name="this_field_is_required">Bu alan zorunludur</string>
- <string name="correct_message">ileti düzelt</string>
- <string name="send_corrected_message">Düzeltilmiş iletiyi gönder</string>
<string name="no_keys_just_confirm">Bu kişiye zaten güveniyor durumdasınız. \"tamam\" seçeneğini işaretleyerek, sadece %s \'in bu grup sohbete katılabileceğini teyid ediyorsunuz.</string>
<string name="select_image_and_crop">Resmi seçip kırpın</string>
<string name="this_account_is_disabled">Bu hesabı devre dışı bıraktınız</string>
diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml
index 935e6fa3..56ae3add 100644
--- a/src/main/res/values-vi/strings.xml
+++ b/src/main/res/values-vi/strings.xml
@@ -497,11 +497,8 @@
<string name="error_fetching_omemo_key">Lỗi nhập khoá OMEMO!</string>
<string name="verified_omemo_key_with_certificate">Khoá OMEMO đã xác minh với chứng nhận!</string>
<string name="device_does_not_support_certificates">Thiết bị không hỗ trợ chọn lựa các chứng chỉ của máy trạm!</string>
- <string name="pref_use_tor">Kết nối đến Tor</string>
- <string name="pref_use_tor_summary">Chuyển toàn bộ kết nối thông qua mạng Tor. Cần có Orbot</string>
<string name="account_settings_hostname">Tên máy chủ</string>
<string name="account_settings_port">Cổng</string>
- <string name="hostname_or_onion">Máy chủ- hoặc địa chỉ .onion-</string>
<string name="not_a_valid_port">Đây không phải là số cổng hợp lệ</string>
<string name="not_valid_hostname">Đây không phải là tên máy chủ hợp lệ</string>
<string name="connected_accounts">%1$d trên %2$d tài khoản đã kết nối</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index 8c770537..3cdf1650 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -494,8 +494,6 @@
<string name="download_failed_server_not_found">下载失败:未找到服务器</string>
<string name="download_failed_file_not_found">下载失败:未找到文件</string>
<string name="download_failed_could_not_connect">下载失败:无法连接到服务器</string>
- <string name="pref_use_white_background">使用白色背景</string>
- <string name="pref_use_white_background_summary">收到的消息将显示为白底黑字</string>
<string name="account_status_tor_unavailable">Tor network 不可用</string>
<string name="server_info_broken">损坏</string>
<string name="pref_presence_settings">存在</string>
@@ -525,18 +523,14 @@
<string name="verified_omemo_key_with_certificate">请用证书验证 OMEMO 密钥!</string>
<string name="device_does_not_support_certificates">您的设备不支持设备证书选择!</string>
<string name="pref_connection_options">连接</string>
- <string name="pref_use_tor">通过 Tor 连接</string>
- <string name="pref_use_tor_summary">所有连接使用 Tor 网络隧道。需要 Orbot</string>
<string name="account_settings_hostname">主机名</string>
<string name="account_settings_port">端口</string>
- <string name="hostname_or_onion">服务器 - 或者 .orion 地址</string>
<string name="not_a_valid_port">该端口号无效</string>
<string name="not_valid_hostname">该主机名无效</string>
<string name="connected_accounts">%2$d 个中的 %1$d 个账户已连接</string>
<plurals name="x_messages">
<item quantity="other">%d 条消息</item>
</plurals>
- <string name="load_more_messages">载入更多消息</string>
<string name="shared_file_with_x">用 %s 分享文件</string>
<string name="shared_image_with_x">用 %s 分享图片</string>
<string name="no_storage_permission">Conversations 需要访问外部存储</string>
@@ -554,8 +548,6 @@
<string name="notify_only_when_highlighted">仅当高亮时显示通知</string>
<string name="notify_never">禁用通知</string>
<string name="notify_paused">暂停通知</string>
- <string name="pref_picture_compression">压缩图片</string>
- <string name="pref_picture_compression_summary">裁剪并压缩图片</string>
<string name="always">总是</string>
<string name="automatically">自动</string>
<string name="battery_optimizations_enabled">启用节电模式</string>
@@ -567,8 +559,6 @@
<string name="selection_too_large">选择区域过大</string>
<string name="no_accounts">(没有激活的账户)</string>
<string name="this_field_is_required">必填</string>
- <string name="correct_message">更正消息</string>
- <string name="send_corrected_message">发送更正后的消息</string>
<string name="no_keys_just_confirm">你已信任此联系人。选择“完成”表示 %s 将成为此讨论组的一部分。</string>
<string name="select_image_and_crop">选择照片并裁剪</string>
<string name="this_account_is_disabled">你已经禁用了此账户</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 952035f3..20202624 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -76,6 +76,8 @@
<string name="pref_xmpp_resource">XMPP 資源</string>
<string name="pref_xmpp_resource_summary">客戶端標示名稱</string>
<string name="pref_accept_files">接收文件</string>
+ <string name="pref_accept_files_size_summary">自動接收小於 … 的文件</string>
+ <string name="pref_notification_settings">通知設定</string>
<string name="pref_accept_files_summary">自動接收小於 … 的文件</string>
<string name="pref_notifications">通知</string>
<string name="pref_notifications_summary">收到新訊息時通知</string>
diff --git a/src/main/res/values/arrays.xml b/src/main/res/values/arrays.xml
index 2058162c..f8028ea3 100644
--- a/src/main/res/values/arrays.xml
+++ b/src/main/res/values/arrays.xml
@@ -58,17 +58,27 @@
<item>location</item>
</string-array>
- <string-array name="picture_compression_values">
- <item>never</item>
- <item>auto</item>
- <item>always</item>
- </string-array>
+ <string-array name="confirm_strings">
+ <item>@string/pref_confirm_messages_none</item>
+ <item>@string/pref_confirm_messages_received</item>
+ <item>@string/pref_confirm_messages_read_and_received</item>
+ </string-array>
- <string-array name="picture_compression_entries">
- <item>@string/never</item>
- <item>@string/automatically</item>
- <item>@string/always</item>
- </string-array>
+ <string-array name="confirm_values">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ </string-array>
+ <string-array name="resize_picture_setting_entries">
+ <item>@string/pref_resize_picture_ask</item>
+ <item>@string/pref_resize_picture_always</item>
+ <item>@string/pref_resize_picture_never</item>
+ </string-array>
+ <string-array name="resize_picture_setting_values">
+ <item>ASK</item>
+ <item>ALWAYS</item>
+ <item>NEVER</item>
+ </string-array>
<string-array name="mam_prefs">
<item>@string/never</item>
diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml
index d471e54a..0d6d19b1 100644
--- a/src/main/res/values/attrs.xml
+++ b/src/main/res/values/attrs.xml
@@ -25,5 +25,8 @@
<attr name="icon_secure" format="reference"/>
<attr name="icon_settings" format="reference"/>
<attr name="icon_import_export" format="reference"/>
-
+ <declare-styleable name="AmbilWarnaPreference">
+ <attr name="supportsAlpha"
+ format="boolean"/>
+ </declare-styleable>
</resources> \ No newline at end of file
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index b3567b44..2d744395 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -1,7 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <color name="primary">#ff259b24</color>
- <color name="primary_dark">#ff0a7e07</color>
+ <color name="primaryBackground">@color/grey50</color>
+ <color name="secondaryBackground">@color/grey200</color>
+ <color name="primaryText">@color/black87</color>
+ <color name="secondaryText">@color/black54</color>
+ <color name="warning">@color/red500</color>
+ <color name="error">@color/red500</color>
+ <color name="online">@color/green500</color>
+ <color name="notification">@color/green500</color>
+
+ <color name="primary">@color/green500</color>
+ <color name="primary_dark">@color/green700</color>
<color name="accent">#ff0091ea</color>
<color name="black87">#de000000</color>
<color name="black54">#8a000000</color>
@@ -17,4 +26,5 @@
<color name="red800">#ffc62828</color>
<color name="orange500">#ffff9800</color>
<color name="green500">#ff259b24</color>
+ <color name="green700">#ff0a7e07</color>
</resources> \ No newline at end of file
diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml
index 95e80055..d5d4aad4 100644
--- a/src/main/res/values/dimens.xml
+++ b/src/main/res/values/dimens.xml
@@ -5,4 +5,8 @@
<dimen name="infocard_padding">16dp</dimen>
<dimen name="conversations_overview_width">288dp</dimen>
<dimen name="image_button_padding">8dp</dimen>
+ <dimen name="ambilwarna_hsvHeight">240dp</dimen>
+ <dimen name="ambilwarna_hsvWidth">240dp</dimen>
+ <dimen name="ambilwarna_hueWidth">30dp</dimen>
+ <dimen name="ambilwarna_spacer">8dp</dimen>
</resources>
diff --git a/src/main/res/values/encryption_settings.xml b/src/main/res/values/encryption_settings.xml
new file mode 100644
index 00000000..33db8387
--- /dev/null
+++ b/src/main/res/values/encryption_settings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="omemo_enabled">false</bool>
+</resources> \ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index f0f2cc3e..8b34ecf1 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -102,6 +102,12 @@
<string name="pref_xmpp_resource_summary">The name this client identifies itself with</string>
<string name="pref_accept_files">Accept files</string>
<string name="pref_accept_files_summary">Automatically accept files smaller than…</string>
+ <string name="pref_accept_files_size">Size</string>
+ <string name="pref_accept_files_size_summary">Automatically accept files smaller than…</string>
+ <string name="pref_accept_files_download">Wi-Fi only</string>
+ <string name="pref_accept_files_download_summary">Download and accept files automatically only when using Wi-Fi</string>
+ <string name="pref_accept_files_download_link">Image links</string>
+ <string name="pref_accept_files_download_link_summary">Automatically download image links</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>
@@ -116,7 +122,12 @@
<string name="pref_never_send_crash_summary">By sending in stack traces you are helping the ongoing development of Conversations</string>
<string name="pref_confirm_messages">Confirm Messages</string>
<string name="pref_confirm_messages_summary">Let your contact know when you have received and read a message</string>
+ <string name="pref_confirm_messages_none">No confirmation</string>
+ <string name="pref_confirm_messages_received">Confirmation for received message</string>
+ <string name="pref_confirm_messages_read_and_received">Confirmation for received and read message</string>
<string name="pref_ui_options">UI</string>
+ <string name="pref_parse_emoticons">Parse Emoticons</string>
+ <string name="pref_parse_emoticons_summary">Replace emoticons with smilies.</string>
<string name="openpgp_error">OpenKeychain reported an error</string>
<string name="error_decrypting_file">I/O Error decrypting file</string>
<string name="accept">Accept</string>
@@ -287,8 +298,8 @@
<string name="title_activity_about">About Conversations</string>
<string name="pref_about_conversations_summary">Build and licensing information</string>
<string name="pref_about_message" translatable="false">
- Conversations • the very last word in instant messaging.
- \n\nCopyright © 2014-2016 Daniel Gultsch
+ Conversations+ is the improved version of Conversations.
+ \n\nThe extensions are designed and developed by thedevstack.de
\n\nThis program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
@@ -299,7 +310,8 @@
GNU General Public License for more details.
\n\nYou should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses
- \n\nDownload the full source code at https://github.com/siacs/Conversations
+ \n\nBased on
+ \n\nhttps://github.com/siacs/Conversations\n(GPL, Version 3.0)
\n\n\nLibraries
\n\nhttps://www.bouncycastle.org\n(The MIT License (MIT))
\n\nhttps://www.gnu.org/software/libidn\n(Apache License, Version 2.0)
@@ -316,6 +328,8 @@
\n\nhttps://github.com/WhisperSystems/libaxolotl-java\n(GPLv3)
\n\nhttps://github.com/vinc3m1/RoundedImageView\n(Apache License, Version 2.0)
\n\nhttps://github.com/jdamcd/android-crop\n(Apache License, Version 2.0)
+ \n\nhttps://github.com/ankushsachdeva/emojicon\n(Apache License, Version 2.0)
+ \n\nhttps://github.com/yukuku/ambilwarna\n(Apache License, Version 2.0)
</string>
<string name="title_pref_quiet_hours">Quiet Hours</string>
<string name="title_pref_quiet_hours_start_time">Start time</string>
@@ -433,6 +447,7 @@
<string name="current_password">Current password</string>
<string name="new_password">New password</string>
<string name="password_should_not_be_empty">Password should not be empty</string>
+ <string name="password_should_not_contain_only_spaces">Password should not contain only spaces</string>
<string name="enable_all_accounts">Enable all accounts</string>
<string name="disable_all_accounts">Disable all accounts</string>
<string name="perform_action_with">Perform action with</string>
@@ -518,6 +533,55 @@
<string name="recently_used">Most recently used</string>
<string name="choose_quick_action">Choose quick action</string>
<string name="search_for_contacts_or_groups">Search for contacts or groups</string>
+ <string name="pref_led_notification_color">LED notification color</string>
+ <string name="pref_led_notification_color_summary">Change the color of the LED notification</string>
+ <string name="msg_ctx_mnu_details">Message Details</string>
+ <string name="dlg_msg_details_title">Message Details</string>
+ <string name="cplus_ok">Ok</string>
+ <string name="dlg_resources_title">%1$s (%2$d)</string><!-- %s = bare jid, %d = count of online resources -->
+ <string name="dlg_msg_details_receipient_resource">Receipient Resource:</string>
+ <string name="dlg_msg_details_receipient_nick">Receipient Nick:</string>
+ <string name="dlg_msg_details_sender_resource">Sender Resource:</string>
+ <string name="dlg_msg_details_sender_nick">Sender Nick:</string>
+ <string name="dlg_msg_details_time_sent">Time Sent:</string>
+ <string name="dlg_msg_details_msg_type">Message Type:</string>
+ <string name="dlg_msg_details_msg_status">Message Status:</string>
+ <string name="dlg_msg_details_file_details">File Details</string>
+ <string name="dlg_msg_details_file_mime">Mime Type</string>
+ <string name="dlg_msg_details_file_size">Size</string>
+ <string name="dlg_msg_details_msg_type_text">Text</string>
+ <string name="dlg_msg_details_msg_type_image">Image</string>
+ <string name="dlg_msg_details_msg_type_file">File</string>
+ <string name="dlg_msg_details_msg_type_status">Status</string>
+ <string name="dlg_msg_details_msg_type_private">Private</string>
+ <string name="dlg_msg_details_msg_status_sent">Sent</string>
+ <string name="dlg_msg_details_msg_status_received">Received</string>
+ <string name="dlg_msg_details_msg_status_waiting">Waiting</string>
+ <string name="dlg_msg_details_msg_status_unsend">Unsend</string>
+ <string name="dlg_msg_details_msg_status_offered">Offered</string>
+ <string name="dlg_msg_details_msg_status_failed">Failed</string>
+ <string name="pref_resize_picture_ask">ask</string>
+ <string name="pref_resize_picture_always">always</string>
+ <string name="pref_resize_picture_never">never</string>
+ <string name="pref_resize_picture_summary">Whether pictures shall be resized or not</string>
+ <string name="pref_resize_picture">Resize pictures</string>
+ <string name="cplus_yes">Yes</string>
+ <string name="cplus_no">No</string>
+ <string name="cplus_remember_userdecision">Remember this decision</string>
+ <string name="userdecision_question_resize_picture">Shall the picture be resized?</string>
+ <string name="title_activity_loginformation">Logoutput</string>
+ <string name="cplus_copy_to_clipboard">Copy to clipboard</string>
+ <string name="cplus_copied_to_clipboard">Copied to clipboard</string>
+ <string name="pref_show_logcat_title">Show logcat output</string>
+ <string name="pref_show_logcat_summary">Shows the output of logcat. This is useful for debugging.</string>
+ <string name="cplus_bugreport_jabberid">c+bugs@conference.thedevstack.de</string>
+ <string name="pref_file_transfer">Folder to save incoming files</string>
+ <string name="pref_file_transfer_folder_summary">This is the subdirectory for incoming files.</string>
+ <string name="pref_img_file_transfer">Folder to save incoming pictures</string>
+ <string name="pref_img_file_transfer_summary">This is the subdirectory in the pictures directory for incoming files.</string>
+ <string name="pref_file_transfer_category">File Transfer</string>
+ <string name="cplus_not_copied_to_clipboard_empty">Nothing to copy.</string>
+ <string name="cplus_me">Me</string>
<string name="send_private_message">Send private message</string>
<string name="user_has_left_conference">%s has left the conference!</string>
<string name="username">Username</string>
@@ -528,8 +592,6 @@
<string name="download_failed_server_not_found">Download failed: Server not found</string>
<string name="download_failed_file_not_found">Download failed: File not found</string>
<string name="download_failed_could_not_connect">Download failed: Could not connect to host</string>
- <string name="pref_use_white_background">Use white background</string>
- <string name="pref_use_white_background_summary">Show received messages as black text on a white background</string>
<string name="account_status_tor_unavailable">Tor network unavailable</string>
<string name="server_info_broken">Broken</string>
<string name="pref_presence_settings">Presence</string>
@@ -559,11 +621,8 @@
<string name="verified_omemo_key_with_certificate">Verified OMEMO key with certificate!</string>
<string name="device_does_not_support_certificates">Your device does not support the selection of client certificates!</string>
<string name="pref_connection_options">Connection</string>
- <string name="pref_use_tor">Connect via Tor</string>
- <string name="pref_use_tor_summary">Tunnel all connections through the Tor network. Requires Orbot</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_port">Port</string>
- <string name="hostname_or_onion">Server- or .onion-Address</string>
<string name="not_a_valid_port">This is not a valid port number</string>
<string name="not_valid_hostname">This is not a valid hostname</string>
<string name="connected_accounts">%1$d of %2$d accounts connected</string>
@@ -571,7 +630,6 @@
<item quantity="one">%d message</item>
<item quantity="other">%d messages</item>
</plurals>
- <string name="load_more_messages">Load more messages</string>
<string name="shared_file_with_x">Shared file with %s</string>
<string name="shared_image_with_x">Shared image with %s</string>
<string name="no_storage_permission">Conversations need access to external storage</string>
@@ -589,8 +647,6 @@
<string name="notify_only_when_highlighted">Notify only when highlighted</string>
<string name="notify_never">Notifications disabled</string>
<string name="notify_paused">Notifications paused</string>
- <string name="pref_picture_compression">Compress Pictures</string>
- <string name="pref_picture_compression_summary">Resize and compress pictures</string>
<string name="always">Always</string>
<string name="automatically">Automatically</string>
<string name="battery_optimizations_enabled">Battery optimizations enabled</string>
@@ -600,8 +656,9 @@
<string name="selection_too_large">The selected area is too large</string>
<string name="no_accounts">(No activated accounts)</string>
<string name="this_field_is_required">This field is required</string>
- <string name="correct_message">Correct message</string>
- <string name="send_corrected_message">Send corrected message</string>
+ <string name="retry_decryption">Retry decryption</string>
+ <string name="pref_omemo_enabled_summary">Enable OMEMO?</string>
+ <string name="pref_omemo_enabled_title">Enable OMEMO</string>
<string name="no_keys_just_confirm">You already trust this contact. By selecting \'done\' you are just confirming that %s is part of this conference.</string>
<string name="select_image_and_crop">Select image and crop</string>
<string name="this_account_is_disabled">You have disabled this account</string>
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index f505baf7..f0de762b 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
<PreferenceCategory android:title="@string/pref_general">
<CheckBoxPreference
@@ -16,78 +17,123 @@
android:summary="@string/pref_xmpp_resource_summary"
android:title="@string/pref_xmpp_resource"/>
<ListPreference
- android:defaultValue="524288"
- android:entries="@array/filesizes"
- android:entryValues="@array/filesizes_values"
- android:key="auto_accept_file_size"
- android:summary="@string/pref_accept_files_summary"
- android:title="@string/pref_accept_files"/>
- <ListPreference
- android:defaultValue="auto"
- android:entries="@array/picture_compression_entries"
- android:entryValues="@array/picture_compression_values"
- android:key="picture_compression"
- android:summary="@string/pref_picture_compression_summary"
- android:title="@string/pref_picture_compression"/>
- <CheckBoxPreference
- android:defaultValue="true"
- android:key="confirm_messages"
+ android:defaultValue="2"
+ android:entries="@array/confirm_strings"
+ android:entryValues="@array/confirm_values"
+ android:key="confirm_messages_list"
android:summary="@string/pref_confirm_messages_summary"
- android:title="@string/pref_confirm_messages"/>
+ android:title="@string/pref_confirm_messages" />
<CheckBoxPreference
android:defaultValue="false"
android:key="chat_states"
android:summary="@string/pref_chat_states_summary"
android:title="@string/pref_chat_states"/>
-
- </PreferenceCategory>
- <PreferenceCategory
- android:key="notifications"
- android:title="@string/pref_notification_settings">
<CheckBoxPreference
android:defaultValue="true"
- android:key="show_notification"
- android:summary="@string/pref_notifications_summary"
- android:title="@string/pref_notifications"/>
+ android:key="parse_emoticons"
+ android:summary="@string/pref_parse_emoticons_summary"
+ android:title="@string/pref_parse_emoticons"/>
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/pref_file_transfer_category">
<PreferenceScreen
- android:dependency="show_notification"
- android:key="quiet_hours"
- android:summary="@string/pref_quiet_hours_summary"
- android:title="@string/title_pref_quiet_hours">
+ android:summary="@string/pref_accept_files_summary"
+ android:title="@string/pref_accept_files">
+
<CheckBoxPreference
- android:defaultValue="false"
- android:key="enable_quiet_hours"
- android:summary="@string/pref_quiet_hours_summary"
- android:title="@string/title_pref_enable_quiet_hours"/>
- <eu.siacs.conversations.ui.TimePreference
- android:dependency="enable_quiet_hours"
- android:key="quiet_hours_start"
- android:negativeButtonText="@string/cancel"
- android:positiveButtonText="@string/set"
- android:title="@string/title_pref_quiet_hours_start_time"/>
- <eu.siacs.conversations.ui.TimePreference
- android:dependency="enable_quiet_hours"
- android:key="quiet_hours_end"
- android:negativeButtonText="@string/cancel"
- android:positiveButtonText="@string/set"
- android:title="@string/title_pref_quiet_hours_end_time"/>
+ android:defaultValue="true"
+ android:key="auto_download_file_link"
+ android:summary="@string/pref_accept_files_download_link_summary"
+ android:title="@string/pref_accept_files_download_link" />
+
+ <ListPreference
+ android:defaultValue="524288"
+ android:entries="@array/filesizes"
+ android:entryValues="@array/filesizes_values"
+ android:key="auto_accept_file_size"
+ android:summary="@string/pref_accept_files_size_summary"
+ android:title="@string/pref_accept_files_size" />
+
+ <CheckBoxPreference
+ android:defaultValue="true"
+ android:key="auto_download_file_wlan"
+ android:summary="@string/pref_accept_files_download_summary"
+ android:title="@string/pref_accept_files_download" />
+
</PreferenceScreen>
- <CheckBoxPreference
- android:defaultValue="true"
- android:dependency="show_notification"
- android:key="vibrate_on_notification"
- android:summary="@string/pref_vibrate_summary"
- android:title="@string/pref_vibrate"/>
+ <EditTextPreference
+ android:title="@string/pref_img_file_transfer"
+ android:summary="@string/pref_file_transfer_folder_summary"
+ android:key="file_transfer_folder"
+ android:defaultValue="Conversations+"/>
+ <EditTextPreference
+ android:title="@string/pref_file_transfer"
+ android:summary="@string/pref_img_file_transfer_summary"
+ android:key="img_transfer_folder"
+ android:defaultValue="Conversations+"/>
+
+ <ListPreference
+ android:defaultValue="ASK"
+ android:entries="@array/resize_picture_setting_entries"
+ android:entryValues="@array/resize_picture_setting_values"
+ android:key="resize_picture"
+ android:summary="@string/pref_resize_picture_summary"
+ android:title="@string/pref_resize_picture"/>
+ </PreferenceCategory>
+ <PreferenceCategory android:title="@string/pref_notification_settings" >
+ <PreferenceScreen
+ android:summary="@string/pref_notification_settings"
+ android:title="@string/pref_notifications" >
+ <CheckBoxPreference
+ android:defaultValue="true"
+ android:key="show_notification"
+ android:summary="@string/pref_notifications_summary"
+ android:title="@string/pref_notifications" />
+ <PreferenceScreen
+ android:dependency="show_notification"
+ android:key="quiet_hours"
+ android:summary="@string/pref_quiet_hours_summary"
+ android:title="@string/title_pref_quiet_hours">
+ <CheckBoxPreference
+ android:defaultValue="false"
+ android:key="enable_quiet_hours"
+ android:summary="@string/pref_quiet_hours_summary"
+ android:title="@string/title_pref_enable_quiet_hours" />
+ <eu.siacs.conversations.ui.TimePreference
+ android:dependency="enable_quiet_hours"
+ android:key="quiet_hours_start"
+ android:negativeButtonText="@string/cancel"
+ android:positiveButtonText="@string/set"
+ android:title="@string/title_pref_quiet_hours_start_time" />
+ <eu.siacs.conversations.ui.TimePreference
+ android:dependency="enable_quiet_hours"
+ android:key="quiet_hours_end"
+ android:negativeButtonText="@string/cancel"
+ android:positiveButtonText="@string/set"
+ android:title="@string/title_pref_quiet_hours_end_time" />
+ </PreferenceScreen>
+ <CheckBoxPreference
+ android:defaultValue="true"
+ android:dependency="show_notification"
+ android:key="vibrate_on_notification"
+ android:summary="@string/pref_vibrate_summary"
+ android:title="@string/pref_vibrate" />
- <RingtonePreference
- android:defaultValue="content://settings/system/notification_sound"
- android:dependency="show_notification"
- android:key="notification_ringtone"
- android:ringtoneType="notification"
- android:summary="@string/pref_sound_summary"
- android:title="@string/pref_sound"/>
+ <RingtonePreference
+ android:defaultValue="content://settings/system/notification_sound"
+ android:dependency="show_notification"
+ android:key="notification_ringtone"
+ android:ringtoneType="notification"
+ android:summary="@string/pref_sound_summary"
+ android:title="@string/pref_sound" />
+ <yuku.ambilwarna.widget.AmbilWarnaPreference
+ android:defaultValue="0xffffffff"
+ android:key="led_notify_color"
+ android:title="@string/pref_led_notification_color"
+ app:supportsAlpha="true"
+ android:summary="@string/pref_led_notification_color_summary"/>
+ </PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_ui_options">
<CheckBoxPreference
@@ -97,11 +143,6 @@
android:title="@string/pref_conference_name"/>
<CheckBoxPreference
android:defaultValue="false"
- android:key="use_white_background"
- android:summary="@string/pref_use_white_background_summary"
- android:title="@string/pref_use_white_background"/>
- <CheckBoxPreference
- android:defaultValue="false"
android:key="use_larger_font"
android:summary="@string/pref_use_larger_font_summary"
android:title="@string/pref_use_larger_font"/>
@@ -144,24 +185,13 @@
android:title="@string/pref_dont_trust_system_cas_title"/>
<Preference
android:key="remove_trusted_certificates"
- android:summary="@string/pref_remove_trusted_certificates_summary"
- android:title="@string/pref_remove_trusted_certificates_title"/>
- <CheckBoxPreference
- android:defaultValue="false"
- android:key="allow_message_correction"
- android:title="@string/pref_allow_message_correction"
- android:summary="@string/pref_allow_message_correction_summary"/>
+ android:summary="@string/pref_remove_trusted_certificates_summary"/>
</PreferenceCategory>
<PreferenceCategory
android:key="connection_options"
android:title="@string/pref_connection_options">
<CheckBoxPreference
android:defaultValue="false"
- android:key="use_tor"
- android:summary="@string/pref_use_tor_summary"
- android:title="@string/pref_use_tor"/>
- <CheckBoxPreference
- android:defaultValue="false"
android:key="show_connection_options"
android:summary="@string/pref_show_connection_options_summary"
android:title="@string/pref_show_connection_options"/>
@@ -196,7 +226,7 @@
android:title="@string/pref_treat_vibrate_as_silent"
android:summary="@string/pref_treat_vibrate_as_silent_summary"/>
</PreferenceCategory>
- <PreferenceCategory android:title="@string/pref_expert_options_other">
+ <PreferenceCategory android:key="other_expert_settings" android:title="@string/pref_expert_options_other">
<CheckBoxPreference
android:key="autojoin"
android:defaultValue="true"
@@ -217,6 +247,14 @@
android:key="export_logs"
android:summary="@string/pref_export_logs_summary"
android:title="@string/pref_export_logs"/>
+ <de.thedevstack.conversationsplus.ui.preferences.LogInformationPreference
+ android:summary="@string/pref_show_logcat_summary"
+ android:title="@string/pref_show_logcat_title"/>
+ <CheckBoxPreference
+ android:defaultValue="@bool/omemo_enabled"
+ android:key="omemo_enabled"
+ android:summary="@string/pref_omemo_enabled_summary"
+ android:title="@string/pref_omemo_enabled_title"/>
</PreferenceCategory>
</PreferenceScreen>