Search in sources :

Example 16 with Conversation

use of eu.siacs.conversations.entities.Conversation in project Conversations by siacs.

the class NotificationService method pushFailedDelivery.

public void pushFailedDelivery(final Message message) {
    final Conversation conversation = (Conversation) message.getConversation();
    final boolean isScreenLocked = !mXmppConnectionService.isScreenLocked();
    if (this.mIsInForeground && isScreenLocked && this.mOpenConversation == message.getConversation()) {
        Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": suppressing failed delivery notification because conversation is open");
        return;
    }
    final PendingIntent pendingIntent = createContentIntent(conversation);
    final int notificationId = generateRequestCode(conversation, 0) + DELIVERY_FAILED_NOTIFICATION_ID;
    final int failedDeliveries = conversation.countFailedDeliveries();
    final Notification notification = new Builder(mXmppConnectionService, "delivery_failed").setContentTitle(conversation.getName()).setAutoCancel(true).setSmallIcon(R.drawable.ic_error_white_24dp).setContentText(mXmppConnectionService.getResources().getQuantityText(R.plurals.some_messages_could_not_be_delivered, failedDeliveries)).setGroup("delivery_failed").setContentIntent(pendingIntent).build();
    final Notification summaryNotification = new Builder(mXmppConnectionService, "delivery_failed").setContentTitle(mXmppConnectionService.getString(R.string.failed_deliveries)).setContentText(mXmppConnectionService.getResources().getQuantityText(R.plurals.some_messages_could_not_be_delivered, 1024)).setSmallIcon(R.drawable.ic_error_white_24dp).setGroup("delivery_failed").setGroupSummary(true).setAutoCancel(true).build();
    notify(notificationId, notification);
    notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification);
}
Also used : Builder(androidx.core.app.NotificationCompat.Builder) Conversation(eu.siacs.conversations.entities.Conversation) PendingIntent(android.app.PendingIntent) Notification(android.app.Notification)

Example 17 with Conversation

use of eu.siacs.conversations.entities.Conversation in project Conversations by siacs.

the class NotificationService method buildSingleConversations.

private Builder buildSingleConversations(final ArrayList<Message> messages, final boolean notify, final boolean quietHours) {
    final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
    if (messages.size() >= 1) {
        final Conversation conversation = (Conversation) messages.get(0).getConversation();
        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;
            // 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, message, messages);
            } else {
                modifyForTextOnly(mBuilder, messages);
            }
            RemoteInput remoteInput = new RemoteInput.Builder("text_reply").setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation)).build();
            PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
            NotificationCompat.Action markReadAction = new NotificationCompat.Action.Builder(R.drawable.ic_drafts_white_24dp, mXmppConnectionService.getString(R.string.mark_as_read), markAsReadPendingIntent).setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ).setShowsUserInterface(false).build();
            final String replyLabel = mXmppConnectionService.getString(R.string.reply);
            final String lastMessageUuid = Iterables.getLast(messages).getUuid();
            final NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(R.drawable.ic_send_text_offline, replyLabel, createReplyIntent(conversation, lastMessageUuid, false)).setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY).setShowsUserInterface(false).addRemoteInput(remoteInput).build();
            final NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply, replyLabel, createReplyIntent(conversation, lastMessageUuid, true)).addRemoteInput(remoteInput).build();
            mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
            int addedActionsCount = 1;
            mBuilder.addAction(markReadAction);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                mBuilder.addAction(replyAction);
                ++addedActionsCount;
            }
            if (displaySnoozeAction(messages)) {
                String label = mXmppConnectionService.getString(R.string.snooze);
                PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
                NotificationCompat.Action snoozeAction = new NotificationCompat.Action.Builder(R.drawable.ic_notifications_paused_white_24dp, label, pendingSnoozeIntent).build();
                mBuilder.addAction(snoozeAction);
                ++addedActionsCount;
            }
            if (addedActionsCount < 3) {
                final Message firstLocationMessage = getFirstLocationMessage(messages);
                if (firstLocationMessage != null) {
                    final PendingIntent pendingShowLocationIntent = createShowLocationIntent(firstLocationMessage);
                    if (pendingShowLocationIntent != null) {
                        final String label = mXmppConnectionService.getResources().getString(R.string.show_location);
                        NotificationCompat.Action locationAction = new NotificationCompat.Action.Builder(R.drawable.ic_room_white_24dp, label, pendingShowLocationIntent).build();
                        mBuilder.addAction(locationAction);
                        ++addedActionsCount;
                    }
                }
            }
            if (addedActionsCount < 3) {
                Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
                if (firstDownloadableMessage != null) {
                    String label = mXmppConnectionService.getResources().getString(R.string.download_x_file, UIHelper.getFileDescriptionString(mXmppConnectionService, firstDownloadableMessage));
                    PendingIntent pendingDownloadIntent = createDownloadIntent(firstDownloadableMessage);
                    NotificationCompat.Action downloadAction = new NotificationCompat.Action.Builder(R.drawable.ic_file_download_white_24dp, label, pendingDownloadIntent).build();
                    mBuilder.addAction(downloadAction);
                    ++addedActionsCount;
                }
            }
        }
        if (conversation.getMode() == Conversation.MODE_SINGLE) {
            Contact contact = conversation.getContact();
            Uri systemAccount = contact.getSystemAccount();
            if (systemAccount != null) {
                mBuilder.addPerson(systemAccount.toString());
            }
        }
        mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
        mBuilder.setSmallIcon(R.drawable.ic_notification);
        mBuilder.setDeleteIntent(createDeleteIntent(conversation));
        mBuilder.setContentIntent(createContentIntent(conversation));
    }
    return mBuilder;
}
Also used : RemoteInput(androidx.core.app.RemoteInput) Message(eu.siacs.conversations.entities.Message) Builder(androidx.core.app.NotificationCompat.Builder) Conversation(eu.siacs.conversations.entities.Conversation) SpannableString(android.text.SpannableString) Uri(android.net.Uri) Contact(eu.siacs.conversations.entities.Contact) NotificationCompat(androidx.core.app.NotificationCompat) PendingIntent(android.app.PendingIntent)

Example 18 with Conversation

use of eu.siacs.conversations.entities.Conversation in project Conversations by siacs.

the class XmppConnectionService method sendMessage.

private void sendMessage(final Message message, final boolean resend, final boolean delay) {
    final Account account = message.getConversation().getAccount();
    if (account.setShowErrorNotification(true)) {
        databaseBackend.updateAccount(account);
        mNotificationService.updateErrorNotification();
    }
    final Conversation conversation = (Conversation) message.getConversation();
    account.deactivateGracePeriod();
    if (QuickConversationsService.isQuicksy() && conversation.getMode() == Conversation.MODE_SINGLE) {
        final Contact contact = conversation.getContact();
        if (!contact.showInRoster() && contact.getOption(Contact.Options.SYNCED_VIA_OTHER)) {
            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": adding " + contact.getJid() + " on sending message");
            createContact(contact, true);
        }
    }
    MessagePacket packet = null;
    final boolean addToConversation = (conversation.getMode() != Conversation.MODE_MULTI || !Patches.BAD_MUC_REFLECTION.contains(account.getServerIdentity())) && !message.edited();
    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);
        }
    }
    final boolean inProgressJoin = isJoinInProgress(conversation);
    if (account.isOnlineAndConnected() && !inProgressJoin) {
        switch(message.getEncryption()) {
            case Message.ENCRYPTION_NONE:
                if (message.needsUploading()) {
                    if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize()) || conversation.getMode() == Conversation.MODE_MULTI || message.fixCounterpart()) {
                        this.sendFileMessage(message, delay);
                    } else {
                        break;
                    }
                } else {
                    packet = mMessageGenerator.generateChat(message);
                }
                break;
            case Message.ENCRYPTION_PGP:
            case Message.ENCRYPTION_DECRYPTED:
                if (message.needsUploading()) {
                    if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize()) || conversation.getMode() == Conversation.MODE_MULTI || message.fixCounterpart()) {
                        this.sendFileMessage(message, delay);
                    } else {
                        break;
                    }
                } else {
                    packet = mMessageGenerator.generatePgpChat(message);
                }
                break;
            case Message.ENCRYPTION_AXOLOTL:
                message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
                if (message.needsUploading()) {
                    if (account.httpUploadAvailable(fileBackend.getFile(message, false).getSize()) || conversation.getMode() == Conversation.MODE_MULTI || message.fixCounterpart()) {
                        this.sendFileMessage(message, delay);
                    } else {
                        break;
                    }
                } else {
                    XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
                    if (axolotlMessage == null) {
                        account.getAxolotlService().preparePayloadMessage(message, delay);
                    } else {
                        packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
                    }
                }
                break;
        }
        if (packet != null) {
            if (account.getXmppConnection().getFeatures().sm() || (conversation.getMode() == Conversation.MODE_MULTI && message.getCounterpart().isBareJid())) {
                message.setStatus(Message.STATUS_UNSEND);
            } else {
                message.setStatus(Message.STATUS_SEND);
            }
        }
    } else {
        switch(message.getEncryption()) {
            case Message.ENCRYPTION_DECRYPTED:
                if (!message.needsUploading()) {
                    String pgpBody = message.getEncryptedBody();
                    String decryptedBody = message.getBody();
                    // TODO might throw NPE
                    message.setBody(pgpBody);
                    message.setEncryption(Message.ENCRYPTION_PGP);
                    if (message.edited()) {
                        message.setBody(decryptedBody);
                        message.setEncryption(Message.ENCRYPTION_DECRYPTED);
                        if (!databaseBackend.updateMessage(message, message.getEditedId())) {
                            Log.e(Config.LOGTAG, "error updated message in DB after edit");
                        }
                        updateConversationUi();
                        return;
                    } else {
                        databaseBackend.createMessage(message);
                        saveInDb = false;
                        message.setBody(decryptedBody);
                        message.setEncryption(Message.ENCRYPTION_DECRYPTED);
                    }
                }
                break;
            case Message.ENCRYPTION_AXOLOTL:
                message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
                break;
        }
    }
    boolean mucMessage = conversation.getMode() == Conversation.MODE_MULTI && !message.isPrivateMessage();
    if (mucMessage) {
        message.setCounterpart(conversation.getMucOptions().getSelf().getFullJid());
    }
    if (resend) {
        if (packet != null && addToConversation) {
            if (account.getXmppConnection().getFeatures().sm() || mucMessage) {
                markMessage(message, Message.STATUS_UNSEND);
            } else {
                markMessage(message, Message.STATUS_SEND);
            }
        }
    } else {
        if (addToConversation) {
            conversation.add(message);
        }
        if (saveInDb) {
            databaseBackend.createMessage(message);
        } else if (message.edited()) {
            if (!databaseBackend.updateMessage(message, message.getEditedId())) {
                Log.e(Config.LOGTAG, "error updated message in DB after edit");
            }
        }
        updateConversationUi();
    }
    if (packet != null) {
        if (delay) {
            mMessageGenerator.addDelay(packet, message.getTimeSent());
        }
        if (conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
            if (this.sendChatStates()) {
                packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
            }
        }
        sendMessagePacket(account, packet);
    }
}
Also used : MessagePacket(eu.siacs.conversations.xmpp.stanzas.MessagePacket) Account(eu.siacs.conversations.entities.Account) Conversation(eu.siacs.conversations.entities.Conversation) XmppAxolotlMessage(eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage) Contact(eu.siacs.conversations.entities.Contact) JabberIdContact(eu.siacs.conversations.android.JabberIdContact)

Example 19 with Conversation

use of eu.siacs.conversations.entities.Conversation in project Conversations by siacs.

the class XmppConnectionService method restoreFromDatabase.

private void restoreFromDatabase() {
    synchronized (this.conversations) {
        final Map<String, Account> accountLookupTable = new Hashtable<>();
        for (Account account : this.accounts) {
            accountLookupTable.put(account.getUuid(), account);
        }
        Log.d(Config.LOGTAG, "restoring conversations...");
        final long startTimeConversationsRestore = SystemClock.elapsedRealtime();
        this.conversations.addAll(databaseBackend.getConversations(Conversation.STATUS_AVAILABLE));
        for (Iterator<Conversation> iterator = conversations.listIterator(); iterator.hasNext(); ) {
            Conversation conversation = iterator.next();
            Account account = accountLookupTable.get(conversation.getAccountUuid());
            if (account != null) {
                conversation.setAccount(account);
            } else {
                Log.e(Config.LOGTAG, "unable to restore Conversations with " + conversation.getJid());
                iterator.remove();
            }
        }
        long diffConversationsRestore = SystemClock.elapsedRealtime() - startTimeConversationsRestore;
        Log.d(Config.LOGTAG, "finished restoring conversations in " + diffConversationsRestore + "ms");
        Runnable runnable = () -> {
            if (DatabaseBackend.requiresMessageIndexRebuild()) {
                DatabaseBackend.getInstance(this).rebuildMessagesIndex();
            }
            final long deletionDate = getAutomaticMessageDeletionDate();
            mLastExpiryRun.set(SystemClock.elapsedRealtime());
            if (deletionDate > 0) {
                Log.d(Config.LOGTAG, "deleting messages that are older than " + AbstractGenerator.getTimestamp(deletionDate));
                databaseBackend.expireOldMessages(deletionDate);
            }
            Log.d(Config.LOGTAG, "restoring roster...");
            for (Account account : accounts) {
                databaseBackend.readRoster(account.getRoster());
                // roster needs to be loaded at this stage
                account.initAccountServices(XmppConnectionService.this);
            }
            getBitmapCache().evictAll();
            loadPhoneContacts();
            Log.d(Config.LOGTAG, "restoring messages...");
            final long startMessageRestore = SystemClock.elapsedRealtime();
            final Conversation quickLoad = QuickLoader.get(this.conversations);
            if (quickLoad != null) {
                restoreMessages(quickLoad);
                updateConversationUi();
                final long diffMessageRestore = SystemClock.elapsedRealtime() - startMessageRestore;
                Log.d(Config.LOGTAG, "quickly restored " + quickLoad.getName() + " after " + diffMessageRestore + "ms");
            }
            for (Conversation conversation : this.conversations) {
                if (quickLoad != conversation) {
                    restoreMessages(conversation);
                }
            }
            mNotificationService.finishBacklog(false);
            restoredFromDatabaseLatch.countDown();
            final long diffMessageRestore = SystemClock.elapsedRealtime() - startMessageRestore;
            Log.d(Config.LOGTAG, "finished restoring messages in " + diffMessageRestore + "ms");
            updateConversationUi();
        };
        // will contain one write command (expiry) but that's fine
        mDatabaseReaderExecutor.execute(runnable);
    }
}
Also used : Account(eu.siacs.conversations.entities.Account) Hashtable(java.util.Hashtable) Conversation(eu.siacs.conversations.entities.Conversation)

Example 20 with Conversation

use of eu.siacs.conversations.entities.Conversation in project Conversations by siacs.

the class XmppConnectionService method persistSelfNick.

public void persistSelfNick(MucOptions.User self) {
    final Conversation conversation = self.getConversation();
    final boolean tookProposedNickFromBookmark = conversation.getMucOptions().isTookProposedNickFromBookmark();
    Jid full = self.getFullJid();
    if (!full.equals(conversation.getJid())) {
        Log.d(Config.LOGTAG, "nick changed. updating");
        conversation.setContactJid(full);
        databaseBackend.updateConversation(conversation);
    }
    final Bookmark bookmark = conversation.getBookmark();
    final String bookmarkedNick = bookmark == null ? null : bookmark.getNick();
    if (bookmark != null && (tookProposedNickFromBookmark || TextUtils.isEmpty(bookmarkedNick)) && !full.getResource().equals(bookmarkedNick)) {
        final Account account = conversation.getAccount();
        final String defaultNick = MucOptions.defaultNick(account);
        if (TextUtils.isEmpty(bookmarkedNick) && full.getResource().equals(defaultNick)) {
            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not overwrite empty bookmark nick with default nick for " + conversation.getJid().asBareJid());
            return;
        }
        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": persist nick '" + full.getResource() + "' into bookmark for " + conversation.getJid().asBareJid());
        bookmark.setNick(full.getResource());
        createBookmark(bookmark.getAccount(), bookmark);
    }
}
Also used : Account(eu.siacs.conversations.entities.Account) Jid(eu.siacs.conversations.xmpp.Jid) Bookmark(eu.siacs.conversations.entities.Bookmark) Conversation(eu.siacs.conversations.entities.Conversation)

Aggregations

Conversation (eu.siacs.conversations.entities.Conversation)110 Account (eu.siacs.conversations.entities.Account)27 Message (eu.siacs.conversations.entities.Message)24 Jid (eu.siacs.conversations.xmpp.Jid)22 Contact (eu.siacs.conversations.entities.Contact)17 MucOptions (eu.siacs.conversations.entities.MucOptions)10 Intent (android.content.Intent)9 Element (eu.siacs.conversations.xml.Element)9 PendingIntent (android.app.PendingIntent)8 XmppAxolotlMessage (eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage)8 MessagePacket (eu.siacs.conversations.xmpp.stanzas.MessagePacket)8 Uri (android.net.Uri)7 Conversational (eu.siacs.conversations.entities.Conversational)7 InvalidJidException (eu.siacs.conversations.xmpp.jid.InvalidJidException)7 SuppressLint (android.annotation.SuppressLint)6 SpannableString (android.text.SpannableString)6 XmppConnection (eu.siacs.conversations.xmpp.XmppConnection)6 Jid (eu.siacs.conversations.xmpp.jid.Jid)6 ArrayList (java.util.ArrayList)6 Fragment (android.app.Fragment)4