aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de')
-rw-r--r--src/main/java/de/pixart/messenger/Config.java5
-rw-r--r--src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java14
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java35
-rw-r--r--src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java1
-rw-r--r--src/main/java/de/pixart/messenger/entities/Account.java9
-rw-r--r--src/main/java/de/pixart/messenger/entities/Bookmark.java5
-rw-r--r--src/main/java/de/pixart/messenger/entities/Contact.java12
-rw-r--r--src/main/java/de/pixart/messenger/entities/Conversation.java108
-rw-r--r--src/main/java/de/pixart/messenger/entities/IndividualMessage.java7
-rw-r--r--src/main/java/de/pixart/messenger/entities/ListItem.java3
-rw-r--r--src/main/java/de/pixart/messenger/entities/Message.java37
-rw-r--r--src/main/java/de/pixart/messenger/entities/MucOptions.java76
-rw-r--r--src/main/java/de/pixart/messenger/entities/Transferable.java38
-rw-r--r--src/main/java/de/pixart/messenger/generator/IqGenerator.java18
-rw-r--r--src/main/java/de/pixart/messenger/http/HttpConnectionManager.java70
-rw-r--r--src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java39
-rw-r--r--src/main/java/de/pixart/messenger/http/HttpUploadConnection.java23
-rw-r--r--src/main/java/de/pixart/messenger/parser/MessageParser.java3
-rw-r--r--src/main/java/de/pixart/messenger/parser/PresenceParser.java2
-rw-r--r--src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java143
-rw-r--r--src/main/java/de/pixart/messenger/persistance/FileBackend.java88
-rw-r--r--src/main/java/de/pixart/messenger/services/AlarmReceiver.java11
-rw-r--r--src/main/java/de/pixart/messenger/services/AvatarService.java105
-rw-r--r--src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java25
-rw-r--r--src/main/java/de/pixart/messenger/services/EventReceiver.java12
-rw-r--r--src/main/java/de/pixart/messenger/services/ExportBackupService.java387
-rw-r--r--src/main/java/de/pixart/messenger/services/ExportLogsService.java248
-rw-r--r--src/main/java/de/pixart/messenger/services/ImportBackupService.java294
-rw-r--r--src/main/java/de/pixart/messenger/services/NotificationService.java163
-rw-r--r--src/main/java/de/pixart/messenger/services/ShortcutService.java1
-rw-r--r--src/main/java/de/pixart/messenger/services/XmppConnectionService.java384
-rw-r--r--src/main/java/de/pixart/messenger/ui/BlocklistActivity.java3
-rw-r--r--src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java1
-rw-r--r--src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java82
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java361
-rw-r--r--src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java3
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConversationFragment.java181
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConversationsActivity.java108
-rw-r--r--src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java (renamed from src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java)12
-rw-r--r--src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java303
-rw-r--r--src/main/java/de/pixart/messenger/ui/EditAccountActivity.java45
-rw-r--r--src/main/java/de/pixart/messenger/ui/EnterJidDialog.java69
-rw-r--r--src/main/java/de/pixart/messenger/ui/EnterNameActivity.java1
-rw-r--r--src/main/java/de/pixart/messenger/ui/ImportBackupActivity.java145
-rw-r--r--src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java8
-rw-r--r--src/main/java/de/pixart/messenger/ui/LocationActivity.java1
-rw-r--r--src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java34
-rw-r--r--src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java50
-rw-r--r--src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java104
-rw-r--r--src/main/java/de/pixart/messenger/ui/MucUsersActivity.java87
-rw-r--r--src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java28
-rw-r--r--src/main/java/de/pixart/messenger/ui/SettingsActivity.java106
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java6
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShareWithActivity.java10
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShortcutActivity.java1
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java5
-rw-r--r--src/main/java/de/pixart/messenger/ui/StartConversationActivity.java191
-rw-r--r--src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java6
-rw-r--r--src/main/java/de/pixart/messenger/ui/WelcomeActivity.java337
-rw-r--r--src/main/java/de/pixart/messenger/ui/XmppActivity.java36
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java143
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/BackupFileAdapter.java167
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java332
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java103
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java16
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java13
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java142
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/UserAdapter.java136
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/UserPreviewAdapter.java70
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/AvatarWorkerTask.java111
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java13
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/GridManager.java3
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/MucConfiguration.java111
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java97
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/MyLinkify.java2
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java2
-rw-r--r--src/main/java/de/pixart/messenger/ui/util/ViewUtil.java13
-rw-r--r--src/main/java/de/pixart/messenger/utils/BackupFileHeader.java84
-rw-r--r--src/main/java/de/pixart/messenger/utils/Compatibility.java18
-rw-r--r--src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java24
-rw-r--r--src/main/java/de/pixart/messenger/utils/CryptoHelper.java3
-rw-r--r--src/main/java/de/pixart/messenger/utils/ExceptionHelper.java2
-rw-r--r--src/main/java/de/pixart/messenger/utils/GeoHelper.java76
-rw-r--r--src/main/java/de/pixart/messenger/utils/Namespace.java1
-rw-r--r--src/main/java/de/pixart/messenger/utils/PermissionUtils.java34
-rw-r--r--src/main/java/de/pixart/messenger/utils/PhoneHelper.java7
-rw-r--r--src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java1
-rw-r--r--src/main/java/de/pixart/messenger/utils/UIHelper.java9
-rw-r--r--src/main/java/de/pixart/messenger/utils/XmppUri.java2
-rw-r--r--src/main/java/de/pixart/messenger/xml/Element.java7
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/XmppConnection.java31
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java6
92 files changed, 3954 insertions, 2515 deletions
diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java
index ce65587b7..b2581f8dc 100644
--- a/src/main/java/de/pixart/messenger/Config.java
+++ b/src/main/java/de/pixart/messenger/Config.java
@@ -18,6 +18,7 @@ public final class Config {
private static final int OMEMO = 8;
private static final int ENCRYPTION_MASK = UNENCRYPTED | OPENPGP | OTR | OMEMO;
+
public static boolean supportUnencrypted() {
return (ENCRYPTION_MASK & UNENCRYPTED) != 0;
}
@@ -63,8 +64,6 @@ public final class Config {
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
-
public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false;
public static final boolean SUPPRESS_ERROR_NOTIFICATION = false;
@@ -77,6 +76,7 @@ public final class Config {
public static final int PING_TIMEOUT = 15;
public static final int SOCKET_TIMEOUT = 15;
public static final int CONNECT_TIMEOUT = 60;
+ public static final int POST_CONNECTIVITY_CHANGE_PING_INTERVAL = 30;
public static final int CONNECT_DISCO_TIMEOUT = 30;
public static final int MINI_GRACE_PERIOD = 750;
@@ -85,6 +85,7 @@ public final class Config {
public static final int FILE_SIZE = 1048576; // 1 MiB
public static final int AVATAR_SIZE = 480;
+ public static final int SYSTEM_UI_AVATAR_SIZE = 48;
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG;
public static final int AVATAR_CHAR_LIMIT = 9400;
diff --git a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java
index 1c1a995a9..cc5e3fe70 100644
--- a/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java
+++ b/src/main/java/de/pixart/messenger/crypto/PgpDecryptionService.java
@@ -135,6 +135,7 @@ public class PgpDecryptionService {
}
private void executeApi(Message message) {
+ boolean skipNotificationPush = false;
synchronized (message) {
Intent params = userInteractionResult != null ? userInteractionResult : new Intent();
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
@@ -176,7 +177,7 @@ public class PgpDecryptionService {
mXmppConnectionService.updateMessage(message);
break;
}
- } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
+ } else if (message.isFileOrImage()) {
try {
final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
@@ -208,9 +209,12 @@ public class PgpDecryptionService {
URL url = message.getFileParams().url;
mXmppConnectionService.getFileBackend().updateFileParams(message, url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- inputFile.delete();
- mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
mXmppConnectionService.updateMessage(message);
+ if (!inputFile.delete()) {
+ Log.w(Config.LOGTAG, "unable to delete pgp encrypted source file " + inputFile.getAbsolutePath());
+ }
+ skipNotificationPush = true;
+ mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile, () -> notifyIfPending(message));
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
synchronized (PgpDecryptionService.this) {
@@ -231,7 +235,9 @@ public class PgpDecryptionService {
}
}
}
- notifyIfPending(message);
+ if (!skipNotificationPush) {
+ notifyIfPending(message);
+ }
}
private synchronized void notifyIfPending(Message message) {
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java
index 09b3b1320..1a3c4436e 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java
@@ -46,7 +46,6 @@ import de.pixart.messenger.entities.Message;
import de.pixart.messenger.parser.IqParser;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.CryptoHelper;
-import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.utils.SerialSingleThreadExecutor;
import de.pixart.messenger.xml.Element;
import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded;
@@ -840,40 +839,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
});
}
- public enum AxolotlCapability {
- FULL,
- MISSING_PRESENCE,
- MISSING_KEYS,
- WRONG_CONFIGURATION,
- NO_MEMBERS
- }
-
- public boolean isConversationAxolotlCapable(Conversation conversation) {
- return conversation.isSingleOrPrivateAndNonAnonymous();
- }
-
- public Pair<AxolotlCapability, Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
- if (conversation.isSingleOrPrivateAndNonAnonymous()) {
- final List<Jid> jids = getCryptoTargets(conversation);
- for (Jid jid : jids) {
- if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
- if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
- return new Pair<>(AxolotlCapability.MISSING_KEYS, jid);
- } else {
- return new Pair<>(AxolotlCapability.MISSING_PRESENCE, jid);
- }
- }
- }
- if (jids.size() > 0) {
- return new Pair<>(AxolotlCapability.FULL, null);
- } else {
- return new Pair<>(AxolotlCapability.NO_MEMBERS, null);
- }
- } else {
- return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null);
- }
- }
-
public List<Jid> getCryptoTargets(Conversation conversation) {
final List<Jid> jids;
if (conversation.getMode() == Conversation.MODE_SINGLE) {
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
index 100ad1c43..60aa1e357 100644
--- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java
@@ -2,7 +2,6 @@ package de.pixart.messenger.crypto.axolotl;
import android.util.Base64;
import android.util.Log;
-import android.util.SparseArray;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
diff --git a/src/main/java/de/pixart/messenger/entities/Account.java b/src/main/java/de/pixart/messenger/entities/Account.java
index 2c7dbfbdc..3ebb1fafa 100644
--- a/src/main/java/de/pixart/messenger/entities/Account.java
+++ b/src/main/java/de/pixart/messenger/entities/Account.java
@@ -29,12 +29,14 @@ import de.pixart.messenger.crypto.OtrService;
import de.pixart.messenger.crypto.PgpDecryptionService;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession;
+import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.utils.XmppUri;
import de.pixart.messenger.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
-public class Account extends AbstractEntity {
+public class Account extends AbstractEntity implements AvatarService.Avatarable {
public static final String TABLENAME = "accounts";
@@ -616,6 +618,11 @@ public class Account extends AbstractEntity {
return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
}
+ @Override
+ public int getAvatarBackgroundColor() {
+ return UIHelper.getColorForName(jid.asBareJid().toString());
+ }
+
public enum State {
DISABLED(false, false),
OFFLINE(false),
diff --git a/src/main/java/de/pixart/messenger/entities/Bookmark.java b/src/main/java/de/pixart/messenger/entities/Bookmark.java
index e670673dd..0d1f5781d 100644
--- a/src/main/java/de/pixart/messenger/entities/Bookmark.java
+++ b/src/main/java/de/pixart/messenger/entities/Bookmark.java
@@ -186,4 +186,9 @@ public class Bookmark extends Element implements ListItem {
}
return StringUtils.changed(before, name);
}
+
+ @Override
+ public int getAvatarBackgroundColor() {
+ return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
+ }
}
diff --git a/src/main/java/de/pixart/messenger/entities/Contact.java b/src/main/java/de/pixart/messenger/entities/Contact.java
index aa7319ad9..8768191ad 100644
--- a/src/main/java/de/pixart/messenger/entities/Contact.java
+++ b/src/main/java/de/pixart/messenger/entities/Contact.java
@@ -4,7 +4,6 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.text.TextUtils;
@@ -463,10 +462,14 @@ public class Contact implements ListItem, Blockable {
}
public boolean setAvatar(Avatar avatar) {
+ return setAvatar(avatar, false);
+ }
+
+ public boolean setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
if (this.avatar != null && this.avatar.equals(avatar)) {
return false;
} else {
- if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
+ if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
return false;
}
this.avatar = avatar;
@@ -599,6 +602,11 @@ public class Contact implements ListItem, Blockable {
return Options.SYNCED_VIA_OTHER;
}
+ @Override
+ public int getAvatarBackgroundColor() {
+ return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
+ }
+
public final class Options {
public static final int TO = 0;
public static final int FROM = 1;
diff --git a/src/main/java/de/pixart/messenger/entities/Conversation.java b/src/main/java/de/pixart/messenger/entities/Conversation.java
index 502f2638d..121e9ed06 100644
--- a/src/main/java/de/pixart/messenger/entities/Conversation.java
+++ b/src/main/java/de/pixart/messenger/entities/Conversation.java
@@ -28,9 +28,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
import de.pixart.messenger.Config;
import de.pixart.messenger.crypto.OmemoSetting;
import de.pixart.messenger.crypto.PgpDecryptionService;
-import de.pixart.messenger.crypto.axolotl.AxolotlService;
+import de.pixart.messenger.persistance.DatabaseBackend;
+import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.services.QuickConversationsService;
import de.pixart.messenger.utils.JidHelper;
+import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xmpp.chatstate.ChatState;
import de.pixart.messenger.xmpp.mam.MamReference;
import rocks.xmpp.addr.Jid;
@@ -38,7 +40,7 @@ import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.entities.Bookmark.printableValue;
-public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational {
+public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
public static final String TABLENAME = "conversations";
public static final int STATUS_AVAILABLE = 0;
@@ -61,9 +63,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
private static final String ATTRIBUTE_NEXT_MESSAGE_TIMESTAMP = "next_message_timestamp";
private static final String ATTRIBUTE_CRYPTO_TARGETS = "crypto_targets";
private static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption";
- public static final String ATTRIBUTE_MEMBERS_ONLY = "members_only";
- public static final String ATTRIBUTE_MODERATED = "moderated";
- public static final String ATTRIBUTE_NON_ANONYMOUS = "non_anonymous";
+ static final String ATTRIBUTE_MEMBERS_ONLY = "members_only";
+ static final String ATTRIBUTE_MODERATED = "moderated";
+ static final String ATTRIBUTE_NON_ANONYMOUS = "non_anonymous";
+ public static final String ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS = "formerly_private_non_anonymous";
protected final ArrayList<Message> messages = new ArrayList<>();
public AtomicBoolean messagesLoaded = new AtomicBoolean(true);
protected Account account = null;
@@ -75,7 +78,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
private int status;
private long created;
private int mode;
- private JSONObject attributes = new JSONObject();
+ private JSONObject attributes;
private Jid nextCounterpart;
private transient SessionImpl otrSession;
private transient String otrFingerprint = null;
@@ -193,26 +196,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
}
- public void findMessagesWithFiles(final OnMessageFound onMessageFound) {
- final ArrayList<Message> results = new ArrayList<>();
- synchronized (this.messages) {
- for (final Message m : this.messages) {
- if (m.isFileOrImage() && m.getEncryption() != Message.ENCRYPTION_PGP) {
- results.add(m);
- }
- }
- }
- for (Message result : results) {
- onMessageFound.onMessageFound(result);
- }
- }
-
public void findFailedMessagesWithFiles(final OnMessageFound onMessageFound) {
final ArrayList<Message> results = new ArrayList<>();
synchronized (this.messages) {
for (final Message m : this.messages) {
if (m.isFileOrImage() && m.getEncryption() != Message.ENCRYPTION_PGP) {
- if (m.getStatus() == Message.STATUS_SEND_FAILED && !m.isDeleted() && m.needsUploading()) {
+ if (m.getStatus() == Message.STATUS_SEND_FAILED && !m.isFileDeleted() && m.needsUploading()) {
results.add(m);
}
}
@@ -227,7 +216,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
final ArrayList<Message> results = new ArrayList<>();
synchronized (this.messages) {
for (final Message m : this.messages) {
- if (m.isDeleted()) {
+ if (m.isFileDeleted()) {
results.add(m);
}
}
@@ -242,7 +231,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
for (final Message message : this.messages) {
if (message.getUuid().equals(uuid)
&& message.getEncryption() != Message.ENCRYPTION_PGP
- && (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.treatAsDownloadable())) {
+ && (message.isFileOrImage() || message.treatAsDownloadable())) {
return message;
}
}
@@ -250,6 +239,42 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null;
}
+ public boolean markAsDeleted(final List<String> uuids) {
+ boolean deleted = false;
+ final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService();
+ synchronized (this.messages) {
+ for (Message message : this.messages) {
+ if (uuids.contains(message.getUuid())) {
+ message.setFileDeleted(true);
+ deleted = true;
+ if (message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) {
+ pgpDecryptionService.discard(message);
+ }
+ }
+ }
+ }
+ return deleted;
+ }
+
+
+ public boolean markAsChanged(final List<DatabaseBackend.FilePathInfo> files) {
+ boolean changed = false;
+ final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService();
+ synchronized (this.messages) {
+ for (Message message : this.messages) {
+ for (final DatabaseBackend.FilePathInfo file : files)
+ if (file.uuid.toString().equals(message.getUuid())) {
+ message.setFileDeleted(file.FileDeleted);
+ changed = true;
+ if (file.FileDeleted && message.getEncryption() == Message.ENCRYPTION_PGP && pgpDecryptionService != null) {
+ pgpDecryptionService.discard(message);
+ }
+ }
+ }
+ }
+ return changed;
+ }
+
public void clearMessages() {
synchronized (this.messages) {
this.messages.clear();
@@ -569,19 +594,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
}
- public String getParticipants() {
- if (getMode() == MODE_MULTI) {
- String generatedName = getMucOptions().createNameFromParticipants();
- if (generatedName != null) {
- return generatedName;
- } else {
- return null;
- }
- } else {
- return null;
- }
- }
-
public String getAccountUuid() {
return this.accountUuid;
}
@@ -799,7 +811,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
- private static boolean suitableForOmemoByDefault(final Conversation conversation) {
+ public static boolean suitableForOmemoByDefault(final Conversation conversation) {
if (conversation.getJid().asBareJid().equals(Config.BUG_REPORTS)) {
return false;
}
@@ -811,12 +823,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (Config.OMEMO_EXCEPTIONS.CONTACT_DOMAINS.contains(contact) || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
return false;
}
- final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
- return axolotlService != null && axolotlService.isConversationAxolotlCapable(conversation);
+ return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute(ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false);
}
- public void setNextEncryption(int encryption) {
- this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, String.valueOf(encryption));
+ public boolean setNextEncryption(int encryption) {
+ return this.setAttribute(ATTRIBUTE_NEXT_ENCRYPTION, encryption);
}
public String getNextMessage() {
@@ -942,15 +953,17 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
public boolean setAttribute(String key, boolean value) {
- boolean prev = getBooleanAttribute(key, false);
- setAttribute(key, Boolean.toString(value));
- return prev != value;
+ return setAttribute(key, String.valueOf(value));
}
private boolean setAttribute(String key, long value) {
return setAttribute(key, Long.toString(value));
}
+ private boolean setAttribute(String key, int value) {
+ return setAttribute(key, String.valueOf(value));
+ }
+
public boolean setAttribute(String key, String value) {
synchronized (this.attributes) {
try {
@@ -1117,7 +1130,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
for (int i = this.messages.size() - 1; i >= 0; --i) {
Message message = this.messages.get(i);
if ((message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) && message.getEncryption() != Message.ENCRYPTION_PGP) {
- if (message.getStatus() == Message.STATUS_SEND_FAILED && !message.isDeleted() && message.needsUploading()){
+ if (message.getStatus() == Message.STATUS_SEND_FAILED && !message.isFileDeleted() && message.needsUploading()){
++count;
}
}
@@ -1179,6 +1192,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return 0;
}
+ @Override
+ public int getAvatarBackgroundColor() {
+ return UIHelper.getColorForName(getName().toString());
+ }
+
public interface OnMessageFound {
void onMessageFound(final Message message);
}
diff --git a/src/main/java/de/pixart/messenger/entities/IndividualMessage.java b/src/main/java/de/pixart/messenger/entities/IndividualMessage.java
index 2a7b4c8da..195ea82c1 100644
--- a/src/main/java/de/pixart/messenger/entities/IndividualMessage.java
+++ b/src/main/java/de/pixart/messenger/entities/IndividualMessage.java
@@ -41,8 +41,8 @@ public class IndividualMessage extends Message {
super(conversation);
}
- private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable) {
- super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable);
+ private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean file_deleted) {
+ super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted);
}
public static Message createDateSeparator(Message message) {
@@ -99,7 +99,8 @@ public class IndividualMessage extends Message {
cursor.getInt(cursor.getColumnIndex(OOB)) > 0,
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
- cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0);
+ cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
+ cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0);
}
@Override
diff --git a/src/main/java/de/pixart/messenger/entities/ListItem.java b/src/main/java/de/pixart/messenger/entities/ListItem.java
index 21fb6c2b6..fba37cc4c 100644
--- a/src/main/java/de/pixart/messenger/entities/ListItem.java
+++ b/src/main/java/de/pixart/messenger/entities/ListItem.java
@@ -4,9 +4,10 @@ import android.content.Context;
import java.util.List;
+import de.pixart.messenger.services.AvatarService;
import rocks.xmpp.addr.Jid;
-public interface ListItem extends Comparable<ListItem> {
+public interface ListItem extends Comparable<ListItem>, AvatarService.Avatarable {
String getDisplayName();
int getOffline();
diff --git a/src/main/java/de/pixart/messenger/entities/Message.java b/src/main/java/de/pixart/messenger/entities/Message.java
index abeda3ad3..ba44a10ae 100644
--- a/src/main/java/de/pixart/messenger/entities/Message.java
+++ b/src/main/java/de/pixart/messenger/entities/Message.java
@@ -2,6 +2,7 @@ package de.pixart.messenger.entities;
import android.content.ContentValues;
import android.database.Cursor;
+import android.graphics.Color;
import android.text.SpannableStringBuilder;
import android.util.Log;
@@ -16,6 +17,7 @@ import java.util.Set;
import de.pixart.messenger.Config;
import de.pixart.messenger.crypto.axolotl.FingerprintStatus;
+import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.Emoticons;
import de.pixart.messenger.utils.GeoHelper;
@@ -25,7 +27,7 @@ import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.utils.XmppUri;
import rocks.xmpp.addr.Jid;
-public class Message extends AbstractEntity {
+public class Message extends AbstractEntity implements AvatarService.Avatarable {
public static final String TABLENAME = "messages";
@@ -73,8 +75,9 @@ public class Message extends AbstractEntity {
public static final String ERROR_MESSAGE = "errorMsg";
public static final String READ_BY_MARKERS = "readByMarkers";
public static final String MARKABLE = "markable";
+ public static final String FILE_DELETED = "file_deleted";
public static final String ME_COMMAND = "/me";
- public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled";
+ public static final String ERROR_MESSAGE_CANCELLED = "de.pixart.messenger.cancelled";
public boolean markable = false;
protected String conversationUuid;
@@ -86,6 +89,7 @@ public class Message extends AbstractEntity {
protected int encryption;
protected int status;
protected int type;
+ protected boolean file_deleted = false;
protected boolean carbon = false;
protected boolean oob = false;
protected String edited = null;
@@ -139,6 +143,7 @@ public class Message extends AbstractEntity {
false,
null,
null,
+ false,
false);
}
@@ -148,7 +153,7 @@ public class Message extends AbstractEntity {
final String remoteMsgId, final String relativeFilePath,
final String serverMsgId, final String fingerprint, final boolean read, final boolean deleted,
final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
- final boolean markable) {
+ final boolean markable, final boolean file_deleted) {
this.conversation = conversation;
this.uuid = uuid;
this.conversationUuid = conversationUUid;
@@ -171,6 +176,7 @@ public class Message extends AbstractEntity {
this.errorMessage = errorMessage;
this.readByMarkers = readByMarkers == null ? new HashSet<ReadByMarker>() : readByMarkers;
this.markable = markable;
+ this.file_deleted = file_deleted;
}
public static Message fromCursor(Cursor cursor, Conversation conversation) {
@@ -219,7 +225,8 @@ public class Message extends AbstractEntity {
cursor.getInt(cursor.getColumnIndex(OOB)) > 0,
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
- cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0);
+ cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
+ cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0);
}
public static Message createStatusMessage(Conversation conversation, String body) {
@@ -269,6 +276,7 @@ public class Message extends AbstractEntity {
values.put(ERROR_MESSAGE, errorMessage);
values.put(READ_BY_MARKERS, ReadByMarker.toJson(readByMarkers).toString());
values.put(MARKABLE, markable ? 1 : 0);
+ values.put(FILE_DELETED, file_deleted ? 1 : 0);
return values;
}
@@ -390,6 +398,14 @@ public class Message extends AbstractEntity {
return this.deleted;
}
+ public boolean isFileDeleted() {
+ return this.file_deleted;
+ }
+
+ public void setFileDeleted(boolean file_deleted) {
+ this.file_deleted = file_deleted;
+ }
+
public void markRead() {
this.read = true;
}
@@ -619,6 +635,15 @@ public class Message extends AbstractEntity {
return this.counterparts;
}
+ @Override
+ public int getAvatarBackgroundColor() {
+ if (type == Message.TYPE_STATUS && getCounterparts() != null && getCounterparts().size() > 1) {
+ return Color.TRANSPARENT;
+ } else {
+ return UIHelper.getColorForName(UIHelper.getMessageDisplayName(this));
+ }
+ }
+
public static class MergeSeparator {
}
@@ -840,10 +865,6 @@ public class Message extends AbstractEntity {
return type == TYPE_FILE || type == TYPE_IMAGE;
}
- public boolean isDeleted() {
- return (type == TYPE_FILE || type == TYPE_IMAGE) && getTransferable() != null && getTransferable().getFileSize() == 0;
- }
-
public boolean hasFileOnRemoteHost() {
return isFileOrImage() && getFileParams().url != null;
}
diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java
index 6954995d9..c3050f3fa 100644
--- a/src/main/java/de/pixart/messenger/entities/MucOptions.java
+++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java
@@ -14,6 +14,7 @@ import java.util.Set;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
+import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.services.MessageArchiveService;
import de.pixart.messenger.utils.JidHelper;
import de.pixart.messenger.utils.UIHelper;
@@ -45,6 +46,7 @@ public class MucOptions {
private User self;
private String password = null;
private boolean tookProposedNickFromBookmark = false;
+
public MucOptions(Conversation conversation) {
this.account = conversation.getAccount();
this.conversation = conversation;
@@ -101,8 +103,9 @@ public class MucOptions {
return tookProposedNickFromBookmark;
}
- void notifyOfBookmarkNick(String nick) {
- if (nick != null && nick.trim().equals(getSelf().getFullJid().getResource())) {
+ void notifyOfBookmarkNick(final String nick) {
+ final String normalized = normalize(account.getJid(), nick);
+ if (normalized != null && normalized.equals(getSelf().getFullJid().getResource())) {
this.tookProposedNickFromBookmark = true;
}
}
@@ -152,13 +155,21 @@ public class MucOptions {
}
public boolean canInvite() {
- Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
- return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
+ return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites();
+ }
+
+ public boolean allowInvites() {
+ final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
+ return field != null && "1".equals(field.getValue());
}
public boolean canChangeSubject() {
- Field field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
- return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
+ return self.getRole().ranks(Role.MODERATOR) || participantsCanChangeSubject();
+ }
+
+ public boolean participantsCanChangeSubject() {
+ final Field field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
+ return field != null && "1".equals(field.getValue());
}
public boolean allowPm() {
@@ -380,6 +391,21 @@ public class MucOptions {
return subset;
}
+ public static List<User> sub(List<User> users, int max) {
+ ArrayList<User> subset = new ArrayList<>();
+ HashSet<Jid> jids = new HashSet<>();
+ for (User user : users) {
+ jids.add(user.getAccount().getJid().asBareJid());
+ if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
+ subset.add(user);
+ }
+ if (subset.size() >= max) {
+ break;
+ }
+ }
+ return subset;
+ }
+
public int getUserCount() {
synchronized (users) {
return users.size();
@@ -388,15 +414,15 @@ public class MucOptions {
private String getProposedNick() {
final Bookmark bookmark = this.conversation.getBookmark();
- final String bookmarkedNick = bookmark == null ? null : bookmark.getNick();
- if (bookmarkedNick != null && !bookmarkedNick.trim().isEmpty()) {
+ final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
+ if (bookmarkedNick != null) {
this.tookProposedNickFromBookmark = true;
- return bookmarkedNick.trim();
+ return bookmarkedNick;
} else if (!conversation.getJid().isBareJid()) {
return conversation.getJid().getResource();
} else {
- final String displayName = account.getDisplayName();
- if (TextUtils.isEmpty(displayName)) {
+ final String displayName = normalize(account.getJid(), account.getDisplayName());
+ if (displayName == null) {
return JidHelper.localPartOrFallback(account.getJid());
} else {
return displayName;
@@ -404,6 +430,18 @@ public class MucOptions {
}
}
+ private static String normalize(Jid account, String nick) {
+ if (account == null || TextUtils.isEmpty(nick)) {
+ return null;
+ }
+ try {
+ return account.withResource(nick).getResource();
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ }
+
public String getActualNick() {
if (this.self.getName() != null) {
return this.self.getName();
@@ -473,7 +511,7 @@ public class MucOptions {
return users;
}
- public String createNameFromParticipants() {
+ String createNameFromParticipants() {
List<User> users = getUsersRelevantForNameAndAvatar();
if (users.size() >= 2) {
StringBuilder builder = new StringBuilder();
@@ -531,7 +569,7 @@ public class MucOptions {
public Jid createJoinJid(String nick) {
try {
- return Jid.of(this.conversation.getJid().asBareJid().toString() + "/" + nick);
+ return conversation.getJid().withResource(nick);
} catch (final IllegalArgumentException e) {
return null;
}
@@ -589,6 +627,7 @@ public class MucOptions {
private int resId;
private int rank;
+
Affiliation(int rank, int resId) {
this.resId = resId;
this.rank = rank;
@@ -631,6 +670,7 @@ public class MucOptions {
private int resId;
private int rank;
+
Role(int resId, int rank) {
this.resId = resId;
this.rank = rank;
@@ -689,7 +729,7 @@ public class MucOptions {
}
- public static class User implements Comparable<User> {
+ public static class User implements Comparable<User>, AvatarService.Avatarable {
private Role role = Role.NONE;
private Affiliation affiliation = Affiliation.NONE;
private Jid realJid;
@@ -825,7 +865,7 @@ public class MucOptions {
}
}
- private String getComparableName() {
+ public String getComparableName() {
Contact contact = getContact();
if (contact != null) {
return contact.getDisplayName();
@@ -850,5 +890,11 @@ public class MucOptions {
this.chatState = chatState;
return true;
}
+
+ @Override
+ public int getAvatarBackgroundColor() {
+ final String seed = realJid != null ? realJid.asBareJid().toString() : null;
+ return UIHelper.getColorForName(seed == null ? getName() : seed);
+ }
}
}
diff --git a/src/main/java/de/pixart/messenger/entities/Transferable.java b/src/main/java/de/pixart/messenger/entities/Transferable.java
index c07ec750d..e1deb744d 100644
--- a/src/main/java/de/pixart/messenger/entities/Transferable.java
+++ b/src/main/java/de/pixart/messenger/entities/Transferable.java
@@ -18,50 +18,12 @@ public interface Transferable {
"gpg",
"otr"
);
- List<String> WELL_KNOWN_EXTENSIONS = Arrays.asList(
- //documents
- "pdf",
- "doc",
- "docx",
- "txt",
- //audio
- "m4a",
- "m4b",
- "mp3",
- "mp2",
- "wav",
- "aac",
- "aif",
- "aiff",
- "aifc",
- "mid",
- "midi",
- "3gpp",
- //video
- "avi",
- "mp4",
- "mpeg",
- "mpg",
- "mpe",
- "mov",
- "3gp",
- //applications
- "apk",
- //contact
- "vcf",
- //calendar
- "ics",
- //compressed
- "zip",
- "rar"
- );
int STATUS_UNKNOWN = 0x200;
int STATUS_CHECKING = 0x201;
int STATUS_FAILED = 0x202;
int STATUS_OFFER = 0x203;
int STATUS_DOWNLOADING = 0x204;
- int STATUS_DELETED = 0x205;
int STATUS_OFFER_CHECK_FILESIZE = 0x206;
int STATUS_UPLOADING = 0x207;
diff --git a/src/main/java/de/pixart/messenger/generator/IqGenerator.java b/src/main/java/de/pixart/messenger/generator/IqGenerator.java
index b2f43cbcf..024e7ab43 100644
--- a/src/main/java/de/pixart/messenger/generator/IqGenerator.java
+++ b/src/main/java/de/pixart/messenger/generator/IqGenerator.java
@@ -466,18 +466,34 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
- public static Bundle defaultRoomConfiguration() {
+ public static Bundle defaultGroupChatConfiguration() {
Bundle options = new Bundle();
options.putString("muc#roomconfig_persistentroom", "1");
options.putString("muc#roomconfig_membersonly", "1");
options.putString("muc#roomconfig_publicroom", "0");
options.putString("muc#roomconfig_whois", "anyone");
+ options.putString("muc#roomconfig_changesubject", "0");
+ options.putString("muc#roomconfig_allowinvites", "0");
options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
options.putString("mam", "1"); //ejabberd community
options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
return options;
}
+ public static Bundle defaultChannelConfiguration() {
+ Bundle options = new Bundle();
+ options.putString("muc#roomconfig_persistentroom", "1");
+ options.putString("muc#roomconfig_membersonly", "0");
+ options.putString("muc#roomconfig_publicroom", "1");
+ options.putString("muc#roomconfig_whois", "moderators");
+ options.putString("muc#roomconfig_changesubject", "0");
+ options.putString("muc#roomconfig_enablearchiving", "1"); //prosody
+ options.putString("mam", "1"); //ejabberd community
+ options.putString("muc#roomconfig_mam", "1"); //ejabberd saas
+ return options;
+ }
+
+
public IqPacket requestPubsubConfiguration(Jid jid, String node) {
return pubsubConfiguration(jid, node, null);
}
diff --git a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java
index fbb93d916..84c4aa1bb 100644
--- a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java
+++ b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java
@@ -1,5 +1,7 @@
package de.pixart.messenger.http;
+import android.util.Log;
+
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import java.io.IOException;
@@ -9,14 +11,15 @@ import java.net.Proxy;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
+import de.pixart.messenger.Config;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.services.AbstractConnectionManager;
@@ -25,28 +28,47 @@ import de.pixart.messenger.utils.TLSSocketFactory;
public class HttpConnectionManager extends AbstractConnectionManager {
+ private final List<HttpDownloadConnection> downloadConnections = new ArrayList<>();
+ private final List<HttpUploadConnection> uploadConnections = new ArrayList<>();
+
public HttpConnectionManager(XmppConnectionService service) {
super(service);
}
- private List<HttpDownloadConnection> downloadConnections = new CopyOnWriteArrayList<>();
- private List<HttpUploadConnection> uploadConnections = new CopyOnWriteArrayList<>();
+ public static Proxy getProxy() throws IOException {
+ return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByAddress(new byte[]{127, 0, 0, 1}), 8118));
+ }
- public HttpDownloadConnection createNewDownloadConnection(Message message) {
- return this.createNewDownloadConnection(message, false);
+ public void createNewDownloadConnection(Message message) {
+ this.createNewDownloadConnection(message, false);
}
- public HttpDownloadConnection createNewDownloadConnection(Message message, boolean interactive) {
- HttpDownloadConnection connection = new HttpDownloadConnection(this);
- connection.init(message, interactive);
- this.downloadConnections.add(connection);
- return connection;
+ public void createNewDownloadConnection(final Message message, boolean interactive) {
+ synchronized (this.downloadConnections) {
+ for (HttpDownloadConnection connection : this.downloadConnections) {
+ if (connection.getMessage() == message) {
+ Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": download already in progress");
+ return;
+ }
+ }
+ final HttpDownloadConnection connection = new HttpDownloadConnection(message, this);
+ connection.init(interactive);
+ this.downloadConnections.add(connection);
+ }
}
- public void createNewUploadConnection(Message message, boolean delay) {
- HttpUploadConnection connection = new HttpUploadConnection(Method.determine(message.getConversation().getAccount()), this);
- connection.init(message, delay);
- this.uploadConnections.add(connection);
+ public void createNewUploadConnection(final Message message, boolean delay) {
+ synchronized (this.uploadConnections) {
+ for (HttpUploadConnection connection : this.uploadConnections) {
+ if (connection.getMessage() == message) {
+ Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": upload already in progress");
+ return;
+ }
+ }
+ HttpUploadConnection connection = new HttpUploadConnection(message, Method.determine(message.getConversation().getAccount()), this);
+ connection.init(delay);
+ this.uploadConnections.add(connection);
+ }
}
public boolean checkConnection(Message message) {
@@ -58,15 +80,19 @@ public class HttpConnectionManager extends AbstractConnectionManager {
return mXmppConnectionService.hasInternetConnection();
}
- public void finishConnection(HttpDownloadConnection connection) {
- this.downloadConnections.remove(connection);
+ void finishConnection(HttpDownloadConnection connection) {
+ synchronized (this.downloadConnections) {
+ this.downloadConnections.remove(connection);
+ }
}
- public void finishUploadConnection(HttpUploadConnection httpUploadConnection) {
- this.uploadConnections.remove(httpUploadConnection);
+ void finishUploadConnection(HttpUploadConnection httpUploadConnection) {
+ synchronized (this.uploadConnections) {
+ this.uploadConnections.remove(httpUploadConnection);
+ }
}
- public void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) {
+ void setupTrustManager(final HttpsURLConnection connection, final boolean interactive) {
final X509TrustManager trustManager;
final HostnameVerifier hostnameVerifier = mXmppConnectionService.getMemorizingTrustManager().wrapHostnameVerifier(new StrictHostnameVerifier(), interactive);
if (interactive) {
@@ -81,8 +107,4 @@ public class HttpConnectionManager extends AbstractConnectionManager {
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
}
}
-
- public static Proxy getProxy() throws IOException {
- return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByAddress(new byte[]{127, 0, 0, 1}), 8118));
- }
-}
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java
index cf867d796..457292e27 100644
--- a/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java
+++ b/src/main/java/de/pixart/messenger/http/HttpDownloadConnection.java
@@ -25,7 +25,6 @@ import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.Transferable;
-import de.pixart.messenger.entities.TransferablePlaceholder;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.services.AbstractConnectionManager;
import de.pixart.messenger.services.XmppConnectionService;
@@ -41,7 +40,7 @@ public class HttpDownloadConnection implements Transferable {
private XmppConnectionService mXmppConnectionService;
private URL mUrl;
- private Message message;
+ private final Message message;
private DownloadableFile file;
private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
@@ -51,8 +50,8 @@ public class HttpDownloadConnection implements Transferable {
private Method method = Method.HTTP_UPLOAD;
private final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
-
- HttpDownloadConnection(HttpConnectionManager manager) {
+ HttpDownloadConnection(Message message, HttpConnectionManager manager) {
+ this.message = message;
this.mHttpConnectionManager = manager;
this.mXmppConnectionService = manager.getXmppConnectionService();
this.mUseTor = mXmppConnectionService.useTorToConnect();
@@ -72,12 +71,7 @@ public class HttpDownloadConnection implements Transferable {
}
}
- public void init(Message message) {
- init(message, false);
- }
-
- public void init(Message message, boolean interactive) {
- this.message = message;
+ public void init(boolean interactive) {
this.message.setTransferable(this);
try {
if (message.hasFileOnRemoteHost()) {
@@ -137,16 +131,14 @@ public class HttpDownloadConnection implements Transferable {
public void cancel() {
this.canceled = true;
mHttpConnectionManager.finishConnection(this);
+ message.setTransferable(null);
if (message.isFileOrImage()) {
- message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- } else {
- message.setTransferable(null);
+ message.setFileDeleted(true);
}
mHttpConnectionManager.updateConversationUi(true);
}
private void finish() {
- mXmppConnectionService.getFileBackend().updateMediaScanner(file);
message.setTransferable(null);
mHttpConnectionManager.finishConnection(this);
boolean notify = acceptedAutomatically && !message.isRead();
@@ -154,9 +146,12 @@ public class HttpDownloadConnection implements Transferable {
notify = message.getConversation().getAccount().getPgpDecryptionService().decrypt(message, notify);
}
mHttpConnectionManager.updateConversationUi(true);
- if (notify) {
- mXmppConnectionService.getNotificationService().push(message);
- }
+ final boolean notifyAfterScan = notify;
+ mXmppConnectionService.getFileBackend().updateMediaScanner(file, () -> {
+ if (notifyAfterScan) {
+ mXmppConnectionService.getNotificationService().push(message);
+ }
+ });
}
private void changeStatus(int status) {
@@ -200,6 +195,10 @@ public class HttpDownloadConnection implements Transferable {
return this.mProgress;
}
+ public Message getMessage() {
+ return message;
+ }
+
private class FileSizeChecker implements Runnable {
private final boolean interactive;
@@ -379,9 +378,9 @@ public class HttpDownloadConnection implements Transferable {
connection.setUseCaches(false);
connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getUserAgent());
connection.setRequestProperty("Accept-Encoding", "identity");
- final boolean tryResume = file.exists() && file.getKey() == null && file.getSize() > 0;
+ final long expected = file.getExpectedSize();
+ final boolean tryResume = file.exists() && file.getKey() == null && file.getSize() > 0 && file.getSize() < expected;
long resumeSize = 0;
- long expected = file.getExpectedSize();
if (tryResume) {
resumeSize = file.getSize();
Log.d(Config.LOGTAG, "http download trying resume after" + resumeSize + " of " + expected);
@@ -466,4 +465,4 @@ public class HttpDownloadConnection implements Transferable {
}
}
-}
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java
index 49debcb78..92ccf86e1 100644
--- a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java
+++ b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java
@@ -43,14 +43,15 @@ public class HttpUploadConnection implements Transferable {
private boolean cancelled = false;
private boolean delayed = false;
private DownloadableFile file;
- private Message message;
+ private final Message message;
private String mime;
private SlotRequester.Slot slot;
private byte[] key = null;
private long transmitted = 0;
- public HttpUploadConnection(Method method, HttpConnectionManager httpConnectionManager) {
+ public HttpUploadConnection(Message message, Method method, HttpConnectionManager httpConnectionManager) {
+ this.message = message;
this.method = method;
this.mHttpConnectionManager = httpConnectionManager;
this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
@@ -87,13 +88,16 @@ public class HttpUploadConnection implements Transferable {
}
private void fail(String errorMessage) {
+ finish();
+ mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage);
+ }
+
+ private void finish() {
mHttpConnectionManager.finishUploadConnection(this);
message.setTransferable(null);
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage);
}
- public void init(Message message, boolean delay) {
- this.message = message;
+ public void init(boolean delay) {
final Account account = message.getConversation().getAccount();
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
@@ -123,6 +127,7 @@ public class HttpUploadConnection implements Transferable {
} else {
md5 = null;
}
+
this.file.setExpectedSize(file.getSize() + (file.getKey() != null ? 16 : 0));
message.resetFileParams();
this.mSlotRequester.request(method, account, file, mime, md5, new SlotRequester.OnSlotRequested() {
@@ -210,7 +215,7 @@ public class HttpUploadConnection implements Transferable {
}
mXmppConnectionService.getFileBackend().updateFileParams(message, get);
mXmppConnectionService.getFileBackend().updateMediaScanner(file);
- message.setTransferable(null);
+ finish();
message.setCounterpart(message.getConversation().getJid().asBareJid());
mXmppConnectionService.resendMessage(message, delayed);
} else {
@@ -230,4 +235,8 @@ public class HttpUploadConnection implements Transferable {
WakeLockHelper.release(wakeLock);
}
}
-}
+
+ public Message getMessage() {
+ return message;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java
index d8461d940..a1b6f498a 100644
--- a/src/main/java/de/pixart/messenger/parser/MessageParser.java
+++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java
@@ -278,6 +278,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (account.getJid().asBareJid().equals(from)) {
if (account.setAvatar(avatar.getFilename())) {
mXmppConnectionService.databaseBackend.updateAccount(account);
+ mXmppConnectionService.notifyAccountAvatarHasChanged(account);
}
mXmppConnectionService.getAvatarService().clear(account);
mXmppConnectionService.updateConversationUi();
@@ -650,7 +651,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
mXmppConnectionService.updateMessage(replacedMessage, uuid);
- mXmppConnectionService.getNotificationService().updateNotification(false);
if (mXmppConnectionService.confirmMessages()
&& replacedMessage.getStatus() == Message.STATUS_RECEIVED
&& (replacedMessage.trusted() || replacedMessage.getType() == Message.TYPE_PRIVATE)
@@ -664,6 +664,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
}
}
+ mXmppConnectionService.getNotificationService().updateNotification();
return;
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
diff --git a/src/main/java/de/pixart/messenger/parser/PresenceParser.java b/src/main/java/de/pixart/messenger/parser/PresenceParser.java
index 0e0b67382..0c90dd4ac 100644
--- a/src/main/java/de/pixart/messenger/parser/PresenceParser.java
+++ b/src/main/java/de/pixart/messenger/parser/PresenceParser.java
@@ -95,7 +95,7 @@ public class PresenceParser extends AbstractParser implements
+ mucOptions.getConversation().getJid().asBareJid()
+ "' created. pushing default configuration");
mXmppConnectionService.pushConferenceConfiguration(mucOptions.getConversation(),
- IqGenerator.defaultRoomConfiguration(),
+ IqGenerator.defaultChannelConfiguration(),
null);
}
if (mXmppConnectionService.getPgpEngine() != null) {
diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
index 85d32d55f..8336a4ed9 100644
--- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
+++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
@@ -60,7 +60,7 @@ import rocks.xmpp.addr.Jid;
public class DatabaseBackend extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "history";
- public static final int DATABASE_VERSION = 44; // = Conversations DATABASE_VERSION + 2
+ public static final int DATABASE_VERSION = 46; // = Conversations DATABASE_VERSION + 2
private static DatabaseBackend instance = null;
private static String CREATE_CONTATCS_STATEMENT = "create table "
@@ -136,6 +136,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ SQLiteAxolotlStore.OWN + " INTEGER, "
+ SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
+ SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
+ + SQLiteAxolotlStore.TRUSTED + " TEXT, "
+ SQLiteAxolotlStore.TRUST + " TEXT, "
+ SQLiteAxolotlStore.ACTIVE + " NUMBER, "
+ SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER,"
@@ -161,6 +162,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ ");";
private static String CREATE_MESSAGE_TIME_INDEX = "create INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")";
private static String CREATE_MESSAGE_CONVERSATION_INDEX = "create INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")";
+ private static String CREATE_MESSAGE_DELETED_INDEX = "create index message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")";
+ private static String CREATE_MESSAGE_FILE_DELETED_INDEX = "create index message_file_deleted_index ON " + Message.TABLENAME + "(" + Message.FILE_DELETED + ")";
+ private static String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "create INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")";
+ private static String CREATE_MESSAGE_TYPE_INDEX = "create INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")";
private static String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING FTS4(uuid TEXT PRIMARY KEY, body TEXT)";
private static String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;";
@@ -233,6 +238,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Message.ERROR_MESSAGE + " TEXT,"
+ Message.READ_BY_MARKERS + " TEXT,"
+ Message.MARKABLE + " NUMBER DEFAULT 0,"
+ + Message.FILE_DELETED + " NUMBER DEFAULT 0,"
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@@ -240,6 +246,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL(CREATE_MESSAGE_TIME_INDEX);
db.execSQL(CREATE_MESSAGE_CONVERSATION_INDEX);
+ db.execSQL(CREATE_MESSAGE_DELETED_INDEX);
+ db.execSQL(CREATE_MESSAGE_FILE_DELETED_INDEX);
+ db.execSQL(CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX);
+ db.execSQL(CREATE_MESSAGE_TYPE_INDEX);
db.execSQL(CREATE_CONTATCS_STATEMENT);
db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
db.execSQL(CREATE_SESSIONS_STATEMENT);
@@ -337,7 +347,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
/* Any migrations that alter the Account table need to happen BEFORE this migration, as it
* depends on account de-serialization.
- */
+ */
if (oldVersion < 17 && newVersion >= 17 && newVersion < 31) {
List<Account> accounts = getAccounts(db);
for (Account account : accounts) {
@@ -535,6 +545,34 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if (oldVersion < 44 && newVersion >= 44) {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.DELETED + " NUMBER DEFAULT 0");
}
+
+ if (oldVersion < 45 && newVersion >= 45) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.FILE_DELETED + " NUMBER DEFAULT 0");
+ db.execSQL(CREATE_MESSAGE_DELETED_INDEX);
+ db.execSQL(CREATE_MESSAGE_FILE_DELETED_INDEX);
+ db.execSQL(CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX);
+ db.execSQL(CREATE_MESSAGE_TYPE_INDEX);
+ }
+
+ if (oldVersion < 46 && newVersion >= 46) {
+ if (!isColumnExisting(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, SQLiteAxolotlStore.TRUSTED)) {
+ db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.TRUSTED); // TODO - just to make old databases importable, column isn't needed at all
+ }
+ }
+ }
+
+ private boolean isColumnExisting(SQLiteDatabase db, String TableName, String ColumnName) {
+ boolean isExist = false;
+ Cursor cursor = db.rawQuery("PRAGMA table_info(" + TableName + ")", null);
+ cursor.moveToFirst();
+ do {
+ String currentColumn = cursor.getString(1);
+ if (currentColumn.equals(ColumnName)) {
+ isExist = true;
+ }
+ } while (cursor.moveToNext());
+ cursor.close();
+ return isExist;
}
private void canonicalizeJids(SQLiteDatabase db) {
@@ -797,9 +835,77 @@ public class DatabaseBackend extends SQLiteOpenHelper {
};
}
+ public List<String> markFileAsDeleted(final File file, final boolean internal) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String selection;
+ String[] selectionArgs;
+ if (internal) {
+ final String name = file.getName();
+ if (name.endsWith(".pgp")) {
+ selection = "(" + Message.RELATIVE_FILE_PATH + " IN(?,?) OR (" + Message.RELATIVE_FILE_PATH + "=? and encryption in(1,4))) and type in (1,2)";
+ selectionArgs = new String[]{file.getAbsolutePath(), name, name.substring(0, name.length() - 4)};
+ } else {
+ selection = Message.RELATIVE_FILE_PATH + " IN(?,?) and type in (1,2)";
+ selectionArgs = new String[]{file.getAbsolutePath(), name};
+ }
+ } else {
+ selection = Message.RELATIVE_FILE_PATH + "=? and type in (1,2)";
+ selectionArgs = new String[]{file.getAbsolutePath()};
+ }
+ final List<String> uuids = new ArrayList<>();
+ Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID}, selection, selectionArgs, null, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ uuids.add(cursor.getString(0));
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ markFileAsDeleted(uuids);
+ return uuids;
+ }
+
+ public void markFileAsDeleted(List<String> uuids) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ final ContentValues contentValues = new ContentValues();
+ final String where = Message.UUID + "=?";
+ contentValues.put(Message.FILE_DELETED, 1);
+ db.beginTransaction();
+ for (String uuid : uuids) {
+ db.update(Message.TABLENAME, contentValues, where, new String[]{uuid});
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void markFilesAsChanged(List<FilePathInfo> files) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ final String where = Message.UUID + "=?";
+ db.beginTransaction();
+ for (FilePathInfo info : files) {
+ final ContentValues contentValues = new ContentValues();
+ contentValues.put(Message.FILE_DELETED, info.FileDeleted ? 1 : 0);
+ db.update(Message.TABLENAME, contentValues, where, new String[]{info.uuid.toString()});
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public List<FilePathInfo> getFilePathInfo() {
+ final SQLiteDatabase db = this.getReadableDatabase();
+ final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH, Message.FILE_DELETED}, "type in (1,2) and " + Message.RELATIVE_FILE_PATH + " is not null", null, null, null, null);
+ final List<FilePathInfo> list = new ArrayList<>();
+ while (cursor != null && cursor.moveToNext()) {
+ list.add(new FilePathInfo(cursor.getString(0), cursor.getString(1), cursor.getInt(2) > 0));
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ return list;
+ }
+
public List<FilePath> getRelativeFilePaths(String account, Jid jid, int limit) {
SQLiteDatabase db = this.getReadableDatabase();
- final String SQL = "select uuid,relativeFilePath from messages where type in (1,2) and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc";
+ final String SQL = "select uuid,relativeFilePath from messages where type in (1,2) and file_deleted=0 and " + Message.RELATIVE_FILE_PATH + " is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc";
final String[] args = {account, jid.toEscapedString(), jid.toEscapedString() + "/%"};
Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + String.valueOf(limit) : ""), args);
List<FilePath> filesPaths = new ArrayList<>();
@@ -820,6 +926,21 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
+ public static class FilePathInfo extends FilePath {
+ public boolean FileDeleted;
+
+ private FilePathInfo(String uuid, String path, boolean deleted) {
+ super(uuid, path);
+ this.FileDeleted = deleted;
+ }
+
+ public boolean setFileDeleted(boolean deleted) {
+ final boolean changed = deleted != this.FileDeleted;
+ this.FileDeleted = deleted;
+ return changed;
+ }
+ }
+
public Conversation findConversation(final Account account, final Jid contactJid) {
SQLiteDatabase db = this.getReadableDatabase();
String[] selectionArgs = {account.getUuid(),
@@ -854,11 +975,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return getAccounts(db);
}
- public List<Jid> getAccountJids() {
+ public List<Jid> getAccountJids(final boolean enabledOnly) {
SQLiteDatabase db = this.getReadableDatabase();
final List<Jid> jids = new ArrayList<>();
final String[] columns = new String[]{Account.USERNAME, Account.SERVER};
- String where = "not options & (1 <<1)";
+ String where = enabledOnly ? "not options & (1 <<1)" : null;
Cursor cursor = db.query(Account.TABLENAME, columns, where, null, null, null, null);
try {
while (cursor.moveToNext()) {
@@ -874,6 +995,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
+
private List<Account> getAccounts(SQLiteDatabase db) {
List<Account> list = new ArrayList<>();
Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
@@ -910,11 +1032,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args) == 1;
}
- public void updateMessage(Message message, String uuid) {
+ public boolean updateMessage(Message message, String uuid) {
SQLiteDatabase db = this.getWritableDatabase();
String[] args = {uuid};
- db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
- + "=?", args);
+ return db.update(Message.TABLENAME, message.getContentValues(), Message.UUID + "=?", args) == 1;
}
public void readRoster(Roster roster) {
@@ -953,10 +1074,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void deleteMessageInConversation(Message message) {
long start = SystemClock.elapsedRealtime();
final SQLiteDatabase db = this.getWritableDatabase();
+ db.beginTransaction();
ContentValues values = new ContentValues();
values.put(Message.DELETED, "1");
String[] args = {message.getUuid()};
int rows = db.update("messages", values, "uuid =?", args);
+ db.setTransactionSuccessful();
+ db.endTransaction();
Log.d(Config.LOGTAG, "deleted " + rows + " message in " + (SystemClock.elapsedRealtime() - start) + "ms");
}
@@ -1527,7 +1651,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
storeIdentityKey(account, account.getJid().asBareJid().toString(), true, CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize()), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false));
}
- public void recreateAxolotlDb(SQLiteDatabase db) {
+
+ private void recreateAxolotlDb(SQLiteDatabase db) {
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
db.execSQL(CREATE_SESSIONS_STATEMENT);
diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java
index 705e5489b..0023eec37 100644
--- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java
+++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java
@@ -3,7 +3,6 @@ package de.pixart.messenger.persistance;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
@@ -15,6 +14,7 @@ import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.media.MediaMetadataRetriever;
+import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
@@ -101,15 +101,57 @@ public class FileBackend {
}
}
+ public static Uri getMediaUri(Context context, File file) {
+ final String filePath = file.getAbsolutePath();
+ final Cursor cursor = context.getContentResolver().query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[]{MediaStore.Images.Media._ID},
+ MediaStore.Images.Media.DATA + "=? ",
+ new String[]{filePath}, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
+ cursor.close();
+ return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
+ } else {
+ return null;
+ }
+ }
+
public void updateMediaScanner(File file) {
+ updateMediaScanner(file, null);
+ }
+
+ public void updateMediaScanner(File file, final Runnable callback) {
if (file.getAbsolutePath().startsWith(getConversationsDirectory("Images"))
|| file.getAbsolutePath().startsWith(getConversationsDirectory("Videos"))) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ MediaScannerConnection.scanFile(mXmppConnectionService, new String[]{file.getAbsolutePath()}, null, new MediaScannerConnection.MediaScannerConnectionClient() {
+ @Override
+ public void onMediaScannerConnected() {
+
+ }
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ if (callback != null && file.getAbsolutePath().equals(path)) {
+ callback.run();
+ } else {
+ Log.d(Config.LOGTAG, "media scanner scanned wrong file");
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
+ });
+ return;
+ /*Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
- mXmppConnectionService.sendBroadcast(intent);
+ mXmppConnectionService.sendBroadcast(intent);*/
} else {
createNoMedia();
}
+ if (callback != null) {
+ callback.run();
+ }
}
public boolean deleteFile(File file) {
@@ -130,6 +172,10 @@ public class FileBackend {
return getFile(message, true);
}
+ public DownloadableFile getFileForPath(String path) {
+ return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path)));
+ }
+
public DownloadableFile getFileForPath(String path, String mime) {
final DownloadableFile file;
if (path.startsWith("/")) {
@@ -148,6 +194,11 @@ public class FileBackend {
return file;
}
+ public boolean isInternalFile(final File file) {
+ final File internalFile = getFileForPath(file.getName());
+ return file.getAbsolutePath().equals(internalFile.getAbsolutePath());
+ }
+
public DownloadableFile getFile(Message message, boolean decrypted) {
final boolean encrypted = !decrypted
&& (message.getEncryption() == Message.ENCRYPTION_PGP
@@ -212,13 +263,8 @@ public class FileBackend {
List<Attachment> attachments = new ArrayList<>();
for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path));
- Log.d(Config.LOGTAG, "mime=" + mime);
- File file = getFileForPath(relativeFilePath.path, mime);
- if (file.exists()) {
- attachments.add(Attachment.of(relativeFilePath.uuid, file, mime));
- } else {
- Log.d(Config.LOGTAG, "file " + file.getAbsolutePath() + " doesn't exist");
- }
+ final File file = getFileForPath(relativeFilePath.path, mime);
+ attachments.add(Attachment.of(relativeFilePath.uuid, file, mime));
}
return attachments;
}
@@ -975,11 +1021,11 @@ public class FileBackend {
public void updateFileParams(Message message, URL url) {
DownloadableFile file = getFile(message);
final String mime = file.getMimeType();
- boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/"));
- boolean video = mime != null && mime.startsWith("video/");
- boolean audio = mime != null && mime.startsWith("audio/");
- boolean vcard = mime != null && mime.contains("vcard");
- boolean apk = mime != null && mime.equals("application/vnd.android.package-archive");
+ final boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/"));
+ final boolean video = mime != null && mime.startsWith("video/");
+ final boolean audio = mime != null && mime.startsWith("audio/");
+ final boolean vcard = mime != null && mime.contains("vcard");
+ final boolean apk = mime != null && mime.equals("application/vnd.android.package-archive");
final StringBuilder body = new StringBuilder();
if (url != null) {
body.append(url.toString());
@@ -1003,16 +1049,8 @@ public class FileBackend {
body.append("|0|0|0|").append(getAPK(file, mXmppConnectionService.getApplicationContext()));
}
message.setBody(body.toString());
- }
-
- public int getMediaRuntime(Uri uri) {
- try {
- MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
- mediaMetadataRetriever.setDataSource(mXmppConnectionService, uri);
- return Integer.parseInt(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
- } catch (RuntimeException e) {
- return 0;
- }
+ message.setFileDeleted(false);
+ message.setType(image ? Message.TYPE_IMAGE : Message.TYPE_FILE);
}
private int getMediaRuntime(File file) {
diff --git a/src/main/java/de/pixart/messenger/services/AlarmReceiver.java b/src/main/java/de/pixart/messenger/services/AlarmReceiver.java
index e4c07366d..887a59684 100644
--- a/src/main/java/de/pixart/messenger/services/AlarmReceiver.java
+++ b/src/main/java/de/pixart/messenger/services/AlarmReceiver.java
@@ -3,7 +3,6 @@ package de.pixart.messenger.services;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.support.v4.content.ContextCompat;
import android.util.Log;
import de.pixart.messenger.Config;
@@ -16,15 +15,7 @@ public class AlarmReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().contains("exportlogs")) {
Log.d(Config.LOGTAG, "Received alarm broadcast to export logs");
- try {
- if (Compatibility.runsAndTargetsTwentySix(context)) {
- ContextCompat.startForegroundService(context, new Intent(context, ExportLogsService.class));
- } else {
- context.startService(new Intent(context, ExportLogsService.class));
- }
- } catch (RuntimeException e) {
- Log.d(Config.LOGTAG, "AlarmReceiver was unable to start ExportLogsService");
- }
+ Compatibility.startService(context, new Intent(context, ExportBackupService.class));
}
}
}
diff --git a/src/main/java/de/pixart/messenger/services/AvatarService.java b/src/main/java/de/pixart/messenger/services/AvatarService.java
index 23e2da4d6..b37811b7f 100644
--- a/src/main/java/de/pixart/messenger/services/AvatarService.java
+++ b/src/main/java/de/pixart/messenger/services/AvatarService.java
@@ -1,8 +1,8 @@
package de.pixart.messenger.services;
+import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
@@ -13,6 +13,7 @@ import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.support.v4.content.res.ResourcesCompat;
import android.text.TextUtils;
@@ -42,12 +43,16 @@ import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded;
import de.pixart.messenger.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
+import static de.pixart.messenger.ui.SettingsActivity.PREFER_XMPP_AVATAR;
+
public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
private static final int FG_COLOR = 0xFFFAFAFA;
private static final int TRANSPARENT = 0x00000000;
private static final int PLACEHOLDER_COLOR = 0xFF202020;
+ public static final int SYSTEM_UI_AVATAR_SIZE = 48;
+
private static final String PREFIX_CONTACT = "contact";
private static final String PREFIX_CONVERSATION = "conversation";
private static final String PREFIX_ACCOUNT = "account";
@@ -62,17 +67,23 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
this.mXmppConnectionService = service;
}
- private static String getFirstLetter(String name) {
- for (Character c : name.toCharArray()) {
- if (Character.isLetterOrDigit(c)) {
- return c.toString();
- }
- }
- return "X";
+ public static int getSystemUiAvatarSize(final Context context) {
+ return (int) (SYSTEM_UI_AVATAR_SIZE * context.getResources().getDisplayMetrics().density);
}
- private static String emptyOnNull(@Nullable Jid value) {
- return value == null ? "" : value.toString();
+ public Bitmap get(final Avatarable avatarable, final int size, final boolean cachedOnly) {
+ if (avatarable instanceof Account) {
+ return get((Account) avatarable, size, cachedOnly);
+ } else if (avatarable instanceof Conversation) {
+ return get((Conversation) avatarable, size, cachedOnly);
+ } else if (avatarable instanceof Message) {
+ return get((Message) avatarable, size, cachedOnly);
+ } else if (avatarable instanceof ListItem) {
+ return get((ListItem) avatarable, size, cachedOnly);
+ } else if (avatarable instanceof MucOptions.User) {
+ return get((MucOptions.User) avatarable, size, cachedOnly);
+ }
+ throw new AssertionError("AvatarService does not know how to generate avatar from "+avatarable.getClass().getName());
}
private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
@@ -84,15 +95,14 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
if (avatar != null || cachedOnly) {
return avatar;
}
- if (contact.getAvatarFilename() != null) {
+ if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy() && mXmppConnectionService.getPreferences().getBoolean(PREFER_XMPP_AVATAR, mXmppConnectionService.getResources().getBoolean(R.bool.prefer_xmpp_avatar))) {
+ avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size);
+ }
+ if (avatar == null && contact.getAvatarFilename() != null && mXmppConnectionService.getPreferences().getBoolean(PREFER_XMPP_AVATAR, mXmppConnectionService.getResources().getBoolean(R.bool.prefer_xmpp_avatar))) {
avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size);
}
if (avatar == null && contact.getProfilePhoto() != null) {
- try {
- avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
- } catch (Exception e) {
- e.printStackTrace();
- }
+ avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
}
if (avatar == null) {
avatar = get(contact.getDisplayName(), contact.getJid().asBareJid().toString(), size, false);
@@ -116,6 +126,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
+
drawAvatar(bitmap, canvas, paint);
if (withIcon) {
drawIcon(canvas, paint);
@@ -311,7 +322,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
if (bitmap != null || cachedOnly) {
return bitmap;
}
+
bitmap = mXmppConnectionService.getFileBackend().getAvatar(mucOptions.getAvatar(), size);
+
if (bitmap == null) {
final List<MucOptions.User> users = mucOptions.getUsersRelevantForNameAndAvatar();
if (users.size() == 0) {
@@ -321,7 +334,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
bitmap = getImpl(users, size);
}
}
+
this.mXmppConnectionService.getBitmapCache().put(KEY, bitmap);
+
return bitmap;
}
@@ -481,10 +496,6 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
}
}
- /*public Bitmap get(String name, int size) {
- return get(name,null, size,false);
- }*/
-
public void clear(MucOptions.User user) {
synchronized (this.sizes) {
for (Integer size : sizes) {
@@ -503,6 +514,10 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
+ String.valueOf(size);
}
+ /*public Bitmap get(String name, int size) {
+ return get(name,null, size,false);
+ }*/
+
public Bitmap get(final String name, String seed, final int size, boolean cachedOnly) {
final String KEY = key(seed == null ? name : name + "\0" + seed, size);
Bitmap bitmap = mXmppConnectionService.getBitmapCache().get(KEY);
@@ -514,7 +529,11 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
return bitmap;
}
- private Bitmap getImpl(final String name, final String seed, final int size) {
+ public static Bitmap get(final Jid jid, final int size) {
+ return getImpl(jid.asBareJid().toEscapedString(), null, size);
+ }
+
+ private static Bitmap getImpl(final String name, final String seed, final int size) {
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
final String trimmedName = name == null ? "" : name.trim();
@@ -531,7 +550,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size);
}
- private boolean drawTile(Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) {
+ private static boolean drawTile(Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) {
letter = letter.toUpperCase(Locale.getDefault());
Paint tilePaint = new Paint(), textPaint = new Paint();
tilePaint.setColor(tileColor);
@@ -554,12 +573,10 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
Contact contact = user.getContact();
if (contact != null) {
Uri uri = null;
- if (contact.getAvatarFilename() != null) {
- try {
- uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename());
- } catch (Exception e) {
- e.printStackTrace();
- }
+ if (contact.getAvatarFilename() != null && QuickConversationsService.isQuicksy() && mXmppConnectionService.getPreferences().getBoolean(PREFER_XMPP_AVATAR, mXmppConnectionService.getResources().getBoolean(R.bool.prefer_xmpp_avatar))) {
+ uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename());
+ } else if (contact.getAvatarFilename() != null && mXmppConnectionService.getPreferences().getBoolean(PREFER_XMPP_AVATAR, mXmppConnectionService.getResources().getBoolean(R.bool.prefer_xmpp_avatar))) {
+ uri = mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename());
} else if (contact.getProfilePhoto() != null) {
uri = Uri.parse(contact.getProfilePhoto());
}
@@ -571,16 +588,6 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
if (drawTile(canvas, uri, left, top, right, bottom)) {
return true;
}
- } else if (user.getAvatar() != null) {
- Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar());
- if (drawTile(canvas, uri, left, top, right, bottom)) {
- return true;
- }
- } else if (user.getAvatar() != null) {
- Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar());
- if (drawTile(canvas, uri, left, top, right, bottom)) {
- return true;
- }
}
if (contact != null) {
String seed = contact.getJid().asBareJid().toString();
@@ -606,7 +613,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
return drawTile(canvas, name, name, left, top, right, bottom);
}
- private boolean drawTile(Canvas canvas, String name, String seed, int left, int top, int right, int bottom) {
+ private static boolean drawTile(Canvas canvas, String name, String seed, int left, int top, int right, int bottom) {
if (name != null) {
final String letter = getFirstLetter(name);
final int color = UIHelper.getColorForName(seed == null ? name : seed);
@@ -616,6 +623,15 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
return false;
}
+ private static String getFirstLetter(String name) {
+ for (Character c : name.toCharArray()) {
+ if (Character.isLetterOrDigit(c)) {
+ return c.toString();
+ }
+ }
+ return "X";
+ }
+
private boolean drawTile(Canvas canvas, Uri uri, int left, int top, int right, int bottom) {
if (uri != null) {
Bitmap bitmap = mXmppConnectionService.getFileBackend()
@@ -644,4 +660,13 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
}
}
}
-}
+
+ private static String emptyOnNull(@Nullable Jid value) {
+ return value == null ? "" : value.toString();
+ }
+
+ public interface Avatarable {
+ @ColorInt
+ int getAvatarBackgroundColor();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java
index 4c0aec3ca..7bda592e5 100644
--- a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java
+++ b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java
@@ -18,31 +18,44 @@ import java.util.List;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.ui.ConversationsActivity;
+import de.pixart.messenger.utils.Compatibility;
@TargetApi(Build.VERSION_CODES.M)
public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection {
private final Object lock = new Object();
-
+ private final int MAX_TARGETS = 5;
private XmppConnectionService mXmppConnectionService;
- private final int MAX_TARGETS = 5;
+ private static boolean textOnly(IntentFilter filter) {
+ for (int i = 0; i < filter.countDataTypes(); ++i) {
+ if (!"text/plain".equals(filter.getDataType(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
@Override
public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
- Intent intent = new Intent(this, XmppConnectionService.class);
+ final ArrayList<ChooserTarget> chooserTargets = new ArrayList<>();
+ if (!EventReceiver.hasEnabledAccounts(this)) {
+ return chooserTargets;
+ }
+ final Intent intent = new Intent(this, XmppConnectionService.class);
intent.setAction("contact_chooser");
+ Compatibility.startService(this, intent);
bindService(intent, this, Context.BIND_AUTO_CREATE);
- ArrayList<ChooserTarget> chooserTargets = new ArrayList<>();
try {
waitForService();
final ArrayList<Conversation> conversations = new ArrayList<>();
if (!mXmppConnectionService.areMessagesInitialized()) {
return chooserTargets;
}
- mXmppConnectionService.populateWithOrderedConversations(conversations, false);
+
+ mXmppConnectionService.populateWithOrderedConversations(conversations, textOnly(matchedFilter));
final ComponentName componentName = new ComponentName(this, ConversationsActivity.class);
- final int pixel = (int) (48 * getResources().getDisplayMetrics().density);
+ final int pixel = AvatarService.getSystemUiAvatarSize(this);
for (Conversation conversation : conversations) {
if (conversation.sentMessagesCount() == 0) {
continue;
diff --git a/src/main/java/de/pixart/messenger/services/EventReceiver.java b/src/main/java/de/pixart/messenger/services/EventReceiver.java
index 03bf68ed9..145c9893b 100644
--- a/src/main/java/de/pixart/messenger/services/EventReceiver.java
+++ b/src/main/java/de/pixart/messenger/services/EventReceiver.java
@@ -4,7 +4,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.preference.PreferenceManager;
-import android.support.v4.content.ContextCompat;
import android.util.Log;
import de.pixart.messenger.Config;
@@ -25,16 +24,7 @@ public class EventReceiver extends BroadcastReceiver {
}
final String action = originalIntent.getAction();
if (action.equals("ui") || hasEnabledAccounts(context)) {
- try {
- if (Compatibility.runsAndTargetsTwentySix(context)) {
- intentForService.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true);
- ContextCompat.startForegroundService(context, intentForService);
- } else {
- context.startService(intentForService);
- }
- } catch (RuntimeException e) {
- Log.d(Config.LOGTAG, "EventReceiver was unable to start service");
- }
+ Compatibility.startService(context, intentForService);
} else {
Log.d(Config.LOGTAG, "EventReceiver ignored action " + intentForService.getAction());
}
diff --git a/src/main/java/de/pixart/messenger/services/ExportBackupService.java b/src/main/java/de/pixart/messenger/services/ExportBackupService.java
new file mode 100644
index 000000000..5ffae41a9
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/services/ExportBackupService.java
@@ -0,0 +1,387 @@
+package de.pixart.messenger.services;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.preference.PreferenceManager;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.GZIPOutputStream;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.SQLiteAxolotlStore;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.persistance.DatabaseBackend;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.utils.BackupFileHeader;
+import de.pixart.messenger.utils.Compatibility;
+import de.pixart.messenger.utils.WakeLockHelper;
+import rocks.xmpp.addr.Jid;
+
+public class ExportBackupService extends Service {
+
+ private PowerManager.WakeLock wakeLock;
+ private PowerManager pm;
+
+ public static final String KEYTYPE = "AES";
+ public static final String CIPHERMODE = "AES/GCM/NoPadding";
+ public static final String PROVIDER = "BC";
+
+ boolean ReadableLogsEnabled = false;
+ private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s";
+ private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
+
+ private static final int NOTIFICATION_ID = 19;
+ private static final int PAGE_SIZE = 20;
+ private static AtomicBoolean running = new AtomicBoolean(false);
+ private DatabaseBackend mDatabaseBackend;
+ private List<Account> mAccounts;
+ private NotificationManager notificationManager;
+
+ private static void accountExport(SQLiteDatabase db, String uuid, PrintWriter writer) {
+ final StringBuilder builder = new StringBuilder();
+ final Cursor accountCursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", new String[]{uuid}, null, null, null);
+ while (accountCursor != null && accountCursor.moveToNext()) {
+ builder.append("INSERT INTO ").append(Account.TABLENAME).append("(");
+ for (int i = 0; i < accountCursor.getColumnCount(); ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ builder.append(accountCursor.getColumnName(i));
+ }
+ builder.append(") VALUES(");
+ for (int i = 0; i < accountCursor.getColumnCount(); ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ final String value = accountCursor.getString(i);
+ if (value == null || Account.ROSTERVERSION.equals(accountCursor.getColumnName(i))) {
+ builder.append("NULL");
+ } else if (value.matches("\\d+")) {
+ int intValue = Integer.parseInt(value);
+ if (Account.OPTIONS.equals(accountCursor.getColumnName(i))) {
+ intValue |= 1 << Account.OPTION_DISABLED;
+ }
+ builder.append(intValue);
+ } else {
+ DatabaseUtils.appendEscapedSQLString(builder, value);
+ }
+ }
+ builder.append(")");
+ builder.append(';');
+ builder.append('\n');
+ }
+ if (accountCursor != null) {
+ accountCursor.close();
+ }
+ writer.append(builder.toString());
+ }
+
+ private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) {
+ final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ writer.write(cursorToString(table, cursor, PAGE_SIZE));
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ public static byte[] getKey(String password, byte[] salt) {
+ try {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)).getEncoded();
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static String cursorToString(String tablename, Cursor cursor, int max) {
+ return cursorToString(tablename, cursor, max, false);
+ }
+
+ private static String cursorToString(String tablename, Cursor cursor, int max, boolean ignore) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("INSERT ");
+ if (ignore) {
+ builder.append("OR IGNORE ");
+ }
+ builder.append("INTO ").append(tablename).append("(");
+ for (int i = 0; i < cursor.getColumnCount(); ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ builder.append(cursor.getColumnName(i));
+ }
+ builder.append(") VALUES");
+ for (int i = 0; i < max; ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ appendValues(cursor, builder);
+ if (i < max - 1 && !cursor.moveToNext()) {
+ break;
+ }
+ }
+ builder.append(';');
+ builder.append('\n');
+ return builder.toString();
+ }
+
+ private static void appendValues(Cursor cursor, StringBuilder builder) {
+ builder.append("(");
+ for (int i = 0; i < cursor.getColumnCount(); ++i) {
+ if (i != 0) {
+ builder.append(',');
+ }
+ final String value = cursor.getString(i);
+ if (value == null) {
+ builder.append("NULL");
+ } else if (value.matches("\\d+")) {
+ builder.append(value);
+ } else {
+ DatabaseUtils.appendEscapedSQLString(builder, value);
+ }
+ }
+ builder.append(")");
+
+ }
+
+ @Override
+ public void onCreate() {
+ mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
+ mAccounts = mDatabaseBackend.getAccounts();
+ notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ final SharedPreferences ReadableLogs = PreferenceManager.getDefaultSharedPreferences(this);
+ ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", getResources().getBoolean(R.bool.plain_text_logs));
+ pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Config.LOGTAG + ": ExportLogsService");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (running.compareAndSet(false, true)) {
+ new Thread(() -> {
+ export();
+ stopForeground(true);
+ running.set(false);
+ WakeLockHelper.release(wakeLock);
+ stopSelf();
+ }).start();
+ return START_STICKY;
+ }
+ return START_NOT_STICKY;
+ }
+
+ private void messageExport(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) {
+ Cursor cursor = db.rawQuery("select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid});
+ int size = cursor != null ? cursor.getCount() : 0;
+ Log.d(Config.LOGTAG, "exporting " + size + " messages");
+ int i = 0;
+ int p = 0;
+ while (cursor != null && cursor.moveToNext()) {
+ writer.write(cursorToString(Message.TABLENAME, cursor, PAGE_SIZE, false));
+ if (i + PAGE_SIZE > size) {
+ i = size;
+ } else {
+ i += PAGE_SIZE;
+ }
+ final int percentage = i * 100 / size;
+ if (p < percentage) {
+ p = percentage;
+ notificationManager.notify(NOTIFICATION_ID, progress.build(p));
+ }
+ }
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ private void export() {
+ wakeLock.acquire(15 * 60 * 1000L /*15 minutes*/);
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
+ mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
+ .setSmallIcon(R.drawable.ic_archive_white_24dp)
+ .setProgress(1, 0, false);
+ startForeground(NOTIFICATION_ID, mBuilder.build());
+ try {
+ int count = 0;
+ final int max = this.mAccounts.size();
+ final SecureRandom secureRandom = new SecureRandom();
+ if (mAccounts.size() >= 1) {
+ if (ReadableLogsEnabled) {
+ List<Conversation> conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE);
+ conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED));
+ for (Conversation conversation : conversations) {
+ writeToFile(conversation);
+ }
+ }
+ }
+ for (Account account : this.mAccounts) {
+ final byte[] IV = new byte[12];
+ final byte[] salt = new byte[16];
+ secureRandom.nextBytes(IV);
+ secureRandom.nextBytes(salt);
+ final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
+ final Progress progress = new Progress(mBuilder, max, count);
+ final File file = new File(FileBackend.getBackupDirectory() + account.getJid().asBareJid().toEscapedString() + ".ceb");
+ if (file.getParentFile().mkdirs()) {
+ Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath());
+ }
+ final FileOutputStream fileOutputStream = new FileOutputStream(file);
+ final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
+ backupFileHeader.write(dataOutputStream);
+ dataOutputStream.flush();
+
+ final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ byte[] key = getKey(account.getPassword(), salt);
+ Log.d(Config.LOGTAG, backupFileHeader.toString());
+ SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(IV);
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
+ CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher);
+
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream);
+ PrintWriter writer = new PrintWriter(gzipOutputStream);
+ SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase();
+ final String uuid = account.getUuid();
+ accountExport(db, uuid, writer);
+ simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer);
+ messageExport(db, uuid, writer, progress);
+ for (String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) {
+ simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer);
+ }
+ writer.flush();
+ writer.close();
+ Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
+ count++;
+ }
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "unable to create backup ", e);
+ }
+ }
+
+ private void writeToFile(Conversation conversation) {
+ Jid accountJid = resolveAccountUuid(conversation.getAccountUuid());
+ Jid contactJid = conversation.getJid();
+
+ File dir = new File(String.format(DIRECTORY_STRING_FORMAT, accountJid.asBareJid().toString()));
+ dir.mkdirs();
+
+ BufferedWriter bw = null;
+ try {
+ for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) {
+ if (message == null)
+ continue;
+ if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) {
+ String date = simpleDateFormat.format(new Date(message.getTimeSent()));
+ if (bw == null) {
+ bw = new BufferedWriter(new FileWriter(
+ new File(dir, contactJid.asBareJid().toString() + ".txt")));
+ }
+ String jid = null;
+ switch (message.getStatus()) {
+ case Message.STATUS_RECEIVED:
+ jid = getMessageCounterpart(message);
+ break;
+ case Message.STATUS_SEND:
+ case Message.STATUS_SEND_RECEIVED:
+ case Message.STATUS_SEND_DISPLAYED:
+ jid = accountJid.asBareJid().toString();
+ break;
+ }
+ if (jid != null) {
+ String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody();
+ bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid,
+ body.replace("\\\n", "\\ \n").replace("\n", "\\ \n")));
+ }
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (bw != null) {
+ bw.close();
+ }
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ private String getMessageCounterpart(Message message) {
+ String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART);
+ if (trueCounterpart != null) {
+ return trueCounterpart;
+ } else {
+ return message.getCounterpart().toString();
+ }
+ }
+
+ private Jid resolveAccountUuid(String accountUuid) {
+ for (Account account : mAccounts) {
+ if (account.getUuid().equals(accountUuid)) {
+ return account.getJid();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private class Progress {
+ private final NotificationCompat.Builder builder;
+ private final int max;
+ private final int count;
+
+ private Progress(NotificationCompat.Builder builder, int max, int count) {
+ this.builder = builder;
+ this.max = max;
+ this.count = count;
+ }
+
+ private Notification build(int percentage) {
+ builder.setProgress(max * 100, count * 100 + percentage, false);
+ return builder.build();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/ExportLogsService.java b/src/main/java/de/pixart/messenger/services/ExportLogsService.java
deleted file mode 100644
index a607fcb66..000000000
--- a/src/main/java/de/pixart/messenger/services/ExportLogsService.java
+++ /dev/null
@@ -1,248 +0,0 @@
-package de.pixart.messenger.services;
-
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.preference.PreferenceManager;
-import android.support.annotation.BoolRes;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.crypto.NoSuchPaddingException;
-
-import de.pixart.messenger.Config;
-import de.pixart.messenger.R;
-import de.pixart.messenger.entities.Account;
-import de.pixart.messenger.entities.Conversation;
-import de.pixart.messenger.entities.Message;
-import de.pixart.messenger.persistance.DatabaseBackend;
-import de.pixart.messenger.persistance.FileBackend;
-import de.pixart.messenger.utils.EncryptDecryptFile;
-import de.pixart.messenger.utils.WakeLockHelper;
-import rocks.xmpp.addr.Jid;
-
-import static de.pixart.messenger.services.NotificationService.BACKUP_CHANNEL_ID;
-import static de.pixart.messenger.services.NotificationService.NOTIFICATION_ID;
-import static de.pixart.messenger.ui.SettingsActivity.USE_MULTI_ACCOUNTS;
-
-public class ExportLogsService extends XmppConnectionService {
-
- private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
- private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s";
- private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
- private static AtomicBoolean running = new AtomicBoolean(false);
- boolean ReadableLogsEnabled = false;
- private DatabaseBackend mDatabaseBackend;
- private List<Account> mAccounts;
- private WakeLock wakeLock;
- private PowerManager pm;
-
- @Override
- public void onCreate() {
- mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
- mAccounts = mDatabaseBackend.getAccounts();
- final SharedPreferences ReadableLogs = PreferenceManager.getDefaultSharedPreferences(this);
- ReadableLogsEnabled = ReadableLogs.getBoolean("export_plain_text_logs", getResources().getBoolean(R.bool.plain_text_logs));
- pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Config.LOGTAG + ": ExportLogsService");
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (running.compareAndSet(false, true)) {
- new Thread(() -> {
- export();
- WakeLockHelper.release(wakeLock);
- stopForeground(true);
- running.set(false);
- stopSelf();
- }).start();
- }
- return START_NOT_STICKY;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- stopForeground(true);
- }
-
- private void export() {
- wakeLock.acquire();
- NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), BACKUP_CHANNEL_ID);
- mBuilder.setContentTitle(getString(R.string.notification_export_logs_title));
- mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp);
- mBuilder.setProgress(0, 0, true);
- startForeground(NOTIFICATION_ID, mBuilder.build());
- List<Conversation> conversations = mDatabaseBackend.getConversations(Conversation.STATUS_AVAILABLE);
- conversations.addAll(mDatabaseBackend.getConversations(Conversation.STATUS_ARCHIVED));
- if (mAccounts.size() >= 1) {
- if (ReadableLogsEnabled) {
- for (Conversation conversation : conversations) {
- writeToFile(conversation);
- }
- }
- try {
- ExportDatabase();
- } catch (IOException e) {
- e.printStackTrace();
- }
- } else {
- Log.d(Config.LOGTAG, "ExportLogsService: no accounts, aborting export");
- }
- }
-
- private void writeToFile(Conversation conversation) {
- Jid accountJid = resolveAccountUuid(conversation.getAccountUuid());
- Jid contactJid = conversation.getJid();
-
- File dir = new File(String.format(DIRECTORY_STRING_FORMAT, accountJid.asBareJid().toString()));
- dir.mkdirs();
-
- BufferedWriter bw = null;
- try {
- for (Message message : mDatabaseBackend.getMessagesIterable(conversation)) {
- if (message == null)
- continue;
- if (message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) {
- String date = simpleDateFormat.format(new Date(message.getTimeSent()));
- if (bw == null) {
- bw = new BufferedWriter(new FileWriter(
- new File(dir, contactJid.asBareJid().toString() + ".txt")));
- }
- String jid = null;
- switch (message.getStatus()) {
- case Message.STATUS_RECEIVED:
- jid = getMessageCounterpart(message);
- break;
- case Message.STATUS_SEND:
- case Message.STATUS_SEND_RECEIVED:
- case Message.STATUS_SEND_DISPLAYED:
- jid = accountJid.asBareJid().toString();
- break;
- }
- if (jid != null) {
- String body = message.hasFileOnRemoteHost() ? message.getFileParams().url.toString() : message.getBody();
- bw.write(String.format(MESSAGE_STRING_FORMAT, date, jid,
- body.replace("\\\n", "\\ \n").replace("\n", "\\ \n")));
- }
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (bw != null) {
- bw.close();
- }
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- }
-
- private Jid resolveAccountUuid(String accountUuid) {
- for (Account account : mAccounts) {
- if (account.getUuid().equals(accountUuid)) {
- return account.getJid();
- }
- }
- return null;
- }
-
- private String getMessageCounterpart(Message message) {
- String trueCounterpart = (String) message.getContentValues().get(Message.TRUE_COUNTERPART);
- if (trueCounterpart != null) {
- return trueCounterpart;
- } else {
- return message.getCounterpart().toString();
- }
- }
-
- public void ExportDatabase() throws IOException {
- Log.d(Config.LOGTAG, "ExportLogsService: start creating backup");
- Account mAccount = mAccounts.get(0);
- String EncryptionKey = null;
- // Get hold of the db:
- FileInputStream InputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
- // Set the output folder on the SDcard
- File directory = new File(FileBackend.getBackupDirectory());
- // Create the folder if it doesn't exist:
- if (!directory.exists()) {
- boolean directory_created = directory.mkdirs();
- Log.d(Config.LOGTAG, "ExportLogsService: backup directory created " + directory_created);
- }
- //Delete old database export file
- File temp_db_file = new File(directory + "/database.bak");
- if (temp_db_file.exists()) {
- Log.d(Config.LOGTAG, "ExportLogsService: Delete temp database backup file from " + temp_db_file.toString());
- boolean temp_db_file_deleted = temp_db_file.delete();
- Log.d(Config.LOGTAG, "ExportLogsService: old backup file deleted " + temp_db_file_deleted);
- }
- // Set the output file stream up:
- FileOutputStream OutputFile = new FileOutputStream(directory.getPath() + "/database.db.crypt");
-
- if (mAccounts.size() == 1 && !multipleAccounts()) {
- EncryptionKey = mAccount.getPassword(); //get account password
- } else {
- SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE);
- EncryptionKey = multiaccount_prefs.getString("BackupPW", null);
- }
- if (EncryptionKey == null) {
- Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: failed to write encryted backup to sdcard because of missing password");
- return;
- }
-
- // encrypt database from the input file to the output file
- try {
- EncryptDecryptFile.encrypt(InputFile, OutputFile, EncryptionKey);
- Log.d(Config.LOGTAG, "ExportLogsService: starting encrypted output to " + OutputFile.toString());
- } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed with " + e);
- e.printStackTrace();
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed (invalid key) with " + e);
- e.printStackTrace();
- } catch (IOException e) {
- Log.d(Config.LOGTAG, "ExportLogsService: Database exporter: encryption failed (IO) with " + e);
- e.printStackTrace();
- } finally {
- Log.d(Config.LOGTAG, "ExportLogsService: backup job finished");
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- public SharedPreferences getPreferences() {
- return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
- }
-
- public boolean getBooleanPreference(String name, @BoolRes int res) {
- return getPreferences().getBoolean(name, getResources().getBoolean(res));
- }
-
- public boolean multipleAccounts() {
- return getBooleanPreference("enable_multi_accounts", R.bool.enable_multi_accounts);
- }
-}
diff --git a/src/main/java/de/pixart/messenger/services/ImportBackupService.java b/src/main/java/de/pixart/messenger/services/ImportBackupService.java
new file mode 100644
index 000000000..1b28640e5
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/services/ImportBackupService.java
@@ -0,0 +1,294 @@
+package de.pixart.messenger.services;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Binder;
+import android.os.IBinder;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.GZIPInputStream;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.persistance.DatabaseBackend;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.ui.ManageAccountActivity;
+import de.pixart.messenger.utils.BackupFileHeader;
+import de.pixart.messenger.utils.Compatibility;
+import de.pixart.messenger.utils.SerialSingleThreadExecutor;
+import rocks.xmpp.addr.Jid;
+
+import static de.pixart.messenger.services.ExportBackupService.CIPHERMODE;
+import static de.pixart.messenger.services.ExportBackupService.KEYTYPE;
+import static de.pixart.messenger.services.ExportBackupService.PROVIDER;
+
+public class ImportBackupService extends Service {
+
+ private static final int NOTIFICATION_ID = 21;
+ private static AtomicBoolean running = new AtomicBoolean(false);
+ private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
+ private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
+ private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>());
+ private DatabaseBackend mDatabaseBackend;
+ private NotificationManager notificationManager;
+
+ private static int count(String input, char c) {
+ int count = 0;
+ for (char aChar : input.toCharArray()) {
+ if (aChar == c) {
+ ++count;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public void onCreate() {
+ mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
+ notificationManager = (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent == null) {
+ return START_NOT_STICKY;
+ }
+ final String password = intent.getStringExtra("password");
+ final String file = intent.getStringExtra("file");
+ if (password == null || file == null) {
+ return START_NOT_STICKY;
+ }
+ if (running.compareAndSet(false, true)) {
+ executor.execute(() -> {
+ startForegroundService();
+ final boolean success = importBackup(new File(file), password);
+ stopForeground(true);
+ running.set(false);
+ if (success) {
+ notifySuccess();
+ }
+ stopSelf();
+ });
+ } else {
+ Log.d(Config.LOGTAG, "backup already running");
+ }
+ return START_NOT_STICKY;
+ }
+
+ public boolean getLoadingState() {
+ return running.get();
+ }
+
+ public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) {
+ executor.execute(() -> {
+ List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
+ final ArrayList<BackupFile> backupFiles = new ArrayList<>();
+ final Set<String> apps = new HashSet<>(Arrays.asList(getString(R.string.app_name)));
+ for (String app : apps) {
+ final File directory = new File(FileBackend.getBackupDirectory());
+ if (!directory.exists() || !directory.isDirectory()) {
+ Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
+ continue;
+ }
+ for (File file : directory.listFiles()) {
+ if (file.isFile() && file.getName().endsWith(".ceb")) {
+ try {
+ final BackupFile backupFile = BackupFile.read(file);
+ if (accounts.contains(backupFile.getHeader().getJid())) {
+ Log.d(Config.LOGTAG, "skipping backup for " + backupFile.getHeader().getJid());
+ } else {
+ backupFiles.add(backupFile);
+ }
+ } catch (IOException e) {
+ Log.d(Config.LOGTAG, "unable to read backup file ", e);
+ }
+ }
+ }
+ }
+ Collections.sort(backupFiles, (a, b) -> a.header.getJid().toString().compareTo(b.header.getJid().toString()));
+ onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
+ });
+ }
+
+ private void startForegroundService() {
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
+ mBuilder.setContentTitle(getString(R.string.restoring_backup))
+ .setSmallIcon(R.drawable.ic_unarchive_white_24dp)
+ .setProgress(1, 0, true);
+ startForeground(NOTIFICATION_ID, mBuilder.build());
+ }
+
+ private boolean importBackup(File file, String password) {
+ Log.d(Config.LOGTAG, "importing backup from file " + file.getAbsolutePath());
+ try {
+ SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
+ final FileInputStream fileInputStream = new FileInputStream(file);
+ final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
+ BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
+ Log.d(Config.LOGTAG, backupFileHeader.toString());
+
+ final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
+ byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
+ SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
+ IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv());
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+ CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher);
+
+ GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
+ String line;
+ StringBuilder multiLineQuery = null;
+ while ((line = reader.readLine()) != null) {
+ int count = count(line, '\'');
+ if (multiLineQuery != null) {
+ multiLineQuery.append('\n');
+ multiLineQuery.append(line);
+ if (count % 2 == 1) {
+ db.execSQL(multiLineQuery.toString());
+ multiLineQuery = null;
+ }
+ } else {
+ if (count % 2 == 0) {
+ db.execSQL(line);
+ } else {
+ multiLineQuery = new StringBuilder(line);
+ }
+ }
+ }
+ final Jid jid = backupFileHeader.getJid();
+ Cursor countCursor = db.rawQuery("select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", new String[]{jid.getEscapedLocal(), jid.getDomain()});
+ countCursor.moveToFirst();
+ int count = countCursor.getInt(0);
+ Log.d(Config.LOGTAG, "restored " + count + " messages");
+ countCursor.close();
+ stopBackgroundService();
+ synchronized (mOnBackupProcessedListeners) {
+ for (OnBackupProcessed l : mOnBackupProcessedListeners) {
+ l.onBackupRestored();
+ }
+ }
+ return true;
+ } catch (Exception e) {
+ Throwable throwable = e.getCause();
+ final boolean reasonWasCrypto;
+ if (throwable instanceof BadPaddingException) {
+ reasonWasCrypto = true;
+ } else {
+ reasonWasCrypto = false;
+ }
+ synchronized (mOnBackupProcessedListeners) {
+ for (OnBackupProcessed l : mOnBackupProcessedListeners) {
+ if (reasonWasCrypto) {
+ l.onBackupDecryptionFailed();
+ } else {
+ l.onBackupRestoreFailed();
+ }
+ }
+ }
+ Log.d(Config.LOGTAG, "error restoring backup " + file.getAbsolutePath(), e);
+ return false;
+ }
+ }
+
+ private void notifySuccess() {
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
+ mBuilder.setContentTitle(getString(R.string.notification_restored_backup_title))
+ .setContentText(getString(R.string.notification_restored_backup_subtitle))
+ .setAutoCancel(true)
+ .setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
+ .setSmallIcon(R.drawable.ic_unarchive_white_24dp);
+ notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
+ }
+
+ private void stopBackgroundService() {
+ Intent intent = new Intent(this, XmppConnectionService.class);
+ stopService(intent);
+ }
+
+ public void removeOnBackupProcessedListener(OnBackupProcessed listener) {
+ synchronized (mOnBackupProcessedListeners) {
+ mOnBackupProcessedListeners.remove(listener);
+ }
+ }
+
+ public void addOnBackupProcessedListener(OnBackupProcessed listener) {
+ synchronized (mOnBackupProcessedListeners) {
+ mOnBackupProcessedListeners.add(listener);
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return this.binder;
+ }
+
+ public interface OnBackupFilesLoaded {
+ void onBackupFilesLoaded(List<BackupFile> files);
+ }
+
+ public interface OnBackupProcessed {
+ void onBackupRestored();
+
+ void onBackupDecryptionFailed();
+
+ void onBackupRestoreFailed();
+ }
+
+ public static class BackupFile {
+ private final File file;
+ private final BackupFileHeader header;
+
+ private BackupFile(File file, BackupFileHeader header) {
+ this.file = file;
+ this.header = header;
+ }
+
+ private static BackupFile read(File file) throws IOException {
+ final FileInputStream fileInputStream = new FileInputStream(file);
+ final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
+ BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
+ fileInputStream.close();
+ return new BackupFile(file, backupFileHeader);
+ }
+
+ public BackupFileHeader getHeader() {
+ return header;
+ }
+
+ public File getFile() {
+ return file;
+ }
+ }
+
+ public class ImportBackupServiceBinder extends Binder {
+ public ImportBackupService getService() {
+ return ImportBackupService.this;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java
index 83441cce1..d045879be 100644
--- a/src/main/java/de/pixart/messenger/services/NotificationService.java
+++ b/src/main/java/de/pixart/messenger/services/NotificationService.java
@@ -57,14 +57,12 @@ import de.pixart.messenger.entities.Message;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.ui.ConversationsActivity;
import de.pixart.messenger.ui.EditAccountActivity;
-import de.pixart.messenger.ui.ManageAccountActivity;
import de.pixart.messenger.ui.TimePreference;
import de.pixart.messenger.utils.AccountUtils;
import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.GeoHelper;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xmpp.XmppConnection;
-import rocks.xmpp.addr.Jid;
public class NotificationService {
@@ -383,7 +381,13 @@ public class NotificationService {
mBuilder.setColor(ContextCompat.getColor(mXmppConnectionService, R.color.primary));
}
- public void updateNotification(final boolean notify) {
+ public void updateNotification() {
+ synchronized (notifications) {
+ updateNotification(false);
+ }
+ }
+
+ private void updateNotification(final boolean notify) {
updateNotification(notify, null, false);
}
@@ -523,15 +527,15 @@ public class NotificationService {
if (messages.size() >= 1) {
final Conversation conversation = (Conversation) messages.get(0).getConversation();
final UnreadConversation.Builder mUnreadBuilder = new UnreadConversation.Builder(conversation.getName().toString());
- mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService()
- .get(conversation, getPixel(64)));
+ mBuilder.setLargeIcon(mXmppConnectionService.getAvatarService().get(conversation, AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
mBuilder.setContentTitle(conversation.getName());
if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
int count = messages.size();
mBuilder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count));
} else {
Message message;
- if ((message = getImage(messages)) != null) {
+ //TODO starting with Android 9 we might want to put images in MessageStyle
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && (message = getImage(messages)) != null) {
modifyForImage(mBuilder, mUnreadBuilder, message, messages);
} else {
modifyForTextOnly(mBuilder, mUnreadBuilder, messages);
@@ -615,8 +619,7 @@ public class NotificationService {
private void modifyForImage(final Builder builder, final UnreadConversation.Builder uBuilder,
final Message message, final ArrayList<Message> messages) {
try {
- final Bitmap bitmap = mXmppConnectionService.getFileBackend()
- .getThumbnail(message, getPixel(288), false);
+ final Bitmap bitmap = mXmppConnectionService.getFileBackend().getThumbnail(message, getPixel(288), false);
final ArrayList<Message> tmp = new ArrayList<>();
for (final Message msg : messages) {
if (msg.getType() == Message.TYPE_TEXT
@@ -651,44 +654,39 @@ public class NotificationService {
} else {
builder.setName(UIHelper.getMessageDisplayName(message));
}
- IconCompat icon = getIcon(message);
- if (icon != null) {
- builder.setIcon(icon);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ builder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(message, AvatarService.getSystemUiAvatarSize(mXmppConnectionService), false)));
}
return builder.build();
}
- private IconCompat getIcon(Message message) {
- final Contact contact;
- if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
- contact = message.getContact();
- } else {
- Jid jid = message.getTrueCounterpart();
- contact = jid == null ? null : message.getConversation().getAccount().getRoster().getContact(jid);
- }
- if (contact != null) {
- if (contact.getProfilePhoto() != null && QuickConversationsService.isConversations()) {
- return IconCompat.createWithContentUri(contact.getProfilePhoto());
- }
- if (contact.getAvatarFilename() != null) {
- return IconCompat.createWithContentUri(mXmppConnectionService.getFileBackend().getAvatarUri(contact.getAvatarFilename()));
- }
- }
- return null;
- }
-
private void modifyForTextOnly(final Builder builder, final UnreadConversation.Builder uBuilder, final ArrayList<Message> messages) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- final Person me = new Person.Builder().setName(mXmppConnectionService.getString(R.string.me)).build();
- NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(me);
final Conversation conversation = (Conversation) messages.get(0).getConversation();
- if (conversation.getMode() == Conversation.MODE_MULTI) {
+ final Person.Builder meBuilder = new Person.Builder().setName(mXmppConnectionService.getString(R.string.me));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ meBuilder.setIcon(IconCompat.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation.getAccount(), AvatarService.getSystemUiAvatarSize(mXmppConnectionService))));
+ }
+ final Person me = meBuilder.build();
+ NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(me);
+ final boolean multiple = conversation.getMode() == Conversation.MODE_MULTI;
+ if (multiple) {
messagingStyle.setConversationTitle(conversation.getName());
}
for (Message message : messages) {
final Person sender = message.getStatus() == Message.STATUS_RECEIVED ? getPerson(message) : null;
- messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isImageMessage(message)) {
+ final Uri dataUri = FileBackend.getMediaUri(mXmppConnectionService, mXmppConnectionService.getFileBackend().getFile(message));
+ NotificationCompat.MessagingStyle.Message imageMessage = new NotificationCompat.MessagingStyle.Message(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
+ if (dataUri != null) {
+ imageMessage.setData(message.getMimeType(), dataUri);
+ }
+ messagingStyle.addMessage(imageMessage);
+ } else {
+ messagingStyle.addMessage(UIHelper.getMessagePreview(mXmppConnectionService, message).first, message.getTimeSent(), sender);
+ }
}
+ messagingStyle.setGroupConversation(multiple);
builder.setStyle(messagingStyle);
} else {
if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
@@ -733,16 +731,21 @@ public class NotificationService {
if (message.getStatus() != Message.STATUS_RECEIVED) {
return null;
}
- if (message.getType() != Message.TYPE_TEXT
- && message.getTransferable() == null
- && message.getEncryption() != Message.ENCRYPTION_PGP
- && message.getFileParams().height > 0) {
+ if (isImageMessage(message)) {
image = message;
}
}
return image;
}
+ private static boolean isImageMessage(Message message) {
+ return message.getType() != Message.TYPE_TEXT
+ && message.getTransferable() == null
+ && !message.isFileDeleted()
+ && message.getEncryption() != Message.ENCRYPTION_PGP
+ && message.getFileParams().height > 0;
+ }
+
private Message getFirstDownloadableMessage(final Iterable<Message> messages) {
for (final Message message : messages) {
if (message.getTransferable() != null || (message.getType() == Message.TYPE_TEXT && message.treatAsDownloadable())) {
@@ -773,7 +776,7 @@ public class NotificationService {
}
private PendingIntent createShowLocationIntent(final Message message) {
- Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(message, mXmppConnectionService);
+ Iterable<Intent> intents = GeoHelper.createGeoIntentsFromMessage(mXmppConnectionService, message);
for (Intent intent : intents) {
if (intent.resolveActivity(mXmppConnectionService.getPackageManager()) != null) {
return PendingIntent.getActivity(mXmppConnectionService, generateRequestCode(message.getConversation(), 18), intent, PendingIntent.FLAG_UPDATE_CURRENT);
@@ -905,57 +908,52 @@ public class NotificationService {
Notification createForegroundNotification() {
final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service));
- if (Compatibility.runsAndTargetsTwentySix(mXmppConnectionService) || Config.SHOW_CONNECTED_ACCOUNTS) {
- List<Account> accounts = mXmppConnectionService.getAccounts();
- int enabled = 0;
- int connected = 0;
- String status;
- Account mAccount = null;
- if (accounts != null) {
- for (Account account : accounts) {
- if (account.isOnlineAndConnected()) {
- connected++;
- enabled++;
- } else if (account.isEnabled()) {
- enabled++;
- }
- }
- if (accounts.size() == 1) {
- mAccount = accounts.get(0);
- if (mAccount.getStatus() == Account.State.ONLINE) {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_online) + ")";
- status = " " + status;
- Log.d(Config.LOGTAG, "Status: " + status);
- mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
- } else if (mAccount.getStatus() == Account.State.CONNECTING) {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_connecting) + ")";
- status = " " + status;
- Log.d(Config.LOGTAG, "Status: " + status);
- mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
- } else {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
- status = " " + status;
- Log.d(Config.LOGTAG, "Status: " + status);
- mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
- }
- } else if (accounts.size() > 1) {
- mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
- } else {
- status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
- status = " " + status;
- Log.d(Config.LOGTAG, "Status: " + status);
- mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
+ Account mAccount = null;
+ String status;
+ final List<Account> accounts = mXmppConnectionService.getAccounts();
+ int enabled = 0;
+ int connected = 0;
+ if (accounts != null) {
+ for (Account account : accounts) {
+ if (account.isOnlineAndConnected()) {
+ connected++;
+ enabled++;
+ } else if (account.isEnabled()) {
+ enabled++;
}
+ }
+ }
+ if (accounts.size() == 1) {
+ mAccount = accounts.get(0);
+ if (mAccount.getStatus() == Account.State.ONLINE) {
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_online) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
+ } else if (mAccount.getStatus() == Account.State.CONNECTING) {
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_connecting) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
} else {
- mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations));
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
}
+ } else if (accounts.size() > 1) {
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service));
} else {
- mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_open_conversations));
+ status = "(" + mXmppConnectionService.getString(R.string.account_status_offline) + ")";
+ status = " " + status;
+ Log.d(Config.LOGTAG, "Status: " + status);
+ mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.conversations_foreground_service) + status);
}
+ mBuilder.setContentText(mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
mBuilder.setContentIntent(createOpenConversationsIntent());
mBuilder.setWhen(0);
mBuilder.setPriority(Notification.PRIORITY_MIN);
- mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp);
+ mBuilder.setSmallIcon(connected > 0 ? R.drawable.ic_link_white_24dp : R.drawable.ic_link_off_white_24dp);
if (Compatibility.runsTwentySix()) {
mBuilder.setChannelId(FOREGROUND_CHANNEL_ID);
}
@@ -1042,7 +1040,6 @@ public class NotificationService {
Notification AppUpdateNotification(PendingIntent intent, String version, String filesize) {
Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.app_name));
- mBuilder.setContentText(mXmppConnectionService.getString(R.string.notification_export_logs_title));
mBuilder.setContentText(String.format(mXmppConnectionService.getString(R.string.update_available), version, filesize));
mBuilder.setSmallIcon(R.drawable.ic_update_notification);
mBuilder.setContentIntent(intent);
diff --git a/src/main/java/de/pixart/messenger/services/ShortcutService.java b/src/main/java/de/pixart/messenger/services/ShortcutService.java
index fa4853665..94bbc15cd 100644
--- a/src/main/java/de/pixart/messenger/services/ShortcutService.java
+++ b/src/main/java/de/pixart/messenger/services/ShortcutService.java
@@ -123,6 +123,7 @@ public class ShortcutService {
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("xmpp:" + contact.getJid().asBareJid().toString()));
intent.putExtra("account", contact.getAccount().getJid().asBareJid().toString());
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
index 98e330ad0..551ff7818 100644
--- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
+++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java
@@ -8,6 +8,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -52,6 +53,7 @@ import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpServiceConnection;
+import java.io.File;
import java.net.URL;
import java.security.SecureRandom;
import java.security.Security;
@@ -94,7 +96,6 @@ import de.pixart.messenger.entities.Bookmark;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Conversational;
-import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.MucOptions;
import de.pixart.messenger.entities.MucOptions.OnRenameListener;
@@ -102,8 +103,6 @@ import de.pixart.messenger.entities.Presence;
import de.pixart.messenger.entities.PresenceTemplate;
import de.pixart.messenger.entities.Roster;
import de.pixart.messenger.entities.ServiceDiscoveryResult;
-import de.pixart.messenger.entities.Transferable;
-import de.pixart.messenger.entities.TransferablePlaceholder;
import de.pixart.messenger.generator.AbstractGenerator;
import de.pixart.messenger.generator.IqGenerator;
import de.pixart.messenger.generator.MessageGenerator;
@@ -116,6 +115,7 @@ import de.pixart.messenger.parser.MessageParser;
import de.pixart.messenger.parser.PresenceParser;
import de.pixart.messenger.persistance.DatabaseBackend;
import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.ui.ChooseAccountForProfilePictureActivity;
import de.pixart.messenger.ui.SettingsActivity;
import de.pixart.messenger.ui.UiCallback;
import de.pixart.messenger.ui.interfaces.OnAvatarPublication;
@@ -174,6 +174,7 @@ public class XmppConnectionService extends Service {
public static final String ACTION_IDLE_PING = "idle_ping";
public static final String ACTION_FCM_TOKEN_REFRESH = "fcm_token_refresh";
public static final String ACTION_FCM_MESSAGE_RECEIVED = "fcm_message_received";
+ private static final String ACTION_POST_CONNECTIVITY_CHANGE = "de.pixart.messenger.POST_CONNECTIVITY_CHANGE";
public static final String FDroid = "org.fdroid.fdroid";
public static final String PlayStore = "com.android.vending";
private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp";
@@ -192,7 +193,8 @@ public class XmppConnectionService extends Service {
private final IBinder mBinder = new XmppConnectionBinder();
private final List<Conversation> conversations = new CopyOnWriteArrayList<>();
private final IqGenerator mIqGenerator = new IqGenerator(this);
- private final List<String> mInProgressAvatarFetches = new ArrayList<>();
+ private final Set<String> mInProgressAvatarFetches = new HashSet<>();
+ private final Set<String> mOmittedPepAvatarFetches = new HashSet<>();
private final HashSet<Jid> mLowPingTimeoutMode = new HashSet<>();
private final OnIqPacketReceived mDefaultIqHandler = (account, packet) -> {
if (packet.getType() != IqPacket.TYPE.RESULT) {
@@ -238,7 +240,6 @@ public class XmppConnectionService extends Service {
) {
@Override
public void onEvent(int event, String path) {
- Log.d(Config.LOGTAG, "event " + event + " path=" + path);
markFileDeleted(path);
}
};
@@ -336,6 +337,7 @@ public class XmppConnectionService extends Service {
syncDirtyContacts(account);
}
};
+ private boolean destroyed = false;
private int unreadCount = -1;
private AtomicLong mLastExpiryRun = new AtomicLong(0);
private SecureRandom mRandom;
@@ -375,7 +377,6 @@ public class XmppConnectionService extends Service {
if (!conversation.startOtrIfNeeded()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": couldn't start OTR with " + conversation.getContact().getJid() + " when needed");
}
- checkDeletedFiles(conversation);
sendUnsentMessages(conversation);
resendFailedFileMessages(conversation);
}
@@ -582,7 +583,7 @@ public class XmppConnectionService extends Service {
final String action = intent == null ? null : intent.getAction();
final boolean needsForegroundService = intent != null && intent.getBooleanExtra(EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
if (needsForegroundService) {
- Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event");
+ Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")");
toggleForegroundService(true);
}
String pushedAccountHash = null;
@@ -591,8 +592,13 @@ public class XmppConnectionService extends Service {
final String uuid = intent.getStringExtra("uuid");
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
- if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
- resetAllAttemptCounts(true, false);
+ if (hasInternetConnection()) {
+ if (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0) {
+ schedulePostConnectivityChange();
+ }
+ if (Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
+ resetAllAttemptCounts(true, false);
+ }
}
break;
case Intent.ACTION_SHUTDOWN:
@@ -705,7 +711,7 @@ public class XmppConnectionService extends Service {
}
synchronized (this) {
WakeLockHelper.acquire(wakeLock);
- boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action);
+ boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action) || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
HashSet<Account> pingCandidates = new HashSet<>();
for (Account account : accounts) {
pingNow |= processAccountState(account,
@@ -1117,6 +1123,7 @@ public class XmppConnectionService extends Service {
@SuppressLint("TrulyRandom")
@Override
public void onCreate() {
+ this.destroyed = false;
OmemoSetting.load(this);
ExceptionHelper.init(getApplicationContext());
try {
@@ -1152,8 +1159,10 @@ public class XmppConnectionService extends Service {
editor.putBoolean(SettingsActivity.SHOW_FOREGROUND_SERVICE, true);
Log.d(Config.LOGTAG, Build.MANUFACTURER + " is on blacklist. enabling foreground service");
}
- editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts()).apply();
+ final boolean hasEnabledAccounts = hasEnabledAccounts();
+ editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
editor.apply();
+ toggleSetProfilePictureActivity(hasEnabledAccounts);
restoreFromDatabase();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
@@ -1161,7 +1170,8 @@ public class XmppConnectionService extends Service {
}
if (Compatibility.hasStoragePermission(this)) {
Log.d(Config.LOGTAG, "starting file observer");
- new Thread(fileObserver::startWatching).start();
+ mFileAddingExecutor.execute(this.fileObserver::startWatching);
+ mFileAddingExecutor.execute(this::checkForDeletedFiles);
}
if (Config.supportOpenPgp()) {
this.pgpServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() {
@@ -1202,6 +1212,32 @@ public class XmppConnectionService extends Service {
CancelAutomaticExport(false);
}
+ private void checkForDeletedFiles() {
+ if (destroyed) {
+ Log.d(Config.LOGTAG, "Do not check for deleted files because service has been destroyed");
+ return;
+ }
+ final long start = SystemClock.elapsedRealtime();
+ final List<DatabaseBackend.FilePathInfo> relativeFilePaths = databaseBackend.getFilePathInfo();
+ final List<DatabaseBackend.FilePathInfo> changed = new ArrayList<>();
+ for (final DatabaseBackend.FilePathInfo filePath : relativeFilePaths) {
+ if (destroyed) {
+ Log.d(Config.LOGTAG, "Stop checking for deleted files because service has been destroyed");
+ return;
+ }
+ final File file = fileBackend.getFileForPath(filePath.path);
+ if (filePath.setFileDeleted(!file.exists())) {
+ changed.add(filePath);
+ }
+ }
+ final long duration = SystemClock.elapsedRealtime() - start;
+ Log.d(Config.LOGTAG, "found " + changed.size() + " changed files on start up. total=" + relativeFilePaths.size() + ". (" + duration + "ms)");
+ if (changed.size() > 0) {
+ databaseBackend.markFilesAsChanged(changed);
+ markChangedFiles(changed);
+ }
+ }
+
public void startContactObserver() {
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, new ContentObserver(null) {
@Override
@@ -1230,6 +1266,7 @@ public class XmppConnectionService extends Service {
} catch (IllegalArgumentException e) {
//ignored
}
+ destroyed = false;
fileObserver.stopWatching();
super.onDestroy();
// cancel scheduled exporter
@@ -1238,7 +1275,8 @@ public class XmppConnectionService extends Service {
public void restartFileObserver() {
Log.d(Config.LOGTAG, "restarting file observer");
- new Thread(fileObserver::restartWatching).start();
+ mFileAddingExecutor.execute(this.fileObserver::restartWatching);
+ mFileAddingExecutor.execute(this::checkForDeletedFiles);
}
public void toggleScreenEventReceiver() {
@@ -1289,13 +1327,15 @@ public class XmppConnectionService extends Service {
private void logoutAndSave(boolean stop) {
int activeAccounts = 0;
- for (final Account account : accounts) {
- if (account.getStatus() != Account.State.DISABLED) {
- databaseBackend.writeRoster(account.getRoster());
- activeAccounts++;
- }
- if (account.getXmppConnection() != null) {
- new Thread(() -> disconnect(account, false)).start();
+ if (accounts != null) {
+ for (final Account account : accounts) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ databaseBackend.writeRoster(account.getRoster());
+ activeAccounts++;
+ }
+ if (account.getXmppConnection() != null) {
+ new Thread(() -> disconnect(account, false)).start();
+ }
}
}
if (stop || activeAccounts == 0) {
@@ -1304,6 +1344,26 @@ public class XmppConnectionService extends Service {
}
}
+ private void schedulePostConnectivityChange() {
+ final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager == null) {
+ return;
+ }
+ final long triggerAtMillis = SystemClock.elapsedRealtime() + (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL * 1000);
+ final Intent intent = new Intent(this, EventReceiver.class);
+ intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE);
+ try {
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, pendingIntent);
+ } else {
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, pendingIntent);
+ }
+ } catch (RuntimeException e) {
+ Log.e(Config.LOGTAG, "unable to schedule alarm for post connectivity change", e);
+ }
+ }
+
public void scheduleWakeUpCall(int seconds, int requestCode) {
final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000;
final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
@@ -1401,15 +1461,16 @@ public class XmppConnectionService extends Service {
boolean saveInDb = addToConversation;
message.setStatus(Message.STATUS_WAITING);
+ if (message.getEncryption() != Message.ENCRYPTION_NONE && conversation.getMode() == Conversation.MODE_MULTI && conversation.isPrivateAndNonAnonymous()) {
+ if (conversation.setAttribute(Conversation.ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, true)) {
+ databaseBackend.updateConversation(conversation);
+ }
+ }
+
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
conversation.endOtrIfNeeded();
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
- new Conversation.OnMessageFound() {
- @Override
- public void onMessageFound(Message message) {
- markMessage(message, Message.STATUS_SEND_FAILED);
- }
- });
+ message1 -> markMessage(message1, Message.STATUS_SEND_FAILED));
}
if (account.isOnlineAndConnected()) {
@@ -1776,7 +1837,6 @@ public class XmppConnectionService extends Service {
private void restoreMessages(Conversation conversation) {
conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE));
- checkDeletedFiles(conversation);
conversation.findUnsentTextMessages(message -> markMessage(message, Message.STATUS_WAITING));
conversation.findUnreadMessages(message -> mNotificationService.pushFromBacklog(message));
}
@@ -1817,45 +1877,52 @@ public class XmppConnectionService extends Service {
return this.conversations;
}
- private void checkDeletedFiles(Conversation conversation) {
- conversation.findMessagesWithFiles(message -> {
- if (!getFileBackend().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) {
- markMessage(message, Message.STATUS_SEND_FAILED);
- }
- }
- });
+ private void markFileDeleted(final String path) {
+ final File file = new File(path);
+ final boolean isInternalFile = fileBackend.isInternalFile(file);
+ final List<String> uuids = databaseBackend.markFileAsDeleted(file, isInternalFile);
+ Log.d(Config.LOGTAG, "deleted file " + path + " internal=" + isInternalFile + ", database hits=" + uuids.size());
+ markUuidsAsDeletedFiles(uuids);
}
- private void markFileDeleted(final String path) {
- Log.d(Config.LOGTAG, "deleted file " + path);
+ private void markUuidsAsDeletedFiles(List<String> uuids) {
+ boolean deleted = false;
for (Conversation conversation : getConversations()) {
- conversation.findMessagesWithFiles(message -> {
- DownloadableFile file = fileBackend.getFile(message);
- if (file.getAbsolutePath().equals(path)) {
- if (!file.exists()) {
- 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) {
- markMessage(message, Message.STATUS_SEND_FAILED);
- } else {
- updateConversationUi();
- }
- } else {
- Log.d(Config.LOGTAG, "found matching message for file " + path + " but file still exists");
- }
- }
- });
+ deleted |= conversation.markAsDeleted(uuids);
+ }
+ if (deleted) {
+ updateConversationUi();
+ }
+ }
+
+ private void markChangedFiles(List<DatabaseBackend.FilePathInfo> infos) {
+ boolean changed = false;
+ for (Conversation conversation : getConversations()) {
+ changed |= conversation.markAsChanged(infos);
+ }
+ if (changed) {
+ updateConversationUi();
}
}
public void populateWithOrderedConversations(final List<Conversation> list) {
- populateWithOrderedConversations(list, true);
+ populateWithOrderedConversations(list, true, true);
+ }
+
+ public void populateWithOrderedConversations(final List<Conversation> list, final boolean includeNoFileUpload) {
+ populateWithOrderedConversations(list, includeNoFileUpload, true);
}
- public void populateWithOrderedConversations(final List<Conversation> list, boolean includeNoFileUpload) {
+ public void populateWithOrderedConversations(final List<Conversation> list, final boolean includeNoFileUpload, final boolean sort) {
+ final List<String> orderedUuids;
+ if (sort) {
+ orderedUuids = null;
+ } else {
+ orderedUuids = new ArrayList<>();
+ for (Conversation conversation : list) {
+ orderedUuids.add(conversation.getUuid());
+ }
+ }
list.clear();
if (includeNoFileUpload) {
list.addAll(getConversations());
@@ -1868,7 +1935,18 @@ public class XmppConnectionService extends Service {
}
}
try {
- Collections.sort(list);
+ if (orderedUuids != null) {
+ Collections.sort(list, (a, b) -> {
+ final int indexA = orderedUuids.indexOf(a.getUuid());
+ final int indexB = orderedUuids.indexOf(b.getUuid());
+ if (indexA == -1 || indexB == -1 || indexA == indexB) {
+ return a.compareTo(b);
+ }
+ return indexA - indexB;
+ });
+ } else {
+ Collections.sort(list);
+ }
} catch (IllegalArgumentException e) {
//ignore
}
@@ -1886,7 +1964,6 @@ public class XmppConnectionService extends Service {
List<Message> messages = databaseBackend.getMessages(conversation, 50, timestamp);
if (messages.size() > 0) {
conversation.addAll(0, messages);
- checkDeletedFiles(conversation);
callback.onMoreMessagesLoaded(messages.size(), conversation);
} else if (conversation.hasMessagesLeftOnServer()
&& account.isOnlineAndConnected()
@@ -2033,7 +2110,6 @@ public class XmppConnectionService extends Service {
}
}
}
- checkDeletedFiles(c);
if (joinAfterCreate) {
joinMuc(c);
@@ -2097,7 +2173,6 @@ public class XmppConnectionService extends Service {
updateConversationUi();
c.messagesLoaded.set(true);
}
- checkDeletedFiles(c);
}
});
updateConversationUi();
@@ -2153,7 +2228,19 @@ public class XmppConnectionService extends Service {
}
private void syncEnabledAccountSetting() {
- getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts()).apply();
+ final boolean hasEnabledAccounts = hasEnabledAccounts();
+ getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
+ toggleSetProfilePictureActivity(hasEnabledAccounts);
+ }
+
+ private void toggleSetProfilePictureActivity(final boolean enabled) {
+ try {
+ final ComponentName name = new ComponentName(this, ChooseAccountForProfilePictureActivity.class);
+ final int targetState = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ getPackageManager().setComponentEnabledSetting(name, targetState, PackageManager.DONT_KILL_APP);
+ } catch (IllegalStateException e) {
+ Log.d(Config.LOGTAG, "unable to toggle profile picture actvitiy");
+ }
}
public void createAccountFromKey(final String alias, final OnAccountCreated callback) {
@@ -2584,6 +2671,9 @@ public class XmppConnectionService extends Service {
if (mucOptions.nonanonymous() && !mucOptions.membersOnly() && !conversation.getBooleanAttribute("accept_non_anonymous", false)) {
mucOptions.setError(MucOptions.Error.NON_ANONYMOUS);
updateConversationUi();
+ if (onConferenceJoined != null) {
+ onConferenceJoined.onConferenceJoined(conversation);
+ }
return;
}
final Jid joinJid = mucOptions.getSelf().getFullJid();
@@ -2867,6 +2957,31 @@ public class XmppConnectionService extends Service {
return null;
}
+ public void createPublicChannel(final Account account, final String name, final Jid address, final UiCallback<Conversation> callback) {
+ joinMuc(findOrCreateConversation(account, address, true, false, true), conversation -> {
+ final Bundle configuration = IqGenerator.defaultChannelConfiguration();
+ if (!TextUtils.isEmpty(name)) {
+ configuration.putString("muc#roomconfig_roomname", name);
+ }
+ pushConferenceConfiguration(conversation, configuration, new OnConfigurationPushed() {
+ @Override
+ public void onPushSucceeded() {
+ saveConversationAsBookmark(conversation, name);
+ callback.success(conversation);
+ }
+
+ @Override
+ public void onPushFailed() {
+ if (conversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
+ callback.error(R.string.unable_to_set_channel_configuration, conversation);
+ } else {
+ callback.error(R.string.joined_an_existing_channel, conversation);
+ }
+ }
+ });
+ });
+ }
+
public boolean createAdhocConference(final Account account,
final String name,
final Iterable<Jid> jids,
@@ -2886,7 +3001,7 @@ public class XmppConnectionService extends Service {
joinMuc(conversation, new OnConferenceJoined() {
@Override
public void onConferenceJoined(final Conversation conversation) {
- final Bundle configuration = IqGenerator.defaultRoomConfiguration();
+ final Bundle configuration = IqGenerator.defaultGroupChatConfiguration();
if (!TextUtils.isEmpty(name)) {
configuration.putString("muc#roomconfig_roomname", name);
}
@@ -3009,6 +3124,10 @@ public class XmppConnectionService extends Service {
}
public void pushConferenceConfiguration(final Conversation conversation, final Bundle options, final OnConfigurationPushed callback) {
+ if (options.getString("muc#roomconfig_whois", "moderators").equals("anyone")) {
+ conversation.setAttribute("accept_non_anonymous", true);
+ updateConversation(conversation);
+ }
IqPacket request = new IqPacket(IqPacket.TYPE.GET);
request.setTo(conversation.getJid().asBareJid());
request.query("http://jabber.org/protocol/muc#owner");
@@ -3018,6 +3137,7 @@ public class XmppConnectionService extends Service {
if (packet.getType() == IqPacket.TYPE.RESULT) {
Data data = Data.parse(packet.query().findChild("x", Namespace.DATA));
data.submit(options);
+ Log.d(Config.LOGTAG, data.toString());
IqPacket set = new IqPacket(IqPacket.TYPE.SET);
set.setTo(conversation.getJid().asBareJid());
set.query("http://jabber.org/protocol/muc#owner").addChild(data);
@@ -3075,18 +3195,12 @@ public class XmppConnectionService extends Service {
sendIqPacket(conference.getAccount(), request, mDefaultIqHandler);
}
- public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role, final OnRoleChanged callback) {
+ public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) {
IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString());
Log.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());
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- callback.onRoleChangedSuccessful(nick);
- } else {
- callback.onRoleChangeFailed(nick, R.string.could_not_change_role);
- }
+ sendIqPacket(conference.getAccount(), request, (account, packet) -> {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick);
}
});
}
@@ -3386,6 +3500,7 @@ public class XmppConnectionService extends Service {
if (account.setAvatar(avatar.getFilename())) {
getAvatarService().clear(account);
databaseBackend.updateAccount(account);
+ notifyAccountAvatarHasChanged(account);
}
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": published avatar " + (avatar.size / 1024) + "KiB");
if (callback != null) {
@@ -3465,7 +3580,7 @@ public class XmppConnectionService extends Service {
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)) {
+ if (mInProgressAvatarFetches.add(KEY)) {
switch (avatar.origin) {
case PEP:
this.mInProgressAvatarFetches.add(KEY);
@@ -3476,6 +3591,10 @@ public class XmppConnectionService extends Service {
fetchAvatarVcard(account, avatar, callback);
break;
}
+ } else if (avatar.origin == Avatar.Origin.PEP) {
+ mOmittedPepAvatarFetches.add(KEY);
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": already fetching " + avatar.origin + " avatar for " + avatar.owner);
}
}
}
@@ -3533,54 +3652,54 @@ public class XmppConnectionService extends Service {
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().asBareJid()
- + ": successfully fetched vCard avatar for " + avatar.owner);
- if (avatar.owner.isBareJid()) {
- if (account.getJid().asBareJid().equals(avatar.owner) && account.getAvatar() == null) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": had no avatar. replacing with vcard");
- account.setAvatar(avatar.getFilename());
- databaseBackend.updateAccount(account);
- getAvatarService().clear(account);
- updateAccountUi();
- } else {
- Contact contact = account.getRoster().getContact(avatar.owner);
- if (contact.setAvatar(avatar)) {
- syncRoster(account);
- getAvatarService().clear(contact);
- updateRosterUi();
- }
- }
- updateConversationUi();
+ this.sendIqPacket(account, packet, (account1, packet1) -> {
+ final boolean previouslyOmittedPepFetch;
+ synchronized (mInProgressAvatarFetches) {
+ final String KEY = generateFetchKey(account1, avatar);
+ mInProgressAvatarFetches.remove(KEY);
+ previouslyOmittedPepFetch = mOmittedPepAvatarFetches.remove(KEY);
+ }
+ if (packet1.getType() == IqPacket.TYPE.RESULT) {
+ Element vCard = packet1.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, account1.getJid().asBareJid()
+ + ": successfully fetched vCard avatar for " + avatar.owner + " omittedPep=" + previouslyOmittedPepFetch);
+ if (avatar.owner.isBareJid()) {
+ if (account1.getJid().asBareJid().equals(avatar.owner) && account1.getAvatar() == null) {
+ Log.d(Config.LOGTAG, account1.getJid().asBareJid() + ": had no avatar. replacing with vcard");
+ account1.setAvatar(avatar.getFilename());
+ databaseBackend.updateAccount(account1);
+ getAvatarService().clear(account1);
+ updateAccountUi();
} else {
- Conversation conversation = find(account, avatar.owner.asBareJid());
- if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
- MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner);
- if (user != null) {
- if (user.setAvatar(avatar)) {
- getAvatarService().clear(user);
- updateConversationUi();
- updateMucRosterUi();
- }
- if (user.getRealJid() != null) {
- Contact contact = account.getRoster().getContact(user.getRealJid());
- if (contact.setAvatar(avatar)) {
- syncRoster(account);
- getAvatarService().clear(contact);
- updateRosterUi();
- }
+ Contact contact = account1.getRoster().getContact(avatar.owner);
+ if (contact.setAvatar(avatar, previouslyOmittedPepFetch)) {
+ syncRoster(account1);
+ getAvatarService().clear(contact);
+ updateRosterUi();
+ }
+ }
+ updateConversationUi();
+ } else {
+ Conversation conversation = find(account1, avatar.owner.asBareJid());
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner);
+ if (user != null) {
+ if (user.setAvatar(avatar)) {
+ getAvatarService().clear(user);
+ updateConversationUi();
+ updateMucRosterUi();
+ }
+ if (user.getRealJid() != null) {
+ Contact contact = account1.getRoster().getContact(user.getRealJid());
+ if (contact.setAvatar(avatar)) {
+ syncRoster(account1);
+ getAvatarService().clear(contact);
+ updateRosterUi();
}
}
}
@@ -3625,6 +3744,23 @@ public class XmppConnectionService extends Service {
});
}
+ public void notifyAccountAvatarHasChanged(final Account account) {
+ final XmppConnection connection = account.getXmppConnection();
+ if (connection != null && connection.getFeatures().bookmarksConversion()) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": avatar changed. resending presence to online group chats");
+ for (Conversation conversation : conversations) {
+ if (conversation.getAccount() == account && conversation.getMode() == Conversational.MODE_MULTI) {
+ final MucOptions mucOptions = conversation.getMucOptions();
+ if (mucOptions.online()) {
+ PresencePacket packet = mPresenceGenerator.selfPresence(account, Presence.Status.ONLINE, mucOptions.nonanonymous());
+ packet.setTo(mucOptions.getSelf().getFullJid());
+ connection.sendPresencePacket(packet);
+ }
+ }
+ }
+ }
+ }
+
public void deleteContactOnServer(Contact contact) {
contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
contact.resetOption(Contact.Options.DIRTY_PUSH);
@@ -4582,12 +4718,6 @@ public class XmppConnectionService extends Service {
void onAffiliationChangeFailed(Jid jid, int resId);
}
- public interface OnRoleChanged {
- void onRoleChangedSuccessful(String nick);
-
- void onRoleChangeFailed(String nick, int resid);
- }
-
public interface OnRoomDestroy {
void onRoomDestroySucceeded(int resId);
diff --git a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java
index 7163f77cc..b642909b0 100644
--- a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java
@@ -25,6 +25,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
BlockContactDialog.show(BlocklistActivity.this, (Contact) getListItems().get(position));
return true;
});
+ this.binding.fab.show();
this.binding.fab.setOnClickListener((v) -> showEnterJidDialog());
}
@@ -38,7 +39,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
}
filterContacts();
Fragment fragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
- if (fragment != null && fragment instanceof OnBackendConnected) {
+ if (fragment instanceof OnBackendConnected) {
((OnBackendConnected) fragment).onBackendConnected();
}
}
diff --git a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java
index e2021e7ea..df9113448 100644
--- a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java
@@ -5,7 +5,6 @@ import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.view.View;
import android.widget.Button;
-import android.widget.EditText;
import android.widget.Toast;
import de.pixart.messenger.R;
diff --git a/src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java
new file mode 100644
index 000000000..2d109809e
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ChooseAccountForProfilePictureActivity.java
@@ -0,0 +1,82 @@
+package de.pixart.messenger.ui;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.ui.adapter.AccountAdapter;
+
+public class ChooseAccountForProfilePictureActivity extends XmppActivity {
+
+ protected final List<Account> accountList = new ArrayList<>();
+ protected ListView accountListView;
+ protected AccountAdapter mAccountAdapter;
+
+ @Override
+ protected void refreshUiReal() {
+ loadEnabledAccounts();
+ mAccountAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_manage_accounts);
+ setSupportActionBar(findViewById(R.id.toolbar));
+ configureActionBar(getSupportActionBar(), false);
+ accountListView = findViewById(R.id.account_list);
+ this.mAccountAdapter = new AccountAdapter(this, accountList, false);
+ accountListView.setAdapter(this.mAccountAdapter);
+ accountListView.setOnItemClickListener((arg0, view, position, arg3) -> {
+ final Account account = accountList.get(position);
+ goToProfilePictureActivity(account);
+ });
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ final int theme = findTheme();
+ if (this.mTheme != theme) {
+ recreate();
+ }
+ }
+
+ @Override
+ void onBackendConnected() {
+ loadEnabledAccounts();
+ if (accountList.size() == 1) {
+ goToProfilePictureActivity(accountList.get(0));
+ return;
+ }
+ mAccountAdapter.notifyDataSetChanged();
+ }
+
+ private void loadEnabledAccounts() {
+ accountList.clear();
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (account.isEnabled()) {
+ accountList.add(account);
+ }
+ }
+ }
+
+ private void goToProfilePictureActivity(Account account) {
+ final Intent startIntent = getIntent();
+ final Uri uri = startIntent == null ? null : startIntent.getData();
+ if (uri != null) {
+ Intent intent = new Intent(this, PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString());
+ intent.setData(uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ startActivity(intent);
+ }
+ finish();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
index 7c9a5830b..646ab8027 100644
--- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
@@ -3,14 +3,8 @@ package de.pixart.messenger.ui;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentSender.SendIntentException;
-import android.content.res.Resources;
import android.databinding.DataBindingUtil;
-import android.graphics.Bitmap;
import android.graphics.PorterDuff;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
@@ -18,45 +12,34 @@ import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
-import android.view.WindowManager;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
import android.widget.Toast;
-import org.openintents.openpgp.util.OpenPgpUtils;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
-import de.pixart.messenger.crypto.PgpEngine;
import de.pixart.messenger.databinding.ActivityMucDetailsBinding;
-import de.pixart.messenger.databinding.ContactBinding;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Bookmark;
-import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.MucOptions;
import de.pixart.messenger.entities.MucOptions.User;
-import de.pixart.messenger.services.EmojiService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate;
import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate;
import de.pixart.messenger.ui.adapter.MediaAdapter;
+import de.pixart.messenger.ui.adapter.UserPreviewAdapter;
import de.pixart.messenger.ui.interfaces.OnMediaLoaded;
import de.pixart.messenger.ui.util.Attachment;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.GridManager;
+import de.pixart.messenger.ui.util.MucConfiguration;
import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper;
import de.pixart.messenger.ui.util.MyLinkify;
import de.pixart.messenger.ui.util.SoftKeyboardUtils;
@@ -66,26 +49,16 @@ import de.pixart.messenger.utils.MenuDoubleTabUtil;
import de.pixart.messenger.utils.StringUtils;
import de.pixart.messenger.utils.StylingHelper;
import de.pixart.messenger.utils.TimeframeUtils;
-import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.utils.XmppUri;
import me.drakeet.support.toast.ToastCompat;
import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.entities.Bookmark.printableValue;
-import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
import static de.pixart.messenger.utils.StringUtils.changed;
-public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded {
+public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded {
public static final String ACTION_VIEW_MUC = "view_muc";
- private static final float INACTIVE_ALPHA = 0.4684f; //compromise between dark and light theme
private Conversation mConversation;
- private OnClickListener inviteListener = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- inviteToConversation(mConversation);
- }
- };
private OnClickListener destroyListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -108,8 +81,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
};
private ActivityMucDetailsBinding binding;
private MediaAdapter mMediaAdapter;
+ private UserPreviewAdapter mUserPreviewAdapter;
private String uuid = null;
- private User mSelectedUser = null;
private boolean mAdvancedMode = false;
@@ -193,84 +166,22 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
public void onClick(View v) {
final MucOptions mucOptions = mConversation.getMucOptions();
AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
- builder.setTitle(R.string.conference_options);
- final String[] options;
- final boolean[] values;
- if (mAdvancedMode) {
- options = new String[]{
- getString(R.string.members_only),
- getString(R.string.moderated),
- getString(R.string.non_anonymous)
- };
- values = new boolean[]{
- mucOptions.membersOnly(),
- mucOptions.moderated(),
- mucOptions.nonanonymous()
- };
- } else {
- options = new String[]{
- getString(R.string.members_only),
- getString(R.string.non_anonymous)
- };
- values = new boolean[]{
- mucOptions.membersOnly(),
- mucOptions.nonanonymous()
- };
- }
- builder.setMultiChoiceItems(options, values, (dialog, which, isChecked) -> values[which] = isChecked);
+ MucConfiguration configuration = MucConfiguration.get(ConferenceDetailsActivity.this, mucOptions);
+ builder.setTitle(configuration.title);
+ final boolean[] values = configuration.values;
+ builder.setMultiChoiceItems(configuration.names, values, (dialog, which, isChecked) -> values[which] = isChecked);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
- if (!mucOptions.membersOnly() && values[0]) {
- xmppConnectionService.changeAffiliationsInConference(mConversation,
- MucOptions.Affiliation.NONE,
- MucOptions.Affiliation.MEMBER);
- }
- Bundle options1 = new Bundle();
- options1.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0");
- if (values.length == 2) {
- options1.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators");
- } else if (values.length == 3) {
- options1.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0");
- options1.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators");
- }
- options1.putString("muc#roomconfig_persistentroom", "1");
- final boolean whois = values.length == 2 ? values[1] : values[2];
- if (values[0] == whois) {
- options1.putString("muc#roomconfig_publicroom", whois ? "0" : "1");
- }
+ Bundle options = configuration.toBundle(values);
+ options.putString("muc#roomconfig_persistentroom", "1");
xmppConnectionService.pushConferenceConfiguration(mConversation,
- options1,
+ options,
ConferenceDetailsActivity.this);
});
builder.create().show();
}
};
- public static boolean cancelPotentialWork(User user, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final User old = bitmapWorkerTask.o;
- if (old == null || user != old) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
@Override
public void onConversationUpdate() {
refreshUi();
@@ -289,18 +200,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji));
- new EmojiService(this).init(useBundledEmoji);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_details);
this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings);
- this.binding.invite.setOnClickListener(inviteListener);
- this.binding.invite.setVisibility(View.GONE);
- this.binding.invite.setOnClickListener(inviteListener);
this.binding.destroy.setVisibility(View.GONE);
this.binding.destroy.setOnClickListener(destroyListener);
this.binding.leaveMuc.setVisibility(View.GONE);
this.binding.addContactButton.setVisibility(View.GONE);
- this.binding.mucMoreDetails.setVisibility(View.GONE);
setSupportActionBar((Toolbar) binding.toolbar);
configureActionBar(getSupportActionBar());
this.binding.editNickButton.setOnClickListener(v -> quickEdit(mConversation.getMucOptions().getActualNick(),
@@ -334,16 +239,27 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
this.binding.mucEditSubject.addTextChangedListener(this);
this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject));
this.binding.autojoinCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- final Bookmark bookmark = mConversation.getBookmark();
- if (bookmark != null) {
- bookmark.setAutojoin(this.binding.autojoinCheckbox.isChecked());
- xmppConnectionService.pushBookmarks(bookmark.getAccount());
- updateView();
+ if (mConversation != null) {
+ final Bookmark bookmark = mConversation.getBookmark();
+ if (bookmark != null) {
+ bookmark.setAutojoin(this.binding.autojoinCheckbox.isChecked());
+ xmppConnectionService.pushBookmarks(bookmark.getAccount());
+ updateView();
+ }
}
});
- mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
+ this.mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
+ this.mUserPreviewAdapter = new UserPreviewAdapter();
this.binding.media.setAdapter(mMediaAdapter);
+ this.binding.users.setAdapter(mUserPreviewAdapter);
GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size);
+ GridManager.setupLayoutManager(this, this.binding.users, R.dimen.media_size);
+ this.binding.invite.setOnClickListener(v -> inviteToConversation(mConversation));
+ this.binding.showUsers.setOnClickListener(v -> {
+ Intent intent = new Intent(this, MucUsersActivity.class);
+ intent.putExtra("uuid", mConversation.getUuid());
+ startActivity(intent);
+ });
}
@Override
@@ -384,6 +300,14 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
return super.onOptionsItemSelected(menuItem);
}
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mUserPreviewAdapter.getSelectedUser(), this)) {
+ return super.onContextItemSelected(item);
+ }
+ return true;
+ }
+
public void onMucEditButtonClicked(View v) {
if (this.binding.mucEditor.getVisibility() == View.GONE) {
final MucOptions mucOptions = mConversation.getMucOptions();
@@ -465,40 +389,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.muc_details, menu);
+ final MenuItem share = menu.findItem(R.id.action_share);
+ share.setVisible(mConversation != null && !mConversation.isPrivateAndNonAnonymous());
return super.onCreateOptionsMenu(menu);
}
@Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
- Object tag = v.getTag();
- if (tag instanceof User) {
- getMenuInflater().inflate(R.menu.muc_details_context, menu);
- final User user = (User) tag;
- this.mSelectedUser = user;
- String name;
- final Contact contact = user.getContact();
- if (contact != null && contact.showInContactList()) {
- name = contact.getDisplayName();
- } else if (user.getRealJid() != null) {
- name = user.getRealJid().asBareJid().toString();
- } else {
- name = user.getName();
- }
- menu.setHeaderTitle(name);
- MucDetailsContextMenuHelper.configureMucDetailsContextMenu(this, menu, mConversation, user);
- }
- super.onCreateContextMenu(menu, v, menuInfo);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (!MucDetailsContextMenuHelper.onContextItemSelected(item, mSelectedUser, mConversation, this)) {
- return super.onContextItemSelected(item);
- }
- return true;
- }
-
- @Override
public void onMediaLoaded(List<Attachment> attachments) {
runOnUiThread(() -> {
int limit = GridManager.getCurrentColumnCount(binding.media);
@@ -568,7 +464,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
} else {
account = mConversation.getAccount().getJid().asBareJid().toString();
}
-
+ setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.conference_details : R.string.channel_details);
this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject()) ? View.VISIBLE : View.GONE);
this.binding.detailsAccount.setText(getString(R.string.using_account, account));
this.binding.jid.setText(mConversation.getJid().asBareJid().toEscapedString());
@@ -577,8 +473,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
} else {
this.binding.detailsAccount.setVisibility(View.GONE);
}
- this.binding.detailsMucAvatar.setImageBitmap(avatarService().get(mConversation, getPixel(Config.AVATAR_SIZE)));
- this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48)));
+ AvatarWorkerTask.loadAvatar(mConversation, binding.detailsMucAvatar, R.dimen.avatar_big);
+ AvatarWorkerTask.loadAvatar(mConversation.getAccount(), binding.yourPhoto, R.dimen.avatar_on_details_screen_size);
+
String roomName = mucOptions.getName();
String subject = mucOptions.getSubject();
final boolean hasTitle;
@@ -607,16 +504,19 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
this.binding.mucYourNick.setText(EmojiWrapper.transform(mucOptions.getActualNick()));
if (mucOptions.online()) {
- this.binding.mucMoreDetails.setVisibility(View.VISIBLE);
- this.binding.mucSettings.setVisibility(View.VISIBLE);
+ this.binding.usersWrapper.setVisibility(View.VISIBLE);
this.binding.mucInfoMore.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
this.binding.jid.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
this.binding.mucRole.setVisibility(View.VISIBLE);
this.binding.mucRole.setText(getStatus(self));
- if (mucOptions.membersOnly()) {
- this.binding.mucConferenceType.setText(R.string.private_conference);
+ if (mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
+ this.binding.mucSettings.setVisibility(View.VISIBLE);
+ this.binding.mucConferenceType.setText(MucConfiguration.describe(this, mucOptions));
+ } else if (!mucOptions.isPrivateAndNonAnonymous() && mucOptions.nonanonymous()) {
+ this.binding.mucSettings.setVisibility(View.VISIBLE);
+ this.binding.mucConferenceType.setText(R.string.group_chat_will_make_your_jabber_id_public);
} else {
- this.binding.mucConferenceType.setText(R.string.public_conference);
+ this.binding.mucSettings.setVisibility(View.GONE);
}
if (mucOptions.mamSupport()) {
this.binding.mucInfoMam.setText(R.string.server_info_available);
@@ -684,7 +584,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
});
}
} else {
- this.binding.mucMoreDetails.setVisibility(View.GONE);
+ this.binding.usersWrapper.setVisibility(View.GONE);
this.binding.mucInfoMore.setVisibility(View.GONE);
this.binding.mucSettings.setVisibility(View.GONE);
}
@@ -708,71 +608,44 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
this.binding.notificationStatusButton.setImageResource(ic_notifications_none);
}
- final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- this.binding.mucMembers.removeAllViews();
- if (inflater == null) {
- return;
- }
- final ArrayList<User> users = mucOptions.getUsers();
- Collections.sort(users);
- for (final User user : users) {
- ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false);
- this.setListItemBackgroundOnView(binding.getRoot());
- binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName()));
- registerForContextMenu(binding.getRoot());
- binding.getRoot().setTag(user);
- if (mAdvancedMode && user.getPgpKeyId() != 0) {
- binding.key.setVisibility(View.VISIBLE);
- binding.key.setOnClickListener(v -> viewPgpKey(user));
- binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
- }
- Contact contact = user.getContact();
- String name = user.getName();
- if (contact != null) {
- binding.contactDisplayName.setText(EmojiWrapper.transform(contact.getDisplayName()));
- binding.contactJid.setText((name != null ? EmojiWrapper.transform(name) + " \u2022 " : "") + getStatus(user));
+ final List<User> users = mucOptions.getUsers();
+ Collections.sort(users, (a, b) -> {
+ if (b.getAffiliation().outranks(a.getAffiliation())) {
+ return 1;
+ } else if (a.getAffiliation().outranks(b.getAffiliation())) {
+ return -1;
} else {
- binding.contactDisplayName.setText(name == null ? "" : EmojiWrapper.transform(name));
- binding.contactJid.setText(getStatus(user));
-
- }
- loadAvatar(user, binding.contactPhoto);
- if (user.getRole() == MucOptions.Role.NONE) {
- binding.contactJid.setAlpha(INACTIVE_ALPHA);
- binding.key.setAlpha(INACTIVE_ALPHA);
- binding.contactDisplayName.setAlpha(INACTIVE_ALPHA);
- binding.contactPhoto.setAlpha(INACTIVE_ALPHA);
+ if (a.getAvatar() != null && b.getAvatar() == null) {
+ return -1;
+ } else if (a.getAvatar() == null && b.getAvatar() != null) {
+ return 1;
+ } else {
+ return a.getComparableName().compareToIgnoreCase(b.getComparableName());
+ }
}
- this.binding.mucMembers.addView(binding.getRoot());
- }
- if (mConversation.getMucOptions().canInvite()) {
- this.binding.invite.setVisibility(View.VISIBLE);
+ });
+ this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
+ this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
+ this.binding.showUsers.setVisibility(users.size() > 0 ? View.VISIBLE : View.GONE);
+ this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
+ if (users.size() == 0) {
+ this.binding.noUsersHints.setText(mucOptions.isPrivateAndNonAnonymous() ? R.string.no_users_hint_group_chat : R.string.no_users_hint_channel);
+ this.binding.noUsersHints.setVisibility(View.VISIBLE);
} else {
- this.binding.invite.setVisibility(View.GONE);
+ this.binding.noUsersHints.setVisibility(View.GONE);
}
}
- private String getStatus(User user) {
- if (mAdvancedMode) {
- return getString(user.getAffiliation().getResId()) +
- " (" + getString(user.getRole().getResId()) + ')';
+ public static String getStatus(Context context, User user, final boolean advanced) {
+ if (advanced) {
+ return String.format("%s (%s)", context.getString(user.getAffiliation().getResId()), context.getString(user.getRole().getResId()));
} else {
- return getString(user.getAffiliation().getResId());
+ return context.getString(user.getAffiliation().getResId());
}
}
- private void viewPgpKey(User user) {
- PgpEngine pgp = xmppConnectionService.getPgpEngine();
- if (pgp != null) {
- PendingIntent intent = pgp.getIntentForKey(user.getPgpKeyId());
- if (intent != null) {
- try {
- startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
- } catch (SendIntentException ignored) {
-
- }
- }
- }
+ private String getStatus(User user) {
+ return getStatus(this, user, mAdvancedMode);
}
@Override
@@ -786,16 +659,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
@Override
- public void onRoleChangedSuccessful(String nick) {
-
- }
-
- @Override
- public void onRoleChangeFailed(String nick, int resId) {
- displayToast(getString(resId, nick));
- }
-
- @Override
public void onPushSucceeded() {
displayToast(getString(R.string.modified_conference_options));
}
@@ -814,28 +677,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
});
}
- public void loadAvatar(User user, ImageView imageView) {
- if (cancelPotentialWork(user, imageView)) {
- final Bitmap bm = avatarService().get(user, getPixel(48), true);
- if (bm != null) {
- cancelPotentialWork(user, imageView);
- imageView.setImageBitmap(bm);
- imageView.setBackgroundColor(0x00000000);
- } else {
- String seed = user.getRealJid() != null ? user.getRealJid().asBareJid().toString() : null;
- imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed));
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.execute(user);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
- }
-
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -862,46 +703,4 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
}
}
-
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
- AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
- }
-
- class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private User o = null;
-
- private BitmapWorkerTask(ImageView imageView) {
- imageViewReference = new WeakReference<>(imageView);
- }
-
- @Override
- protected Bitmap doInBackground(User... params) {
- this.o = params[0];
- if (imageViewReference.get() == null) {
- return null;
- }
- return avatarService().get(this.o, getPixel(48), isCancelled());
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null && !isCancelled()) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
- }
- }
}
diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
index 9db7ce7c9..7fc150aa7 100644
--- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
@@ -50,6 +50,7 @@ import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate;
import de.pixart.messenger.ui.adapter.MediaAdapter;
import de.pixart.messenger.ui.interfaces.OnMediaLoaded;
import de.pixart.messenger.ui.util.Attachment;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.GridManager;
import de.pixart.messenger.ui.util.JidDialog;
import de.pixart.messenger.utils.Compatibility;
@@ -527,7 +528,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
account = contact.getAccount().getJid().asBareJid().toString();
}
binding.detailsAccount.setText(getString(R.string.using_account, account));
- binding.detailsContactBadge.setImageBitmap(avatarService().get(contact, getPixel(Config.AVATAR_SIZE)));
+ AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_big);
binding.detailsContactBadge.setOnClickListener(this.onBadgeClick);
if (xmppConnectionService.multipleAccounts()) {
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
index 4df6391da..94ddc3f8e 100644
--- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
+++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
@@ -107,7 +107,10 @@ import de.pixart.messenger.ui.util.ScrollState;
import de.pixart.messenger.ui.util.SendButtonAction;
import de.pixart.messenger.ui.util.SendButtonTool;
import de.pixart.messenger.ui.util.ShareUtil;
+import de.pixart.messenger.ui.util.ViewUtil;
import de.pixart.messenger.ui.widget.EditMessage;
+import de.pixart.messenger.utils.Compatibility;
+import de.pixart.messenger.utils.GeoHelper;
import de.pixart.messenger.utils.MenuDoubleTabUtil;
import de.pixart.messenger.utils.MessageUtils;
import de.pixart.messenger.utils.NickValidityChecker;
@@ -123,6 +126,9 @@ import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.ui.XmppActivity.EXTRA_ACCOUNT;
import static de.pixart.messenger.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION;
import static de.pixart.messenger.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
+import static de.pixart.messenger.utils.PermissionUtils.allGranted;
+import static de.pixart.messenger.utils.PermissionUtils.getFirstDenied;
+import static de.pixart.messenger.utils.PermissionUtils.writeGranted;
import static de.pixart.messenger.xmpp.Patches.ENCRYPTION_EXCEPTIONS;
public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, MessageAdapter.OnContactPictureLongClicked, MessageAdapter.OnContactPictureClicked {
@@ -377,7 +383,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private OnClickListener mHideUnencryptionHint = new OnClickListener() {
@Override
public void onClick(View v) {
- if (Config.supportOmemo() && conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) {
+ if (Config.supportOmemo() && Conversation.suitableForOmemoByDefault(conversation) && conversation.isSingleOrPrivateAndNonAnonymous()) {
conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
activity.xmppConnectionService.updateConversation(conversation);
activity.refreshUi();
@@ -593,33 +599,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return getConversation(activity, R.id.main_fragment);
}
- private static boolean allGranted(int[] grantResults) {
- for (int grantResult : grantResults) {
- if (grantResult != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean writeGranted(int[] grantResults, String[] permission) {
- for (int i = 0; i < grantResults.length; ++i) {
- if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission[i])) {
- return grantResults[i] == PackageManager.PERMISSION_GRANTED;
- }
- }
- return false;
- }
-
- private static String getFirstDenied(int[] grantResults, String[] permissions) {
- for (int i = 0; i < grantResults.length; ++i) {
- if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
- return permissions[i];
- }
- }
- return null;
- }
-
private static boolean scrolledToBottom(AbsListView listView) {
final int count = listView.getCount();
if (count == 0) {
@@ -1150,7 +1129,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (conversation != null) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
- menuInviteContact.setVisible(true);
+ menuInviteContact.setVisible(conversation.getMucOptions().canInvite());
menuArchiveChat.setTitle(R.string.action_end_conversation_muc);
} else {
menuInviteContact.setVisible(false);
@@ -1277,12 +1256,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
- final boolean deleted = t != null && t instanceof TransferablePlaceholder;
+ final boolean deleted = m.isFileDeleted();
final boolean encrypted = m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED
|| m.getEncryption() == Message.ENCRYPTION_PGP;
final boolean receiving = m.getStatus() == Message.STATUS_RECEIVED && (t instanceof JingleConnection || t instanceof HttpDownloadConnection);
activity.getMenuInflater().inflate(R.menu.message_context, menu);
menu.setHeaderTitle(R.string.message_options);
+ MenuItem openWith = menu.findItem(R.id.open_with);
MenuItem copyMessage = menu.findItem(R.id.copy_message);
MenuItem copyLink = menu.findItem(R.id.copy_link);
MenuItem quoteMessage = menu.findItem(R.id.quote_message);
@@ -1309,14 +1289,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
copyLink.setVisible(true);
}
}
- if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
+ if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED && !deleted) {
retryDecryption.setVisible(true);
}
if (!showError
&& relevantForCorrection.getType() == Message.TYPE_TEXT
+ && !m.isGeoUri()
&& relevantForCorrection.isLastCorrectableMessage()
- && m.getConversation() instanceof Conversation
- && (((Conversation) m.getConversation()).getMucOptions().nonanonymous() || m.getConversation().getMode() == Conversation.MODE_SINGLE)) {
+ && m.getConversation() instanceof Conversation) {
correctMessage.setVisible(true);
}
if ((m.isFileOrImage() && !deleted && !receiving) || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())) {
@@ -1355,6 +1335,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (showError) {
showErrorMessage.setVisible(true);
}
+ final String mime = m.isFileOrImage() ? m.getMimeType() : null;
+ if ((m.isGeoUri() && GeoHelper.openInOsmAnd(getActivity(), m)) || (mime != null && mime.startsWith("audio/"))) {
+ openWith.setVisible(true);
+ }
}
}
@@ -1400,6 +1384,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
case R.id.show_error_message:
showErrorMessage(selectedMessage);
return true;
+ case R.id.open_with:
+ openWith(selectedMessage);
+ return true;
default:
return super.onContextItemSelected(item);
}
@@ -1499,38 +1486,43 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (conversation == null) {
return;
}
+ final boolean updated;
switch (item.getItemId()) {
case R.id.encryption_choice_none:
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ updated = conversation.setNextEncryption(Message.ENCRYPTION_NONE);
item.setChecked(true);
break;
case R.id.encryption_choice_otr:
- conversation.setNextEncryption(Message.ENCRYPTION_OTR);
+ updated = conversation.setNextEncryption(Message.ENCRYPTION_OTR);
item.setChecked(true);
break;
case R.id.encryption_choice_pgp:
if (activity.hasPgp()) {
if (conversation.getAccount().getPgpSignature() != null) {
- conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+ updated = conversation.setNextEncryption(Message.ENCRYPTION_PGP);
item.setChecked(true);
} else {
+ updated = false;
activity.announcePgp(conversation.getAccount(), conversation, null, activity.onOpenPGPKeyPublished);
}
} else {
activity.showInstallPgpDialog();
+ updated = false;
}
break;
case R.id.encryption_choice_axolotl:
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
+ "Enabled axolotl for Contact " + conversation.getContact().getJid());
- conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
+ updated = conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
item.setChecked(true);
break;
default:
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ updated = conversation.setNextEncryption(Message.ENCRYPTION_NONE);
break;
}
- activity.xmppConnectionService.updateConversation(conversation);
+ if (updated) {
+ activity.xmppConnectionService.updateConversation(conversation);
+ }
updateChatMsgHint();
getActivity().invalidateOptionsMenu();
activity.refreshUi();
@@ -1670,7 +1662,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
Log.d(Config.LOGTAG, "type: " + transferable.getClass().getName());
Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
}
- } else if (message.treatAsDownloadable()) {
+ } else if (message.treatAsDownloadable() || message.hasFileOnRemoteHost()) {
createNewConnection(message);
}
}
@@ -1843,6 +1835,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return null;
}
+ private void openWith(final Message message) {
+ if (message.isGeoUri()) {
+ GeoHelper.view(getActivity(), message);
+ } else {
+ final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ ViewUtil.view(activity, file);
+ }
+ }
+
private void showErrorMessage(final Message message) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.error_message);
@@ -1856,13 +1857,36 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private void deleteMessage(Message message) {
+ while (message.mergeable(message.next())) {
+ message = message.next();
+ }
final Conversation conversation = (Conversation) message.getConversation();
+ Message relevantForCorrection = message;
+ while (relevantForCorrection.mergeable(relevantForCorrection.next())) {
+ relevantForCorrection = relevantForCorrection.next();
+ }
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setNegativeButton(R.string.cancel, null);
builder.setTitle(R.string.delete_message_dialog);
builder.setMessage(R.string.delete_message_dialog_msg);
+ Message finalRelevantForCorrection = relevantForCorrection;
+ Message finalMessage = message;
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
- activity.xmppConnectionService.deleteMessage(conversation, message);
+ if (finalRelevantForCorrection.getType() == Message.TYPE_TEXT
+ && !finalMessage.isGeoUri()
+ && finalRelevantForCorrection.isLastCorrectableMessage()
+ && finalMessage.getConversation() instanceof Conversation
+ && (((Conversation) finalMessage.getConversation()).getMucOptions().nonanonymous() || finalMessage.getConversation().getMode() == Conversation.MODE_SINGLE)) {
+ this.conversation.setCorrectingMessage(finalMessage);
+ Message deletedmessage = conversation.getCorrectingMessage();
+ deletedmessage.setBody(getString(R.string.message_deleted));
+ deletedmessage.setEdited(deletedmessage.getUuid());
+ deletedmessage.setUuid(UUID.randomUUID().toString());
+ sendMessage(deletedmessage);
+ activity.xmppConnectionService.deleteMessage(conversation, deletedmessage);
+ refresh();
+ }
+ activity.xmppConnectionService.deleteMessage(conversation, finalMessage);
activity.onConversationsListItemUpdated();
refresh();
});
@@ -1876,7 +1900,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
builder.setMessage(R.string.delete_file_dialog_msg);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) {
- message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
+ message.setFileDeleted(true);
+ activity.xmppConnectionService.updateMessage(message, false);
activity.onConversationsListItemUpdated();
refresh();
}
@@ -1890,8 +1915,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
final Conversation conversation = (Conversation) message.getConversation();
- DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
- if (file.exists()) {
+ final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ if ((file.exists() && file.canRead()) || message.hasFileOnRemoteHost()) {
final XmppConnection xmppConnection = conversation.getAccount().getXmppConnection();
if (!message.hasFileOnRemoteHost()
&& xmppConnection != null
@@ -1907,9 +1932,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
});
return;
}
+ } else if (!Compatibility.hasStoragePermission(getActivity())) {
+ Toast.makeText(activity, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ return;
} else {
Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
- message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
+ message.setFileDeleted(true);
+ activity.xmppConnectionService.updateMessage(message, false);
activity.onConversationsListItemUpdated();
refresh();
return;
@@ -2419,26 +2448,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
&& conversation.isWithStranger()) {
showSnackbar(R.string.received_message_from_stranger, R.string.block, mBlockClickListener);
} else if (activity.xmppConnectionService.warnUnecryptedChat()) {
- AxolotlService axolotlService = account.getAxolotlService();
- if ((mode == Conversation.MODE_SINGLE) && (conversation.getNextEncryption() == Message.ENCRYPTION_NONE &&
- ((Config.supportOmemo() && axolotlService != null && conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) ||
- (Config.supportOpenPgp() && account.isPgpDecryptionServiceConnected()) ||
- Config.supportOtr()))) {
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_NONE && conversation.isSingleOrPrivateAndNonAnonymous() && ((Config.supportOmemo() && Conversation.suitableForOmemoByDefault(conversation)) ||
+ (Config.supportOpenPgp() && account.isPgpDecryptionServiceConnected()) || (
+ mode == Conversation.MODE_SINGLE && Config.supportOtr()))) {
if (ENCRYPTION_EXCEPTIONS.contains(conversation.getJid().toString()) || conversation.getJid().toString().equals(account.getJid().getDomain())) {
hideSnackbar();
} else {
showSnackbar(R.string.conversation_unencrypted_hint, R.string.ok, mHideUnencryptionHint, null);
}
- } else if ((mode == Conversation.MODE_MULTI && conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous()) &&
- (conversation.getNextEncryption() == Message.ENCRYPTION_NONE &&
- ((Config.supportOmemo() && axolotlService != null && conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) ||
- (Config.supportOpenPgp() && account.isPgpDecryptionServiceConnected())))) {
- if (ENCRYPTION_EXCEPTIONS.contains(conversation.getJid().toString()) || conversation.getJid().toString().equals(account.getJid().getDomain())) {
- Log.d(Config.LOGTAG, "Don't show unencrypted warning because " + conversation.getJid().toString() + " is on exception list");
- hideSnackbar();
- } else {
- showSnackbar(R.string.conversation_unencrypted_hint, R.string.ok, mHideUnencryptionHint, null);
- }
} else {
hideSnackbar();
}
@@ -3086,36 +3103,32 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onContactPictureLongClicked(View v, final Message message) {
- if (message.getStatus() <= Message.STATUS_RECEIVED) {
- final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
+ final String fingerprint;
+ if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ fingerprint = "pgp";
+ } else {
+ fingerprint = message.getFingerprint();
+ }
+ final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
+ final Contact contact = message.getContact();
+ if (message.getStatus() <= Message.STATUS_RECEIVED && (contact == null || !contact.isSelf())) {
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
final Jid cp = message.getCounterpart();
if (cp == null || cp.isBareJid()) {
return;
}
- Jid tcp = message.getTrueCounterpart();
- User userByRealJid = tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp, cp) : null;
+ final Jid tcp = message.getTrueCounterpart();
+ final User userByRealJid = tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp, cp) : null;
final User user = userByRealJid != null ? userByRealJid : conversation.getMucOptions().findUserByFullJid(cp);
popupMenu.inflate(R.menu.muc_details_context);
final Menu menu = popupMenu.getMenu();
MucDetailsContextMenuHelper.configureMucDetailsContextMenu(activity, menu, conversation, user);
- popupMenu.setOnMenuItemClickListener(menuItem -> MucDetailsContextMenuHelper.onContextItemSelected(menuItem, user, conversation, activity));
+ popupMenu.setOnMenuItemClickListener(menuItem -> MucDetailsContextMenuHelper.onContextItemSelected(menuItem, user, activity, fingerprint));
} else {
- final Contact contact = message.getContact();
- if (contact.isSelf()) {
- activity.showQrCode(conversation.getAccount().getShareableUri());
- return;
- }
popupMenu.inflate(R.menu.one_on_one_context);
popupMenu.setOnMenuItemClickListener(item -> {
switch (item.getItemId()) {
case R.id.action_contact_details:
- String fingerprint;
- if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
- fingerprint = "pgp";
- } else {
- fingerprint = message.getFingerprint();
- }
activity.switchToContactDetails(message.getContact(), fingerprint);
break;
case R.id.action_show_qr_code:
@@ -3125,10 +3138,22 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return true;
});
}
- popupMenu.show();
} else {
- activity.showQrCode(conversation.getAccount().getShareableUri());
+ popupMenu.inflate(R.menu.account_context);
+ final Menu menu = popupMenu.getMenu();
+ popupMenu.setOnMenuItemClickListener(item -> {
+ switch (item.getItemId()) {
+ case R.id.action_show_qr_code:
+ activity.showQrCode(conversation.getAccount().getShareableUri());
+ break;
+ case R.id.action_account_details:
+ activity.switchToAccount(message.getConversation().getAccount(), fingerprint);
+ break;
+ }
+ return true;
+ });
}
+ popupMenu.show();
}
@Override
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java
index 400519ab3..e7343ce63 100644
--- a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java
@@ -75,7 +75,6 @@ import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.MucOptions;
import de.pixart.messenger.entities.Presence;
-import de.pixart.messenger.services.EmojiService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.interfaces.OnBackendConnected;
import de.pixart.messenger.ui.interfaces.OnConversationArchived;
@@ -96,9 +95,8 @@ import de.pixart.messenger.xmpp.chatstate.ChatState;
import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
-import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
-public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged {
+public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged {
public static final String ACTION_VIEW_CONVERSATION = "de.pixart.messenger.VIEW";
public static final String EXTRA_CONVERSATION = "conversationUuid";
@@ -418,7 +416,6 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
super.onCreate(savedInstanceState);
ConversationMenuConfigurator.reloadFeatures(this);
OmemoSetting.load(this);
- new EmojiService(this).init(useBundledEmoji());
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
setSupportActionBar((Toolbar) binding.toolbar);
configureActionBar(getSupportActionBar());
@@ -491,15 +488,6 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
displayToast(getString(resId, jid.asBareJid().toString()));
}
- @Override
- public void onRoleChangedSuccessful(String nick) {
- }
-
- @Override
- public void onRoleChangeFailed(String nick, int resId) {
- displayToast(getString(resId, nick));
- }
-
private void openConversation(Conversation conversation, Bundle extras) {
ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
final boolean mainNeedsRefresh;
@@ -766,65 +754,49 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
}
}
} else {
- if (conversation.getParticipants() != null) {
- ChatState state = ChatState.COMPOSING;
- List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
- if (userWithChatStates.size() == 0) {
- state = ChatState.PAUSED;
- userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
- }
- List<MucOptions.User> users = conversation.getMucOptions().getUsers(true);
- if (state == ChatState.COMPOSING) {
- if (userWithChatStates.size() > 0) {
- if (userWithChatStates.size() == 1) {
- MucOptions.User user = userWithChatStates.get(0);
- absubtitle.setText(EmojiWrapper.transform(getString(R.string.contact_is_typing, UIHelper.getDisplayName(user))));
- } else {
- StringBuilder builder = new StringBuilder();
- for (MucOptions.User user : userWithChatStates) {
- if (builder.length() != 0) {
- builder.append(", ");
- }
- builder.append(UIHelper.getDisplayName(user));
+ ChatState state = ChatState.COMPOSING;
+ List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
+ if (userWithChatStates.size() == 0) {
+ state = ChatState.PAUSED;
+ userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
+ }
+ List<MucOptions.User> users = conversation.getMucOptions().getUsers(true);
+ if (state == ChatState.COMPOSING) {
+ if (userWithChatStates.size() > 0) {
+ if (userWithChatStates.size() == 1) {
+ MucOptions.User user = userWithChatStates.get(0);
+ absubtitle.setText(EmojiWrapper.transform(getString(R.string.contact_is_typing, UIHelper.getDisplayName(user))));
+ } else {
+ StringBuilder builder = new StringBuilder();
+ for (MucOptions.User user : userWithChatStates) {
+ if (builder.length() != 0) {
+ builder.append(", ");
}
- absubtitle.setText(EmojiWrapper.transform(getString(R.string.contacts_are_typing, builder.toString())));
+ builder.append(UIHelper.getDisplayName(user));
}
- }
- } else {
- if (users.size() == 0) {
- absubtitle.setText(getString(R.string.one_participant));
- } else {
- int size = users.size() + 1;
- absubtitle.setText(getString(R.string.more_participants, size));
+ absubtitle.setText(EmojiWrapper.transform(getString(R.string.contacts_are_typing, builder.toString())));
}
}
- absubtitle.setSelected(true);
- absubtitle.setOnClickListener(view15 -> {
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- switchToContactDetails(conversation.getContact());
- } else if (conversation.getMode() == Conversation.MODE_MULTI) {
- Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
- intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
- intent.putExtra("uuid", conversation.getUuid());
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- }
- });
} else {
- absubtitle.setText(R.string.no_participants);
- abtitle.setSelected(true);
- absubtitle.setOnClickListener(view16 -> {
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- switchToContactDetails(conversation.getContact());
- } else if (conversation.getMode() == Conversation.MODE_MULTI) {
- Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
- intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
- intent.putExtra("uuid", conversation.getUuid());
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- }
- });
+ if (users.size() == 0) {
+ absubtitle.setText(getString(R.string.one_participant));
+ } else {
+ int size = users.size() + 1;
+ absubtitle.setText(getString(R.string.more_participants, size));
+ }
}
+ absubtitle.setSelected(true);
+ absubtitle.setOnClickListener(view15 -> {
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ switchToContactDetails(conversation.getContact());
+ } else if (conversation.getMode() == Conversation.MODE_MULTI) {
+ Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
+ intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
+ intent.putExtra("uuid", conversation.getUuid());
+ startActivity(intent);
+ overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ }
+ });
}
return;
}
@@ -837,10 +809,6 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
}
}
- public boolean useBundledEmoji() {
- return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji));
- }
-
public void verifyOtrSessionDialog(final Conversation conversation, View view) {
if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show();
diff --git a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java
index 900f4a42d..c77211e9d 100644
--- a/src/main/java/de/pixart/messenger/ui/CreateConferenceDialog.java
+++ b/src/main/java/de/pixart/messenger/ui/CreatePrivateGroupChatDialog.java
@@ -19,15 +19,15 @@ import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.util.DelayedHintHelper;
-public class CreateConferenceDialog extends DialogFragment {
+public class CreatePrivateGroupChatDialog extends DialogFragment {
private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
private static final String MULTIPLE_ACCOUNTS = "multiple_accounts_enabled";
public XmppConnectionService xmppConnectionService;
private CreateConferenceDialogListener mListener;
- public static CreateConferenceDialog newInstance(List<String> accounts, boolean multipleAccounts) {
- CreateConferenceDialog dialog = new CreateConferenceDialog();
+ public static CreatePrivateGroupChatDialog newInstance(List<String> accounts, boolean multipleAccounts) {
+ CreatePrivateGroupChatDialog dialog = new CreatePrivateGroupChatDialog();
Bundle bundle = new Bundle();
bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) accounts);
bundle.putBoolean(MULTIPLE_ACCOUNTS, multipleAccounts);
@@ -45,7 +45,7 @@ public class CreateConferenceDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.create_conference);
+ builder.setTitle(R.string.create_private_group_chat);
CreateConferenceDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_conference_dialog, null, false);
if (getArguments().getBoolean(MULTIPLE_ACCOUNTS)) {
binding.yourAccount.setVisibility(View.VISIBLE);
@@ -60,6 +60,10 @@ public class CreateConferenceDialog extends DialogFragment {
builder.setPositiveButton(R.string.choose_participants, (dialog, which) -> mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim()));
builder.setNegativeButton(R.string.cancel, null);
DelayedHintHelper.setHint(R.string.providing_a_name_is_optional, binding.groupChatName);
+ binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> {
+ mListener.onCreateDialogPositiveClick(binding.account, binding.groupChatName.getText().toString().trim());
+ return true;
+ });
return builder.create();
}
diff --git a/src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java b/src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java
new file mode 100644
index 000000000..cbaf3468c
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/CreatePublicChannelDialog.java
@@ -0,0 +1,303 @@
+package de.pixart.messenger.ui;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AlertDialog;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.CreatePublicChannelDialogBinding;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.adapter.KnownHostsAdapter;
+import de.pixart.messenger.ui.interfaces.OnBackendConnected;
+import de.pixart.messenger.ui.util.DelayedHintHelper;
+import de.pixart.messenger.utils.CryptoHelper;
+import de.pixart.messenger.xmpp.XmppConnection;
+import rocks.xmpp.addr.Jid;
+
+public class CreatePublicChannelDialog extends DialogFragment implements OnBackendConnected {
+
+ private static final char[] FORBIDDEN = new char[]{'\u0022', '&', '\'', '/', ':', '<', '>', '@'};
+
+ private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
+ private static final String MULTIPLE_ACCOUNTS = "multiple_accounts_enabled";
+ private CreatePublicChannelDialogListener mListener;
+ private KnownHostsAdapter knownHostsAdapter;
+ private boolean jidWasModified = false;
+ private boolean nameEntered = false;
+ private boolean skipTetxWatcher = false;
+ private static final SecureRandom RANDOM = new SecureRandom();
+
+ public static CreatePublicChannelDialog newInstance(List<String> accounts) {
+ CreatePublicChannelDialog dialog = new CreatePublicChannelDialog();
+ Bundle bundle = new Bundle();
+ bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) accounts);
+ dialog.setArguments(bundle);
+ return dialog;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setRetainInstance(true);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ jidWasModified = savedInstanceState != null && savedInstanceState.getBoolean("jid_was_modified_false", false);
+ nameEntered = savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.create_public_channel);
+ final CreatePublicChannelDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_public_channel_dialog, null, false);
+ if (getArguments().getBoolean(MULTIPLE_ACCOUNTS)) {
+ binding.yourAccount.setVisibility(View.VISIBLE);
+ binding.account.setVisibility(View.VISIBLE);
+ } else {
+ binding.yourAccount.setVisibility(View.GONE);
+ binding.account.setVisibility(View.GONE);
+ }
+ binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ updateJidSuggestion(binding);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ });
+ binding.jid.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (skipTetxWatcher) {
+ return;
+ }
+ if (jidWasModified) {
+ jidWasModified = !TextUtils.isEmpty(s);
+ } else {
+ jidWasModified = !s.toString().equals(getJidSuggestion(binding));
+ }
+ }
+ });
+ updateInputs(binding, false);
+ ArrayList<String> mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY);
+ StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account);
+ builder.setView(binding.getRoot());
+ builder.setPositiveButton(nameEntered ? R.string.create : R.string.next, null);
+ builder.setNegativeButton(nameEntered ? R.string.back : R.string.cancel, null);
+ DelayedHintHelper.setHint(R.string.channel_bare_jid_example, binding.jid);
+ this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
+ binding.jid.setAdapter(knownHostsAdapter);
+ final AlertDialog dialog = builder.create();
+ binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> {
+ submit(dialog, binding);
+ return true;
+ });
+ dialog.setOnShowListener(dialogInterface -> {
+ dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> goBack(dialog, binding));
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> submit(dialog, binding));
+ });
+ return dialog;
+ }
+
+ private void updateJidSuggestion(CreatePublicChannelDialogBinding binding) {
+ if (jidWasModified) {
+ return;
+ }
+ String jid = getJidSuggestion(binding);
+ skipTetxWatcher = true;
+ binding.jid.setText(jid);
+ skipTetxWatcher = false;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putBoolean("jid_was_modified", jidWasModified);
+ outState.putBoolean("name_entered", nameEntered);
+ super.onSaveInstanceState(outState);
+ }
+
+ private static String getJidSuggestion(CreatePublicChannelDialogBinding binding) {
+ final Account account = StartConversationActivity.getSelectedAccount(binding.getRoot().getContext(), binding.account);
+ final XmppConnection connection = account == null ? null : account.getXmppConnection();
+ if (connection == null) {
+ return "";
+ }
+ final Editable nameText = binding.groupChatName.getText();
+ final String name = nameText == null ? "" : nameText.toString().trim();
+ final String domain = connection.getMucServer();
+ if (domain == null) {
+ return "";
+ }
+ final String localpart = clean(name);
+ if (TextUtils.isEmpty(localpart)) {
+ return "";
+ } else {
+ try {
+ return Jid.of(localpart, domain, null).toEscapedString();
+ } catch (IllegalArgumentException e) {
+ return Jid.of(CryptoHelper.pronounceable(RANDOM), domain, null).toEscapedString();
+ }
+ }
+ }
+
+ private static String clean(String name) {
+ for (char c : FORBIDDEN) {
+ name = name.replace(String.valueOf(c), "");
+ }
+ return name.replaceAll("\\s+", "-");
+ }
+
+ private void goBack(AlertDialog dialog, CreatePublicChannelDialogBinding binding) {
+ if (nameEntered) {
+ nameEntered = false;
+ updateInputs(binding, true);
+ updateButtons(dialog);
+ } else {
+ dialog.dismiss();
+ }
+ }
+
+ private void submit(AlertDialog dialog, CreatePublicChannelDialogBinding binding) {
+ final Context context = binding.getRoot().getContext();
+ final Editable nameText = binding.groupChatName.getText();
+ final String name = nameText == null ? "" : nameText.toString().trim();
+ final Editable addressText = binding.jid.getText();
+ final String address = addressText == null ? "" : addressText.toString().trim();
+ if (nameEntered) {
+ binding.nameLayout.setError(null);
+ if (address.isEmpty()) {
+ binding.xmppAddressLayout.setError(context.getText(R.string.please_enter_xmpp_address));
+ } else {
+ final Jid jid;
+ try {
+ jid = Jid.ofEscaped(address);
+ } catch (IllegalArgumentException e) {
+ binding.xmppAddressLayout.setError(context.getText(R.string.invalid_jid));
+ return;
+ }
+ final Account account = StartConversationActivity.getSelectedAccount(context, binding.account);
+ if (account == null) {
+ return;
+ }
+ final XmppConnectionService service = ((XmppActivity) context).xmppConnectionService;
+ if (service != null && service.findFirstMuc(jid) != null) {
+ binding.xmppAddressLayout.setError(context.getString(R.string.channel_already_exists));
+ return;
+ }
+ mListener.onCreatePublicChannel(account, name, jid);
+ dialog.dismiss();
+ }
+ } else {
+ binding.xmppAddressLayout.setError(null);
+ if (name.isEmpty()) {
+ binding.nameLayout.setError(context.getText(R.string.please_enter_name));
+ } else if (StartConversationActivity.isValidJid(name)) {
+ binding.nameLayout.setError(context.getText(R.string.this_is_an_xmpp_address));
+ } else {
+ binding.nameLayout.setError(null);
+ nameEntered = true;
+ updateInputs(binding, true);
+ updateButtons(dialog);
+ binding.jid.setText("");
+ binding.jid.append(getJidSuggestion(binding));
+ }
+ }
+ }
+
+
+ private void updateInputs(CreatePublicChannelDialogBinding binding, boolean requestFocus) {
+ binding.xmppAddressLayout.setVisibility(nameEntered ? View.VISIBLE : View.GONE);
+ binding.nameLayout.setVisibility(nameEntered ? View.GONE : View.VISIBLE);
+ if (!requestFocus) {
+ return;
+ }
+ if (nameEntered) {
+ binding.xmppAddressLayout.requestFocus();
+ } else {
+ binding.nameLayout.requestFocus();
+ }
+ }
+
+ private void updateButtons(AlertDialog dialog) {
+ final Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ final Button negative = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
+ positive.setText(nameEntered ? R.string.create : R.string.next);
+ negative.setText(nameEntered ? R.string.back : R.string.cancel);
+ }
+
+ @Override
+ public void onBackendConnected() {
+ refreshKnownHosts();
+ }
+
+ private void refreshKnownHosts() {
+ Activity activity = getActivity();
+ if (activity instanceof XmppActivity) {
+ Collection<String> hosts = ((XmppActivity) activity).xmppConnectionService.getKnownConferenceHosts();
+ this.knownHostsAdapter.refresh(hosts);
+ }
+ }
+
+ public interface CreatePublicChannelDialogListener {
+ void onCreatePublicChannel(Account account, String name, Jid address);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ try {
+ mListener = (CreatePublicChannelDialogListener) context;
+ } catch (ClassCastException e) {
+ throw new ClassCastException(context.toString()
+ + " must implement CreateConferenceDialogListener");
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final Activity activity = getActivity();
+ if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) {
+ refreshKnownHosts();
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ Dialog dialog = getDialog();
+ if (dialog != null && getRetainInstance()) {
+ dialog.setDismissMessage(null);
+ }
+ super.onDestroyView();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
index a20190d17..bec621534 100644
--- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
@@ -16,6 +16,7 @@ import android.preference.PreferenceManager;
import android.provider.Settings;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
+import android.support.annotation.NonNull;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
@@ -29,7 +30,6 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
-import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
@@ -63,6 +63,7 @@ import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate;
import de.pixart.messenger.services.XmppConnectionService.OnCaptchaRequested;
import de.pixart.messenger.ui.adapter.KnownHostsAdapter;
import de.pixart.messenger.ui.adapter.PresenceTemplateAdapter;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.PendingItem;
import de.pixart.messenger.ui.util.SoftKeyboardUtils;
import de.pixart.messenger.utils.CryptoHelper;
@@ -82,6 +83,9 @@ import de.pixart.messenger.xmpp.forms.Data;
import de.pixart.messenger.xmpp.pep.Avatar;
import rocks.xmpp.addr.Jid;
+import static de.pixart.messenger.utils.PermissionUtils.allGranted;
+import static de.pixart.messenger.utils.PermissionUtils.writeGranted;
+
public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
@@ -90,6 +94,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private static final int REQUEST_DATA_SAVER = 0xf244;
private static final int REQUEST_CHANGE_STATUS = 0xee11;
private static final int REQUEST_ORBOT = 0xff22;
+ private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
private AlertDialog mCaptchaDialog = null;
private final AtomicBoolean mPendingReconnect = new AtomicBoolean(false);
@@ -619,7 +624,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
private void onEditYourNameClicked(View view) {
- quickEdit(mAccount.getDisplayName(), R.string.your_name, value -> {
+ String nick = "";
+ if (mAccount.getDisplayName() != null) {
+ nick = mAccount.getDisplayName();
+ }
+ quickEdit(nick, R.string.your_name, value -> {
final String displayName = value.trim();
updateDisplayName(displayName);
mAccount.setDisplayName(displayName);
@@ -630,7 +639,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
private void refreshAvatar() {
- binding.avater.setImageBitmap(avatarService().get(mAccount, (int) getResources().getDimension(R.dimen.avatar_on_details_screen_size)));
+ AvatarWorkerTask.loadAvatar(mAccount, binding.avater, R.dimen.avatar_on_details_screen_size);
}
@Override
@@ -825,6 +834,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
return false;
}
switch (item.getItemId()) {
+ case R.id.action_import_backup:
+ if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ }
+ overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ break;
case R.id.mgmt_account_reconnect:
XmppConnection connection = mAccount.getXmppConnection();
if (connection != null) {
@@ -978,7 +993,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
binding.xa.setChecked(true);
break;
case AWAY:
- binding.xa.setChecked(true);
+ binding.away.setChecked(true);
break;
default:
binding.online.setChecked(true);
@@ -1042,7 +1057,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (!mInitMode) {
binding.avater.setVisibility(View.VISIBLE);
- binding.avater.setImageBitmap(avatarService().get(this.mAccount, getPixel(Config.AVATAR_SIZE)));
+ AvatarWorkerTask.loadAvatar(mAccount, binding.avater, R.dimen.avatar_big);
this.binding.accountJid.setEnabled(false);
} else {
binding.avater.setVisibility(View.GONE);
@@ -1449,6 +1464,26 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
@Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ if (grantResults.length > 0) {
+ if (allGranted(grantResults)) {
+ switch (requestCode) {
+ case REQUEST_IMPORT_BACKUP:
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ break;
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+ if (writeGranted(grantResults, permissions)) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.restartFileObserver();
+ }
+ }
+ }
+
+ @Override
public void OnUpdateBlocklist(Status status) {
if (isFinishing()) {
return;
diff --git a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java
index d0c7980fd..02aa46277 100644
--- a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java
+++ b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java
@@ -114,42 +114,51 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
AlertDialog dialog = builder.create();
View.OnClickListener dialogOnClick = v -> {
- final Jid accountJid;
- if (!binding.account.isEnabled() && account == null) {
- return;
- }
- try {
- if (Config.DOMAIN_LOCK != null) {
- accountJid = Jid.of((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null);
- } else {
- accountJid = Jid.of((String) binding.account.getSelectedItem());
- }
- } catch (final IllegalArgumentException e) {
- return;
- }
- final Jid contactJid;
- try {
- contactJid = Jid.of(binding.jid.getText().toString());
- } catch (final IllegalArgumentException e) {
- binding.jid.setError(getActivity().getString(R.string.invalid_jid));
- return;
- }
-
- if (mListener != null) {
- try {
- if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) {
- dialog.dismiss();
- }
- } catch (JidError error) {
- binding.jid.setError(error.toString());
- }
- }
+ handleEnter(binding, account, dialog);
};
+ binding.jid.setOnEditorActionListener((v, actionId, event) -> {
+ handleEnter(binding, account, dialog);
+ return true;
+ });
+
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(dialogOnClick);
return dialog;
}
+ private void handleEnter(EnterJidDialogBinding binding, String account, Dialog dialog) {
+ final Jid accountJid;
+ if (!binding.account.isEnabled() && account == null) {
+ return;
+ }
+ try {
+ if (Config.DOMAIN_LOCK != null) {
+ accountJid = Jid.of((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null);
+ } else {
+ accountJid = Jid.of((String) binding.account.getSelectedItem());
+ }
+ } catch (final IllegalArgumentException e) {
+ return;
+ }
+ final Jid contactJid;
+ try {
+ contactJid = Jid.of(binding.jid.getText().toString());
+ } catch (final IllegalArgumentException e) {
+ binding.jid.setError(getActivity().getString(R.string.invalid_jid));
+ return;
+ }
+
+ if (mListener != null) {
+ try {
+ if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) {
+ dialog.dismiss();
+ }
+ } catch (JidError error) {
+ binding.jid.setError(error.toString());
+ }
+ }
+ }
+
public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
this.mListener = listener;
}
diff --git a/src/main/java/de/pixart/messenger/ui/EnterNameActivity.java b/src/main/java/de/pixart/messenger/ui/EnterNameActivity.java
index ac400b9f3..a1a6b28b5 100644
--- a/src/main/java/de/pixart/messenger/ui/EnterNameActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/EnterNameActivity.java
@@ -7,6 +7,7 @@ import android.support.v7.widget.Toolbar;
import android.view.View;
import java.util.concurrent.atomic.AtomicBoolean;
+
import de.pixart.messenger.R;
import de.pixart.messenger.databinding.ActivityEnterNameBinding;
import de.pixart.messenger.entities.Account;
diff --git a/src/main/java/de/pixart/messenger/ui/ImportBackupActivity.java b/src/main/java/de/pixart/messenger/ui/ImportBackupActivity.java
new file mode 100644
index 000000000..5397eeaf2
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ImportBackupActivity.java
@@ -0,0 +1,145 @@
+package de.pixart.messenger.ui;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.design.widget.Snackbar;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.ActivityImportBackupBinding;
+import de.pixart.messenger.databinding.DialogEnterPasswordBinding;
+import de.pixart.messenger.services.ImportBackupService;
+import de.pixart.messenger.ui.adapter.BackupFileAdapter;
+
+public class ImportBackupActivity extends XmppActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed {
+
+ private ActivityImportBackupBinding binding;
+
+ private BackupFileAdapter backupFileAdapter;
+ private ImportBackupService service;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
+ setSupportActionBar((Toolbar) binding.toolbar);
+ configureActionBar(getSupportActionBar());
+ this.backupFileAdapter = new BackupFileAdapter();
+ this.binding.list.setAdapter(this.backupFileAdapter);
+ this.backupFileAdapter.setOnItemClickedListener(this);
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (this.service != null) {
+ this.service.removeOnBackupProcessedListener(this);
+ }
+ unbindService(this);
+ }
+
+ @Override
+ void onBackendConnected() {
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ ImportBackupService.ImportBackupServiceBinder binder = (ImportBackupService.ImportBackupServiceBinder) service;
+ this.service = binder.getService();
+ this.service.addOnBackupProcessedListener(this);
+ setLoadingState(this.service.getLoadingState());
+ this.service.loadBackupFiles(this);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ this.service = null;
+ }
+
+ @Override
+ public void onBackupFilesLoaded(final List<ImportBackupService.BackupFile> files) {
+ runOnUiThread(() -> backupFileAdapter.setFiles(files));
+ }
+
+ @Override
+ public void onClick(ImportBackupService.BackupFile backupFile) {
+ final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
+ Log.d(Config.LOGTAG, "attempting to import " + backupFile.getFile().getAbsolutePath());
+ enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString()));
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setView(enterPasswordBinding.getRoot());
+ builder.setTitle(R.string.enter_password);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.restore, (dialog, which) -> {
+ final String password = enterPasswordBinding.accountPassword.getEditableText().toString();
+ Intent intent = new Intent(this, ImportBackupService.class);
+ intent.putExtra("password", password);
+ intent.putExtra("file", backupFile.getFile().getAbsolutePath());
+ setLoadingState(true);
+ ContextCompat.startForegroundService(this, intent);
+ });
+ builder.setCancelable(false);
+ builder.create().show();
+ }
+
+ private void setLoadingState(final boolean loadingState) {
+ binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE);
+ binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
+ setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
+ configureActionBar(getSupportActionBar(), !loadingState);
+ }
+
+ @Override
+ public void onBackupRestored() {
+ runOnUiThread(this::restart);
+ }
+
+ private void restart() {
+ Log.d(Config.LOGTAG, "Restarting " + getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName()));
+ Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ System.exit(0);
+ }
+
+ @Override
+ public void onBackupDecryptionFailed() {
+ runOnUiThread(() -> {
+ setLoadingState(false);
+ Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show();
+ });
+ }
+
+ @Override
+ public void onBackupRestoreFailed() {
+ runOnUiThread(() -> {
+ setLoadingState(false);
+ Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show();
+ });
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java b/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java
index a8702ebb6..67cc3a9d2 100644
--- a/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java
+++ b/src/main/java/de/pixart/messenger/ui/JoinConferenceDialog.java
@@ -54,9 +54,9 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.join_conference);
+ builder.setTitle(R.string.join_public_channel);
DialogJoinConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_join_conference, null, false);
- DelayedHintHelper.setHint(R.string.conference_address_example, binding.jid);
+ DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid);
this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
binding.jid.setAdapter(knownHostsAdapter);
String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);
@@ -77,6 +77,10 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
AlertDialog dialog = builder.create();
dialog.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(view -> mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked()));
+ binding.jid.setOnEditorActionListener((v, actionId, event) -> {
+ mListener.onJoinDialogPositiveClick(dialog, binding.account, binding.jid, binding.bookmark.isChecked());
+ return true;
+ });
return dialog;
}
diff --git a/src/main/java/de/pixart/messenger/ui/LocationActivity.java b/src/main/java/de/pixart/messenger/ui/LocationActivity.java
index ec8893e5e..c7668a142 100644
--- a/src/main/java/de/pixart/messenger/ui/LocationActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/LocationActivity.java
@@ -8,7 +8,6 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
-import android.support.v7.app.AppCompatActivity;
import de.pixart.messenger.Config;
diff --git a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java
index b5f2c02e7..89da3d3d8 100644
--- a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java
@@ -1,5 +1,6 @@
package de.pixart.messenger.ui;
+import android.app.AlertDialog;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
@@ -12,7 +13,6 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.Toast;
import java.security.SecureRandom;
import java.util.Arrays;
@@ -85,8 +85,9 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad
} else {
mUsername.setError(null);
Account account = xmppConnectionService.findAccountByJid(jid);
+ String password = CryptoHelper.createPassword(new SecureRandom());
if (account == null) {
- account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
+ account = new Account(jid, password);
account.setOption(Account.OPTION_REGISTER, true);
account.setOption(Account.OPTION_DISABLED, true);
account.setOption(Account.OPTION_MAGIC_CREATE, true);
@@ -96,12 +97,27 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad
intent.putExtra("jid", account.getJid().asBareJid().toString());
intent.putExtra("init", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_LONG).show();
- StartConversationActivity.addInviteUri(intent, getIntent());
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- finish();
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.create_account));
+ builder.setCancelable(false);
+ StringBuilder messasge = new StringBuilder();
+ messasge.append(getString(R.string.secure_password_generated));
+ messasge.append("\n\n");
+ messasge.append(getString(R.string.password));
+ messasge.append(": ");
+ messasge.append(password);
+ builder.setMessage(messasge);
+ builder.setPositiveButton(getString(R.string.copy_to_clipboard), (dialogInterface, i) -> {
+ if (copyTextToClipboard(password, R.string.create_account)) {
+ StartConversationActivity.addInviteUri(intent, getIntent());
+ startActivity(intent);
+ overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ finish();
+ overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ }
+ });
+ builder.create().show();
}
} catch (IllegalArgumentException e) {
mUsername.setError(getString(R.string.invalid_username));
@@ -136,7 +152,7 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad
generateJID(mUsername.getText().toString());
}
- private void generateJID (String s) {
+ private void generateJID(String s) {
domain = mServer.getSelectedItem().toString();
if (s.trim().length() > 0) {
try {
diff --git a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java
index bca4d3dbc..fc5563df0 100644
--- a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java
@@ -5,6 +5,7 @@ import android.content.Intent;
import android.os.Bundle;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
+import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.util.Pair;
@@ -13,9 +14,7 @@ import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;
@@ -35,10 +34,15 @@ import de.pixart.messenger.utils.MenuDoubleTabUtil;
import de.pixart.messenger.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
-public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated {
+import static de.pixart.messenger.utils.PermissionUtils.allGranted;
+import static de.pixart.messenger.utils.PermissionUtils.writeGranted;
+
+public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
private final String STATE_SELECTED_ACCOUNT = "selected_account";
+ private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
+
protected Account selectedAccount = null;
protected Jid selectedAccountJid = null;
@@ -75,7 +79,6 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_manage_accounts);
-
setSupportActionBar(findViewById(R.id.toolbar));
configureActionBar(getSupportActionBar());
if (savedInstanceState != null) {
@@ -92,14 +95,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
accountListView = findViewById(R.id.account_list);
this.mAccountAdapter = new AccountAdapter(this, accountList);
accountListView.setAdapter(this.mAccountAdapter);
- accountListView.setOnItemClickListener(new OnItemClickListener() {
-
- @Override
- public void onItemClick(AdapterView<?> arg0, View view,
- int position, long arg3) {
- switchToAccount(accountList.get(position));
- }
- });
+ accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
registerForContextMenu(accountListView);
}
@@ -163,6 +159,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
addAccount.setVisible(false);
addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
+
return true;
}
@@ -194,7 +191,13 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
}
switch (item.getItemId()) {
case R.id.action_add_account:
- startActivity(new Intent(getApplicationContext(), EditAccountActivity.class));
+ startActivity(new Intent(this, EditAccountActivity.class));
+ overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
+ break;
+ case R.id.action_import_backup:
+ if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ }
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
break;
case R.id.action_add_account_with_cert:
@@ -207,6 +210,26 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
}
@Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ if (grantResults.length > 0) {
+ if (allGranted(grantResults)) {
+ switch (requestCode) {
+ case REQUEST_IMPORT_BACKUP:
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ break;
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+ if (writeGranted(grantResults, permissions)) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.restartFileObserver();
+ }
+ }
+ }
+
+ @Override
public boolean onNavigateUp() {
if (xmppConnectionService.getConversations().size() == 0) {
Intent contactsIntent = new Intent(this,
@@ -227,6 +250,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
}
}
+ @Override
public void onClickTglAccountState(Account account, boolean enable) {
if (enable) {
enableAccount(account);
diff --git a/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java b/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java
index 2f2dcdf97..a51ef21ab 100644
--- a/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java
@@ -7,21 +7,16 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.databinding.DataBindingUtil;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
-import android.support.v7.view.menu.MenuBuilder;
-import android.support.v7.view.menu.MenuPopupHelper;
-import android.support.v7.widget.PopupMenu;
import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.webkit.MimeTypeMap;
@@ -29,8 +24,6 @@ import android.widget.ImageView;
import android.widget.Toast;
import com.davemorrissey.labs.subscaleview.ImageSource;
-import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
-import com.github.rtoshiro.view.video.FullscreenVideoLayout;
import java.io.File;
import java.io.FileNotFoundException;
@@ -40,28 +33,25 @@ import java.util.List;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.ActivityMediaViewerBinding;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.utils.ExifHelper;
import de.pixart.messenger.utils.MimeUtils;
-import pl.droidsonroids.gif.GifImageView;
import static de.pixart.messenger.persistance.FileBackend.close;
public class MediaViewerActivity extends XmppActivity {
Integer oldOrientation;
- SubsamplingScaleImageView mImage;
- GifImageView mGIF;
- FullscreenVideoLayout mVideo;
ImageView mFullscreenbutton;
Uri mFileUri;
File mFile;
- FloatingActionButton fab;
int height = 0;
int width = 0;
int rotation = 0;
boolean isImage = false;
boolean isVideo = false;
+ private ActivityMediaViewerBinding binding;
public static String getMimeType(String path) {
try {
@@ -80,6 +70,7 @@ public class MediaViewerActivity extends XmppActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_viewer);
this.mTheme = findTheme();
setTheme(this.mTheme);
@@ -97,56 +88,27 @@ public class MediaViewerActivity extends XmppActivity {
getWindow().setAttributes(layout);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
- setContentView(R.layout.activity_media_viewer);
- mImage = findViewById(R.id.message_image_view);
- mGIF = findViewById(R.id.message_gif_view);
- mVideo = findViewById(R.id.message_video_view);
mFullscreenbutton = findViewById(R.id.vcv_img_fullscreen);
- fab = findViewById(R.id.fab);
- fab.setOnClickListener(v -> {
- PopupMenu popup = new PopupMenu(MediaViewerActivity.this, v);
- popup.inflate(R.menu.media_viewer);
- final Menu menu = popup.getMenu();
- MenuItem delete = menu.findItem(R.id.action_delete);
- MenuItem open = menu.findItem(R.id.action_open);
- Log.d(Config.LOGTAG, "Path = " + mFile.toString());
- if (mFile == null || !mFile.toString().startsWith("/") || mFile.toString().contains(FileBackend.getConversationsDirectory("null"))) {
- delete.setVisible(true);
- } else {
- delete.setVisible(false);
- }
- if (isVideo) {
- if (isDarkTheme()) {
- open.setIcon(R.drawable.ic_video_white_24dp);
- } else {
- open.setIcon(R.drawable.ic_video_black_24dp);
- }
- } else if (isImage) {
- if (isDarkTheme()) {
- open.setIcon(R.drawable.ic_image_white_24dp);
- } else {
- open.setIcon(R.drawable.ic_image_black_24dp);
- }
- }
- popup.setOnMenuItemClickListener(item -> {
- switch (item.getItemId()) {
- case R.id.action_share:
- share();
- break;
- case R.id.action_open:
- open();
- break;
- case R.id.action_delete:
+ binding.speedDial.inflate(R.menu.media_viewer);
+ binding.speedDial.setOnActionSelectedListener(actionItem -> {
+ switch (actionItem.getId()) {
+ case R.id.action_share:
+ share();
+ break;
+ case R.id.action_open:
+ open();
+ break;
+ /*
+ case R.id.action_delete:
+ if (mFile == null || !mFile.toString().startsWith("/") || mFile.toString().contains(FileBackend.getConversationsDirectory("null"))) {
deleteFile();
- break;
- default:
- return false;
- }
- return true;
- });
- MenuPopupHelper menuHelper = new MenuPopupHelper(MediaViewerActivity.this, (MenuBuilder) menu, v);
- menuHelper.setForceShowIcon(true);
- menuHelper.show();
+ }
+ break;
+ */
+ default:
+ return false;
+ }
+ return false;
});
}
@@ -262,11 +224,11 @@ public class MediaViewerActivity extends XmppActivity {
}
try {
if (gif) {
- mGIF.setVisibility(View.VISIBLE);
- mGIF.setImageURI(FileUri);
+ binding.messageGifView.setVisibility(View.VISIBLE);
+ binding.messageGifView.setImageURI(FileUri);
} else {
- mImage.setVisibility(View.VISIBLE);
- mImage.setImage(ImageSource.uri(FileUri));
+ binding.messageImageView.setVisibility(View.VISIBLE);
+ binding.messageImageView.setImage(ImageSource.uri(FileUri));
}
} catch (Exception e) {
Toast.makeText(this, getString(R.string.error_file_corrupt), Toast.LENGTH_LONG).show();
@@ -296,10 +258,10 @@ public class MediaViewerActivity extends XmppActivity {
rotateScreen(width, height, rotation);
}
try {
- mVideo.setVisibility(View.VISIBLE);
- mVideo.setVideoURI(uri);
+ binding.messageVideoView.setVisibility(View.VISIBLE);
+ binding.messageVideoView.setVideoURI(uri);
mFullscreenbutton.setVisibility(View.INVISIBLE);
- mVideo.setShouldAutoplay(true);
+ binding.messageVideoView.setShouldAutoplay(true);
} catch (IOException e) {
Toast.makeText(this, getString(R.string.error_file_corrupt), Toast.LENGTH_LONG).show();
@@ -348,13 +310,13 @@ public class MediaViewerActivity extends XmppActivity {
}
getWindow().setAttributes(layout);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mVideo.setShouldAutoplay(true);
+ binding.messageVideoView.setShouldAutoplay(true);
super.onResume();
}
@Override
public void onPause() {
- mVideo.reset();
+ binding.messageVideoView.reset();
WindowManager.LayoutParams layout = getWindow().getAttributes();
if (useMaxBrightness()) {
layout.screenBrightness = -1;
@@ -367,7 +329,7 @@ public class MediaViewerActivity extends XmppActivity {
@Override
public void onStop() {
- mVideo.reset();
+ binding.messageVideoView.reset();
WindowManager.LayoutParams layout = getWindow().getAttributes();
if (useMaxBrightness()) {
layout.screenBrightness = -1;
diff --git a/src/main/java/de/pixart/messenger/ui/MucUsersActivity.java b/src/main/java/de/pixart/messenger/ui/MucUsersActivity.java
new file mode 100644
index 000000000..9961e5512
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/MucUsersActivity.java
@@ -0,0 +1,87 @@
+package de.pixart.messenger.ui;
+
+import android.content.Intent;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.ActivityMucUsersBinding;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.adapter.UserAdapter;
+import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper;
+import rocks.xmpp.addr.Jid;
+
+public class MucUsersActivity extends XmppActivity implements XmppConnectionService.OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged {
+
+ private UserAdapter userAdapter;
+
+ private Conversation mConversation = null;
+
+ @Override
+ protected void refreshUiReal() {
+ }
+
+ @Override
+ void onBackendConnected() {
+ final Intent intent = getIntent();
+ final String uuid = intent == null ? null : intent.getStringExtra("uuid");
+ if (uuid != null) {
+ mConversation = xmppConnectionService.findConversationByUuid(uuid);
+ }
+ loadAndSubmitUsers();
+ }
+
+ private void loadAndSubmitUsers() {
+ if (mConversation != null) {
+ ArrayList<MucOptions.User> users = mConversation.getMucOptions().getUsers();
+ Collections.sort(users);
+ userAdapter.submitList(users);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (!MucDetailsContextMenuHelper.onContextItemSelected(item, userAdapter.getSelectedUser(), this)) {
+ return super.onContextItemSelected(item);
+ }
+ return true;
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ActivityMucUsersBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_users);
+ setSupportActionBar((Toolbar) binding.toolbar);
+ configureActionBar(getSupportActionBar(), true);
+ this.userAdapter = new UserAdapter(getPreferences().getBoolean("advanced_muc_mode", false));
+ binding.list.setAdapter(this.userAdapter);
+ }
+
+
+ @Override
+ public void onMucRosterUpdate() {
+ loadAndSubmitUsers();
+ }
+
+ private void displayToast(final String msg) {
+ runOnUiThread(() -> Toast.makeText(this, msg, Toast.LENGTH_SHORT).show());
+ }
+
+ @Override
+ public void onAffiliationChangedSuccessful(Jid jid) {
+
+ }
+
+ @Override
+ public void onAffiliationChangeFailed(Jid jid, int resId) {
+ displayToast(getString(resId, jid.asBareJid().toString()));
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java
index 469ce8021..e38591848 100644
--- a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java
@@ -15,6 +15,8 @@ import android.widget.Toast;
import com.theartofdev.edmodo.cropper.CropImage;
+import java.util.concurrent.atomic.AtomicBoolean;
+
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
@@ -34,6 +36,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
private Account account;
private boolean support = false;
private boolean publishing = false;
+ private AtomicBoolean handledExternalUri = new AtomicBoolean(false);
private OnLongClickListener backToDefaultListener = new OnLongClickListener() {
@Override
@@ -103,6 +106,19 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
});
this.avatar.setOnClickListener(v -> chooseAvatar());
this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext());
+ if (savedInstanceState != null) {
+ this.avatarUri = savedInstanceState.getParcelable("uri");
+ this.handledExternalUri.set(savedInstanceState.getBoolean("handle_external_uri", false));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ if (this.avatarUri != null) {
+ outState.putParcelable("uri", this.avatarUri);
+ }
+ outState.putBoolean("handle_external_uri", handledExternalUri.get());
+ super.onSaveInstanceState(outState);
}
@@ -160,10 +176,20 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
final Intent intent = getIntent();
this.mInitialAccountSetup = intent != null && intent.getBooleanExtra("setup", false);
+ final Uri uri = intent != null ? intent.getData() : null;
+
+ if (uri != null && handledExternalUri.compareAndSet(false, true)) {
+ CropImage.activity(uri).setOutputCompressFormat(Bitmap.CompressFormat.PNG)
+ .setAspectRatio(1, 1)
+ .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE)
+ .start(this);
+ return;
+ }
+
if (this.mInitialAccountSetup) {
this.cancelButton.setText(R.string.skip);
}
- configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup);
+ configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get());
}
protected void loadImageIntoPreview(Uri uri) {
diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
index dc41011fb..ecae24588 100644
--- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
@@ -20,9 +20,6 @@ import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
@@ -36,10 +33,10 @@ import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.OmemoSetting;
import de.pixart.messenger.entities.Account;
-import de.pixart.messenger.services.ExportLogsService;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.services.ExportBackupService;
import de.pixart.messenger.services.MemorizingTrustManager;
import de.pixart.messenger.ui.util.StyledAttributes;
-import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.TimeframeUtils;
import rocks.xmpp.addr.Jid;
@@ -63,8 +60,9 @@ public class SettingsActivity extends XmppActivity implements
public static final String QUICK_SHARE_ATTACHMENT_CHOICE = "quick_share_attachment_choice";
public static final String NUMBER_OF_ACCOUNTS = "number_of_accounts";
public static final String PLAY_GIF_INSIDE = "play_gif_inside";
+ public static final String PREFER_XMPP_AVATAR = "prefer_xmpp_avatar";
- public static final int REQUEST_WRITE_LOGS = 0xbf8701;
+ public static final int REQUEST_CREATE_BACKUP = 0xbf8701;
Preference multiAccountPreference;
Preference BundledEmojiPreference;
Preference QuickShareAttachmentChoicePreference;
@@ -249,11 +247,12 @@ public class SettingsActivity extends XmppActivity implements
});
}
- final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
- if (exportLogsPreference != null) {
- exportLogsPreference.setOnPreferenceClickListener(preference -> {
- if (hasStoragePermission(REQUEST_WRITE_LOGS)) {
- startExport();
+ final Preference createBackupPreference = mSettingsFragment.findPreference("create_backup");
+ if (createBackupPreference != null) {
+ createBackupPreference.setSummary(getString(R.string.pref_create_backup_summary, FileBackend.getBackupDirectory()));
+ createBackupPreference.setOnPreferenceClickListener(preference -> {
+ if (hasStoragePermission(REQUEST_CREATE_BACKUP)) {
+ createBackup();
}
return true;
});
@@ -424,75 +423,9 @@ public class SettingsActivity extends XmppActivity implements
private void enableMultiAccounts() {
if (!isMultiAccountChecked) {
multiAccountPreference.setEnabled(true);
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setCancelable(false);
- builder.setTitle(R.string.pref_enable_multi_accounts_title);
- builder.setMessage(R.string.pref_enable_multi_accounts_summary);
- builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
- ((CheckBoxPreference) multiAccountPreference).setChecked(false);
- });
- builder.setPositiveButton(R.string.enter_password, (dialog, which) -> {
- ((CheckBoxPreference) multiAccountPreference).setChecked(false);
- enterPasswordDialog();
- });
- AlertDialog dialog = builder.create();
- dialog.show();
}
}
- public void enterPasswordDialog() {
- LayoutInflater li = LayoutInflater.from(this);
- View promptsView = li.inflate(R.layout.password, null);
-
- final Preference preference = mSettingsFragment.findPreference("enable_multi_accounts");
-
- final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
- alertDialogBuilder.setView(promptsView);
- final EditText password = promptsView.findViewById(R.id.password);
- final EditText confirm_password = promptsView.findViewById(R.id.confirm_password);
- confirm_password.setVisibility(View.VISIBLE);
- alertDialogBuilder.setTitle(R.string.enter_password);
- alertDialogBuilder.setMessage(R.string.enter_password);
- alertDialogBuilder
- .setCancelable(false)
- .setPositiveButton(R.string.ok,
- (dialog, id) -> {
- final String pw1 = password.getText().toString();
- final String pw2 = confirm_password.getText().toString();
- if (!pw1.equals(pw2)) {
- ((CheckBoxPreference) preference).setChecked(false);
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.error);
- builder.setMessage(R.string.passwords_do_not_match);
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.try_again, (dialog12, id12) -> enterPasswordDialog());
- builder.create().show();
- } else if (pw1.trim().isEmpty()) {
- ((CheckBoxPreference) preference).setChecked(false);
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.error);
- builder.setMessage(R.string.password_should_not_be_empty);
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.try_again, (dialog1, id1) -> enterPasswordDialog());
- builder.create().show();
- } else {
- ((CheckBoxPreference) preference).setChecked(true);
- SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = multiaccount_prefs.edit();
- editor.putString("BackupPW", pw1);
- boolean passwordstored = editor.commit();
- Log.d(Config.LOGTAG, "saving multiaccount password " + passwordstored);
- if (passwordstored) {
- recreate();
- } else {
- //handleMultiAccountChanges();
- }
- }
- })
- .setNegativeButton(R.string.cancel, null);
- alertDialogBuilder.create().show();
- }
-
@Override
public void onStop() {
@@ -538,26 +471,19 @@ public class SettingsActivity extends XmppActivity implements
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- if (grantResults.length > 0)
+ if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- if (requestCode == REQUEST_WRITE_LOGS) {
- startExport();
+ if (requestCode == REQUEST_CREATE_BACKUP) {
+ createBackup();
}
} else {
Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
}
+ }
}
- private void startExport() {
- try {
- if (Compatibility.runsAndTargetsTwentySix(this)) {
- ContextCompat.startForegroundService(this, new Intent(this, ExportLogsService.class));
- } else {
- this.startService(new Intent(this, ExportLogsService.class));
- }
- } catch (RuntimeException e) {
- Log.d(Config.LOGTAG, "SettingsActivity was unable to start ExportLogsService");
- }
+ private void createBackup() {
+ ContextCompat.startForegroundService(this, new Intent(this, ExportBackupService.class));
}
private void displayToast(final String msg) {
diff --git a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java
index 2d535a44c..4f0e81e0d 100644
--- a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java
@@ -1,7 +1,6 @@
package de.pixart.messenger.ui;
import android.os.Bundle;
-import android.support.v7.app.ActionBar;
import android.widget.ListView;
import java.util.ArrayList;
@@ -27,11 +26,6 @@ public class ShareViaAccountActivity extends XmppActivity {
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
}
- ActionBar actionBar = getSupportActionBar();
- if (actionBar != null) {
- actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
- actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
- }
mAccountAdapter.notifyDataSetChanged();
}
diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
index 0d1a3286d..26d8debc5 100644
--- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
@@ -18,13 +18,10 @@ import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Conversation;
-import de.pixart.messenger.services.EmojiService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.adapter.ConversationAdapter;
import rocks.xmpp.addr.Jid;
-import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
-
public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
private static final int REQUEST_STORAGE_PERMISSION = 0x733f32;
@@ -73,8 +70,6 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji));
- new EmojiService(this).init(useBundledEmoji);
setContentView(R.layout.activity_share_with);
setSupportActionBar(findViewById(R.id.toolbar));
if (getSupportActionBar() != null) {
@@ -130,7 +125,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
if (xmppConnectionServiceBound) {
- xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
+ xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0, false);
}
}
@@ -182,7 +177,8 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
}
public void refreshUiReal() {
- xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0);
+ //TODO inject desired order to not resort on refresh
+ xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0, false);
mAdapter.notifyDataSetChanged();
}
diff --git a/src/main/java/de/pixart/messenger/ui/ShortcutActivity.java b/src/main/java/de/pixart/messenger/ui/ShortcutActivity.java
index 68ad0cb86..9afb2659f 100644
--- a/src/main/java/de/pixart/messenger/ui/ShortcutActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ShortcutActivity.java
@@ -5,7 +5,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
-import android.view.View;
import android.view.inputmethod.InputMethodManager;
import java.util.Arrays;
diff --git a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java
index d8c565345..26865aed0 100644
--- a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java
@@ -28,11 +28,8 @@ import java.util.Locale;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
-import de.pixart.messenger.services.EmojiService;
import de.pixart.messenger.utils.MenuDoubleTabUtil;
-import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
-
public class ShowLocationActivity extends XmppActivity {
FloatingActionButton fab;
private Location location;
@@ -65,8 +62,6 @@ public class ShowLocationActivity extends XmppActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji));
- new EmojiService(this).init(useBundledEmoji);
setContentView(R.layout.activity_show_locaction);
setTitle(getString(R.string.show_location));
setSupportActionBar(findViewById(R.id.toolbar));
diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
index 825494d90..b87a26b0c 100644
--- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
@@ -13,7 +13,6 @@ import android.databinding.DataBindingUtil;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@@ -21,7 +20,6 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
@@ -63,7 +61,7 @@ import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.ListItem;
import de.pixart.messenger.entities.Presence;
-import de.pixart.messenger.services.EmojiService;
+import de.pixart.messenger.services.QuickConversationsService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate;
import de.pixart.messenger.ui.adapter.ListItemAdapter;
@@ -77,11 +75,9 @@ import de.pixart.messenger.xmpp.OnUpdateBlocklist;
import de.pixart.messenger.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
-import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
+public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
-public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreateConferenceDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener {
-
- public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
+ public static final String EXTRA_INVITE_URI = "de.pixart.messenger.invite_uri";
private final int REQUEST_SYNC_CONTACTS = 0x28cf;
private final int REQUEST_CREATE_CONFERENCE = 0x39da;
@@ -115,7 +111,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
}
});
-
+ if (binding.speedDial.isOpen()) {
+ binding.speedDial.close();
+ }
return true;
}
@@ -202,12 +200,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
return true;
}
};
- private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
- @Override
- public void onPageSelected(int position) {
- onTabChanged();
- }
- };
public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
if (accounts.size() > 0) {
@@ -261,32 +253,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- new EmojiService(this).init(useBundledEmoji());
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_start_conversation);
Toolbar toolbar = (Toolbar) binding.toolbar;
setSupportActionBar(toolbar);
configureActionBar(getSupportActionBar());
- this.binding.fab.setOnClickListener((v) -> {
- if (binding.startConversationViewPager.getCurrentItem() == 0) {
- String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
- if (searchString != null && !searchString.trim().isEmpty()) {
- try {
- Jid jid = Jid.of(searchString);
- if (jid.getLocal() != null && jid.isBareJid() && jid.getDomain().contains(".")) {
- showCreateContactDialog(jid.toString(), null);
- return;
- }
- } catch (IllegalArgumentException ignored) {
- //ignore and fall through
- }
- }
- showCreateContactDialog(null, null);
- } else {
- showCreateConferenceDialog();
- }
- });
+ binding.speedDial.inflate(R.menu.start_conversation_fab_submenu);
binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
- binding.startConversationViewPager.addOnPageChangeListener(mOnPageChangeListener);
mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager());
binding.startConversationViewPager.setAdapter(mListPagerAdapter);
@@ -295,7 +267,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
mContactsAdapter.setOnTagClickedListener(this.mOnTagClickedListener);
final SharedPreferences preferences = getPreferences();
- this.mHideOfflineContacts = preferences.getBoolean("hide_offline", false);
+ this.mHideOfflineContacts = QuickConversationsService.isConversations() && preferences.getBoolean("hide_offline", false);
final boolean startSearching = preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
@@ -318,6 +290,40 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} else if (startSearching && mInitialSearchValue.peek() == null) {
mInitialSearchValue.push("");
}
+ mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission", false));
+ binding.speedDial.setOnActionSelectedListener(actionItem -> {
+ final String searchString = mSearchEditText != null ? mSearchEditText.getText().toString() : null;
+ final String prefilled;
+ if (isValidJid(searchString)) {
+ prefilled = Jid.ofEscaped(searchString).toEscapedString();
+ } else {
+ prefilled = null;
+ }
+ switch (actionItem.getId()) {
+ case R.id.join_public_channel:
+ showJoinConferenceDialog(prefilled);
+ break;
+ case R.id.create_private_group_chat:
+ showCreatePrivateGroupChatDialog();
+ break;
+ case R.id.create_public_channel:
+ showPublicChannelDialog();
+ break;
+ case R.id.create_contact:
+ showCreateContactDialog(prefilled, null);
+ break;
+ }
+ return false;
+ });
+ }
+
+ public static boolean isValidJid(String input) {
+ try {
+ Jid jid = Jid.ofEscaped(input);
+ return !jid.isDomainJid();
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
}
@Override
@@ -367,10 +373,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
switchToConversation(conversation);
}
- protected void openConversationForBookmark() {
- openConversationForBookmark(conference_context_id);
- }
-
protected void openConversationForBookmark(int position) {
Bookmark bookmark = (Bookmark) conferences.get(position);
openConversationsForBookmark(bookmark);
@@ -476,8 +478,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
ft.addToBackStack(null);
EnterJidDialog dialog = EnterJidDialog.newInstance(
mActivatedAccounts,
- getString(R.string.create_contact),
- getString(R.string.create),
+ getString(R.string.add_contact),
+ getString(R.string.add),
prefilledJid,
null,
invite == null || !invite.hasFingerprints(),
@@ -528,32 +530,51 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
}
- private void showCreateConferenceDialog() {
+ private void showCreatePrivateGroupChatDialog() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
- CreateConferenceDialog createConferenceFragment = CreateConferenceDialog.newInstance(mActivatedAccounts, xmppConnectionService.multipleAccounts());
+ CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts, xmppConnectionService.multipleAccounts());
createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
}
- private Account getSelectedAccount(Spinner spinner) {
- if (!spinner.isEnabled()) {
+ private void showPublicChannelDialog() {
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_DIALOG);
+ if (prev != null) {
+ ft.remove(prev);
+ }
+ ft.addToBackStack(null);
+ CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
+ dialog.show(ft, FRAGMENT_TAG_DIALOG);
+ }
+
+ public static Account getSelectedAccount(Context context, Spinner spinner) {
+ if (spinner == null || !spinner.isEnabled()) {
return null;
}
- Jid jid;
- try {
- if (Config.DOMAIN_LOCK != null) {
- jid = Jid.of((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
- } else {
- jid = Jid.of((String) spinner.getSelectedItem());
+ if (context instanceof XmppActivity) {
+ Jid jid;
+ try {
+ if (Config.DOMAIN_LOCK != null) {
+ jid = Jid.of((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
+ } else {
+ jid = Jid.of((String) spinner.getSelectedItem());
+ }
+ } catch (final IllegalArgumentException e) {
+ return null;
}
- } catch (final IllegalArgumentException e) {
+ final XmppConnectionService service = ((XmppActivity) context).xmppConnectionService;
+ if (service == null) {
+ return null;
+ }
+ return service.findAccountByJid(jid);
+ } else {
return null;
}
- return xmppConnectionService.findAccountByJid(jid);
}
protected void switchToConversation(Contact contact) {
@@ -598,9 +619,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} else {
menuActionAccounts.setTitle(R.string.action_accounts);
}
- MenuItem joinGroupChat = menu.findItem(R.id.action_join_conference);
MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
- joinGroupChat.setVisible(binding.startConversationViewPager.getCurrentItem() == 1);
qrCodeScanMenuItem.setVisible(isCameraFeatureAvailable());
menuHideOffline.setChecked(this.mHideOfflineContacts);
mMenuSearchView = menu.findItem(R.id.action_search);
@@ -628,9 +647,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
case android.R.id.home:
navigateBack();
return true;
- case R.id.action_join_conference:
- showJoinConferenceDialog(null);
- return true;
case R.id.action_scan_qr_code:
UriHandlerActivity.scan(this);
return true;
@@ -901,17 +917,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
mConferenceAdapter.notifyDataSetChanged();
}
- private void onTabChanged() {
- @DrawableRes final int fabDrawable;
- if (binding.startConversationViewPager.getCurrentItem() == 0) {
- fabDrawable = R.drawable.ic_person_add_white_24dp;
- } else {
- fabDrawable = R.drawable.ic_group_add_white_24dp;
- }
- binding.fab.setImageResource(fabDrawable);
- invalidateOptionsMenu();
- }
-
@Override
public void OnUpdateBlocklist(final Status status) {
refreshUi();
@@ -927,6 +932,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
@Override
public void onBackPressed() {
+ if (binding.speedDial.isOpen()) {
+ binding.speedDial.close();
+ return;
+ }
navigateBack();
}
@@ -939,16 +948,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
finish();
}
- public boolean useBundledEmoji() {
- return getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji));
- }
-
@Override
public void onCreateDialogPositiveClick(Spinner spinner, String name) {
if (!xmppConnectionServiceBound) {
return;
}
- final Account account = getSelectedAccount(spinner);
+ final Account account = getSelectedAccount(this, spinner);
if (account == null) {
return;
}
@@ -967,7 +972,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (!xmppConnectionServiceBound) {
return;
}
- final Account account = getSelectedAccount(spinner);
+ final Account account = getSelectedAccount(this, spinner);
if (account == null) {
return;
}
@@ -1010,6 +1015,35 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
refreshUi();
}
+ @Override
+ public void onCreatePublicChannel(Account account, String name, Jid address) {
+ mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
+ mToast.show();
+ xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
+ @Override
+ public void success(Conversation conversation) {
+ runOnUiThread(() -> {
+ hideToast();
+ switchToConversation(conversation);
+ });
+
+ }
+
+ @Override
+ public void error(int errorCode, Conversation conversation) {
+ runOnUiThread(() -> {
+ replaceToast(getString(errorCode));
+ switchToConversation(conversation);
+ });
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Conversation object) {
+
+ }
+ });
+ }
+
public static class MyListFragment extends ListFragment {
private AdapterView.OnItemClickListener mOnItemClickListener;
private int mResContextMenu;
@@ -1091,9 +1125,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
case R.id.context_delete_contact:
activity.deleteContact();
break;
- case R.id.context_join_conference:
- activity.openConversationForBookmark();
- break;
case R.id.context_share_uri:
activity.shareBookmarkUri();
break;
@@ -1158,7 +1189,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
case 0:
return getResources().getString(R.string.contacts);
case 1:
- return getResources().getString(R.string.conferences);
+ return getResources().getString(R.string.bookmarks);
default:
return super.getPageTitle(position);
}
diff --git a/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java b/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java
index 559e20648..4a6dfa234 100644
--- a/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java
@@ -7,7 +7,6 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
@@ -16,7 +15,6 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.persistance.DatabaseBackend;
import de.pixart.messenger.utils.SignupUtils;
@@ -86,7 +84,7 @@ public class UriHandlerActivity extends AppCompatActivity {
private void handleUri(Uri uri, final boolean scanned) {
final Intent intent;
final XmppUri xmppUri = new XmppUri(uri);
- final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(); //TODO only look at enabled accounts
+ final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(true);
if (accounts.size() == 0) {
if (xmppUri.isJidValid()) {
intent = SignupUtils.getSignUpIntent(this);
@@ -102,7 +100,7 @@ public class UriHandlerActivity extends AppCompatActivity {
if (jid != null) {
Class clazz;
try {
- clazz = Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");
+ clazz = Class.forName("de.pixart.messenger.ui.ShareViaAccountActivity");
} catch (ClassNotFoundException e) {
clazz = null;
}
diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
index 75493d6c8..0ea648370 100644
--- a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
@@ -1,49 +1,27 @@
package de.pixart.messenger.ui;
-import android.Manifest;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
-import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
-import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
-import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
import java.util.List;
-import javax.crypto.NoSuchPaddingException;
-
-import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.Account;
-import de.pixart.messenger.persistance.DatabaseBackend;
-import de.pixart.messenger.persistance.FileBackend;
-import de.pixart.messenger.utils.EncryptDecryptFile;
-import de.pixart.messenger.utils.XmppUri;
+
+import static de.pixart.messenger.utils.PermissionUtils.allGranted;
+import static de.pixart.messenger.utils.PermissionUtils.writeGranted;
public class WelcomeActivity extends XmppActivity {
- boolean importSuccessful = false;
+ private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
@Override
protected void refreshUiReal() {
@@ -85,21 +63,14 @@ public class WelcomeActivity extends XmppActivity {
ab.setDisplayHomeAsUpEnabled(false);
}
- //check if there is a backed up database --
- if (hasStoragePermission(REQUEST_READ_EXTERNAL_STORAGE)) {
- BackupAvailable();
- }
-
-
final Button ImportDatabase = findViewById(R.id.import_database);
final TextView ImportText = findViewById(R.id.import_text);
-
- if (BackupAvailable() != 0) {
+ if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
ImportDatabase.setVisibility(View.VISIBLE);
ImportText.setVisibility(View.VISIBLE);
}
+ ImportDatabase.setOnClickListener(v -> startActivity(new Intent(this, ImportBackupActivity.class)));
- ImportDatabase.setOnClickListener(v -> enterPasswordDialog(BackupAvailable()));
final Button createAccount = findViewById(R.id.create_account);
createAccount.setOnClickListener(v -> {
@@ -127,290 +98,34 @@ public class WelcomeActivity extends XmppActivity {
}
- public void enterPasswordDialog(final int backup_type) {
- if (backup_type == 1) {
- LayoutInflater li = LayoutInflater.from(WelcomeActivity.this);
- View promptsView = li.inflate(R.layout.password, null);
- final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(WelcomeActivity.this);
- alertDialogBuilder.setView(promptsView);
- final EditText userInput = promptsView
- .findViewById(R.id.password);
- alertDialogBuilder.setTitle(R.string.enter_password);
- alertDialogBuilder.setMessage(R.string.enter_account_password);
- alertDialogBuilder
- .setCancelable(false)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- final String password = userInput.getText().toString();
- final ProgressDialog pd = ProgressDialog.show(WelcomeActivity.this, getString(R.string.please_wait), getString(R.string.databaseimport_started), true);
- if (!password.isEmpty()) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- checkDatabase(password);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (Exception e) {
- e.printStackTrace();
- }
- pd.dismiss();
- }
- }).start();
- } else {
- AlertDialog.Builder builder = new AlertDialog.Builder(WelcomeActivity.this);
- builder.setTitle(R.string.error);
- builder.setMessage(R.string.password_should_not_be_empty);
- builder.setNegativeButton(R.string.cancel, null);
- builder.setPositiveButton(R.string.try_again, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- enterPasswordDialog(backup_type);
- }
- });
- builder.create().show();
- }
- }
- })
- .setNegativeButton(R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- Toast.makeText(WelcomeActivity.this, R.string.import_canceled, Toast.LENGTH_LONG).show();
- dialog.dismiss();
- }
- }
- );
- WelcomeActivity.this.runOnUiThread(new Runnable() {
- public void run() {
- // create alert dialog
- AlertDialog alertDialog = alertDialogBuilder.create();
- // show it
- alertDialog.show();
- }
- });
- } else {
- try {
- checkDatabase(null);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
+ public void addInviteUri(Intent intent) {
+ StartConversationActivity.addInviteUri(intent, getIntent());
}
- private int BackupAvailable() {
- // Set the folder on the SDcard
- File filePath_enc = new File(FileBackend.getBackupDirectory() + "/database.db.crypt");
- File filePath_dec = new File(FileBackend.getBackupDirectory() + "/database.db");
- if (filePath_enc.exists()) {
- Log.d(Config.LOGTAG, "DB Path existing (encrypted)");
- return 1;
- } else if (filePath_dec.exists()) {
- Log.d(Config.LOGTAG, "DB Path existing (decrypted)");
- return 2;
- } else {
- Log.d(Config.LOGTAG, "DB Path not existing");
- return 0;
- }
+ public static void launch(AppCompatActivity activity) {
+ Intent intent = new Intent(activity, WelcomeActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ activity.startActivity(intent);
+ activity.overridePendingTransition(0, 0);
}
- private void checkDatabase(String DecryptionKey) throws IOException {
- if (DecryptionKey != null) {
- // Set the folder on the SDcard
- File directory = new File(FileBackend.getBackupDirectory());
- // Set the input file stream up:
- FileInputStream InputFile = new FileInputStream(directory.getPath() + "/database.db.crypt");
- // Temp output for DB checks
- File TempFile = new File(directory.getPath() + "database.bak");
- FileOutputStream OutputTemp = new FileOutputStream(TempFile);
-
- try {
- EncryptDecryptFile.decrypt(InputFile, OutputTemp, DecryptionKey);
- } catch (NoSuchAlgorithmException e) {
- Log.d(Config.LOGTAG, "Database importer: decryption failed with " + e);
- e.printStackTrace();
- } catch (NoSuchPaddingException e) {
- Log.d(Config.LOGTAG, "Database importer: decryption failed with " + e);
- e.printStackTrace();
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, "Database importer: decryption failed (invalid key) with " + e);
- e.printStackTrace();
- } catch (IOException e) {
- Log.d(Config.LOGTAG, "Database importer: decryption failed (IO) with " + e);
- e.printStackTrace();
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "Database importer: Error " + e);
- e.printStackTrace();
- }
-
- SQLiteDatabase checkDB = null;
- int DB_Version = DatabaseBackend.DATABASE_VERSION;
- int Backup_DB_Version = 0;
-
- try {
- String dbPath = TempFile.toString();
- checkDB = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
- Backup_DB_Version = checkDB.getVersion();
- Log.d(Config.LOGTAG, "Backup found: " + checkDB + " Version: " + checkDB.getVersion());
- } catch (SQLiteException e) {
- //database does't exist yet.
- Log.d(Config.LOGTAG, "No backup found: " + checkDB);
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "Error importing backup: " + e);
- }
-
- if (checkDB != null) {
- checkDB.close();
- }
- if (checkDB != null) {
- Log.d(Config.LOGTAG, "checkDB = " + checkDB.toString() + ", Backup DB = " + Backup_DB_Version + ", DB = " + DB_Version);
- }
- if (checkDB != null && Backup_DB_Version != 0 && Backup_DB_Version <= DB_Version) {
- try {
- ImportDatabase();
- importSuccessful = true;
- } catch (Exception e) {
- importSuccessful = false;
- e.printStackTrace();
- } finally {
- if (importSuccessful) {
- restart();
- }
- }
- } else if (checkDB != null && Backup_DB_Version == 0) {
- WelcomeActivity.this.runOnUiThread(new Runnable() {
- public void run() {
- Toast.makeText(WelcomeActivity.this, R.string.Password_wrong, Toast.LENGTH_LONG).show();
- enterPasswordDialog(1);
- }
- });
- } else {
- WelcomeActivity.this.runOnUiThread(new Runnable() {
- public void run() {
- Toast.makeText(WelcomeActivity.this, R.string.Import_failed, Toast.LENGTH_LONG).show();
- }
- });
- }
- } else {
- // Set the folder on the SDcard
- File directory = new File(FileBackend.getBackupDirectory());
- // Set the input file stream up:
- FileInputStream InputFile = new FileInputStream(directory.getPath() + "/database.db");
- // Temp output for DB checks
- File TempFile = new File(directory.getPath() + "database.bak");
- FileOutputStream OutputTemp = new FileOutputStream(TempFile);
-
- try {
- // Transfer bytes from in to out
- byte[] buf = new byte[1024];
- int len;
- while ((len = InputFile.read(buf)) > 0) {
- OutputTemp.write(buf, 0, len);
- }
- } finally {
- OutputTemp.close();
- }
-
- SQLiteDatabase checkDB = null;
- int DB_Version = DatabaseBackend.DATABASE_VERSION;
- int Backup_DB_Version = 0;
-
- try {
- String dbPath = TempFile.toString();
- checkDB = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
- Backup_DB_Version = checkDB.getVersion();
- Log.d(Config.LOGTAG, "Backup found: " + checkDB + " Version: " + checkDB.getVersion());
- } catch (SQLiteException e) {
- //database does't exist yet.
- Log.d(Config.LOGTAG, "No backup found: " + checkDB);
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "Error importing backup: " + e);
- }
-
- if (checkDB != null) {
- checkDB.close();
- }
- if (checkDB != null) {
- Log.d(Config.LOGTAG, "checkDB = " + checkDB.toString() + ", Backup DB = " + Backup_DB_Version + ", DB = " + DB_Version);
- }
- if (checkDB != null && Backup_DB_Version != 0 && Backup_DB_Version <= DB_Version) {
- try {
- ImportDatabase();
- importSuccessful = true;
- } catch (Exception e) {
- importSuccessful = false;
- e.printStackTrace();
- } finally {
- if (importSuccessful) {
- restart();
- }
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
+ if (grantResults.length > 0) {
+ if (allGranted(grantResults)) {
+ switch (requestCode) {
+ case REQUEST_IMPORT_BACKUP:
+ startActivity(new Intent(this, ImportBackupActivity.class));
+ break;
}
} else {
- WelcomeActivity.this.runOnUiThread(new Runnable() {
- public void run() {
- Toast.makeText(WelcomeActivity.this, R.string.Import_failed, Toast.LENGTH_LONG).show();
- }
- });
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
}
}
- }
-
- private void ImportDatabase() throws Exception {
- // Set location for the db:
- final OutputStream OutputFile = new FileOutputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
- // Set the folder on the SDcard
- File directory = new File(FileBackend.getBackupDirectory());
- // Set the input file stream up:
- final InputStream InputFile = new FileInputStream(directory.getPath() + "database.bak");
- //set temp file
- File TempFile = new File(directory.getPath() + "database.bak");
-
- // Transfer bytes from the input file to the output file
- byte[] buffer = new byte[1024];
- int length;
- while ((length = InputFile.read(buffer)) > 0) {
- OutputFile.write(buffer, 0, length);
- }
- if (TempFile.exists()) {
- Log.d(Config.LOGTAG, "Delete temp file from " + TempFile.toString());
- TempFile.delete();
- }
- }
-
- private void restart() {
- //restart app
- Log.d(Config.LOGTAG, "Restarting " + getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName()));
- Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
- System.exit(0);
- }
-
- public boolean hasStoragePermission(int requestCode) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, requestCode);
- return false;
- } else {
- return true;
+ if (writeGranted(grantResults, permissions)) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.restartFileObserver();
}
- } else {
- return true;
}
}
-
- public void addInviteUri(Intent intent) {
- StartConversationActivity.addInviteUri(intent, getIntent());
- }
-
- public static void launch(AppCompatActivity activity) {
- Intent intent = new Intent(activity, WelcomeActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- activity.startActivity(intent);
- activity.overridePendingTransition(0, 0);
- }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java
index f0a180353..b64b4ae71 100644
--- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java
+++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java
@@ -38,6 +38,7 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.BoolRes;
+import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
@@ -76,6 +77,7 @@ import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.Presences;
import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.services.BarcodeProvider;
+import de.pixart.messenger.services.EmojiService;
import de.pixart.messenger.services.UpdateService;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.services.XmppConnectionService.XmppConnectionBinder;
@@ -92,6 +94,8 @@ import de.pixart.messenger.xmpp.XmppConnection;
import pl.droidsonroids.gif.GifDrawable;
import rocks.xmpp.addr.Jid;
+import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
+
public abstract class XmppActivity extends ActionBarActivity {
protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
@@ -238,7 +242,11 @@ public abstract class XmppActivity extends ActionBarActivity {
public void connectToBackend() {
Intent intent = new Intent(this, XmppConnectionService.class);
intent.setAction("ui");
- startService(intent);
+ try {
+ startService(intent);
+ } catch (IllegalStateException e) {
+ Log.w(Config.LOGTAG, "unable to start service from " + getClass().getSimpleName());
+ }
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@@ -392,6 +400,7 @@ public abstract class XmppActivity extends ActionBarActivity {
setVolumeControlStream(AudioManager.STREAM_NOTIFICATION);
metrics = getResources().getDisplayMetrics();
ExceptionHelper.init(getApplicationContext());
+ new EmojiService(this).init(getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)));
this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
mColorRed = ContextCompat.getColor(this, R.color.red800);
if (isDarkTheme()) {
@@ -947,6 +956,10 @@ public abstract class XmppActivity extends ActionBarActivity {
Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
return;
}
+ if (xmppConnectionService.getAccounts() == null){
+ Toast.makeText(this, R.string.no_accounts, Toast.LENGTH_SHORT).show();
+ return;
+ }
if (!xmppConnectionService.multipleAccounts()) {
Account mAccount = xmppConnectionService.getAccounts().get(0);
String user = Jid.of(mAccount.getJid()).getLocal();
@@ -1122,7 +1135,7 @@ public abstract class XmppActivity extends ActionBarActivity {
if (cancelPotentialWork(message, imageView)) {
imageView.setBackgroundColor(0xff333333);
imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(this, imageView);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(
getResources(), null, task);
imageView.setImageDrawable(asyncDrawable);
@@ -1177,11 +1190,9 @@ public abstract class XmppActivity extends ActionBarActivity {
static class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
- private final WeakReference<XmppActivity> activity;
private Message message = null;
- private BitmapWorkerTask(XmppActivity activity, ImageView imageView) {
- this.activity = new WeakReference<>(activity);
+ private BitmapWorkerTask(ImageView imageView) {
this.imageViewReference = new WeakReference<>(imageView);
}
@@ -1192,7 +1203,7 @@ public abstract class XmppActivity extends ActionBarActivity {
}
message = params[0];
try {
- XmppActivity activity = this.activity.get();
+ final XmppActivity activity = find(imageViewReference);
if (activity != null && activity.xmppConnectionService != null) {
return activity.xmppConnectionService.getFileBackend().getThumbnail(message, (int) (activity.metrics.density * 288), false);
} else {
@@ -1230,6 +1241,19 @@ public abstract class XmppActivity extends ActionBarActivity {
}
}
+ public static XmppActivity find(@NonNull WeakReference<ImageView> viewWeakReference) {
+ final View view = viewWeakReference.get();
+ return view == null ? null : find(view);
+ }
+
+ public static XmppActivity find(@NonNull final View view) {
+ final Context context = view.getContext();
+ if (context instanceof XmppActivity) {
+ return (XmppActivity) context;
+ }
+ return null;
+ }
+
protected boolean installFromUnknownSourceAllowed() {
boolean installFromUnknownSource = false;
final PackageManager packageManager = this.getPackageManager();
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
index e37ea6121..e37c0ea16 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
@@ -1,31 +1,21 @@
package de.pixart.messenger.ui.adapter;
-import android.content.Context;
-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.databinding.DataBindingUtil;
import android.support.annotation.NonNull;
-import android.support.v7.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-import java.lang.ref.WeakReference;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.AccountRowBinding;
import de.pixart.messenger.entities.Account;
-import de.pixart.messenger.ui.ManageAccountActivity;
import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.StyledAttributes;
-import de.pixart.messenger.utils.UIHelper;
public class AccountAdapter extends ArrayAdapter<Account> {
@@ -44,136 +34,63 @@ public class AccountAdapter extends ArrayAdapter<Account> {
this.showStateButton = true;
}
- @NonNull
@Override
public View getView(int position, View view, @NonNull ViewGroup parent) {
final Account account = getItem(position);
+ final ViewHolder viewHolder;
if (view == null) {
- LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- view = inflater.inflate(R.layout.account_row, parent, false);
+ AccountRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.account_row, parent, false);
+ view = binding.getRoot();
+ viewHolder = new ViewHolder(binding);
+ view.setTag(viewHolder);
+ } else {
+ viewHolder = (ViewHolder) view.getTag();
}
- TextView jid = view.findViewById(R.id.account_jid);
if (Config.DOMAIN_LOCK != null) {
- jid.setText(account.getJid().getLocal());
+ viewHolder.binding.accountJid.setText(account.getJid().getLocal());
} else {
- jid.setText(account.getJid().asBareJid().toString());
+ viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toString());
}
- TextView statusView = view.findViewById(R.id.account_status);
- ImageView imageView = view.findViewById(R.id.account_image);
- loadAvatar(account,imageView);
- statusView.setText(getContext().getString(account.getStatus().getReadableId()));
+ AvatarWorkerTask.loadAvatar(account, viewHolder.binding.accountImage, R.dimen.avatar);
+ viewHolder.binding.accountStatus.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
- statusView.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline));
+ viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline));
break;
case DISABLED:
case CONNECTING:
- statusView.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary));
+ viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary));
break;
default:
- statusView.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError));
+ viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError));
break;
}
- final SwitchCompat tglAccountState = view.findViewById(R.id.tgl_account_status);
final boolean isDisabled = (account.getStatus() == Account.State.DISABLED);
- tglAccountState.setOnCheckedChangeListener(null);
- tglAccountState.setChecked(!isDisabled);
+ viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener(null);
+ viewHolder.binding.tglAccountStatus.setChecked(!isDisabled);
if (this.showStateButton) {
- tglAccountState.setVisibility(View.VISIBLE);
+ viewHolder.binding.tglAccountStatus.setVisibility(View.VISIBLE);
} else {
- tglAccountState.setVisibility(View.GONE);
+ viewHolder.binding.tglAccountStatus.setVisibility(View.GONE);
}
- tglAccountState.setOnCheckedChangeListener((compoundButton, b) -> {
- if (b == isDisabled && activity instanceof ManageAccountActivity) {
- ((ManageAccountActivity) activity).onClickTglAccountState(account, b);
+ viewHolder.binding.tglAccountStatus.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (b == isDisabled && activity instanceof OnTglAccountState) {
+ ((OnTglAccountState) activity).onClickTglAccountState(account, b);
}
});
return view;
}
- public static boolean cancelPotentialWork(Account account, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Account oldAccount = bitmapWorkerTask.account;
- if (oldAccount == null || account != oldAccount) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
- public void loadAvatar(Account account, ImageView imageView) {
- if (cancelPotentialWork(account, imageView)) {
- final Bitmap bm = activity.avatarService().get(account, activity.getPixel(56), true);
- if (bm != null) {
- cancelPotentialWork(account, imageView);
- imageView.setImageBitmap(bm);
- imageView.setBackgroundColor(0x00000000);
- } else {
- imageView.setBackgroundColor(UIHelper.getColorForName(account.getJid().asBareJid().toString()));
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.executeOnExecutor(BitmapWorkerTask.THREAD_POOL_EXECUTOR, account);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
- }
-
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+ private static class ViewHolder {
+ private final AccountRowBinding binding;
- public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- public BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
+ private ViewHolder(AccountRowBinding binding) {
+ this.binding = binding;
}
}
- class BitmapWorkerTask extends AsyncTask<Account, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private Account account = null;
- public BitmapWorkerTask(ImageView imageView) {
- imageViewReference = new WeakReference<>(imageView);
- }
-
- @Override
- protected Bitmap doInBackground(Account... params) {
- this.account = params[0];
- return activity.avatarService().get(this.account, activity.getPixel(56), isCancelled());
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null && !isCancelled()) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
- }
+ public interface OnTglAccountState {
+ void onClickTglAccountState(Account account, boolean state);
}
}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/BackupFileAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/BackupFileAdapter.java
new file mode 100644
index 000000000..9a7f70d12
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/BackupFileAdapter.java
@@ -0,0 +1,167 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.res.Resources;
+import android.databinding.DataBindingUtil;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.text.format.DateUtils;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.AccountRowBinding;
+import de.pixart.messenger.services.AvatarService;
+import de.pixart.messenger.services.ImportBackupService;
+import de.pixart.messenger.utils.BackupFileHeader;
+import de.pixart.messenger.utils.UIHelper;
+import rocks.xmpp.addr.Jid;
+
+public class BackupFileAdapter extends RecyclerView.Adapter<BackupFileAdapter.BackupFileViewHolder> {
+
+ private OnItemClickedListener listener;
+
+ private final List<ImportBackupService.BackupFile> files = new ArrayList<>();
+
+
+ @NonNull
+ @Override
+ public BackupFileViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+ return new BackupFileViewHolder(DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.account_row, viewGroup, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull BackupFileViewHolder backupFileViewHolder, int position) {
+ final ImportBackupService.BackupFile backupFile = files.get(position);
+ final BackupFileHeader header = backupFile.getHeader();
+ backupFileViewHolder.binding.accountJid.setText(header.getJid().asBareJid().toString());
+ backupFileViewHolder.binding.accountStatus.setText(String.format("%s · %s", header.getApp(), DateUtils.formatDateTime(backupFileViewHolder.binding.getRoot().getContext(), header.getTimestamp(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_TIME)));
+ backupFileViewHolder.binding.tglAccountStatus.setVisibility(View.GONE);
+ backupFileViewHolder.binding.getRoot().setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onClick(backupFile);
+ }
+ });
+ loadAvatar(header.getJid(), backupFileViewHolder.binding.accountImage);
+ }
+
+ @Override
+ public int getItemCount() {
+ return files.size();
+ }
+
+ public void setFiles(List<ImportBackupService.BackupFile> files) {
+ this.files.clear();
+ this.files.addAll(files);
+ notifyDataSetChanged();
+ }
+
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ this.listener = listener;
+ }
+
+ static class BackupFileViewHolder extends RecyclerView.ViewHolder {
+ private final AccountRowBinding binding;
+
+ BackupFileViewHolder(AccountRowBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+
+ public interface OnItemClickedListener {
+ void onClick(ImportBackupService.BackupFile backupFile);
+ }
+
+ static class BitmapWorkerTask extends AsyncTask<Jid, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Jid jid = null;
+ private final int size;
+
+ BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ DisplayMetrics metrics = imageView.getContext().getResources().getDisplayMetrics();
+ this.size = ((int) (48 * metrics.density));
+ }
+
+ @Override
+ protected Bitmap doInBackground(Jid... params) {
+ this.jid = params[0];
+ return AvatarService.get(this.jid, size);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ private void loadAvatar(Jid jid, ImageView imageView) {
+ if (cancelPotentialWork(jid, imageView)) {
+ imageView.setBackgroundColor(UIHelper.getColorForName(jid.asBareJid().toString()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getContext().getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(jid);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+
+ private static boolean cancelPotentialWork(Jid jid, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Jid oldJid = bitmapWorkerTask.jid;
+ if (oldJid == null || jid != oldJid) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
index d61510611..27d623117 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
@@ -1,13 +1,8 @@
package de.pixart.messenger.ui.adapter;
-import android.content.Context;
import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
+import android.databinding.DataBindingUtil;
import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
@@ -16,24 +11,18 @@ import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-import java.lang.ref.WeakReference;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.ConversationListRowBinding;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.MucOptions;
-import de.pixart.messenger.entities.Transferable;
import de.pixart.messenger.ui.ConversationFragment;
import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.StyledAttributes;
-import de.pixart.messenger.ui.widget.FailedCountCustomView;
-import de.pixart.messenger.ui.widget.UnreadCountCustomView;
import de.pixart.messenger.utils.EmojiWrapper;
import de.pixart.messenger.utils.IrregularUnicodeDetector;
import de.pixart.messenger.utils.UIHelper;
@@ -44,44 +33,17 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
private XmppActivity activity;
private List<Conversation> conversations;
- private OnConversationClickListener listener;
+ private OnConversationClickListener listener;
public ConversationAdapter(XmppActivity activity, List<Conversation> conversations) {
this.activity = activity;
this.conversations = conversations;
}
- private static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Conversation oldConversation = bitmapWorkerTask.conversation;
- if (oldConversation == null || conversation != oldConversation) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
@NonNull
@Override
public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View view = inflater.inflate(R.layout.conversation_list_row, parent, false);
- return ConversationViewHolder.get(view);
+ return new ConversationViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.conversation_list_row, parent, false));
}
@Override
@@ -92,15 +54,15 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
}
CharSequence name = conversation.getName();
if (name instanceof Jid) {
- viewHolder.name.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
+ viewHolder.binding.conversationName.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
} else {
- viewHolder.name.setText(EmojiWrapper.transform(name));
+ viewHolder.binding.conversationName.setText(EmojiWrapper.transform(name));
}
if (conversation == ConversationFragment.getConversation(activity)) {
- viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary));
+ viewHolder.binding.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary));
} else {
- viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_secondary));
+ viewHolder.binding.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_secondary));
}
Message message = conversation.getLatestMessage();
@@ -109,37 +71,39 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
final boolean isRead = conversation.isRead();
final Conversation.Draft draft = isRead ? conversation.getDraft() : null;
- viewHolder.receivedStatus.setVisibility(View.GONE);
- viewHolder.readStatus.setVisibility(View.GONE);
+ viewHolder.binding.indicatorReceived.setVisibility(View.GONE);
+ viewHolder.binding.indicatorRead.setVisibility(View.GONE);
+ viewHolder.binding.unreadCount.setVisibility(View.GONE);
+ viewHolder.binding.failedCount.setVisibility(View.GONE);
if (isRead) {
- viewHolder.name.setTypeface(null, Typeface.NORMAL);
+ viewHolder.binding.conversationName.setTypeface(null, Typeface.NORMAL);
} else {
- viewHolder.name.setTypeface(null, Typeface.BOLD);
+ viewHolder.binding.conversationName.setTypeface(null, Typeface.BOLD);
}
if (unreadCount > 0) {
- viewHolder.unreadCount.setVisibility(View.VISIBLE);
- viewHolder.unreadCount.setUnreadCount(unreadCount);
+ viewHolder.binding.unreadCount.setVisibility(View.VISIBLE);
+ viewHolder.binding.unreadCount.setUnreadCount(unreadCount);
} else {
- viewHolder.unreadCount.setVisibility(View.GONE);
+ viewHolder.binding.unreadCount.setVisibility(View.GONE);
}
if (failedCount > 0) {
- viewHolder.failedCount.setVisibility(View.VISIBLE);
- viewHolder.failedCount.setFailedCount(failedCount);
+ viewHolder.binding.failedCount.setVisibility(View.VISIBLE);
+ viewHolder.binding.failedCount.setFailedCount(failedCount);
} else {
- viewHolder.failedCount.setVisibility(View.GONE);
+ viewHolder.binding.failedCount.setVisibility(View.GONE);
}
if (draft != null) {
- viewHolder.lastMessageIcon.setVisibility(View.GONE);
- viewHolder.lastMessage.setText(EmojiWrapper.transform(draft.getMessage()));
- viewHolder.sender.setText(R.string.draft);
- viewHolder.sender.setVisibility(View.VISIBLE);
- viewHolder.lastMessage.setTypeface(null, Typeface.NORMAL);
- viewHolder.sender.setTypeface(null, Typeface.ITALIC);
+ viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
+ viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(draft.getMessage()));
+ viewHolder.binding.senderName.setText(R.string.draft);
+ viewHolder.binding.senderName.setVisibility(View.VISIBLE);
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
+ viewHolder.binding.senderName.setTypeface(null, Typeface.ITALIC);
} else {
- final boolean fileAvailable = message.getTransferable() == null || message.getTransferable().getStatus() != Transferable.STATUS_DELETED;
+ final boolean fileAvailable = !message.isFileDeleted();
final boolean showPreviewText;
if (fileAvailable && (message.isFileOrImage() || message.treatAsDownloadable() || message.isGeoUri())) {
final int imageResource;
@@ -167,65 +131,66 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
break;
}
}
- viewHolder.lastMessageIcon.setImageResource(imageResource);
- viewHolder.lastMessageIcon.setVisibility(View.VISIBLE);
+ viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
+ viewHolder.binding.conversationLastmsgImg.setVisibility(View.VISIBLE);
} else {
- viewHolder.lastMessageIcon.setVisibility(View.GONE);
+ viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
showPreviewText = true;
}
- final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.lastMessage.getCurrentTextColor());
+ final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
if (showPreviewText) {
- viewHolder.lastMessage.setText(EmojiWrapper.transform(UIHelper.shorten(preview.first)));
+ viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(preview.first)));
} else {
- viewHolder.lastMessageIcon.setContentDescription(preview.first);
+ viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
}
- viewHolder.lastMessage.setVisibility(showPreviewText ? View.VISIBLE : View.GONE);
+ viewHolder.binding.conversationLastmsg.setVisibility(showPreviewText ? View.VISIBLE : View.GONE);
if (preview.second) {
if (isRead) {
- viewHolder.lastMessage.setTypeface(null, Typeface.ITALIC);
- viewHolder.sender.setTypeface(null, Typeface.NORMAL);
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.ITALIC);
+ viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
} else {
- viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
- viewHolder.sender.setTypeface(null, Typeface.BOLD);
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
+ viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
}
} else {
if (isRead) {
- viewHolder.lastMessage.setTypeface(null, Typeface.NORMAL);
- viewHolder.sender.setTypeface(null, Typeface.NORMAL);
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
+ viewHolder.binding.senderName.setTypeface(null, Typeface.NORMAL);
} else {
- viewHolder.lastMessage.setTypeface(null, Typeface.BOLD);
- viewHolder.sender.setTypeface(null, Typeface.BOLD);
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD);
+ viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
}
}
if (message.getStatus() == Message.STATUS_RECEIVED) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
- viewHolder.sender.setVisibility(View.VISIBLE);
- viewHolder.sender.setText(UIHelper.getMessageDisplayName(message).split("\\s+")[0] + ':');
+ viewHolder.binding.senderName.setVisibility(View.VISIBLE);
+ viewHolder.binding.senderName.setText(UIHelper.getMessageDisplayName(message).split("\\s+")[0] + ':');
} else {
- viewHolder.sender.setVisibility(View.GONE);
+ viewHolder.binding.senderName.setVisibility(View.GONE);
}
} else if (message.getType() != Message.TYPE_STATUS) {
- viewHolder.sender.setVisibility(View.VISIBLE);
- viewHolder.sender.setText(activity.getString(R.string.me) + ':');
+ viewHolder.binding.senderName.setVisibility(View.VISIBLE);
+ viewHolder.binding.senderName.setText(activity.getString(R.string.me) + ':');
} else {
- viewHolder.sender.setVisibility(View.GONE);
+ viewHolder.binding.senderName.setVisibility(View.GONE);
}
}
long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
if (muted_till == Long.MAX_VALUE) {
+ viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
int ic_notifications_off = activity.getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
- viewHolder.notificationIcon.setImageResource(ic_notifications_off);
+ viewHolder.binding.notificationStatus.setImageResource(ic_notifications_off);
} else if (muted_till >= System.currentTimeMillis()) {
- viewHolder.notificationIcon.setVisibility(View.VISIBLE);
+ viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
int ic_notifications_paused = activity.getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
- viewHolder.notificationIcon.setImageResource(ic_notifications_paused);
+ viewHolder.binding.notificationStatus.setImageResource(ic_notifications_paused);
} else if (conversation.alwaysNotify()) {
- viewHolder.notificationIcon.setVisibility(View.GONE);
+ viewHolder.binding.notificationStatus.setVisibility(View.GONE);
} else {
- viewHolder.notificationIcon.setVisibility(View.VISIBLE);
+ viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
int ic_notifications_none = activity.getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
- viewHolder.notificationIcon.setImageResource(ic_notifications_none);
+ viewHolder.binding.notificationStatus.setImageResource(ic_notifications_none);
}
long timestamp;
@@ -234,81 +199,83 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
} else {
timestamp = conversation.getLatestMessage().getTimeSent();
}
- viewHolder.timestamp.setText(UIHelper.readableTimeDifference(activity, timestamp));
- loadAvatar(conversation, viewHolder.avatar);
+ viewHolder.binding.conversationLastupdate.setText(UIHelper.readableTimeDifference(activity, timestamp));
+ AvatarWorkerTask.loadAvatar(conversation, viewHolder.binding.conversationImage, R.dimen.avatar_on_conversation_overview);
+ viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v, conversation));
if (conversation.getMode() == Conversation.MODE_SINGLE && ShowPresenceColoredNames()) {
switch (conversation.getContact().getPresences().getShownStatus()) {
case CHAT:
case ONLINE:
- viewHolder.name.setTextColor(ContextCompat.getColor(activity, R.color.online));
+ viewHolder.binding.conversationName.setTextColor(ContextCompat.getColor(activity, R.color.online));
break;
case AWAY:
- viewHolder.name.setTextColor(ContextCompat.getColor(activity, R.color.away));
+ viewHolder.binding.conversationName.setTextColor(ContextCompat.getColor(activity, R.color.away));
break;
case XA:
case DND:
- viewHolder.name.setTextColor(ContextCompat.getColor(activity, R.color.notavailable));
+ viewHolder.binding.conversationName.setTextColor(ContextCompat.getColor(activity, R.color.notavailable));
break;
default:
- viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
+ viewHolder.binding.conversationName.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
break;
}
} else {
- viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
+ viewHolder.binding.conversationName.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main));
}
if (activity.xmppConnectionService.indicateReceived()) {
switch (message.getMergedStatus()) {
case Message.STATUS_SEND_RECEIVED:
- viewHolder.receivedStatus.setVisibility(View.VISIBLE);
+ viewHolder.binding.indicatorReceived.setVisibility(View.VISIBLE);
break;
case Message.STATUS_SEND_DISPLAYED:
- viewHolder.receivedStatus.setVisibility(View.VISIBLE);
- viewHolder.readStatus.setVisibility(View.VISIBLE);
+ viewHolder.binding.indicatorReceived.setVisibility(View.VISIBLE);
+ viewHolder.binding.indicatorRead.setVisibility(View.VISIBLE);
break;
+ default:
+ viewHolder.binding.indicatorReceived.setVisibility(View.GONE);
+ viewHolder.binding.indicatorRead.setVisibility(View.GONE);
}
}
if (conversation.getMode() == Conversation.MODE_SINGLE) {
if (conversation.getIncomingChatState().equals(ChatState.COMPOSING)) {
- viewHolder.lastMessage.setText(R.string.is_typing);
- viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
- viewHolder.sender.setVisibility(View.GONE);
+ viewHolder.binding.conversationLastmsg.setText(R.string.is_typing);
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
+ viewHolder.binding.senderName.setVisibility(View.GONE);
}
} else {
- if (conversation.getParticipants() != null) {
- ChatState state = ChatState.COMPOSING;
- List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
- if (userWithChatStates.size() == 0) {
- state = ChatState.PAUSED;
- userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
- }
- if (state == ChatState.COMPOSING) {
- if (userWithChatStates.size() > 0) {
- if (userWithChatStates.size() == 1) {
- MucOptions.User user = userWithChatStates.get(0);
- viewHolder.lastMessage.setText(activity.getString(R.string.contact_is_typing, UIHelper.getDisplayName(user)));
- viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
- viewHolder.sender.setVisibility(View.GONE);
- } else {
- StringBuilder builder = new StringBuilder();
- for (MucOptions.User user : userWithChatStates) {
- if (builder.length() != 0) {
- builder.append(", ");
- }
- builder.append(UIHelper.getDisplayName(user));
+ ChatState state = ChatState.COMPOSING;
+ List<MucOptions.User> userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
+ if (userWithChatStates.size() == 0) {
+ state = ChatState.PAUSED;
+ userWithChatStates = conversation.getMucOptions().getUsersWithChatState(state, 5);
+ }
+ if (state == ChatState.COMPOSING) {
+ if (userWithChatStates.size() > 0) {
+ if (userWithChatStates.size() == 1) {
+ MucOptions.User user = userWithChatStates.get(0);
+ viewHolder.binding.conversationLastmsg.setText(activity.getString(R.string.contact_is_typing, UIHelper.getDisplayName(user)));
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
+ viewHolder.binding.senderName.setVisibility(View.GONE);
+ } else {
+ StringBuilder builder = new StringBuilder();
+ for (MucOptions.User user : userWithChatStates) {
+ if (builder.length() != 0) {
+ builder.append(", ");
}
- viewHolder.lastMessage.setText(activity.getString(R.string.contacts_are_typing, builder.toString()));
- viewHolder.lastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
- viewHolder.sender.setVisibility(View.GONE);
+ builder.append(UIHelper.getDisplayName(user));
}
+ viewHolder.binding.conversationLastmsg.setText(activity.getString(R.string.contacts_are_typing, builder.toString()));
+ viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.BOLD_ITALIC);
+ viewHolder.binding.senderName.setVisibility(View.GONE);
}
}
}
}
- viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v, conversation));
}
+
@Override
public int getItemCount() {
return conversations.size();
@@ -318,27 +285,6 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
this.listener = listener;
}
- private void loadAvatar(Conversation conversation, ImageView imageView) {
- if (cancelPotentialWork(conversation, imageView)) {
- final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
- if (bm != null) {
- cancelPotentialWork(conversation, imageView);
- imageView.setImageBitmap(bm);
- imageView.setBackgroundColor(0x00000000);
- } else {
- imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName().toString()));
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.executeOnExecutor(BitmapWorkerTask.THREAD_POOL_EXECUTOR, conversation);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
- }
-
public void insert(Conversation c, int position) {
conversations.add(position, c);
notifyDataSetChanged();
@@ -349,94 +295,24 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
notifyItemRemoved(position);
}
- public static class ConversationViewHolder extends RecyclerView.ViewHolder {
- private TextView name;
- private TextView lastMessage;
- private ImageView lastMessageIcon;
- private TextView timestamp;
- private TextView sender;
- private ImageView notificationIcon;
- private UnreadCountCustomView unreadCount;
- private FailedCountCustomView failedCount;
- private ImageView receivedStatus;
- private ImageView readStatus;
- private ImageView avatar;
- private FrameLayout frame;
-
- private ConversationViewHolder(View view) {
- super(view);
- }
-
- public static ConversationViewHolder get(View layout) {
- ConversationViewHolder conversationViewHolder = (ConversationViewHolder) layout.getTag();
- if (conversationViewHolder == null) {
- conversationViewHolder = new ConversationViewHolder(layout);
- conversationViewHolder.frame = layout.findViewById(R.id.frame);
- conversationViewHolder.name = layout.findViewById(R.id.conversation_name);
- conversationViewHolder.lastMessage = layout.findViewById(R.id.conversation_lastmsg);
- conversationViewHolder.lastMessageIcon = layout.findViewById(R.id.conversation_lastmsg_img);
- conversationViewHolder.timestamp = layout.findViewById(R.id.conversation_lastupdate);
- conversationViewHolder.avatar = layout.findViewById(R.id.conversation_image);
- conversationViewHolder.sender = layout.findViewById(R.id.sender_name);
- conversationViewHolder.notificationIcon = layout.findViewById(R.id.notification_status);
- conversationViewHolder.unreadCount = layout.findViewById(R.id.conversation_unread);
- conversationViewHolder.failedCount = layout.findViewById(R.id.conversation_failed);
- conversationViewHolder.receivedStatus = layout.findViewById(R.id.indicator_received);
- conversationViewHolder.readStatus = layout.findViewById(R.id.indicator_read);
- layout.setTag(conversationViewHolder);
- }
- return conversationViewHolder;
- }
- }
-
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
- public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- public BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
+ public interface OnConversationClickListener {
+ void onConversationClick(View view, Conversation conversation);
}
- class BitmapWorkerTask extends AsyncTask<Conversation, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private Conversation conversation = null;
-
- public BitmapWorkerTask(ImageView imageView) {
- imageViewReference = new WeakReference<>(imageView);
- }
+ static class ConversationViewHolder extends RecyclerView.ViewHolder {
+ private final ConversationListRowBinding binding;
- @Override
- protected Bitmap doInBackground(Conversation... params) {
- this.conversation = params[0];
- return activity.avatarService().get(this.conversation, activity.getPixel(56), isCancelled());
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
+ private ConversationViewHolder(ConversationListRowBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
}
}
- public boolean ShowPresenceColoredNames() {
+ private boolean ShowPresenceColoredNames() {
return getPreferences().getBoolean("presence_colored_names", activity.getResources().getBoolean(R.bool.presence_colored_names));
}
protected SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
}
-
- public interface OnConversationClickListener {
- void onConversationClick(View view, Conversation conversation);
- }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
index e99353f13..93a822ea3 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
@@ -1,12 +1,7 @@
package de.pixart.messenger.ui.adapter;
import android.content.SharedPreferences;
-import android.content.res.Resources;
import android.databinding.DataBindingUtil;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
@@ -17,19 +12,17 @@ import android.widget.TextView;
import com.wefika.flowlayout.FlowLayout;
-import java.lang.ref.WeakReference;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import de.pixart.messenger.R;
import de.pixart.messenger.databinding.ContactBinding;
import de.pixart.messenger.entities.ListItem;
import de.pixart.messenger.ui.SettingsActivity;
import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.StyledAttributes;
import de.pixart.messenger.utils.EmojiWrapper;
import de.pixart.messenger.utils.IrregularUnicodeDetector;
-import de.pixart.messenger.utils.UIHelper;
import rocks.xmpp.addr.Jid;
public class ListItemAdapter extends ArrayAdapter<ListItem> {
@@ -74,9 +67,8 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
} else {
viewHolder = (ViewHolder) view.getTag();
}
-
+ view.setBackground(StyledAttributes.getDrawable(view.getContext(), R.attr.list_item_background));
List<ListItem.Tag> tags = item.getTags(activity);
-
if (tags.size() == 0 || !this.showDynamicTags) {
viewHolder.tags.setVisibility(View.GONE);
} else {
@@ -121,7 +113,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
viewHolder.avatar.setAlpha(ACTIVE_ALPHA);
viewHolder.tags.setAlpha(ACTIVE_ALPHA);
}
- loadAvatar(item, viewHolder.avatar);
+ AvatarWorkerTask.loadAvatar(item, viewHolder.avatar, R.dimen.avatar);
return view;
}
@@ -133,92 +125,6 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
void onTagClicked(String tag);
}
- class BitmapWorkerTask extends AsyncTask<ListItem, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private ListItem item = null;
-
- public BitmapWorkerTask(ImageView imageView) {
- imageViewReference = new WeakReference<>(imageView);
- }
-
- @Override
- protected Bitmap doInBackground(ListItem... params) {
- this.item = params[0];
- return activity.avatarService().get(this.item, activity.getPixel(56), isCancelled());
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null && !isCancelled()) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
- }
- }
-
- public void loadAvatar(ListItem item, ImageView imageView) {
- if (cancelPotentialWork(item, imageView)) {
- final Bitmap bm = activity.avatarService().get(item, activity.getPixel(48), true);
- if (bm != null) {
- cancelPotentialWork(item, imageView);
- imageView.setImageBitmap(bm);
- imageView.setBackgroundColor(0x00000000);
- } else {
- String seed = item.getJid() != null ? item.getJid().asBareJid().toString() : item.getDisplayName();
- imageView.setBackgroundColor(UIHelper.getColorForName(seed));
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.executeOnExecutor(BitmapWorkerTask.THREAD_POOL_EXECUTOR, item);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
- }
-
- public static boolean cancelPotentialWork(ListItem item, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final ListItem oldItem = bitmapWorkerTask.item;
- if (oldItem == null || item != oldItem) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
- public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- public BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
- }
-
private static class ViewHolder {
private TextView name;
private TextView jid;
@@ -239,8 +145,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
}
}
-
- public boolean ShowPresenceColoredNames() {
+ private boolean ShowPresenceColoredNames() {
return getPreferences().getBoolean("presence_colored_names", activity.getResources().getBoolean(R.bool.presence_colored_names));
}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java
index 8b8c05d05..e8968bbac 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java
@@ -89,7 +89,7 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
return attr;
}
- public static void renderPreview(Context context, Attachment attachment, ImageView imageView) {
+ static void renderPreview(Context context, Attachment attachment, ImageView imageView) {
imageView.setBackgroundColor(StyledAttributes.getColor(context, R.attr.color_background_tertiary));
imageView.setImageAlpha(Math.round(StyledAttributes.getFloat(context, R.attr.icon_alpha) * 255));
imageView.setImageDrawable(StyledAttributes.getDrawable(context, getImageAttr(attachment)));
@@ -138,7 +138,7 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
cancelPotentialWork(attachment, holder.binding.media);
renderPreview(activity, attachment, holder.binding.media);
}
- holder.binding.media.setOnClickListener(v -> ViewUtil.view(activity, attachment));
+ holder.binding.getRoot().setOnClickListener(v -> ViewUtil.view(activity, attachment));
}
public void setAttachments(List<Attachment> attachments) {
@@ -161,7 +161,7 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
} else {
imageView.setBackgroundColor(0xff333333);
imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final BitmapWorkerTask task = new BitmapWorkerTask(mediaSize, imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
imageView.setImageDrawable(asyncDrawable);
try {
@@ -200,17 +200,23 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
}
}
- class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
+ private static class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private Attachment attachment = null;
+ private final int mediaSize;
- BitmapWorkerTask(ImageView imageView) {
+ BitmapWorkerTask(int mediaSize, ImageView imageView) {
+ this.mediaSize = mediaSize;
imageViewReference = new WeakReference<>(imageView);
}
@Override
protected Bitmap doInBackground(Attachment... params) {
this.attachment = params[0];
+ final XmppActivity activity = XmppActivity.find(imageViewReference);
+ if (activity == null) {
+ return null;
+ }
return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, mediaSize, false);
}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java
index 64cb63439..f27158927 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java
@@ -1,6 +1,5 @@
package de.pixart.messenger.ui.adapter;
-import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.databinding.DataBindingUtil;
@@ -158,7 +157,7 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
}
}
- class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
+ private static class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private Attachment attachment = null;
@@ -168,14 +167,12 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
@Override
protected Bitmap doInBackground(Attachment... params) {
- Activity activity = conversationFragment.getActivity();
- if (activity instanceof XmppActivity) {
- final XmppActivity xmppActivity = (XmppActivity) activity;
- this.attachment = params[0];
- return xmppActivity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, Math.round(xmppActivity.getResources().getDimension(R.dimen.media_preview_size)), false);
- } else {
+ this.attachment = params[0];
+ final XmppActivity activity = XmppActivity.find(imageViewReference);
+ if (activity == null) {
return null;
}
+ return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, Math.round(activity.getResources().getDimension(R.dimen.media_preview_size)), false);
}
@Override
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
index 1c53f1b5d..7e3d26728 100644
--- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
@@ -5,16 +5,9 @@ import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-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;
import android.net.Uri;
-import android.os.AsyncTask;
import android.preference.PreferenceManager;
-import android.support.annotation.ColorInt;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.Spannable;
@@ -45,12 +38,9 @@ import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
-import java.io.IOException;
import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -74,6 +64,7 @@ import de.pixart.messenger.ui.ConversationsActivity;
import de.pixart.messenger.ui.XmppActivity;
import de.pixart.messenger.ui.text.DividerSpan;
import de.pixart.messenger.ui.text.QuoteSpan;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
import de.pixart.messenger.ui.util.MyLinkify;
import de.pixart.messenger.ui.util.ViewUtil;
import de.pixart.messenger.ui.widget.ClickableMovementMethod;
@@ -86,7 +77,6 @@ import de.pixart.messenger.utils.GeoHelper;
import de.pixart.messenger.utils.StylingHelper;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xmpp.mam.MamReference;
-import pl.droidsonroids.gif.GifDrawable;
import pl.droidsonroids.gif.GifImageView;
import static de.pixart.messenger.ui.SettingsActivity.PLAY_GIF_INSIDE;
@@ -121,31 +111,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
updatePreferences();
}
- public static boolean cancelPotentialWork(Message message, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Message oldMessage = bitmapWorkerTask.message;
- if (oldMessage == null || message != oldMessage) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
private static void resetClickListener(View... views) {
for (View view : views) {
view.setOnClickListener(null);
@@ -246,13 +211,13 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
final Transferable transferable = message.getTransferable();
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
- if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || transferable != null) {
+ if (message.isFileOrImage() || transferable != null) {
FileParams params = message.getFileParams();
if (params.size > (1 * 1024 * 1024)) {
filesize = Math.round(params.size * 1f / (1024 * 1024)) + " MiB";
} else if (params.size >= (1 * 1024)) {
filesize = Math.round(params.size * 1f / 1024) + " KiB";
- } else if (params.size > 0){
+ } else if (params.size > 0) {
filesize = params.size + " B";
}
if (transferable != null && transferable.getStatus() == Transferable.STATUS_FAILED) {
@@ -605,7 +570,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
if (highlightedTerm != null) {
StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody));
}
- MyLinkify.addLinks(body,true);
+ MyLinkify.addLinks(body, true);
viewHolder.messageBody.setAutoLinkMask(0);
viewHolder.messageBody.setText(EmojiWrapper.transform(body));
viewHolder.messageBody.setTextIsSelectable(true);
@@ -922,10 +887,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
boolean showAvatar;
if (conversation.getMode() == Conversation.MODE_SINGLE) {
showAvatar = true;
- loadAvatar(message,viewHolder.contact_picture,activity.getPixel(32));
+ AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message);
} else if (message.getCounterpart() != null || message.getTrueCounterpart() != null || (message.getCounterparts() != null && message.getCounterparts().size() > 0)) {
showAvatar = true;
- loadAvatar(message,viewHolder.contact_picture,activity.getPixel(32));
+ AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar_on_status_message);
} else {
showAvatar = false;
}
@@ -938,7 +903,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
return view;
} else {
- loadAvatar(message, viewHolder.contact_picture, activity.getPixel(48));
+ AvatarWorkerTask.loadAvatar(message, viewHolder.contact_picture, R.dimen.avatar);
}
resetClickListener(viewHolder.message_box, viewHolder.messageBody);
@@ -962,10 +927,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
});
final Transferable transferable = message.getTransferable();
- if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) {
- if (transferable.getStatus() == Transferable.STATUS_OFFER) {
+ if (message.isFileDeleted() || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING)) {
+ if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER) {
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
- } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
+ } else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
} else {
displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first, darkBackground);
@@ -1035,7 +1000,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
viewHolder.encryption.setVisibility(View.GONE);
viewHolder.encryption.setTextColor(this.getMessageTextColor(darkBackground, false));
} else {
- viewHolder.message_box.setBackgroundResource(darkBackground ? R.drawable.message_bubble_received_warning_dark: R.drawable.message_bubble_received_warning);
+ viewHolder.message_box.setBackgroundResource(darkBackground ? R.drawable.message_bubble_received_warning_dark : R.drawable.message_bubble_received_warning);
viewHolder.encryption.setVisibility(View.VISIBLE);
viewHolder.encryption.setTextColor(this.getWarningTextColor(darkBackground));
if (omemoEncryption && !message.isTrusted()) {
@@ -1119,20 +1084,12 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_OPEN_MESSAGE);
return;
}
- DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
- if (!file.exists()) {
- Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
- return;
- }
- String mime = file.getMimeType();
- if (mime == null) {
- mime = "*/*";
- }
- ViewUtil.view(activity, file, mime);
+ final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ ViewUtil.view(activity, file);
}
- public void showLocation(Message message) {
- for (Intent intent : GeoHelper.createGeoIntentsFromMessage(message, this.getContext())) {
+ private void showLocation(Message message) {
+ for (Intent intent : GeoHelper.createGeoIntentsFromMessage(this.getContext(), message)) {
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
getContext().startActivity(intent);
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
@@ -1147,33 +1104,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
this.mIndicateReceived = p.getBoolean("indicate_received", activity.getResources().getBoolean(R.bool.indicate_received));
}
- public void loadAvatar(Message message, ImageView imageView, int size) {
- if (cancelPotentialWork(message, imageView)) {
- final Bitmap bm = activity.avatarService().get(message, size, true);
- if (bm != null) {
- cancelPotentialWork(message, imageView);
- imageView.setImageBitmap(bm);
- imageView.setBackgroundColor(Color.TRANSPARENT);
- } else {
- @ColorInt int bg;
- if (message.getType() == Message.TYPE_STATUS && message.getCounterparts() != null && message.getCounterparts().size() > 1) {
- bg = Color.TRANSPARENT;
- } else {
- bg = UIHelper.getColorForName(UIHelper.getMessageDisplayName(message));
- }
- imageView.setBackgroundColor(bg);
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView, size);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.executeOnExecutor(BitmapWorkerTask.THREAD_POOL_EXECUTOR, message);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
- }
-
public void setHighlightedTerm(List<String> terms) {
this.highlightedTerm = terms == null ? null : StylingHelper.filterHighlightedWords(terms);
}
@@ -1210,19 +1140,6 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
protected TextView encryption;
}
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
-
- public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- public BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
- }
-
private class MessageBodyActionModeCallback implements ActionMode.Callback {
private final TextView textView;
@@ -1265,34 +1182,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
@Override
- public void onDestroyActionMode(ActionMode mode) {}
- }
-
- class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
- private final WeakReference<ImageView> imageViewReference;
- private final int size;
- private Message message = null;
-
- public BitmapWorkerTask(ImageView imageView, int size) {
- imageViewReference = new WeakReference<>(imageView);
- this.size = size;
- }
-
- @Override
- protected Bitmap doInBackground(Message... params) {
- this.message = params[0];
- return activity.avatarService().get(this.message, size, isCancelled());
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null && !isCancelled()) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
+ public void onDestroyActionMode(ActionMode mode) {
}
}
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/UserAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/UserAdapter.java
new file mode 100644
index 000000000..e615f2f42
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/UserAdapter.java
@@ -0,0 +1,136 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.app.PendingIntent;
+import android.content.IntentSender;
+import android.databinding.DataBindingUtil;
+import android.support.annotation.NonNull;
+import android.support.v7.recyclerview.extensions.ListAdapter;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.widget.RecyclerView;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.PgpEngine;
+import de.pixart.messenger.databinding.ContactBinding;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.ConferenceDetailsActivity;
+import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
+import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper;
+import rocks.xmpp.addr.Jid;
+
+public class UserAdapter extends ListAdapter<MucOptions.User, UserAdapter.ViewHolder> implements View.OnCreateContextMenuListener {
+
+ static final DiffUtil.ItemCallback<MucOptions.User> DIFF = new DiffUtil.ItemCallback<MucOptions.User>() {
+ @Override
+ public boolean areItemsTheSame(@NonNull MucOptions.User a, @NonNull MucOptions.User b) {
+ final Jid fullA = a.getFullJid();
+ final Jid fullB = b.getFullJid();
+ final Jid realA = a.getRealJid();
+ final Jid realB = b.getRealJid();
+ if (fullA != null && fullB != null) {
+ return fullA.equals(fullB);
+ } else if (realA != null && realB != null) {
+ return realA.equals(realB);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull MucOptions.User a, @NonNull MucOptions.User b) {
+ return a.equals(b);
+ }
+ };
+ private final boolean advancedMode;
+ private MucOptions.User selectedUser = null;
+
+ public UserAdapter(final boolean advancedMode) {
+ super(DIFF);
+ this.advancedMode = advancedMode;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
+ return new ViewHolder(DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.contact, viewGroup, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
+ final MucOptions.User user = getItem(position);
+ AvatarWorkerTask.loadAvatar(user, viewHolder.binding.contactPhoto, R.dimen.avatar);
+ viewHolder.binding.getRoot().setOnClickListener(v -> {
+ final XmppActivity activity = XmppActivity.find(v);
+ if (activity != null) {
+ activity.highlightInMuc(user.getConversation(), user.getName());
+ }
+ });
+ viewHolder.binding.getRoot().setTag(user);
+ viewHolder.binding.getRoot().setOnCreateContextMenuListener(this);
+ viewHolder.binding.getRoot().setOnLongClickListener(v -> {
+ selectedUser = user;
+ return false;
+ });
+ final String name = user.getName();
+ final Contact contact = user.getContact();
+ if (contact != null) {
+ viewHolder.binding.contactDisplayName.setText(contact.getDisplayName());
+ if (name != null) {
+ viewHolder.binding.contactJid.setText(String.format("%s \u2022 %s", name, ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode)));
+ } else {
+ viewHolder.binding.contactJid.setText(ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode));
+ }
+ } else {
+ viewHolder.binding.contactDisplayName.setText(name == null ? "" : name);
+ viewHolder.binding.contactJid.setText(ConferenceDetailsActivity.getStatus(viewHolder.binding.getRoot().getContext(), user, advancedMode));
+ }
+ if (advancedMode && user.getPgpKeyId() != 0) {
+ viewHolder.binding.key.setVisibility(View.VISIBLE);
+ viewHolder.binding.key.setOnClickListener(v -> {
+ final XmppActivity activity = XmppActivity.find(v);
+ final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
+ final PgpEngine pgpEngine = service == null ? null : service.getPgpEngine();
+ if (pgpEngine != null) {
+ PendingIntent intent = pgpEngine.getIntentForKey(user.getPgpKeyId());
+ if (intent != null) {
+ try {
+ activity.startIntentSenderForResult(intent.getIntentSender(), 0, null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException ignored) {
+
+ }
+ }
+ }
+ });
+ viewHolder.binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
+ }
+
+
+ }
+
+ public MucOptions.User getSelectedUser() {
+ return selectedUser;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ MucDetailsContextMenuHelper.onCreateContextMenu(menu, v);
+ }
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+
+ private final ContactBinding binding;
+
+ private ViewHolder(ContactBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/UserPreviewAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/UserPreviewAdapter.java
new file mode 100644
index 000000000..0a0e9f587
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/UserPreviewAdapter.java
@@ -0,0 +1,70 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.databinding.DataBindingUtil;
+import android.support.annotation.NonNull;
+import android.support.v7.recyclerview.extensions.ListAdapter;
+import android.support.v7.widget.RecyclerView;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.databinding.UserPreviewBinding;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.ui.util.AvatarWorkerTask;
+import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper;
+
+public class UserPreviewAdapter extends ListAdapter<MucOptions.User, UserPreviewAdapter.ViewHolder> implements View.OnCreateContextMenuListener {
+
+ private MucOptions.User selectedUser = null;
+
+ public UserPreviewAdapter() {
+ super(UserAdapter.DIFF);
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
+ return new ViewHolder(DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.user_preview, viewGroup, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
+ final MucOptions.User user = getItem(position);
+ AvatarWorkerTask.loadAvatar(user, viewHolder.binding.avatar, R.dimen.media_size);
+ viewHolder.binding.getRoot().setOnClickListener(v -> {
+ final XmppActivity activity = XmppActivity.find(v);
+ if (activity != null) {
+ activity.highlightInMuc(user.getConversation(), user.getName());
+ }
+ });
+ viewHolder.binding.getRoot().setOnCreateContextMenuListener(this);
+ viewHolder.binding.getRoot().setTag(user);
+ viewHolder.binding.getRoot().setOnLongClickListener(v -> {
+ selectedUser = user;
+ return false;
+ });
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ MucDetailsContextMenuHelper.onCreateContextMenu(menu, v);
+ }
+
+ public MucOptions.User getSelectedUser() {
+ return selectedUser;
+ }
+
+
+ class ViewHolder extends RecyclerView.ViewHolder {
+
+ private final UserPreviewBinding binding;
+
+ private ViewHolder(UserPreviewBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/AvatarWorkerTask.java b/src/main/java/de/pixart/messenger/ui/util/AvatarWorkerTask.java
new file mode 100644
index 000000000..afddb7e8b
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/util/AvatarWorkerTask.java
@@ -0,0 +1,111 @@
+package de.pixart.messenger.ui.util;
+
+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.support.annotation.DimenRes;
+import android.widget.ImageView;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.services.AvatarService;
+import de.pixart.messenger.ui.XmppActivity;
+
+public class AvatarWorkerTask extends AsyncTask<AvatarService.Avatarable, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private AvatarService.Avatarable avatarable = null;
+ private @DimenRes
+ int size;
+
+ public AvatarWorkerTask(ImageView imageView, @DimenRes int size) {
+ imageViewReference = new WeakReference<>(imageView);
+ this.size = size;
+ }
+
+ @Override
+ protected Bitmap doInBackground(AvatarService.Avatarable... params) {
+ this.avatarable = params[0];
+ final XmppActivity activity = XmppActivity.find(imageViewReference);
+ if (activity == null) {
+ return null;
+ }
+ return activity.avatarService().get(avatarable, (int) activity.getResources().getDimension(size), isCancelled());
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(AvatarService.Avatarable avatarable, ImageView imageView) {
+ final AvatarWorkerTask workerTask = getBitmapWorkerTask(imageView);
+
+ if (workerTask != null) {
+ final AvatarService.Avatarable old = workerTask.avatarable;
+ if (old == null || avatarable != old) {
+ workerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static AvatarWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getAvatarWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ public static void loadAvatar(final AvatarService.Avatarable avatarable, final ImageView imageView, final @DimenRes int size) {
+ if (cancelPotentialWork(avatarable, imageView)) {
+ final XmppActivity activity = XmppActivity.find(imageView);
+ if (activity == null) {
+ return;
+ }
+ final Bitmap bm = activity.avatarService().get(avatarable, (int) activity.getResources().getDimension(size), true);
+ if (bm != null) {
+ cancelPotentialWork(avatarable, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(avatarable.getAvatarBackgroundColor());
+ imageView.setImageDrawable(null);
+ final AvatarWorkerTask task = new AvatarWorkerTask(imageView, size);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(avatarable);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<AvatarWorkerTask> avatarWorkerTaskReference;
+
+ AsyncDrawable(Resources res, Bitmap bitmap, AvatarWorkerTask workerTask) {
+ super(res, bitmap);
+ avatarWorkerTaskReference = new WeakReference<>(workerTask);
+ }
+
+ AvatarWorkerTask getAvatarWorkerTask() {
+ return avatarWorkerTaskReference.get();
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java
index dd69b2337..07a03fbab 100644
--- a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java
+++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java
@@ -38,7 +38,6 @@ import android.view.MenuItem;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.OmemoSetting;
-import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Conversational;
import de.pixart.messenger.entities.Message;
@@ -101,11 +100,17 @@ public class ConversationMenuConfigurator {
final MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp);
final MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl);
+ final int next = conversation.getNextEncryption();
+
boolean visible;
if (OmemoSetting.isAlways()) {
visible = false;
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
- visible = (Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices();
+ if (next == Message.ENCRYPTION_NONE && !conversation.isPrivateAndNonAnonymous() && !conversation.getBooleanAttribute(Conversation.ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false)) {
+ visible = false;
+ } else {
+ visible = (Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices();
+ }
} else {
visible = Config.multipleEncryptionChoices();
}
@@ -127,10 +132,6 @@ public class ConversationMenuConfigurator {
pgp.setVisible(Config.supportOpenPgp());
none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI);
axolotl.setVisible(Config.supportOmemo());
- final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
- if (axolotlService == null || !axolotlService.isConversationAxolotlCapable(conversation)) {
- axolotl.setEnabled(false);
- }
switch (conversation.getNextEncryption()) {
case Message.ENCRYPTION_NONE:
none.setChecked(true);
diff --git a/src/main/java/de/pixart/messenger/ui/util/GridManager.java b/src/main/java/de/pixart/messenger/ui/util/GridManager.java
index 0b445f3ec..9cad626ff 100644
--- a/src/main/java/de/pixart/messenger/ui/util/GridManager.java
+++ b/src/main/java/de/pixart/messenger/ui/util/GridManager.java
@@ -29,7 +29,8 @@ public class GridManager {
}
final ColumnInfo columnInfo = calculateColumnCount(context, recyclerView.getMeasuredWidth(), desiredSize);
Log.d(Config.LOGTAG, "final count " + columnInfo.count);
- if (recyclerView.getAdapter().getItemCount() != 0) {
+ final RecyclerView.Adapter adapter = recyclerView.getAdapter();
+ if (adapter != null && adapter.getItemCount() != 0) {
Log.e(Config.LOGTAG, "adapter already has items; just go with it now");
return;
}
diff --git a/src/main/java/de/pixart/messenger/ui/util/MucConfiguration.java b/src/main/java/de/pixart/messenger/ui/util/MucConfiguration.java
new file mode 100644
index 000000000..4630a5a93
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/util/MucConfiguration.java
@@ -0,0 +1,111 @@
+package de.pixart.messenger.ui.util;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.StringRes;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.MucOptions;
+
+public class MucConfiguration {
+
+ public final @StringRes
+ int title;
+ public final String[] names;
+ public final boolean[] values;
+ public final Option[] options;
+
+ private MucConfiguration(@StringRes int title, String[] names, boolean[] values, Option[] options) {
+ this.title = title;
+ this.names = names;
+ this.values = values;
+ this.options = options;
+ }
+
+ public static MucConfiguration get(Context context, MucOptions mucOptions) {
+ if (mucOptions.isPrivateAndNonAnonymous()) {
+ String[] names = new String[]{
+ context.getString(R.string.allow_participants_to_edit_subject),
+ context.getString(R.string.allow_participants_to_invite_others)
+ };
+ boolean[] values = new boolean[]{
+ mucOptions.participantsCanChangeSubject(),
+ mucOptions.allowInvites()
+ };
+ final Option[] options = new Option[]{
+ new Option("muc#roomconfig_changesubject"),
+ new Option("muc#roomconfig_allowinvites")
+ };
+ return new MucConfiguration(R.string.conference_options, names, values, options);
+ } else {
+ String[] names = new String[]{
+ context.getString(R.string.non_anonymous),
+ context.getString(R.string.allow_participants_to_edit_subject),
+ };
+ boolean[] values = new boolean[]{
+ mucOptions.nonanonymous(),
+ mucOptions.participantsCanChangeSubject()
+ };
+ final Option[] options = new Option[]{
+ new Option("muc#roomconfig_whois", "anyone", "moderators"),
+ new Option("muc#roomconfig_changesubject")
+ };
+ return new MucConfiguration(R.string.channel_options, names, values, options);
+ }
+ }
+
+ public static String describe(final Context context, final MucOptions mucOptions) {
+ final StringBuilder builder = new StringBuilder();
+ if (mucOptions.isPrivateAndNonAnonymous()) {
+ if (mucOptions.participantsCanChangeSubject()) {
+ builder.append(context.getString(R.string.anyone_can_edit_subject));
+ } else {
+ builder.append(context.getString(R.string.owners_can_edit_subject));
+ }
+ builder.append(' ');
+ if (mucOptions.allowInvites()) {
+ builder.append(context.getString(R.string.anyone_can_invite_others));
+ } else {
+ builder.append(context.getString(R.string.owners_can_invite_others));
+ }
+ } else {
+ if (mucOptions.nonanonymous()) {
+ builder.append(context.getString(R.string.jabber_ids_are_visible_to_anyone));
+ } else {
+ builder.append(context.getString(R.string.jabber_ids_are_visible_to_admins));
+ }
+ builder.append(' ');
+ if (mucOptions.participantsCanChangeSubject()) {
+ builder.append(context.getString(R.string.anyone_can_edit_subject));
+ } else {
+ builder.append(context.getString(R.string.admins_can_edit_subject));
+ }
+ }
+ return builder.toString();
+ }
+
+ public Bundle toBundle(boolean[] values) {
+ Bundle bundle = new Bundle();
+ for (int i = 0; i < values.length; ++i) {
+ final Option option = options[i];
+ bundle.putString(option.name, option.values[values[i] ? 0 : 1]);
+ }
+ return bundle;
+ }
+
+ private static class Option {
+ public final String name;
+ public final String[] values;
+
+ private Option(String name) {
+ this.name = name;
+ this.values = new String[]{"1", "0"};
+ }
+
+ private Option(String name, String on, String off) {
+ this.name = name;
+ this.values = new String[]{on, off};
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java
index f4777e8f3..c5c69577d 100644
--- a/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java
+++ b/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java
@@ -6,8 +6,10 @@ import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.TypefaceSpan;
+import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.View;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
@@ -19,13 +21,36 @@ import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.ConferenceDetailsActivity;
import de.pixart.messenger.ui.ConversationFragment;
import de.pixart.messenger.ui.ConversationsActivity;
+import de.pixart.messenger.ui.MucUsersActivity;
import de.pixart.messenger.ui.XmppActivity;
import rocks.xmpp.addr.Jid;
public final class MucDetailsContextMenuHelper {
+
+ public static void onCreateContextMenu(ContextMenu menu, View v) {
+ final XmppActivity activity = XmppActivity.find(v);
+ final Object tag = v.getTag();
+ if (tag instanceof MucOptions.User && activity != null) {
+ activity.getMenuInflater().inflate(R.menu.muc_details_context, menu);
+ final MucOptions.User user = (MucOptions.User) tag;
+ String name;
+ final Contact contact = user.getContact();
+ if (contact != null && contact.showInContactList()) {
+ name = contact.getDisplayName();
+ } else if (user.getRealJid() != null) {
+ name = user.getRealJid().asBareJid().toString();
+ } else {
+ name = user.getName();
+ }
+ menu.setHeaderTitle(name);
+ MucDetailsContextMenuHelper.configureMucDetailsContextMenu(activity, menu, user.getConversation(), user);
+ }
+ }
+
public static void configureMucDetailsContextMenu(Activity activity, Menu menu, Conversation conversation, User user) {
final boolean advancedMode = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("advanced_muc_mode", false);
final MucOptions mucOptions = conversation.getMucOptions();
+ final boolean isGroupChat = mucOptions.isPrivateAndNonAnonymous();
MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message);
if (user != null && user.getRealJid() != null) {
MenuItem showContactDetails = menu.findItem(R.id.action_contact_details);
@@ -34,8 +59,13 @@ public final class MucDetailsContextMenuHelper {
MenuItem removeMembership = menu.findItem(R.id.remove_membership);
MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges);
MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges);
+ MenuItem giveOwnerPrivileges = menu.findItem(R.id.give_owner_privileges);
+ MenuItem removeOwnerPrivileges = menu.findItem(R.id.revoke_owner_privileges);
MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room);
+ MenuItem managePermisisons = menu.findItem(R.id.manage_permissions);
+ removeFromRoom.setTitle(isGroupChat ? R.string.remove_from_room : R.string.remove_from_channel);
MenuItem banFromConference = menu.findItem(R.id.ban_from_conference);
+ banFromConference.setTitle(isGroupChat ? R.string.ban_from_conference : R.string.ban_from_channel);
MenuItem invite = menu.findItem(R.id.invite);
MenuItem highlightInMuc = menu.findItem(R.id.highlight_in_muc);
startConversation.setVisible(true);
@@ -44,7 +74,7 @@ public final class MucDetailsContextMenuHelper {
if (contact != null && contact.showInRoster()) {
showContactDetails.setVisible(!contact.isSelf());
}
- if (activity instanceof ConferenceDetailsActivity && user.getRole() == MucOptions.Role.NONE) {
+ if ((activity instanceof ConferenceDetailsActivity || activity instanceof MucUsersActivity) && user.getRole() == MucOptions.Role.NONE) {
invite.setVisible(true);
}
if (activity instanceof ConversationsActivity) {
@@ -52,12 +82,13 @@ public final class MucDetailsContextMenuHelper {
} else if (activity instanceof ConferenceDetailsActivity) {
highlightInMuc.setVisible(true);
}
- if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) &&
- self.getAffiliation().outranks(user.getAffiliation())) {
+ boolean managePermissionsVisible = false;
+ if ((self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) || self.getAffiliation() == MucOptions.Affiliation.OWNER) {
if (advancedMode) {
- if (user.getAffiliation() == MucOptions.Affiliation.NONE) {
+ managePermissionsVisible = true;
+ if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
giveMembership.setVisible(true);
- } else {
+ } else if (user.getAffiliation() == MucOptions.Affiliation.MEMBER) {
removeMembership.setVisible(true);
}
if (!Config.DISABLE_BAN) {
@@ -65,36 +96,55 @@ public final class MucDetailsContextMenuHelper {
}
} else {
if (!Config.DISABLE_BAN || conversation.getMucOptions().membersOnly()) {
+ managePermissionsVisible = true;
removeFromRoom.setVisible(true);
}
}
- if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) {
- giveAdminPrivileges.setVisible(true);
- } else {
- removeAdminPrivileges.setVisible(true);
+ }
+ if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
+ if (isGroupChat || advancedMode || user.getAffiliation() == MucOptions.Affiliation.OWNER) {
+ managePermissionsVisible = true;
+ if (!user.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
+ giveOwnerPrivileges.setVisible(true);
+ } else if (user.getAffiliation() == MucOptions.Affiliation.OWNER) {
+ removeOwnerPrivileges.setVisible(true);
+ }
+ }
+ if (!isGroupChat || advancedMode || user.getAffiliation() == MucOptions.Affiliation.ADMIN) {
+ managePermissionsVisible = true;
+ if (!user.getAffiliation().ranks(MucOptions.Affiliation.ADMIN)) {
+ giveAdminPrivileges.setVisible(true);
+ } else if (user.getAffiliation() == MucOptions.Affiliation.ADMIN) {
+ removeAdminPrivileges.setVisible(true);
+ }
}
}
+ managePermisisons.setVisible(managePermissionsVisible);
sendPrivateMessage.setVisible(true);
sendPrivateMessage.setEnabled(mucOptions.allowPm());
} else {
sendPrivateMessage.setVisible(true);
- sendPrivateMessage.setEnabled(user != null && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR));
+ sendPrivateMessage.setVisible(!isGroupChat && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR));
}
}
- public static boolean onContextItemSelected(MenuItem item, User user, Conversation conversation, XmppActivity activity) {
+ public static boolean onContextItemSelected(MenuItem item, User user, XmppActivity activity) {
+ return onContextItemSelected(item, user, activity, null);
+ }
+
+ public static boolean onContextItemSelected(MenuItem item, User user, XmppActivity activity, final String fingerprint) {
+ final Conversation conversation = user.getConversation();
final XmppConnectionService.OnAffiliationChanged onAffiliationChanged = activity instanceof XmppConnectionService.OnAffiliationChanged ? (XmppConnectionService.OnAffiliationChanged) activity : null;
- final XmppConnectionService.OnRoleChanged onRoleChanged = activity instanceof XmppConnectionService.OnRoleChanged ? (XmppConnectionService.OnRoleChanged) activity : null;
Jid jid = user.getRealJid();
switch (item.getItemId()) {
case R.id.action_contact_details:
Contact contact = user.getContact();
if (contact != null) {
- activity.switchToContactDetails(contact);
+ activity.switchToContactDetails(contact, fingerprint);
}
return true;
case R.id.start_conversation:
- startConversation(user, conversation, activity);
+ startConversation(user, activity);
return true;
case R.id.give_admin_privileges:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.ADMIN, onAffiliationChanged);
@@ -102,19 +152,23 @@ public final class MucDetailsContextMenuHelper {
case R.id.give_membership:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
return true;
+ case R.id.give_owner_privileges:
+ activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.OWNER, onAffiliationChanged);
+ return true;
case R.id.remove_membership:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.NONE, onAffiliationChanged);
return true;
case R.id.remove_admin_privileges:
+ case R.id.revoke_owner_privileges:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.MEMBER, onAffiliationChanged);
return true;
case R.id.remove_from_room:
- removeFromRoom(user, conversation, activity, onAffiliationChanged, onRoleChanged);
+ removeFromRoom(user, activity, onAffiliationChanged);
return true;
case R.id.ban_from_conference:
activity.xmppConnectionService.changeAffiliationInConference(conversation, jid, MucOptions.Affiliation.OUTCAST, onAffiliationChanged);
if (user.getRole() != MucOptions.Role.NONE) {
- activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE, onRoleChanged);
+ activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE);
}
return true;
case R.id.send_private_message:
@@ -138,11 +192,12 @@ public final class MucDetailsContextMenuHelper {
}
}
- public static void removeFromRoom(final User user, Conversation conversation, XmppActivity activity, XmppConnectionService.OnAffiliationChanged onAffiliationChanged, XmppConnectionService.OnRoleChanged onRoleChanged) {
+ private static void removeFromRoom(final User user, XmppActivity activity, XmppConnectionService.OnAffiliationChanged onAffiliationChanged) {
+ final Conversation conversation = user.getConversation();
if (conversation.getMucOptions().membersOnly()) {
activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.NONE, onAffiliationChanged);
if (user.getRole() != MucOptions.Role.NONE) {
- activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE, onRoleChanged);
+ activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE);
}
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -158,16 +213,16 @@ public final class MucDetailsContextMenuHelper {
builder.setPositiveButton(R.string.ban_now, (dialog, which) -> {
activity.xmppConnectionService.changeAffiliationInConference(conversation, user.getRealJid(), MucOptions.Affiliation.OUTCAST, onAffiliationChanged);
if (user.getRole() != MucOptions.Role.NONE) {
- activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE, onRoleChanged);
+ activity.xmppConnectionService.changeRoleInConference(conversation, user.getName(), MucOptions.Role.NONE);
}
});
builder.create().show();
}
}
- public static void startConversation(User user, Conversation conversation, XmppActivity activity) {
+ private static void startConversation(User user, XmppActivity activity) {
if (user.getRealJid() != null) {
- Conversation newConversation = activity.xmppConnectionService.findOrCreateConversation(conversation.getAccount(), user.getRealJid().asBareJid(), false, true);
+ Conversation newConversation = activity.xmppConnectionService.findOrCreateConversation(user.getAccount(), user.getRealJid().asBareJid(), false, true);
activity.switchToConversation(newConversation);
}
}
diff --git a/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java b/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java
index 8f2aa4e61..4b276e392 100644
--- a/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java
+++ b/src/main/java/de/pixart/messenger/ui/util/MyLinkify.java
@@ -40,8 +40,6 @@ import de.pixart.messenger.utils.GeoHelper;
import de.pixart.messenger.utils.Patterns;
import de.pixart.messenger.utils.XmppUri;
-import static java.lang.Character.isAlphabetic;
-
public class MyLinkify {
private static final Linkify.TransformFilter WEBURL_TRANSFORM_FILTER = (matcher, url) -> {
diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java
index c7feb2105..825ca3f17 100644
--- a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java
+++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java
@@ -32,8 +32,8 @@ package de.pixart.messenger.ui.util;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_CHOOSE_IMAGE;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_LOCATION;
-import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VOICE;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VIDEO;
+import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VOICE;
import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_PHOTO;
public enum SendButtonAction {
diff --git a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java
index 07e25e31d..db729f0e4 100644
--- a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java
+++ b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java
@@ -14,6 +14,7 @@ import java.util.List;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
+import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.persistance.FileBackend;
import de.pixart.messenger.ui.MediaViewerActivity;
@@ -25,6 +26,18 @@ public class ViewUtil {
view(context, file, mime);
}
+ public static void view(Context context, DownloadableFile file) {
+ if (!file.exists()) {
+ Toast.makeText(context, R.string.file_deleted, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ String mime = file.getMimeType();
+ if (mime == null) {
+ mime = "*/*";
+ }
+ view(context, file, mime);
+ }
+
public static void view(Context context, File file, String mime) {
Uri uri;
try {
diff --git a/src/main/java/de/pixart/messenger/utils/BackupFileHeader.java b/src/main/java/de/pixart/messenger/utils/BackupFileHeader.java
new file mode 100644
index 000000000..19ad9bff9
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/utils/BackupFileHeader.java
@@ -0,0 +1,84 @@
+package de.pixart.messenger.utils;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import rocks.xmpp.addr.Jid;
+
+public class BackupFileHeader {
+
+ private static final int VERSION = 1;
+
+ private String app;
+ private Jid jid;
+ private long timestamp;
+ private byte[] iv;
+ private byte[] salt;
+
+
+ @Override
+ public String toString() {
+ return "BackupFileHeader{" +
+ "app='" + app + '\'' +
+ ", jid=" + jid +
+ ", timestamp=" + timestamp +
+ ", iv=" + CryptoHelper.bytesToHex(iv) +
+ ", salt=" + CryptoHelper.bytesToHex(salt) +
+ '}';
+ }
+
+ public BackupFileHeader(String app, Jid jid, long timestamp, byte[] iv, byte[] salt) {
+ this.app = app;
+ this.jid = jid;
+ this.timestamp = timestamp;
+ this.iv = iv;
+ this.salt = salt;
+ }
+
+ public void write(DataOutputStream dataOutputStream) throws IOException {
+ dataOutputStream.writeInt(VERSION);
+ dataOutputStream.writeUTF(app);
+ dataOutputStream.writeUTF(jid.asBareJid().toEscapedString());
+ dataOutputStream.writeLong(timestamp);
+ dataOutputStream.write(iv);
+ dataOutputStream.write(salt);
+ }
+
+ public static BackupFileHeader read(DataInputStream inputStream) throws IOException {
+ final int version = inputStream.readInt();
+ if (version > VERSION) {
+ throw new IllegalArgumentException("Backup File version was " + version + " but app only supports up to version " + VERSION);
+ }
+ String app = inputStream.readUTF();
+ String jid = inputStream.readUTF();
+ long timestamp = inputStream.readLong();
+ byte[] iv = new byte[12];
+ inputStream.readFully(iv);
+ byte[] salt = new byte[16];
+ inputStream.readFully(salt);
+
+ return new BackupFileHeader(app, Jid.of(jid), timestamp, iv, salt);
+
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public byte[] getIv() {
+ return iv;
+ }
+
+ public Jid getJid() {
+ return jid;
+ }
+
+ public String getApp() {
+ return app;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/Compatibility.java b/src/main/java/de/pixart/messenger/utils/Compatibility.java
index 38f9db378..c8922e18b 100644
--- a/src/main/java/de/pixart/messenger/utils/Compatibility.java
+++ b/src/main/java/de/pixart/messenger/utils/Compatibility.java
@@ -1,6 +1,7 @@
package de.pixart.messenger.utils;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -11,15 +12,19 @@ import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.annotation.BoolRes;
import android.support.v4.content.ContextCompat;
+import android.util.Log;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.ui.SettingsActivity;
import de.pixart.messenger.ui.SettingsFragment;
+import static de.pixart.messenger.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
+
public class Compatibility {
private static final List<String> UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList(
SettingsActivity.SHOW_FOREGROUND_SERVICE,
@@ -102,4 +107,17 @@ public class Compatibility {
}
}
}
+
+ public static void startService(Context context, Intent intent) {
+ try {
+ if (Compatibility.runsAndTargetsTwentySix(context)) {
+ intent.putExtra(EXTRA_NEEDS_FOREGROUND_SERVICE, true);
+ ContextCompat.startForegroundService(context, intent);
+ } else {
+ context.startService(intent);
+ }
+ } catch (RuntimeException e) {
+ Log.d(Config.LOGTAG, context.getClass().getSimpleName() + " was unable to start service");
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java
index 73d5589e4..5e2948b5b 100644
--- a/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java
+++ b/src/main/java/de/pixart/messenger/utils/ConversationsFileObserver.java
@@ -8,6 +8,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
+import java.util.concurrent.atomic.AtomicBoolean;
import de.pixart.messenger.Config;
@@ -21,16 +22,26 @@ public abstract class ConversationsFileObserver {
private final String path;
private final List<SingleFileObserver> mObservers = new ArrayList<>();
+ private final AtomicBoolean shouldStop = new AtomicBoolean(true);
protected ConversationsFileObserver(String path) {
this.path = path;
}
- public synchronized void startWatching() {
+ public void startWatching() {
+ shouldStop.set(false);
+ startWatchingInternal();
+ }
+
+ private synchronized void startWatchingInternal() {
Stack<String> stack = new Stack<>();
stack.push(path);
while (!stack.empty()) {
+ if (shouldStop.get()) {
+ Log.d(Config.LOGTAG, "file observer received command to stop");
+ return;
+ }
String parent = stack.pop();
mObservers.add(new SingleFileObserver(parent, FileObserver.DELETE | FileObserver.MOVED_FROM));
final File path = new File(parent);
@@ -44,6 +55,10 @@ public abstract class ConversationsFileObserver {
continue;
}
for (File file : files) {
+ if (shouldStop.get()) {
+ Log.d(Config.LOGTAG, "file observer received command to stop");
+ return;
+ }
if (file.isDirectory() && file.getName().charAt(0) != '.') {
final String currentPath = file.getAbsolutePath();
if (depth(file) <= 8 && !stack.contains(currentPath) && !observing(currentPath)) {
@@ -74,7 +89,12 @@ public abstract class ConversationsFileObserver {
return false;
}
- public synchronized void stopWatching() {
+ public void stopWatching() {
+ shouldStop.set(true);
+ stopWatchingInternal();
+ }
+
+ private synchronized void stopWatchingInternal() {
for (FileObserver observer : mObservers) {
observer.stopWatching();
}
diff --git a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java
index 2104349d8..5d1e7980c 100644
--- a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java
@@ -63,7 +63,8 @@ public final class CryptoHelper {
}
public static String pronounceable(SecureRandom random) {
- char[] output = new char[random.nextInt(4) * 2 + 5];
+ final int rand = random.nextInt(4);
+ char[] output = new char[rand * 2 + (5 - rand)];
boolean vowel = random.nextBoolean();
for (int i = 0; i < output.length; ++i) {
output[i] = vowel ? VOWELS[random.nextInt(VOWELS.length)] : CONSONANTS[random.nextInt(CONSONANTS.length)];
diff --git a/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java b/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java
index 18238ae60..428aa616e 100644
--- a/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java
@@ -17,7 +17,6 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.List;
import java.util.Locale;
import de.pixart.messenger.Config;
@@ -27,7 +26,6 @@ import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.XmppActivity;
-import rocks.xmpp.addr.Jid;
public class ExceptionHelper {
private static final String FILENAME = "stacktrace.txt";
diff --git a/src/main/java/de/pixart/messenger/utils/GeoHelper.java b/src/main/java/de/pixart/messenger/utils/GeoHelper.java
index 209fd75db..d978c89e0 100644
--- a/src/main/java/de/pixart/messenger/utils/GeoHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/GeoHelper.java
@@ -4,6 +4,8 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import org.osmdroid.util.GeoPoint;
+
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
@@ -11,8 +13,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
import de.pixart.messenger.entities.Contact;
-import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Conversational;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.ui.ShowLocationActivity;
@@ -43,41 +45,43 @@ public class GeoHelper {
return "https://xmpp.pix-art.de/staticmap/staticmap.php?center=" + latitude + "," + longitude + "&size=500x500&markers=" + latitude + "," + longitude + "&zoom= " + Config.DEFAULT_ZOOM;
}
- public static ArrayList<Intent> createGeoIntentsFromMessage(Message message, Context context) {
- final ArrayList<Intent> intents = new ArrayList<>();
- Matcher matcher = GEO_URI.matcher(message.getBody());
+ private static GeoPoint parseGeoPoint(String body) throws IllegalArgumentException {
+ Matcher matcher = GEO_URI.matcher(body);
+ ;
if (!matcher.matches()) {
- return intents;
+ throw new IllegalArgumentException("Invalid geo uri");
}
double latitude;
double longitude;
try {
latitude = Double.parseDouble(matcher.group(1));
if (latitude > 90.0 || latitude < -90.0) {
- return intents;
+ throw new IllegalArgumentException("Invalid geo uri");
}
longitude = Double.parseDouble(matcher.group(2));
if (longitude > 180.0 || longitude < -180.0) {
- return intents;
+ throw new IllegalArgumentException("Invalid geo uri");
}
- } catch (NumberFormatException nfe) {
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid geo uri", e);
+ }
+ return new GeoPoint(latitude, longitude);
+ }
+
+ public static ArrayList<Intent> createGeoIntentsFromMessage(Context context, Message message) {
+ final ArrayList<Intent> intents = new ArrayList<>();
+ final GeoPoint geoPoint;
+ try {
+ geoPoint = parseGeoPoint(message.getBody());
+ } catch (IllegalArgumentException e) {
return intents;
}
final Conversational conversation = message.getConversation();
- String label;
- if (conversation instanceof Conversation && conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
- try {
- label = "(" + URLEncoder.encode(((Conversation) conversation).getName().toString(), "UTF-8") + ")";
- } catch (UnsupportedEncodingException e) {
- label = "";
- }
- } else {
- label = "";
- }
+ final String label = getLabel(context, message);
Intent locationPluginIntent = new Intent(context, ShowLocationActivity.class);
- locationPluginIntent.putExtra("latitude", latitude);
- locationPluginIntent.putExtra("longitude", longitude);
+ locationPluginIntent.putExtra("latitude", geoPoint.getLatitude());
+ locationPluginIntent.putExtra("longitude", geoPoint.getLongitude());
if (message.getStatus() != Message.STATUS_RECEIVED) {
locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString());
locationPluginIntent.putExtra("name", conversation.getAccount().getJid().getLocal());
@@ -93,8 +97,38 @@ public class GeoHelper {
intents.add(locationPluginIntent);
Intent geoIntent = new Intent(Intent.ACTION_VIEW);
- geoIntent.setData(Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude) + "?q=" + String.valueOf(latitude) + "," + String.valueOf(longitude) + label));
+ geoIntent.setData(Uri.parse("geo:" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + "?q=" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + label));
intents.add(geoIntent);
return intents;
}
+
+ public static void view(Context context, Message message) {
+ final GeoPoint geoPoint = parseGeoPoint(message.getBody());
+ final String label = getLabel(context, message);
+ context.startActivity(geoIntent(geoPoint, label));
+ }
+
+ private static Intent geoIntent(GeoPoint geoPoint, String label) {
+ Intent geoIntent = new Intent(Intent.ACTION_VIEW);
+ geoIntent.setData(Uri.parse("geo:" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + "?q=" + String.valueOf(geoPoint.getLatitude()) + "," + String.valueOf(geoPoint.getLongitude()) + "(" + label + ")"));
+ return geoIntent;
+ }
+
+ public static boolean openInOsmAnd(Context context, Message message) {
+ final GeoPoint geoPoint = parseGeoPoint(message.getBody());
+ final String label = getLabel(context, message);
+ return geoIntent(geoPoint, label).resolveActivity(context.getPackageManager()) != null;
+ }
+
+ private static String getLabel(Context context, Message message) {
+ if (message.getStatus() == Message.STATUS_RECEIVED) {
+ try {
+ return URLEncoder.encode(UIHelper.getMessageDisplayName(message), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ } else {
+ return context.getString(R.string.me);
+ }
+ }
} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/Namespace.java b/src/main/java/de/pixart/messenger/utils/Namespace.java
index 83d63cdaa..3a3e32f73 100644
--- a/src/main/java/de/pixart/messenger/utils/Namespace.java
+++ b/src/main/java/de/pixart/messenger/utils/Namespace.java
@@ -24,4 +24,5 @@ public final class Namespace {
public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0";
public static final String BOOKMARKS = "storage:bookmarks";
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
+ public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0";
}
diff --git a/src/main/java/de/pixart/messenger/utils/PermissionUtils.java b/src/main/java/de/pixart/messenger/utils/PermissionUtils.java
new file mode 100644
index 000000000..b21ca8869
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/utils/PermissionUtils.java
@@ -0,0 +1,34 @@
+package de.pixart.messenger.utils;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+
+public class PermissionUtils {
+
+ public static boolean allGranted(int[] grantResults) {
+ for (int grantResult : grantResults) {
+ if (grantResult != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean writeGranted(int[] grantResults, String[] permission) {
+ for (int i = 0; i < grantResults.length; ++i) {
+ if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission[i])) {
+ return grantResults[i] == PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return false;
+ }
+
+ public static String getFirstDenied(int[] grantResults, String[] permissions) {
+ for (int i = 0; i < grantResults.length; ++i) {
+ if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
+ return permissions[i];
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/utils/PhoneHelper.java b/src/main/java/de/pixart/messenger/utils/PhoneHelper.java
index 327ae1904..86a9bed1a 100644
--- a/src/main/java/de/pixart/messenger/utils/PhoneHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/PhoneHelper.java
@@ -3,20 +3,13 @@ package de.pixart.messenger.utils;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
-import android.content.CursorLoader;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
-import android.os.Bundle;
-import android.provider.ContactsContract;
import android.provider.ContactsContract.Profile;
import android.provider.Settings;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
-
public class PhoneHelper {
@SuppressLint("HardwareIds")
diff --git a/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java b/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java
index 9846146a8..dc2f2bc09 100644
--- a/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java
+++ b/src/main/java/de/pixart/messenger/utils/SerialSingleThreadExecutor.java
@@ -1,6 +1,5 @@
package de.pixart.messenger.utils;
-import android.os.Looper;
import android.util.Log;
import java.util.ArrayDeque;
diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java
index 03af31e23..ea1878557 100644
--- a/src/main/java/de/pixart/messenger/utils/UIHelper.java
+++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java
@@ -263,8 +263,6 @@ public class UIHelper {
case Transferable.STATUS_OFFER_CHECK_FILESIZE:
return new Pair<>(context.getString(R.string.x_file_offered_for_download,
getFileDescriptionString(context, message)), true);
- case Transferable.STATUS_DELETED:
- return new Pair<>(context.getString(R.string.file_deleted), true);
case Transferable.STATUS_FAILED:
return new Pair<>(context.getString(R.string.file_transmission_failed), true);
case Transferable.STATUS_UPLOADING:
@@ -278,6 +276,8 @@ public class UIHelper {
default:
return new Pair<>("", false);
}
+ } else if (message.isFileOrImage() && message.isFileDeleted()) {
+ return new Pair<>(context.getString(R.string.file_deleted), true);
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
return new Pair<>(context.getString(R.string.pgp_message), true);
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
@@ -286,7 +286,7 @@ public class UIHelper {
return new Pair<>(context.getString(R.string.not_encrypted_for_this_device), true);
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
return new Pair<>(context.getString(R.string.omemo_decryption_failed), true);
- } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
+ } else if (message.isFileOrImage()) {
return new Pair<>(getFileDescriptionString(context, message), true);
} else {
final String body = MessageUtils.filterLtrRtl(message.getBody());
@@ -307,6 +307,9 @@ public class UIHelper {
SpannableStringBuilder builder = new SpannableStringBuilder();
for (CharSequence l : CharSequenceUtils.split(styledBody, '\n')) {
if (l.length() > 0) {
+ if (l.toString().equals("```")) {
+ continue;
+ }
char first = l.charAt(0);
if ((first != '>' || !isPositionFollowedByQuoteableCharacter(l,0)) && first != '\u00bb') {
CharSequence line = CharSequenceUtils.trim(l);
diff --git a/src/main/java/de/pixart/messenger/utils/XmppUri.java b/src/main/java/de/pixart/messenger/utils/XmppUri.java
index 9c0b3e666..1ee90aa84 100644
--- a/src/main/java/de/pixart/messenger/utils/XmppUri.java
+++ b/src/main/java/de/pixart/messenger/utils/XmppUri.java
@@ -171,7 +171,7 @@ public class XmppUri {
private boolean hasAction(String query, String action) {
for(String pair : query == null ? new String[0] : query.split(";")) {
final String[] parts = pair.split("=",2);
- if (parts.length == 1 && parts[0].equals(action)) {
+ if (parts.length == 1 && parts[0].toLowerCase(Locale.US).startsWith(action)) {
return true;
}
}
diff --git a/src/main/java/de/pixart/messenger/xml/Element.java b/src/main/java/de/pixart/messenger/xml/Element.java
index d7cace655..d1d3c50e9 100644
--- a/src/main/java/de/pixart/messenger/xml/Element.java
+++ b/src/main/java/de/pixart/messenger/xml/Element.java
@@ -74,7 +74,7 @@ public class Element {
}
private String findInternationalizedChildContent(String name, @NonNull String language) {
- HashMap<String, String> contents = new HashMap<>();
+ final HashMap<String, String> contents = new HashMap<>();
for (Element child : this.children) {
if (name.equals(child.getName())) {
String lang = child.getAttribute("xml:lang");
@@ -88,11 +88,12 @@ public class Element {
}
}
}
- String value = contents.get(null);
+ final String value = contents.get(null);
if (value != null) {
return value;
}
- return contents.size() > 0 ? contents.values().iterator().next() : null;
+ final String[] values = contents.values().toArray(new String[0]);
+ return values.length == 0 ? null : values[0];
}
public Element findChild(String name, String xmlns) {
diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
index 5e3205684..0eb5a5dcb 100644
--- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
+++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java
@@ -286,7 +286,7 @@ public class XmppConnection implements Runnable {
}
} else if (useTor) {
String destination;
- if (account.getHostname().isEmpty()) {
+ if (account.getHostname().isEmpty() || account.isOnion()) {
destination = account.getServer();
} else {
destination = account.getHostname();
@@ -372,9 +372,8 @@ public class XmppConnection implements Runnable {
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), verifiedHostname, ((SSLSocket) localSocket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
- if (!iterator.hasNext()) {
- throw new StateChangingException(Account.State.TLS_ERROR);
- }
+ FileBackend.close(localSocket);
+ throw new StateChangingException(Account.State.TLS_ERROR);
}
}
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
@@ -385,13 +384,13 @@ public class XmppConnection implements Runnable {
}
break; // successfully connected to server that speaks xmpp
} else {
- localSocket.close();
- if (!iterator.hasNext()) {
- throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
- }
+ FileBackend.close(localSocket);
+ throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
} catch (final StateChangingException e) {
- throw e;
+ if (!iterator.hasNext()) {
+ throw e;
+ }
} catch (InterruptedException e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": thread was interrupted before beginning stream");
return;
@@ -867,7 +866,7 @@ public class XmppConnection implements Runnable {
private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException {
this.streamFeatures = tagReader.readElement(currentTag);
- final boolean isSecure = features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS;
+ final boolean isSecure = features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion();
final boolean needsBinding = !isBound && !account.isOptionSet(Account.OPTION_REGISTER);
if (this.streamFeatures.hasChild("starttls") && !features.encryptionEnabled) {
sendStartTLS();
@@ -1297,7 +1296,7 @@ public class XmppConnection implements Runnable {
for (Jid jid : items) {
sendServiceDiscoveryInfo(jid);
}
- getInviteAdHoc(server);
+ getAdHocFeatures(server);
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not query disco items of " + server);
}
@@ -1310,9 +1309,8 @@ public class XmppConnection implements Runnable {
});
}
- private void getInviteAdHoc(final Jid server) {
+ private void getAdHocFeatures(final Jid server) {
mPendingServiceDiscoveries.incrementAndGet();
- ArrayList<String> nodes = new ArrayList<String>();
IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
iq.setTo(Jid.ofDomain(server.getDomain()));
iq.setFrom(Jid.of(account.getJid().asBareJid()));
@@ -1332,12 +1330,10 @@ public class XmppConnection implements Runnable {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": query disco commands of " + server + " was successful");
getAdHocInviteUrl(server);
}
- nodes.add(node);
}
}
}
} else {
- features.adhocinvite = false;
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not query disco commands of " + server);
}
if (packet.getType() != IqPacket.TYPE.TIMEOUT) {
@@ -1346,7 +1342,6 @@ public class XmppConnection implements Runnable {
finalizeBind();
}
}
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": query disco commands of " + server + " was " + features.adhocinvite);
});
}
@@ -1853,6 +1848,10 @@ public class XmppConnection implements Runnable {
return hasDiscoFeature(account.getJid().asBareJid(), Namespace.BOOKMARKS_CONVERSION) && pepPublishOptions();
}
+ public boolean avatarConversion() {
+ return hasDiscoFeature(account.getJid().asBareJid(), Namespace.AVATAR_CONVERSION) && pepPublishOptions();
+ }
+
public boolean blocking() {
return hasDiscoFeature(Jid.of(account.getServer()), Namespace.BLOCKING);
}
diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java
index 2f3ec09ea..d4c6c7eba 100644
--- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java
@@ -117,8 +117,10 @@ public class JingleConnection implements Transferable {
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
account.getPgpDecryptionService().decrypt(message, true);
} else {
- JingleConnection.this.mXmppConnectionService.getNotificationService().push(message);
+ mXmppConnectionService.getFileBackend().updateMediaScanner(file, () -> JingleConnection.this.mXmppConnectionService.getNotificationService().push(message));
}
+ Log.d(Config.LOGTAG, "successfully transmitted file:" + file.getAbsolutePath() + " (" + CryptoHelper.bytesToHex(file.getSha1Sum()) + ")");
+ return;
}
} else {
if (ftVersion == Content.Version.FT_5) { //older Conversations will break when receiving a session-info
@@ -483,7 +485,7 @@ public class JingleConnection implements Transferable {
private void sendInitRequest() {
JinglePacket packet = this.bootstrapPacket("session-initiate");
Content content = new Content(this.contentCreator, this.contentName);
- if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
+ if (message.isFileOrImage()) {
content.setTransportId(this.transportId);
this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
if (message.getEncryption() == Message.ENCRYPTION_OTR) {