diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 8be85e414..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 90 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - feature - - security - - bug -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/CHANGELOG.md b/CHANGELOG.md index a24790fa9..89f2e0d30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### Version 2.8.10 + +* Handle GPX files +* Improve performance for backup restore +* bug fixes + ### Version 2.8.9 * add 'Return to chat' to audio call screen diff --git a/build.gradle b/build.gradle index 958444bd3..880678aca 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:4.0.1' } } @@ -96,8 +96,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 25 - versionCode 395 - versionName "2.8.9" + versionCode 397 + versionName "2.8.10" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/397.txt b/fastlane/metadata/android/en-US/changelogs/397.txt new file mode 100644 index 000000000..207b36708 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/397.txt @@ -0,0 +1,3 @@ +* Handle GPX files +* Improve performance for backup restore +* bug fixes diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 852a0aba2..9ad8d1f7c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 19 11:51:26 CET 2020 +#Sun Jul 26 11:32:42 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index e50179c8d..3fee92855 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -17,6 +17,7 @@ import android.support.v4.app.NotificationManagerCompat; import android.util.Log; import com.google.common.base.Charsets; +import com.google.common.base.Stopwatch; import com.google.common.io.CountingInputStream; import org.bouncycastle.crypto.engines.AESEngine; @@ -189,6 +190,7 @@ public class ImportBackupService extends Service { private boolean importBackup(final Uri uri, final String password) { Log.d(Config.LOGTAG, "importing backup from " + uri); + final Stopwatch stopwatch = Stopwatch.createStarted(); try { final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); final InputStream inputStream; @@ -233,12 +235,13 @@ public class ImportBackupService extends Service { final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv())); final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher); final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); - BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8)); + final BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8)); + db.beginTransaction(); String line; StringBuilder multiLineQuery = null; while ((line = reader.readLine()) != null) { @@ -260,11 +263,13 @@ public class ImportBackupService extends Service { } } } + db.setTransactionSuccessful(); + db.endTransaction(); 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().toEscapedString()}); + final 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().toEscapedString()}); countCursor.moveToFirst(); - int count = countCursor.getInt(0); - Log.d(Config.LOGTAG, "restored " + count + " messages"); + final int count = countCursor.getInt(0); + Log.d(Config.LOGTAG, String.format("restored %d messages in %s", count, stopwatch.stop().toString())); countCursor.close(); stopBackgroundService(); synchronized (mOnBackupProcessedListeners) { @@ -274,7 +279,7 @@ public class ImportBackupService extends Service { } return true; } catch (final Exception e) { - Throwable throwable = e.getCause(); + final Throwable throwable = e.getCause(); final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException; synchronized (mOnBackupProcessedListeners) { for (OnBackupProcessed l : mOnBackupProcessedListeners) { diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml index 63a1c2379..aa9fc670c 100644 --- a/src/conversations/res/values-it/strings.xml +++ b/src/conversations/res/values-it/strings.xml @@ -10,4 +10,5 @@ In ogni caso per facilitare puoi creare facilmente un account su chat.sum7.eu, u Sei stato invitato su %1$s. Ti guideremo nel procedimento per creare un account.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. Sei stato invitato su %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un account.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo. Il tuo invito al server - + Codice di approvvigionamento formattato male + diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml index 8a9f8b405..3c2468f88 100644 --- a/src/conversations/res/values-pt-rBR/strings.xml +++ b/src/conversations/res/values-pt-rBR/strings.xml @@ -8,4 +8,5 @@ Você foi convidado para %1$s. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nAo escolher %1$s como um provedor você conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo. Seu convite do servidor - + Código de provisionamento formatado de maneira imprópria + diff --git a/src/conversations/res/values-ru/strings.xml b/src/conversations/res/values-ru/strings.xml index 906d279d4..db0eabbe7 100644 --- a/src/conversations/res/values-ru/strings.xml +++ b/src/conversations/res/values-ru/strings.xml @@ -8,4 +8,5 @@ Вас пригласили на %1$s. Мы проведём вас через процесс создания аккаунта. Аккаунт на %1$s позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес. Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта. Этот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес. Ваше приглашение - + Неправильный формат кода + diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml index 882190af6..989810dec 100644 --- a/src/conversations/res/values-zh-rCN/strings.xml +++ b/src/conversations/res/values-zh-rCN/strings.xml @@ -8,4 +8,5 @@ 您已受邀参加%1$s。 我们将指导您完成创建帐户的过程。\n选择%1$s作为提供者后,您可以通过提供其他人的完整XMPP地址与其他提供者的用户进行交流。 您已受邀参加%1$s。 已经为您选择了一个用户名。 我们将指导您完成创建帐户的过程。\n您可以通过向其他提供商的用户提供完整的XMPP地址来与他们进行交流。 你的服务器邀请 - + 格式不正确的配置代码 + diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 90b2bdc61..a03166685 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -621,7 +621,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public boolean isEditable() { - return getStatus() != STATUS_RECEIVED && !isCarbon(); + return status != STATUS_RECEIVED && !isCarbon() && type != Message.TYPE_RTP_SESSION; } public boolean mergeable(final Message message) { diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index fd05a5282..89ddec12b 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -225,19 +225,31 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private void execute(final Query query) { final Account account = query.getAccount(); if (account.getStatus() == Account.State.ONLINE) { + final Conversation conversation = query.getConversation(); + if (conversation != null && conversation.getStatus() == Conversation.STATUS_ARCHIVED) { + throw new IllegalStateException("Attempted to run MAM query for archived conversation"); + } Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString()); - IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { - Element fin = p.findChild("fin", query.version.namespace); + final Element fin = p.findChild("fin", query.version.namespace); if (p.getType() == IqPacket.TYPE.TIMEOUT) { - synchronized (MessageArchiveService.this.queries) { - MessageArchiveService.this.queries.remove(query); + synchronized (this.queries) { + this.queries.remove(query); if (query.hasCallback()) { query.callback(false); } } } else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) { - processFin(query, fin); + final boolean running; + synchronized (this.queries) { + running = this.queries.contains(query); + } + if (running) { + processFin(query, fin); + } else { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring MAM iq result because query had been killed"); + } } else if (p.getType() == IqPacket.TYPE.RESULT && query.isLegacy()) { //do nothing } else { @@ -252,9 +264,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } - private void finalizeQuery(Query query, boolean done) { + private void finalizeQuery(final Query query, boolean done) { synchronized (this.queries) { - this.queries.remove(query); + if (!this.queries.remove(query)) { + throw new IllegalStateException("Unable to remove query from queries"); + } } final Conversation conversation = query.getConversation(); if (conversation != null) { @@ -377,7 +391,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { void kill(Conversation conversation) { final ArrayList toBeKilled = new ArrayList<>(); synchronized (this.queries) { - for (Query q : queries) { + for (final Query q : queries) { if (q.conversation == conversation) { toBeKilled.add(q); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 812ca3716..8bc43c99f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -239,8 +239,8 @@ public class XmppConnectionService extends Service { Environment.getExternalStorageDirectory().getAbsolutePath() ) { @Override - public void onEvent(int event, String path) { - markFileDeleted(path); + public void onEvent(final int event, final File file) { + markFileDeleted(file); } }; private final OnMessageAcknowledged mOnMessageAcknowledgedListener = new OnMessageAcknowledged() { @@ -1859,17 +1859,16 @@ public class XmppConnectionService extends Service { return this.conversations; } - private void markFileDeleted(final String path) { + private void markFileDeleted(final File file) { synchronized (FILENAMES_TO_IGNORE_DELETION) { - if (FILENAMES_TO_IGNORE_DELETION.remove(path)) { - Log.d(Config.LOGTAG, "ignored deletion of " + path); + if (FILENAMES_TO_IGNORE_DELETION.remove(file.getAbsolutePath())) { + Log.d(Config.LOGTAG, "ignored deletion of " + file.getAbsolutePath()); return; } } - final File file = new File(path); final boolean isInternalFile = fileBackend.isInternalFile(file); final List uuids = databaseBackend.markFileAsDeleted(file, isInternalFile); - Log.d(Config.LOGTAG, "deleted file " + path + " internal=" + isInternalFile + ", database hits=" + uuids.size()); + Log.d(Config.LOGTAG, "deleted file " + file.getAbsolutePath() + " internal=" + isInternalFile + ", database hits=" + uuids.size()); markUuidsAsDeletedFiles(uuids); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index c027b56bf..0136e99e0 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -2752,8 +2752,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (lastEditableMessage != null) { correctMessage(lastEditableMessage); return true; + } else { + Toast.makeText(getActivity(),R.string.could_not_correct_message, Toast.LENGTH_LONG).show(); + return false; } - return false; } @Override @@ -2762,7 +2764,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (service == null) { return; } - Account.State status = conversation.getAccount().getStatus(); + final Account.State status = conversation.getAccount().getStatus(); if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { service.sendChatState(conversation); } @@ -2775,7 +2777,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (service == null) { return; } - Account.State status = conversation.getAccount().getStatus(); + final Account.State status = conversation.getAccount().getStatus(); if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) { service.sendChatState(conversation); } @@ -2787,7 +2789,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (service == null) { return; } - Account.State status = conversation.getAccount().getStatus(); + final Account.State status = conversation.getAccount().getStatus(); if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) { service.sendChatState(conversation); } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index d192fa0c6..92d6f2093 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -33,6 +33,8 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.Toast; +import com.google.common.base.CharMatcher; + import org.openintents.openpgp.util.OpenPgpUtils; import java.net.URL; @@ -69,13 +71,13 @@ import eu.siacs.conversations.utils.TorServiceUtils; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection.Features; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.pep.Avatar; -import eu.siacs.conversations.xmpp.Jid; public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { @@ -215,12 +217,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat removeErrorsOnAllBut(binding.accountJidLayout); return; } - String hostname = null; + final String hostname; int numericPort = 5222; if (mShowOptions) { - hostname = binding.hostname.getText().toString().replaceAll("\\s", ""); - final String port = binding.port.getText().toString().replaceAll("\\s", ""); - if (hostname.contains(" ")) { + hostname = CharMatcher.whitespace().removeFrom(binding.hostname.getText()); + final String port = CharMatcher.whitespace().removeFrom(binding.port.getText()); + if (Resolver.invalidHostname(hostname)) { binding.hostnameLayout.setError(getString(R.string.not_valid_hostname)); binding.hostname.requestFocus(); removeErrorsOnAllBut(binding.hostnameLayout); @@ -243,6 +245,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat return; } } + } else { + hostname = null; } if (jid.getLocal() == null) { diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 33835119f..0fd9ee187 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -371,15 +371,23 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe binding.with.setText(account.getRoster().getContact(with).getDisplayName()); } else if (Intent.ACTION_VIEW.equals(action)) { final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE); - if (extraLastState != null) { + final RtpEndUserState state = extraLastState == null ? null : RtpEndUserState.valueOf(extraLastState); + if (state != null) { Log.d(Config.LOGTAG, "restored last state from intent extra"); - RtpEndUserState state = RtpEndUserState.valueOf(extraLastState); updateButtonConfiguration(state); updateStateDisplay(state); updateProfilePicture(state); invalidateOptionsMenu(); } binding.with.setText(account.getRoster().getContact(with).getDisplayName()); + if (xmppConnectionService.getJingleConnectionManager().fireJingleRtpConnectionStateUpdates()) { + return; + } + if (END_CARD.contains(state) || xmppConnectionService.getJingleConnectionManager().hasMatchingProposal(account, with)) { + return; + } + Log.d(Config.LOGTAG, "restored state (" + state + ") was not an end card. finishing"); + finish(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java index bf6cc0689..03920f2dc 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -11,6 +11,7 @@ import android.support.annotation.AttrRes; import android.support.annotation.DimenRes; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.ImageView; @@ -21,6 +22,7 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.RejectedExecutionException; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.MediaBinding; import eu.siacs.conversations.services.ExportBackupService; @@ -51,14 +53,16 @@ public class MediaAdapter extends RecyclerView.Adapter mObservers = new ArrayList<>(); @@ -34,50 +40,47 @@ public abstract class ConversationsFileObserver { } private synchronized void startWatchingInternal() { - Stack stack = new Stack<>(); + final Stack stack = new Stack<>(); stack.push(path); while (!stack.empty()) { if (shouldStop.get()) { - Log.d(Config.LOGTAG,"file observer received command to stop"); + 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 String parent = stack.pop(); final File path = new File(parent); + mObservers.add(new SingleFileObserver(path, MASK)); final File[] files = path.listFiles(); - if (files == null) { - continue; - } - for(File file : files) { + for (final File file : (files == null ? new File[0] : files)) { if (shouldStop.get()) { - Log.d(Config.LOGTAG,"file observer received command to stop"); + 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)) { + if (depth(file) <= 8 && !stack.contains(currentPath) && !observing(file)) { stack.push(currentPath); } } } } - for(FileObserver observer : mObservers) { + for (FileObserver observer : mObservers) { observer.startWatching(); } } private static int depth(File file) { int depth = 0; - while((file = file.getParentFile()) != null) { + while ((file = file.getParentFile()) != null) { depth++; } return depth; } - private boolean observing(String path) { - for(SingleFileObserver observer : mObservers) { - if(path.equals(observer.path)) { + private boolean observing(final File path) { + for (final SingleFileObserver observer : mObservers) { + if (path.equals(observer.path)) { return true; } } @@ -90,13 +93,13 @@ public abstract class ConversationsFileObserver { } private synchronized void stopWatchingInternal() { - for(FileObserver observer : mObservers) { + for (FileObserver observer : mObservers) { observer.stopWatching(); } mObservers.clear(); } - abstract public void onEvent(int event, String path); + abstract public void onEvent(final int event, File path); public void restartWatching() { stopWatching(); @@ -104,21 +107,33 @@ public abstract class ConversationsFileObserver { } private class SingleFileObserver extends FileObserver { - private final String path; + private final File path; - SingleFileObserver(String path, int mask) { - super(path, mask); + SingleFileObserver(final File path, final int mask) { + super(path.getAbsolutePath(), mask); this.path = path; } @Override - public void onEvent(int event, String filename) { + public void onEvent(final int event, final String filename) { if (filename == null) { - Log.d(Config.LOGTAG,"ignored file event with NULL filename (event="+event+")"); + Log.d(Config.LOGTAG, "ignored file event with NULL filename (event=" + event + ")"); return; } - ConversationsFileObserver.this.onEvent(event, path+'/'+filename); + EVENT_EXECUTOR.execute(() -> { + final File file = new File(this.path, filename); + if ((event & FileObserver.ALL_EVENTS) == FileObserver.CREATE) { + if (file.isDirectory()) { + Log.d(Config.LOGTAG, "file observer observed new directory creation " + file); + if (!observing(file)) { + final SingleFileObserver observer = new SingleFileObserver(file, MASK); + observer.startWatching(); + } + } + return; + } + ConversationsFileObserver.this.onEvent(event, file); + }); } - } } diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index e26010d69..1aea44565 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -14,8 +14,11 @@ * limitations under the License. */ package eu.siacs.conversations.utils; + import android.content.Context; +import android.database.Cursor; import android.net.Uri; +import android.provider.OpenableColumns; import android.util.Log; import java.io.File; @@ -38,6 +41,7 @@ import eu.siacs.conversations.services.ExportBackupService; public final class MimeUtils { private static final Map mimeTypeToExtensionMap = new HashMap<>(); private static final Map extensionToMimeTypeMap = new HashMap<>(); + static { // The following table is based on /etc/mime.types data minus // chemical/* MIME types and MIME types that don't map to any @@ -52,7 +56,8 @@ public final class MimeUtils { // by guessExtensionFromMimeType. add("application/andrew-inset", "ez"); add("application/dsptype", "tsp"); - add("application/epub+zip","epub"); + add("application/epub+zip", "epub"); + add("application/gpx+xml", "gpx"); add("application/hta", "hta"); add("application/mac-binhex40", "hqx"); add("application/mathematica", "nb"); @@ -68,9 +73,9 @@ public final class MimeUtils { add("application/rdf+xml", "rdf"); add("application/rss+xml", "rss"); add("application/zip", "zip"); - add("application/vnd.amazon.mobi8-ebook","azw3"); - add("application/vnd.amazon.mobi8-ebook","azw"); - add("application/vnd.amazon.mobi8-ebook","kfx"); + add("application/vnd.amazon.mobi8-ebook", "azw3"); + add("application/vnd.amazon.mobi8-ebook", "azw"); + add("application/vnd.amazon.mobi8-ebook", "kfx"); add("application/vnd.android.package-archive", "apk"); add("application/vnd.cinderella", "cdy"); add(ExportBackupService.MIME_TYPE, "ceb"); @@ -183,7 +188,7 @@ public final class MimeUtils { add("application/x-maker", "book"); add("application/x-maker", "fbdoc"); add("application/x-mif", "mif"); - add("application/x-mobipocket-ebook","mobi"); + add("application/x-mobipocket-ebook", "mobi"); add("application/x-ms-wmd", "wmd"); add("application/x-ms-wmz", "wmz"); add("application/x-msi", "msi"); @@ -242,7 +247,7 @@ public final class MimeUtils { add("audio/mpeg", "mp2"); add("audio/mpeg", "m4a"); add("audio/mpegurl", "m3u"); - add("audio/ogg","oga"); + add("audio/ogg", "oga"); add("audio/prs.sid", "sid"); add("audio/x-aiff", "aif"); add("audio/x-aiff", "aiff"); @@ -268,7 +273,7 @@ public final class MimeUtils { add("image/ico", "cur"); add("image/ico", "ico"); add("image/ief", "ief"); - add("image/heic","heic"); + add("image/heic", "heic"); // add ".jpg" first so it will be the default for guessExtensionFromMimeType add("image/jpeg", "jpg"); add("image/jpeg", "jpeg"); @@ -367,7 +372,7 @@ public final class MimeUtils { add("video/fli", "fli"); add("video/m4v", "m4v"); add("video/mp2ts", "ts"); - add("video/ogg","ogv"); + add("video/ogg", "ogv"); add("video/mpeg", "mpeg"); add("video/mpeg", "mpg"); add("video/mpeg", "mpe"); @@ -393,6 +398,7 @@ public final class MimeUtils { add("x-epoc/x-sisx-app", "sisx"); applyOverrides(); } + private static void add(String mimeType, String extension) { // If we have an existing x -> y mapping, we do not want to // override it with another mapping x -> y2. @@ -406,6 +412,7 @@ public final class MimeUtils { extensionToMimeTypeMap.put(extension, mimeType); } } + private static InputStream getContentTypesPropertiesStream() { // User override? String userTable = System.getProperty("content.types.user.table"); @@ -428,6 +435,7 @@ public final class MimeUtils { } return null; } + /** * This isn't what the RI does. The RI doesn't have hard-coded defaults, so supplying your * own "content.types.user.table" means you don't get any of the built-ins, and the built-ins @@ -456,10 +464,13 @@ public final class MimeUtils { } catch (IOException ignored) { } } + private MimeUtils() { } + /** * Returns true if the given MIME type has an entry in the map. + * * @param mimeType A MIME type (i.e. text/plain) * @return True iff there is a mimeType entry in the map. */ @@ -469,8 +480,10 @@ public final class MimeUtils { } return mimeTypeToExtensionMap.containsKey(mimeType); } + /** * Returns the MIME type for the given extension. + * * @param extension A file extension without the leading '.' * @return The MIME type for the given extension or null iff there is none. */ @@ -480,8 +493,10 @@ public final class MimeUtils { } return extensionToMimeTypeMap.get(extension.toLowerCase()); } + /** * Returns true if the given extension has a registered MIME type. + * * @param extension A file extension without the leading '.' * @return True iff there is an extension entry in the map. */ @@ -491,10 +506,12 @@ public final class MimeUtils { } return extensionToMimeTypeMap.containsKey(extension); } + /** * Returns the registered extension for the given MIME type. Note that some * MIME types map to multiple extensions. This call will return the most * common extension for the given MIME type. + * * @param mimeType A MIME type (i.e. text/plain) * @return The extension for the given MIME type or null iff there is none. */ @@ -506,7 +523,7 @@ public final class MimeUtils { } public static String guessMimeTypeFromUriAndMime(final Context context, final Uri uri, final String mime) { - Log.d(Config.LOGTAG,"guessMimeTypeFromUriAndMime "+uri+" and mime="+mime); + Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime " + uri + " and mime=" + mime); if (mime == null || mime.equals("application/octet-stream")) { final String guess = guessMimeTypeFromUri(context, uri); if (guess != null) { @@ -515,7 +532,7 @@ public final class MimeUtils { return mime; } } - return guessMimeTypeFromUri(context ,uri); + return guessMimeTypeFromUri(context, uri); } public static String guessMimeTypeFromUri(Context context, Uri uri) { @@ -523,18 +540,20 @@ public final class MimeUtils { String mimeType; try { mimeType = context.getContentResolver().getType(uri); - } catch (Throwable throwable) { + } catch (final Throwable throwable) { mimeType = null; } // try the extension - if ((mimeType == null || mimeType.equals("application/octet-stream")) && uri.getPath() != null) { - String path = uri.getPath(); - int start = path.lastIndexOf('.') + 1; - if (start < path.length()) { - final String guess = MimeUtils.guessMimeTypeFromExtension(path.substring(start)); - if (guess != null) { - mimeType = guess; - } + if (mimeType == null || mimeType.equals("application/octet-stream")) { + final String path = uri.getPath(); + if (path != null) { + mimeType = guessFromPath(path); + } + } + if (mimeType == null && "content".equals(uri.getScheme())) { + final String name = getDisplayName(context, uri); + if (name != null) { + mimeType = guessFromPath(name); } } // sometimes this works (as with the commit content api) @@ -544,6 +563,23 @@ public final class MimeUtils { return mimeType; } + private static String getDisplayName(final Context context, final Uri uri) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } + return null; + } + + private static String guessFromPath(final String path) { + final int start = path.lastIndexOf('.') + 1; + if (start < path.length()) { + return MimeUtils.guessMimeTypeFromExtension(path.substring(start)); + } + return null; + } + public static String extractRelevantExtension(URL url) { String path = url.getPath(); return extractRelevantExtension(path, true); @@ -565,7 +601,7 @@ public final class MimeUtils { String extension = filename.substring(dotPosition + 1); // we want the real file extension, not the crypto one if (ignoreCryptoExtension && Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) { - return extractRelevantExtension(filename.substring(0,dotPosition)); + return extractRelevantExtension(filename.substring(0, dotPosition)); } else { return extension; } diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index 7c1ff5837..bd3121530 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -90,7 +90,6 @@ public class Resolver { return happyEyeball(resolveNoSrvRecords(DNSName.from(hostname), port, true)); } - public static boolean useDirectTls(final int port) { return port == 443 || port == 5223; } diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 238fd0875..bf87dd0bd 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -32,564 +32,566 @@ import eu.siacs.conversations.xmpp.Jid; public class UIHelper { - private static int[] UNSAFE_COLORS = { - 0xFFF44336, //red 500 - 0xFFE53935, //red 600 - 0xFFD32F2F, //red 700 - 0xFFC62828, //red 800 + private static int[] UNSAFE_COLORS = { + 0xFFF44336, //red 500 + 0xFFE53935, //red 600 + 0xFFD32F2F, //red 700 + 0xFFC62828, //red 800 - 0xFFEF6C00, //orange 800 + 0xFFEF6C00, //orange 800 - 0xFFF4511E, //deep orange 600 - 0xFFE64A19, //deep orange 700 - 0xFFD84315, //deep orange 800, - }; + 0xFFF4511E, //deep orange 600 + 0xFFE64A19, //deep orange 700 + 0xFFD84315, //deep orange 800, + }; - private static int[] SAFE_COLORS = { - 0xFFE91E63, //pink 500 - 0xFFD81B60, //pink 600 - 0xFFC2185B, //pink 700 - 0xFFAD1457, //pink 800 + private static int[] SAFE_COLORS = { + 0xFFE91E63, //pink 500 + 0xFFD81B60, //pink 600 + 0xFFC2185B, //pink 700 + 0xFFAD1457, //pink 800 - 0xFF9C27B0, //purple 500 - 0xFF8E24AA, //purple 600 - 0xFF7B1FA2, //purple 700 - 0xFF6A1B9A, //purple 800 + 0xFF9C27B0, //purple 500 + 0xFF8E24AA, //purple 600 + 0xFF7B1FA2, //purple 700 + 0xFF6A1B9A, //purple 800 - 0xFF673AB7, //deep purple 500, - 0xFF5E35B1, //deep purple 600 - 0xFF512DA8, //deep purple 700 - 0xFF4527A0, //deep purple 800, + 0xFF673AB7, //deep purple 500, + 0xFF5E35B1, //deep purple 600 + 0xFF512DA8, //deep purple 700 + 0xFF4527A0, //deep purple 800, - 0xFF3F51B5, //indigo 500, - 0xFF3949AB,//indigo 600 - 0xFF303F9F,//indigo 700 - 0xFF283593, //indigo 800 + 0xFF3F51B5, //indigo 500, + 0xFF3949AB,//indigo 600 + 0xFF303F9F,//indigo 700 + 0xFF283593, //indigo 800 - 0xFF2196F3, //blue 500 - 0xFF1E88E5, //blue 600 - 0xFF1976D2, //blue 700 - 0xFF1565C0, //blue 800 + 0xFF2196F3, //blue 500 + 0xFF1E88E5, //blue 600 + 0xFF1976D2, //blue 700 + 0xFF1565C0, //blue 800 - 0xFF03A9F4, //light blue 500 - 0xFF039BE5, //light blue 600 - 0xFF0288D1, //light blue 700 - 0xFF0277BD, //light blue 800 + 0xFF03A9F4, //light blue 500 + 0xFF039BE5, //light blue 600 + 0xFF0288D1, //light blue 700 + 0xFF0277BD, //light blue 800 - 0xFF00BCD4, //cyan 500 - 0xFF00ACC1, //cyan 600 - 0xFF0097A7, //cyan 700 - 0xFF00838F, //cyan 800 + 0xFF00BCD4, //cyan 500 + 0xFF00ACC1, //cyan 600 + 0xFF0097A7, //cyan 700 + 0xFF00838F, //cyan 800 - 0xFF009688, //teal 500, - 0xFF00897B, //teal 600 - 0xFF00796B, //teal 700 - 0xFF00695C, //teal 800, + 0xFF009688, //teal 500, + 0xFF00897B, //teal 600 + 0xFF00796B, //teal 700 + 0xFF00695C, //teal 800, - //0xFF558B2F, //light green 800 + //0xFF558B2F, //light green 800 - //0xFFC0CA33, //lime 600 - 0xFF9E9D24, //lime 800 + //0xFFC0CA33, //lime 600 + 0xFF9E9D24, //lime 800 - 0xFF795548, //brown 500, - //0xFF4E342E, //brown 800 - 0xFF607D8B, //blue grey 500, - //0xFF37474F //blue grey 800 - }; + 0xFF795548, //brown 500, + //0xFF4E342E, //brown 800 + 0xFF607D8B, //blue grey 500, + //0xFF37474F //blue grey 800 + }; - private static final int[] COLORS; + private static final int[] COLORS; - static { - COLORS = Arrays.copyOf(SAFE_COLORS, SAFE_COLORS.length + UNSAFE_COLORS.length); - System.arraycopy(UNSAFE_COLORS, 0, COLORS, SAFE_COLORS.length, UNSAFE_COLORS.length); - } + static { + COLORS = Arrays.copyOf(SAFE_COLORS, SAFE_COLORS.length + UNSAFE_COLORS.length); + System.arraycopy(UNSAFE_COLORS, 0, COLORS, SAFE_COLORS.length, UNSAFE_COLORS.length); + } - private static final List LOCATION_QUESTIONS = Arrays.asList( - "where are you", //en - "where are you now", //en - "where are you right now", //en - "whats your 20", //en - "what is your 20", //en - "what's your 20", //en - "whats your twenty", //en - "what is your twenty", //en - "what's your twenty", //en - "wo bist du", //de - "wo bist du jetzt", //de - "wo bist du gerade", //de - "wo seid ihr", //de - "wo seid ihr jetzt", //de - "wo seid ihr gerade", //de - "dónde estás", //es - "donde estas" //es - ); + private static final List LOCATION_QUESTIONS = Arrays.asList( + "where are you", //en + "where are you now", //en + "where are you right now", //en + "whats your 20", //en + "what is your 20", //en + "what's your 20", //en + "whats your twenty", //en + "what is your twenty", //en + "what's your twenty", //en + "wo bist du", //de + "wo bist du jetzt", //de + "wo bist du gerade", //de + "wo seid ihr", //de + "wo seid ihr jetzt", //de + "wo seid ihr gerade", //de + "dónde estás", //es + "donde estas" //es + ); - private static final List PUNCTIONATION = Arrays.asList('.', ',', '?', '!', ';', ':'); + private static final List PUNCTIONATION = Arrays.asList('.', ',', '?', '!', ';', ':'); - private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; - private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; + private static final int SHORT_DATE_FLAGS = DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NO_YEAR | DateUtils.FORMAT_ABBREV_ALL; + private static final int FULL_DATE_FLAGS = DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE; - public static String readableTimeDifference(Context context, long time) { - return readableTimeDifference(context, time, false); - } + public static String readableTimeDifference(Context context, long time) { + return readableTimeDifference(context, time, false); + } - public static String readableTimeDifferenceFull(Context context, long time) { - return readableTimeDifference(context, time, true); - } + public static String readableTimeDifferenceFull(Context context, long time) { + return readableTimeDifference(context, time, true); + } - private static String readableTimeDifference(Context context, long time, - boolean fullDate) { - if (time == 0) { - return context.getString(R.string.just_now); - } - Date date = new Date(time); - long difference = (System.currentTimeMillis() - time) / 1000; - if (difference < 60) { - return context.getString(R.string.just_now); - } else if (difference < 60 * 2) { - return context.getString(R.string.minute_ago); - } else if (difference < 60 * 15) { - return context.getString(R.string.minutes_ago, Math.round(difference / 60.0)); - } else if (today(date)) { - java.text.DateFormat df = DateFormat.getTimeFormat(context); - return df.format(date); - } else { - if (fullDate) { - return DateUtils.formatDateTime(context, date.getTime(), - FULL_DATE_FLAGS); - } else { - return DateUtils.formatDateTime(context, date.getTime(), - SHORT_DATE_FLAGS); - } - } - } + private static String readableTimeDifference(Context context, long time, + boolean fullDate) { + if (time == 0) { + return context.getString(R.string.just_now); + } + Date date = new Date(time); + long difference = (System.currentTimeMillis() - time) / 1000; + if (difference < 60) { + return context.getString(R.string.just_now); + } else if (difference < 60 * 2) { + return context.getString(R.string.minute_ago); + } else if (difference < 60 * 15) { + return context.getString(R.string.minutes_ago, Math.round(difference / 60.0)); + } else if (today(date)) { + java.text.DateFormat df = DateFormat.getTimeFormat(context); + return df.format(date); + } else { + if (fullDate) { + return DateUtils.formatDateTime(context, date.getTime(), + FULL_DATE_FLAGS); + } else { + return DateUtils.formatDateTime(context, date.getTime(), + SHORT_DATE_FLAGS); + } + } + } - private static boolean today(Date date) { - return sameDay(date, new Date(System.currentTimeMillis())); - } + private static boolean today(Date date) { + return sameDay(date, new Date(System.currentTimeMillis())); + } - public static boolean today(long date) { - return sameDay(date, System.currentTimeMillis()); - } + public static boolean today(long date) { + return sameDay(date, System.currentTimeMillis()); + } - public static boolean yesterday(long date) { - Calendar cal1 = Calendar.getInstance(); - Calendar cal2 = Calendar.getInstance(); - cal1.add(Calendar.DAY_OF_YEAR, -1); - cal2.setTime(new Date(date)); - return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) - && cal1.get(Calendar.DAY_OF_YEAR) == cal2 - .get(Calendar.DAY_OF_YEAR); - } + public static boolean yesterday(long date) { + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + cal1.add(Calendar.DAY_OF_YEAR, -1); + cal2.setTime(new Date(date)); + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) + && cal1.get(Calendar.DAY_OF_YEAR) == cal2 + .get(Calendar.DAY_OF_YEAR); + } - public static boolean sameDay(long a, long b) { - return sameDay(new Date(a), new Date(b)); - } + public static boolean sameDay(long a, long b) { + return sameDay(new Date(a), new Date(b)); + } - private static boolean sameDay(Date a, Date b) { - Calendar cal1 = Calendar.getInstance(); - Calendar cal2 = Calendar.getInstance(); - cal1.setTime(a); - cal2.setTime(b); - return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) - && cal1.get(Calendar.DAY_OF_YEAR) == cal2 - .get(Calendar.DAY_OF_YEAR); - } + private static boolean sameDay(Date a, Date b) { + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + cal1.setTime(a); + cal2.setTime(b); + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) + && cal1.get(Calendar.DAY_OF_YEAR) == cal2 + .get(Calendar.DAY_OF_YEAR); + } - public static String lastseen(Context context, boolean active, long time) { - long difference = (System.currentTimeMillis() - time) / 1000; - if (active) { - return context.getString(R.string.online_right_now); - } else if (difference < 60) { - return context.getString(R.string.last_seen_now); - } else if (difference < 60 * 2) { - return context.getString(R.string.last_seen_min); - } else if (difference < 60 * 60) { - return context.getString(R.string.last_seen_mins, Math.round(difference / 60.0)); - } else if (difference < 60 * 60 * 2) { - return context.getString(R.string.last_seen_hour); - } else if (difference < 60 * 60 * 24) { - return context.getString(R.string.last_seen_hours, - Math.round(difference / (60.0 * 60.0))); - } else if (difference < 60 * 60 * 48) { - return context.getString(R.string.last_seen_day); - } else { - return context.getString(R.string.last_seen_days, - Math.round(difference / (60.0 * 60.0 * 24.0))); - } - } + public static String lastseen(Context context, boolean active, long time) { + long difference = (System.currentTimeMillis() - time) / 1000; + if (active) { + return context.getString(R.string.online_right_now); + } else if (difference < 60) { + return context.getString(R.string.last_seen_now); + } else if (difference < 60 * 2) { + return context.getString(R.string.last_seen_min); + } else if (difference < 60 * 60) { + return context.getString(R.string.last_seen_mins, Math.round(difference / 60.0)); + } else if (difference < 60 * 60 * 2) { + return context.getString(R.string.last_seen_hour); + } else if (difference < 60 * 60 * 24) { + return context.getString(R.string.last_seen_hours, + Math.round(difference / (60.0 * 60.0))); + } else if (difference < 60 * 60 * 48) { + return context.getString(R.string.last_seen_day); + } else { + return context.getString(R.string.last_seen_days, + Math.round(difference / (60.0 * 60.0 * 24.0))); + } + } - public static int getColorForName(String name) { - return getColorForName(name, false); - } + public static int getColorForName(String name) { + return getColorForName(name, false); + } - public static int getColorForName(String name, boolean safe) { - if (Config.XEP_0392) { - return XEP0392Helper.rgbFromNick(name); - } - if (name == null || name.isEmpty()) { - return 0xFF202020; - } - if (safe) { - return SAFE_COLORS[(int) (getLongForName(name) % SAFE_COLORS.length)]; - } else { - return COLORS[(int) (getLongForName(name) % COLORS.length)]; - } - } + public static int getColorForName(String name, boolean safe) { + if (Config.XEP_0392) { + return XEP0392Helper.rgbFromNick(name); + } + if (name == null || name.isEmpty()) { + return 0xFF202020; + } + if (safe) { + return SAFE_COLORS[(int) (getLongForName(name) % SAFE_COLORS.length)]; + } else { + return COLORS[(int) (getLongForName(name) % COLORS.length)]; + } + } - private static long getLongForName(String name) { - try { - final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); - return Math.abs(new BigInteger(messageDigest.digest(name.getBytes())).longValue()); - } catch (Exception e) { - return 0; - } - } + private static long getLongForName(String name) { + try { + final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + return Math.abs(new BigInteger(messageDigest.digest(name.getBytes())).longValue()); + } catch (Exception e) { + return 0; + } + } - public static Pair getMessagePreview(final Context context, final Message message) { - return getMessagePreview(context, message, 0); - } + public static Pair getMessagePreview(final Context context, final Message message) { + return getMessagePreview(context, message, 0); + } - public static Pair getMessagePreview(final Context context, final Message message, @ColorInt int textColor) { - final Transferable d = message.getTransferable(); - if (d != null) { - switch (d.getStatus()) { - case Transferable.STATUS_CHECKING: - return new Pair<>(context.getString(R.string.checking_x, - getFileDescriptionString(context, message)), true); - case Transferable.STATUS_DOWNLOADING: - return new Pair<>(context.getString(R.string.receiving_x_file, - getFileDescriptionString(context, message), - d.getProgress()), true); - case Transferable.STATUS_OFFER: - 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_FAILED: - return new Pair<>(context.getString(R.string.file_transmission_failed), true); - case Transferable.STATUS_CANCELLED: - return new Pair<>(context.getString(R.string.file_transmission_cancelled), true); - case Transferable.STATUS_UPLOADING: - if (message.getStatus() == Message.STATUS_OFFERED) { - return new Pair<>(context.getString(R.string.offering_x_file, - getFileDescriptionString(context, message)), true); - } else { - return new Pair<>(context.getString(R.string.sending_x_file, - getFileDescriptionString(context, message)), true); - } - default: - return new Pair<>("", false); - } - } else if (message.isFileOrImage() && message.isDeleted()) { - 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) { - return new Pair<>(context.getString(R.string.decryption_failed), true); - } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { - 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.isFileOrImage()) { - return new Pair<>(getFileDescriptionString(context, message), true); - } else if (message.getType() == Message.TYPE_RTP_SESSION) { - RtpSessionStatus rtpSessionStatus = RtpSessionStatus.of(message.getBody()); - final boolean received = message.getStatus() == Message.STATUS_RECEIVED; - if (!rtpSessionStatus.successful && received) { - return new Pair<>(context.getString(R.string.missed_call),true); - } else { - return new Pair<>(context.getString(received ? R.string.incoming_call : R.string.outgoing_call), true); - } - } else { - final String body = MessageUtils.filterLtrRtl(message.getBody()); - if (body.startsWith(Message.ME_COMMAND)) { - return new Pair<>(body.replaceAll("^" + Message.ME_COMMAND, - UIHelper.getMessageDisplayName(message) + " "), false); - } else if (message.isGeoUri()) { - return new Pair<>(context.getString(R.string.location), true); - } else if (message.treatAsDownloadable() || MessageUtils.unInitiatedButKnownSize(message)) { - return new Pair<>(context.getString(R.string.x_file_offered_for_download, - getFileDescriptionString(context, message)), true); - } else { - SpannableStringBuilder styledBody = new SpannableStringBuilder(body); - if (textColor != 0) { - StylingHelper.format(styledBody, 0, styledBody.length() - 1, textColor); - } - 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); - if (line.length() == 0) { - continue; - } - char last = line.charAt(line.length() - 1); - if (builder.length() != 0) { - builder.append(' '); - } - builder.append(line); - if (!PUNCTIONATION.contains(last)) { - break; - } - } - } - } - if (builder.length() == 0) { - builder.append(body.trim()); - } - return new Pair<>(builder, false); - } - } - } + public static Pair getMessagePreview(final Context context, final Message message, @ColorInt int textColor) { + final Transferable d = message.getTransferable(); + if (d != null) { + switch (d.getStatus()) { + case Transferable.STATUS_CHECKING: + return new Pair<>(context.getString(R.string.checking_x, + getFileDescriptionString(context, message)), true); + case Transferable.STATUS_DOWNLOADING: + return new Pair<>(context.getString(R.string.receiving_x_file, + getFileDescriptionString(context, message), + d.getProgress()), true); + case Transferable.STATUS_OFFER: + 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_FAILED: + return new Pair<>(context.getString(R.string.file_transmission_failed), true); + case Transferable.STATUS_CANCELLED: + return new Pair<>(context.getString(R.string.file_transmission_cancelled), true); + case Transferable.STATUS_UPLOADING: + if (message.getStatus() == Message.STATUS_OFFERED) { + return new Pair<>(context.getString(R.string.offering_x_file, + getFileDescriptionString(context, message)), true); + } else { + return new Pair<>(context.getString(R.string.sending_x_file, + getFileDescriptionString(context, message)), true); + } + default: + return new Pair<>("", false); + } + } else if (message.isFileOrImage() && message.isDeleted()) { + 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) { + return new Pair<>(context.getString(R.string.decryption_failed), true); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE) { + 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.isFileOrImage()) { + return new Pair<>(getFileDescriptionString(context, message), true); + } else if (message.getType() == Message.TYPE_RTP_SESSION) { + RtpSessionStatus rtpSessionStatus = RtpSessionStatus.of(message.getBody()); + final boolean received = message.getStatus() == Message.STATUS_RECEIVED; + if (!rtpSessionStatus.successful && received) { + return new Pair<>(context.getString(R.string.missed_call), true); + } else { + return new Pair<>(context.getString(received ? R.string.incoming_call : R.string.outgoing_call), true); + } + } else { + final String body = MessageUtils.filterLtrRtl(message.getBody()); + if (body.startsWith(Message.ME_COMMAND)) { + return new Pair<>(body.replaceAll("^" + Message.ME_COMMAND, + UIHelper.getMessageDisplayName(message) + " "), false); + } else if (message.isGeoUri()) { + return new Pair<>(context.getString(R.string.location), true); + } else if (message.treatAsDownloadable() || MessageUtils.unInitiatedButKnownSize(message)) { + return new Pair<>(context.getString(R.string.x_file_offered_for_download, + getFileDescriptionString(context, message)), true); + } else { + SpannableStringBuilder styledBody = new SpannableStringBuilder(body); + if (textColor != 0) { + StylingHelper.format(styledBody, 0, styledBody.length() - 1, textColor); + } + 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); + if (line.length() == 0) { + continue; + } + char last = line.charAt(line.length() - 1); + if (builder.length() != 0) { + builder.append(' '); + } + builder.append(line); + if (!PUNCTIONATION.contains(last)) { + break; + } + } + } + } + if (builder.length() == 0) { + builder.append(body.trim()); + } + return new Pair<>(builder, false); + } + } + } - public static boolean isLastLineQuote(String body) { - if (body.endsWith("\n")) { - return false; - } - String[] lines = body.split("\n"); - if (lines.length == 0) { - return false; - } - String line = lines[lines.length - 1]; - if (line.isEmpty()) { - return false; - } - char first = line.charAt(0); - return first == '>' && isPositionFollowedByQuoteableCharacter(line,0) || first == '\u00bb'; - } + public static boolean isLastLineQuote(String body) { + if (body.endsWith("\n")) { + return false; + } + String[] lines = body.split("\n"); + if (lines.length == 0) { + return false; + } + String line = lines[lines.length - 1]; + if (line.isEmpty()) { + return false; + } + char first = line.charAt(0); + return first == '>' && isPositionFollowedByQuoteableCharacter(line, 0) || first == '\u00bb'; + } - public static CharSequence shorten(CharSequence input) { - return input.length() > 256 ? StylingHelper.subSequence(input, 0, 256) : input; - } + public static CharSequence shorten(CharSequence input) { + return input.length() > 256 ? StylingHelper.subSequence(input, 0, 256) : input; + } - public static boolean isPositionFollowedByQuoteableCharacter(CharSequence body, int pos) { - return !isPositionFollowedByNumber(body, pos) - && !isPositionFollowedByEmoticon(body, pos) - && !isPositionFollowedByEquals(body, pos); - } + public static boolean isPositionFollowedByQuoteableCharacter(CharSequence body, int pos) { + return !isPositionFollowedByNumber(body, pos) + && !isPositionFollowedByEmoticon(body, pos) + && !isPositionFollowedByEquals(body, pos); + } - private static boolean isPositionFollowedByNumber(CharSequence body, int pos) { - boolean previousWasNumber = false; - for (int i = pos + 1; i < body.length(); i++) { - char c = body.charAt(i); - if (Character.isDigit(body.charAt(i))) { - previousWasNumber = true; - } else if (previousWasNumber && (c == '.' || c == ',')) { - previousWasNumber = false; - } else { - return (Character.isWhitespace(c) || c == '%' || c == '+') && previousWasNumber; - } - } - return previousWasNumber; - } + private static boolean isPositionFollowedByNumber(CharSequence body, int pos) { + boolean previousWasNumber = false; + for (int i = pos + 1; i < body.length(); i++) { + char c = body.charAt(i); + if (Character.isDigit(body.charAt(i))) { + previousWasNumber = true; + } else if (previousWasNumber && (c == '.' || c == ',')) { + previousWasNumber = false; + } else { + return (Character.isWhitespace(c) || c == '%' || c == '+') && previousWasNumber; + } + } + return previousWasNumber; + } - private static boolean isPositionFollowedByEquals(CharSequence body, int pos) { - return body.length() > pos + 1 && body.charAt(pos + 1) == '='; - } + private static boolean isPositionFollowedByEquals(CharSequence body, int pos) { + return body.length() > pos + 1 && body.charAt(pos + 1) == '='; + } - private static boolean isPositionFollowedByEmoticon(CharSequence body, int pos) { - if (body.length() <= pos + 1) { - return false; - } else { - final char first = body.charAt(pos + 1); - return first == ';' - || first == ':' - || closingBeforeWhitespace(body, pos + 1); - } - } + private static boolean isPositionFollowedByEmoticon(CharSequence body, int pos) { + if (body.length() <= pos + 1) { + return false; + } else { + final char first = body.charAt(pos + 1); + return first == ';' + || first == ':' + || closingBeforeWhitespace(body, pos + 1); + } + } - private static boolean closingBeforeWhitespace(CharSequence body, int pos) { - for (int i = pos; i < body.length(); ++i) { - final char c = body.charAt(i); - if (Character.isWhitespace(c)) { - return false; - } else if (c == '<' || c == '>') { - return body.length() == i + 1 || Character.isWhitespace(body.charAt(i + 1)); - } - } - return false; - } + private static boolean closingBeforeWhitespace(CharSequence body, int pos) { + for (int i = pos; i < body.length(); ++i) { + final char c = body.charAt(i); + if (Character.isWhitespace(c)) { + return false; + } else if (c == '<' || c == '>') { + return body.length() == i + 1 || Character.isWhitespace(body.charAt(i + 1)); + } + } + return false; + } - public static boolean isPositionFollowedByQuote(CharSequence body, int pos) { - if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) { - return false; - } - boolean previousWasWhitespace = false; - for (int i = pos + 1; i < body.length(); i++) { - char c = body.charAt(i); - if (c == '\n' || c == '»') { - return false; - } else if (c == '«' && !previousWasWhitespace) { - return true; - } else { - previousWasWhitespace = Character.isWhitespace(c); - } - } - return false; - } + public static boolean isPositionFollowedByQuote(CharSequence body, int pos) { + if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) { + return false; + } + boolean previousWasWhitespace = false; + for (int i = pos + 1; i < body.length(); i++) { + char c = body.charAt(i); + if (c == '\n' || c == '»') { + return false; + } else if (c == '«' && !previousWasWhitespace) { + return true; + } else { + previousWasWhitespace = Character.isWhitespace(c); + } + } + return false; + } - public static String getDisplayName(MucOptions.User user) { - Contact contact = user.getContact(); - if (contact != null) { - return contact.getDisplayName(); - } else { - final String name = user.getName(); - if (name != null) { - return name; - } - final Jid realJid = user.getRealJid(); - if (realJid != null) { - return JidHelper.localPartOrFallback(realJid); - } - return null; - } - } + public static String getDisplayName(MucOptions.User user) { + Contact contact = user.getContact(); + if (contact != null) { + return contact.getDisplayName(); + } else { + final String name = user.getName(); + if (name != null) { + return name; + } + final Jid realJid = user.getRealJid(); + if (realJid != null) { + return JidHelper.localPartOrFallback(realJid); + } + return null; + } + } - public static String concatNames(List users) { - return concatNames(users, users.size()); - } + public static String concatNames(List users) { + return concatNames(users, users.size()); + } - public static String concatNames(List users, int max) { - StringBuilder builder = new StringBuilder(); - final boolean shortNames = users.size() >= 3; - for (int i = 0; i < Math.min(users.size(), max); ++i) { - if (builder.length() != 0) { - builder.append(", "); - } - final String name = UIHelper.getDisplayName(users.get(i)); - if (name != null) { - builder.append(shortNames ? name.split("\\s+")[0] : name); - } - } - return builder.toString(); - } + public static String concatNames(List users, int max) { + StringBuilder builder = new StringBuilder(); + final boolean shortNames = users.size() >= 3; + for (int i = 0; i < Math.min(users.size(), max); ++i) { + if (builder.length() != 0) { + builder.append(", "); + } + final String name = UIHelper.getDisplayName(users.get(i)); + if (name != null) { + builder.append(shortNames ? name.split("\\s+")[0] : name); + } + } + return builder.toString(); + } - public static String getFileDescriptionString(final Context context, final Message message) { - if (message.getType() == Message.TYPE_IMAGE) { - return context.getString(R.string.image); - } - final String mime = message.getMimeType(); - if (mime == null) { - return context.getString(R.string.file); - } else if (mime.startsWith("audio/")) { - return context.getString(R.string.audio); - } else if (mime.startsWith("video/")) { - return context.getString(R.string.video); - } else if (mime.equals("image/gif")) { - return context.getString(R.string.gif); - } else if (mime.startsWith("image/")) { - return context.getString(R.string.image); - } else if (mime.contains("pdf")) { - return context.getString(R.string.pdf_document); - } else if (mime.equals("application/vnd.android.package-archive")) { - return context.getString(R.string.apk); - } else if (mime.equals(ExportBackupService.MIME_TYPE)) { - return context.getString(R.string.conversations_backup); - } else if (mime.contains("vcard")) { - return context.getString(R.string.vcard); - } else if (mime.equals("text/x-vcalendar") || mime.equals("text/calendar")) { - return context.getString(R.string.event); - } else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) { - return context.getString(R.string.ebook); - } else { - return mime; - } - } + public static String getFileDescriptionString(final Context context, final Message message) { + if (message.getType() == Message.TYPE_IMAGE) { + return context.getString(R.string.image); + } + final String mime = message.getMimeType(); + if (mime == null) { + return context.getString(R.string.file); + } else if (mime.startsWith("audio/")) { + return context.getString(R.string.audio); + } else if (mime.startsWith("video/")) { + return context.getString(R.string.video); + } else if (mime.equals("image/gif")) { + return context.getString(R.string.gif); + } else if (mime.startsWith("image/")) { + return context.getString(R.string.image); + } else if (mime.contains("pdf")) { + return context.getString(R.string.pdf_document); + } else if (mime.equals("application/vnd.android.package-archive")) { + return context.getString(R.string.apk); + } else if (mime.equals(ExportBackupService.MIME_TYPE)) { + return context.getString(R.string.conversations_backup); + } else if (mime.contains("vcard")) { + return context.getString(R.string.vcard); + } else if (mime.equals("text/x-vcalendar") || mime.equals("text/calendar")) { + return context.getString(R.string.event); + } else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) { + return context.getString(R.string.ebook); + } else if (mime.equals("application/gpx+xml")) { + return context.getString(R.string.gpx_track); + } else { + return mime; + } + } - public static String getMessageDisplayName(final Message message) { - final Conversational conversation = message.getConversation(); - if (message.getStatus() == Message.STATUS_RECEIVED) { - final Contact contact = message.getContact(); - if (conversation.getMode() == Conversation.MODE_MULTI) { - if (contact != null) { - return contact.getDisplayName(); - } else { - return getDisplayedMucCounterpart(message.getCounterpart()); - } - } else { - return contact != null ? contact.getDisplayName() : ""; - } - } else { - if (conversation instanceof Conversation && conversation.getMode() == Conversation.MODE_MULTI) { - return ((Conversation) conversation).getMucOptions().getSelf().getName(); - } else { - final Jid jid = conversation.getAccount().getJid(); - return jid.getLocal() != null ? jid.getLocal() : jid.getDomain().toString(); - } - } - } + public static String getMessageDisplayName(final Message message) { + final Conversational conversation = message.getConversation(); + if (message.getStatus() == Message.STATUS_RECEIVED) { + final Contact contact = message.getContact(); + if (conversation.getMode() == Conversation.MODE_MULTI) { + if (contact != null) { + return contact.getDisplayName(); + } else { + return getDisplayedMucCounterpart(message.getCounterpart()); + } + } else { + return contact != null ? contact.getDisplayName() : ""; + } + } else { + if (conversation instanceof Conversation && conversation.getMode() == Conversation.MODE_MULTI) { + return ((Conversation) conversation).getMucOptions().getSelf().getName(); + } else { + final Jid jid = conversation.getAccount().getJid(); + return jid.getLocal() != null ? jid.getLocal() : jid.getDomain().toString(); + } + } + } - public static String getMessageHint(Context context, Conversation conversation) { - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_NONE: - if (Config.multipleEncryptionChoices()) { - return context.getString(R.string.send_unencrypted_message); - } else { - return context.getString(R.string.send_message_to_x, conversation.getName()); - } - case Message.ENCRYPTION_AXOLOTL: - AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); - if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { - return context.getString(R.string.send_omemo_x509_message); - } else { - return context.getString(R.string.send_omemo_message); - } - case Message.ENCRYPTION_PGP: - return context.getString(R.string.send_pgp_message); - default: - return ""; - } - } + public static String getMessageHint(Context context, Conversation conversation) { + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_NONE: + if (Config.multipleEncryptionChoices()) { + return context.getString(R.string.send_unencrypted_message); + } else { + return context.getString(R.string.send_message_to_x, conversation.getName()); + } + case Message.ENCRYPTION_AXOLOTL: + AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); + if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { + return context.getString(R.string.send_omemo_x509_message); + } else { + return context.getString(R.string.send_omemo_message); + } + case Message.ENCRYPTION_PGP: + return context.getString(R.string.send_pgp_message); + default: + return ""; + } + } - public static String getDisplayedMucCounterpart(final Jid counterpart) { - if (counterpart == null) { - return ""; - } else if (!counterpart.isBareJid()) { - return counterpart.getResource().trim(); - } else { - return counterpart.toString().trim(); - } - } + public static String getDisplayedMucCounterpart(final Jid counterpart) { + if (counterpart == null) { + return ""; + } else if (!counterpart.isBareJid()) { + return counterpart.getResource().trim(); + } else { + return counterpart.toString().trim(); + } + } - public static boolean receivedLocationQuestion(Message message) { - if (message == null - || message.getStatus() != Message.STATUS_RECEIVED - || message.getType() != Message.TYPE_TEXT) { - return false; - } - String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault()); - body = body.replace("?", "").replace("¿", ""); - return LOCATION_QUESTIONS.contains(body); - } + public static boolean receivedLocationQuestion(Message message) { + if (message == null + || message.getStatus() != Message.STATUS_RECEIVED + || message.getType() != Message.TYPE_TEXT) { + return false; + } + String body = message.getBody() == null ? null : message.getBody().trim().toLowerCase(Locale.getDefault()); + body = body.replace("?", "").replace("¿", ""); + return LOCATION_QUESTIONS.contains(body); + } - public static ListItem.Tag getTagForStatus(Context context, Presence.Status status) { - switch (status) { - case CHAT: - return new ListItem.Tag(context.getString(R.string.presence_chat), 0xff259b24); - case AWAY: - return new ListItem.Tag(context.getString(R.string.presence_away), 0xffff9800); - case XA: - return new ListItem.Tag(context.getString(R.string.presence_xa), 0xfff44336); - case DND: - return new ListItem.Tag(context.getString(R.string.presence_dnd), 0xfff44336); - default: - return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24); - } - } + public static ListItem.Tag getTagForStatus(Context context, Presence.Status status) { + switch (status) { + case CHAT: + return new ListItem.Tag(context.getString(R.string.presence_chat), 0xff259b24); + case AWAY: + return new ListItem.Tag(context.getString(R.string.presence_away), 0xffff9800); + case XA: + return new ListItem.Tag(context.getString(R.string.presence_xa), 0xfff44336); + case DND: + return new ListItem.Tag(context.getString(R.string.presence_dnd), 0xfff44336); + default: + return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24); + } + } - public static String filesizeToString(long size) { - if (size > (1.5 * 1024 * 1024)) { - return Math.round(size * 1f / (1024 * 1024)) + " MiB"; - } else if (size >= 1024) { - return Math.round(size * 1f / 1024) + " KiB"; - } else { - return size + " B"; - } - } + public static String filesizeToString(long size) { + if (size > (1.5 * 1024 * 1024)) { + return Math.round(size * 1f / (1024 * 1024)) + " MiB"; + } else if (size >= 1024) { + return Math.round(size * 1f / 1024) + " KiB"; + } else { + return size + " B"; + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index aa6323f68..19fa4a431 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -683,7 +683,9 @@ public class XmppConnection implements Runnable { if (Config.BACKGROUND_STANZA_LOGGING && mXmppConnectionService.checkListeners()) { Log.d(Config.LOGTAG, "[background stanza] " + element); } - if (element instanceof IqPacket && element.hasChild("jingle", Namespace.JINGLE)) { + if (element instanceof IqPacket + && (((IqPacket) element).getType() == IqPacket.TYPE.SET) + && element.hasChild("jingle", Namespace.JINGLE)) { return JinglePacket.upgrade((IqPacket) element); } else { return element; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 03bafb275..67ff221b7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -456,6 +456,21 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } + public boolean fireJingleRtpConnectionStateUpdates() { + boolean firedUpdates = false; + for (final AbstractJingleConnection connection : this.connections.values()) { + if (connection instanceof JingleRtpConnection) { + final JingleRtpConnection jingleRtpConnection = (JingleRtpConnection) connection; + if (jingleRtpConnection.isTerminated()) { + continue; + } + jingleRtpConnection.fireStateUpdate(); + firedUpdates = true; + } + } + return firedUpdates; + } + void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) { if (Config.DISABLE_PROXY_LOOKUP) { listener.onPrimaryCandidateFound(false, null); @@ -576,6 +591,18 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } + public boolean hasMatchingProposal(final Account account, final Jid with) { + synchronized (this.rtpSessionProposals) { + for (Map.Entry entry : this.rtpSessionProposals.entrySet()) { + final RtpSessionProposal proposal = entry.getKey(); + if (proposal.account == account && with.asBareJid().equals(proposal.with)) { + return true; + } + } + } + return false; + } + public void deliverIbbPacket(Account account, IqPacket packet) { final String sid; final Element payload; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 830695831..bc3487ad6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -239,7 +239,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } final Set> candidates = contentMap.contents.entrySet(); if (this.state == State.SESSION_ACCEPTED) { - processCandidates(candidates); + try { + processCandidates(candidates); + } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": PeerConnection was not initialized when processing transport info. this usually indicates a race condition that can be ignored"); + } } else { pendingIceCandidates.push(candidates); } @@ -807,7 +811,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return RtpEndUserState.CONNECTING; } case SESSION_ACCEPTED: - final PeerConnection.PeerConnectionState state = webRTCWrapper.getState(); + final PeerConnection.PeerConnectionState state; + try { + state = webRTCWrapper.getState(); + } catch (final WebRTCWrapper.PeerConnectionNotInitialized e) { + //We usually close the WebRTCWrapper *before* transitioning so we might still + //be in SESSION_ACCEPTED even though the peerConnection has been torn down + return RtpEndUserState.ENDING_CALL; + } if (state == PeerConnection.PeerConnectionState.CONNECTED) { return RtpEndUserState.CONNECTED; } else if (state == PeerConnection.PeerConnectionState.NEW || state == PeerConnection.PeerConnectionState.CONNECTING) { @@ -1256,6 +1267,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.proposedMedia = media; } + public void fireStateUpdate() { + final RtpEndUserState endUserState = getEndUserState(); + xmppConnectionService.notifyJingleRtpConnectionUpdate(id.account, id.with, id.sessionId, endUserState); + } + private interface OnIceServersDiscovered { void onIceServersDiscovered(List iceServers); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java index 58dfa4e5f..454bb7a73 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -552,7 +552,7 @@ public class WebRTCWrapper { private PeerConnection requirePeerConnection() { final PeerConnection peerConnection = this.peerConnection; if (peerConnection == null) { - throw new IllegalStateException("initialize PeerConnection first"); + throw new PeerConnectionNotInitialized(); } return peerConnection; } @@ -617,6 +617,14 @@ public class WebRTCWrapper { } } + public static class PeerConnectionNotInitialized extends IllegalStateException { + + private PeerConnectionNotInitialized() { + super("initialize PeerConnection first"); + } + + } + private static class CapturerChoice { private final CameraVideoCapturer cameraVideoCapturer; private final CameraEnumerationAndroid.CaptureFormat captureFormat; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java index 2195ba47b..21b1d5089 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java @@ -29,6 +29,7 @@ public class JinglePacket extends IqPacket { public static JinglePacket upgrade(final IqPacket iqPacket) { Preconditions.checkArgument(iqPacket.hasChild("jingle", Namespace.JINGLE)); + Preconditions.checkArgument(iqPacket.getType() == TYPE.SET); final JinglePacket jinglePacket = new JinglePacket(); jinglePacket.setAttributes(iqPacket.getAttributes()); jinglePacket.setChildren(iqPacket.getChildren()); @@ -70,11 +71,11 @@ public class JinglePacket extends IqPacket { public ReasonWrapper getReason() { final Element reasonElement = getJingleChild("reason"); if (reasonElement == null) { - return new ReasonWrapper(Reason.UNKNOWN,null); + return new ReasonWrapper(Reason.UNKNOWN, null); } String text = null; Reason reason = Reason.UNKNOWN; - for(Element child : reasonElement.getChildren()) { + for (Element child : reasonElement.getChildren()) { if ("text".equals(child.getName())) { text = child.getContent(); } else { diff --git a/src/main/res/drawable-hdpi/baseline_tour_black_48.png b/src/main/res/drawable-hdpi/baseline_tour_black_48.png new file mode 100644 index 000000000..3652cab68 Binary files /dev/null and b/src/main/res/drawable-hdpi/baseline_tour_black_48.png differ diff --git a/src/main/res/drawable-hdpi/baseline_tour_white_48.png b/src/main/res/drawable-hdpi/baseline_tour_white_48.png new file mode 100644 index 000000000..b1f85b798 Binary files /dev/null and b/src/main/res/drawable-hdpi/baseline_tour_white_48.png differ diff --git a/src/main/res/drawable-mdpi/baseline_tour_black_48.png b/src/main/res/drawable-mdpi/baseline_tour_black_48.png new file mode 100644 index 000000000..10e2511d1 Binary files /dev/null and b/src/main/res/drawable-mdpi/baseline_tour_black_48.png differ diff --git a/src/main/res/drawable-mdpi/baseline_tour_white_48.png b/src/main/res/drawable-mdpi/baseline_tour_white_48.png new file mode 100644 index 000000000..d220b1447 Binary files /dev/null and b/src/main/res/drawable-mdpi/baseline_tour_white_48.png differ diff --git a/src/main/res/drawable-xhdpi/baseline_tour_black_48.png b/src/main/res/drawable-xhdpi/baseline_tour_black_48.png new file mode 100644 index 000000000..1cb89f437 Binary files /dev/null and b/src/main/res/drawable-xhdpi/baseline_tour_black_48.png differ diff --git a/src/main/res/drawable-xhdpi/baseline_tour_white_48.png b/src/main/res/drawable-xhdpi/baseline_tour_white_48.png new file mode 100644 index 000000000..80dae18b6 Binary files /dev/null and b/src/main/res/drawable-xhdpi/baseline_tour_white_48.png differ diff --git a/src/main/res/drawable-xxhdpi/baseline_tour_black_48.png b/src/main/res/drawable-xxhdpi/baseline_tour_black_48.png new file mode 100644 index 000000000..ade2fdb4d Binary files /dev/null and b/src/main/res/drawable-xxhdpi/baseline_tour_black_48.png differ diff --git a/src/main/res/drawable-xxhdpi/baseline_tour_white_48.png b/src/main/res/drawable-xxhdpi/baseline_tour_white_48.png new file mode 100644 index 000000000..03dc2a066 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/baseline_tour_white_48.png differ diff --git a/src/main/res/drawable-xxxhdpi/baseline_tour_black_48.png b/src/main/res/drawable-xxxhdpi/baseline_tour_black_48.png new file mode 100644 index 000000000..d5c00c8af Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/baseline_tour_black_48.png differ diff --git a/src/main/res/drawable-xxxhdpi/baseline_tour_white_48.png b/src/main/res/drawable-xxxhdpi/baseline_tour_white_48.png new file mode 100644 index 000000000..bf6ee424c Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/baseline_tour_white_48.png differ diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index aea2260b5..0da16ab13 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -363,7 +363,6 @@ مكسر حالة الحضور غائب عند إطفاء الشاشة - \"لا تزعجني\" في وضع السكوت إعدادات الربط الموسعة عرض اسم المضيف وإعدادات المنفذ عند تنصيب حساب xmpp.example.com diff --git a/src/main/res/values-bg/strings.xml b/src/main/res/values-bg/strings.xml index 8b9f2c6a4..00e7b3c3b 100644 --- a/src/main/res/values-bg/strings.xml +++ b/src/main/res/values-bg/strings.xml @@ -368,11 +368,7 @@ Повредено Присъствие Отсъстващ, когато екранът е изключен - Преминава в състояние „отсъстващ“ когато екранът бъде изключен - „Отпочиващ“ в тих режим - Преминава в състояние „отсъстващ“, когато устройството е в тих режим Тих режим при режим на вибриране - Преминава в състояние „отсъстващ“, когато устройството е в режим на вибриране. Разширени настройки за връзката Показване на настройките за сървър и порт при установка на профил xmpp.example.com diff --git a/src/main/res/values-ca/strings.xml b/src/main/res/values-ca/strings.xml index 1f999f224..f5a3db49d 100644 --- a/src/main/res/values-ca/strings.xml +++ b/src/main/res/values-ca/strings.xml @@ -366,11 +366,7 @@ Trencat Disponibilitat Fora quan la pantalla està apagada - Marqueu el vostre recurs quan la pantalla estigui desactivada - \"No molesteu\" en modo silenciós - Marqueu el vostre recurs com \"No molesteu\" quan el dispositiu estigui en modo silenciós Tracteu de vibrar al modo silenciós - Marqueu el vostre recurs com \"No molesteu\" quan el dispositiu estigui en vibració Configuració de connexió estesa Mostra el nom de la màquina i la configuració del port quan configureu un compte xmpp.example.com diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index c231f9bee..103c03430 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -316,7 +316,6 @@ Bind chyba Rozbité Pryč při vypnuté obrazovce - Při vypnuté obrazovce označí váš stav jako pryč Vibrační mód brát stejně jako tichý Rozšířená nastavení připojení Zobrazovat nastavení hostname a port při vytváření účtu diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index eec74d269..e79d6e9ed 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -460,12 +460,12 @@ Der Server ist nicht für diese Domain verantwortlich Fehlerhaft Status - Abwesend bei abgeschaltetem Bildschirm - Setzt deinen Status auf \"abwesend\", solange dein Bildschirm abgeschaltet ist - \"Bitte nicht stören\" bei Stummschaltung - Setzt deinen Status auf \"Bitte nicht stören\", solange dein Gerät stummgeschaltet ist + Abwesend bei ausgeschaltetem Bildschirm + Als abwesend anzeigen, wenn der Bildschirm ausgeschaltet ist + Beschäftigt im lautlosen Modus + Als Beschäftigt anzeigen, wenn sich das Gerät im lautlosen Modus befindet Vibration als Lautlos behandeln - Setzt deinen Status auf \"Bitte nicht stören\", solange das Gerät auf Vibration geschaltet ist + Als Beschäftigt anzeigen, wenn das Gerät auf Vibration eingestellt ist Erweiterte Verbindungsoptionen Hostname- und Port-Optionen bei Kontoeinrichtung anzeigen xmpp.domain.de @@ -923,6 +923,8 @@ Kamera konnte nicht gewechselt werden Zu Favoriten hinzufügen Von Favoriten entfernen + GPX-Strecke + Nachricht konnte nicht korrigiert werden %1$d Teilnehmer anzeigen %1$d Teilnehmer anzeigen diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index c0be73378..6948212ed 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -402,11 +402,7 @@ Χαλασμένος Διαθεσιμότητα Εκτός χρήσης όταν η οθόνη είναι σβηστή - Σημειώνει την παρουσία σας ως εκτός χρήσης όταν σβήνει η οθόνη - \"Μην ενοχλείτε\" όταν βρίσκεται σε σιωπηρή λειτουργία - Σημειώνει την παρουσία σας ως \"Μην ενοχλείτε\" όταν η συσκευή είναι σε κατάσταση σιωπής Χρήση της κατάστασης δόνησης ως σιωπηρή κατάσταση - Σημειώνει την παρουσία σας ως \"Μην ενοχλείτε\" όταν η συσκευή είναι σε κατάσταση δόνησης Περισσότερες ρυθμίσεις σύνδεσης Εμφάνιση ονόματος μηχανήματος και ρυθμίσεων πόρτας όταν δημιουργείται ένας νέος λογαριασμός xmpp.example.com diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index fa8f782cc..1daf200c5 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -461,11 +461,11 @@ Error Disponibilidad Ausente con pantalla apagada - Cambia tu estado a ausente cuando la pantalla está apagada - \"No molestar\" en modo silencio - Cambia tu estado a \"no molestar\" cuando el dispositivo está en modo silencio + Mostrar como Ausente cuando la pantalla esté apagada + Ocupado en modo silencio + Mostrar como Ocupado cuando el dispositivo esté en modo silencio Modo vibración como modo silencio - Cambia tu estado a \"no molestar\" cuando el dispositivo está en modo vibración + Mostrar como Ocupado cuando el dispositivo esté en modo vibración Opciones de conexión Mostrar el hostname y el puerto cuando se está creando una cuenta xmpp.ejemplo.com @@ -923,6 +923,8 @@ No se ha podido cambiar de cámara Añadir a los favoritos Eliminar de favoritos + Recorrido GPX + No se pudo corregir el mensaje Ver %1$d Participante Ver %1$d Participantes diff --git a/src/main/res/values-eu/strings.xml b/src/main/res/values-eu/strings.xml index 687db96db..bf595e4a5 100644 --- a/src/main/res/values-eu/strings.xml +++ b/src/main/res/values-eu/strings.xml @@ -399,11 +399,7 @@ Hondatuta Eskuragarritasuna Urrun pantaila itzalita dagoenean - Zure baliabidea urrun bezala markatzen du pantaila itzalita dagoenean - \"Ez gogaitu\" modu isilean - Zure baliabidea \"Ez gogaitu\" bezala markatzen du gailua modu isilean dagoenean Dardara modu isila bezala tratatu - Zure baliabidea \"Ez gogaitu\" bezala markatzen du gailua dardara moduan dagoenean Konexioaren ezarpen luzatuak Ostalariaren izena eta ataka ezarpenak erakutsi kontu bat ezartzerakoan xmpp.adibidea.com diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index 0ef628729..a6ebebacf 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -460,11 +460,7 @@ Détraqué Disponibilité Absent quand l\'écran est éteint - Marquer cette ressource comme absente quand l\'écran est éteint. - Indisponible en mode silencieux - Marquer cette ressource comme Indisponible quand l\'appareil est en mode silencieux Indisponible en mode vibreur - Marquer cette ressource comme Indisponible quand l\'appareil est en mode vibreur Paramètres de connexion avancés Montrer le nom d\'hôte et le port lors du paramétrage d\'un compte xmpp.example.com diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index eafeb2478..a3c7a63e7 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -461,11 +461,11 @@ Roto Disponibilidade Non dispoñible cando se apaga a pantalla - Marca o seu recurso como non dipoñíble cando se apaga a pantalla - \"Non molestar\" en modo silencioso - Marca o seu recurso como \"Non molestar\" cando o dispositivo está en modo silencioso + Mostrar como Fóra cando se apaga a pantalla + En modolo silencioso, Ocupado + Mostrar como Ocupado se o dispositivo está en silencio Trata a vibración como modo silencioso - Marca o seu recurso como \"Non molestar\" cando o dispositivo está en modo vibración + Mostrar como Ocupado cando o dispositivo está en vibración Axustes ampliados de conexión Mostar axustes de servidor e porto cando se configura unha conta xmpp.exemplo.com @@ -923,6 +923,8 @@ Non se puido activar a cámara Engadir a favoritas Eliminar das favoritas + Ruta GPX + No se pode correxir a mensaxe Ver %1$d Participante Ver %1$d Participantes diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index 055499efe..0aff10aa7 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -443,11 +443,7 @@ Törött Elérhetőség „Távol”, ha a kijelző ki van kapcsolva - „Távol” állapotként jelöli az erőforrást, ha a kijelző ki van kapcsolva - „Ne zavarjon” csendes módban - „Ne zavarjon” állapotként jelöli az erőforrást, ha az eszköz csendes módban van Rezgés kezelése csendes módként - „Ne zavarjon” állapotként jelöli az erőforrást, ha az eszköz rezgő módban van Kiterjesztett kapcsolati beállítások Gépnév és port beállításainak megjelenítése egy fiók beállításakor xmpp.example.com diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 7ea9504ca..c42c050d0 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -401,6 +401,7 @@ Segna come già letto Input Invio spedisce + Usa il tasto Invio per spedire il messaggio. Puoi sempre usare Ctrl+Invio per spedire, anche se questa opzione è disattivata. Mostra il tasto invio Cambia il tasto delle faccine nel tasto di invio audio @@ -460,11 +461,7 @@ Rotto Disponibilità \"Non disponibile\" a schermo spento - Imposta la tua risorsa come non disponibile quando lo schermo è spento - \"Non disturbare\" in modalità silenziosa - Segna la tua risorsa come \"Non disturbare\" quando il dispositivo è in modalità silenziosa Tratta vibrazione come modalità silenziosa - Segna la tua risorsa come \"Non disturbare\" quando il dispositivo ha solo la vibrazione Impostazioni estese di connessione Mostra nome host e impostazioni della porta quando configuri un account xmpp.esempio.it @@ -915,6 +912,7 @@ Chiamata vocale Chiamata video Aiuto + Passa alla conversazione Il tuo microfono non è disponibile Puoi fare solo una chiamata alla volta. Torna alla chiamata in corso diff --git a/src/main/res/values-iw/strings.xml b/src/main/res/values-iw/strings.xml index 2427c5098..e12b2b5b3 100644 --- a/src/main/res/values-iw/strings.xml +++ b/src/main/res/values-iw/strings.xml @@ -266,7 +266,6 @@ ההורדה נכשלה: נכשל ביצוע חיבור לשרת לא עובד העבר למצב \"לא נמצא\" כאשר המסך כבוי - מעביר את המכשיר לסטטוס \"לא נמצא\" כאשר המסך כבוי חידוש תעודה שגיאה בתפיסת OMEMO! התחבר דרך Tor diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 36f223084..870ac2ac2 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -367,11 +367,7 @@ 壊れています 可用性 画面がオフのときは離席 - 画面がオフになっているとき、リソースを離席としてマークします - サイレントモード時 “邪魔しないで” - デバイスがサイレントモードの時に、リソースを “邪魔しないで” としてマークします バイブレートをサイレントモードとして扱う - デバイスがバイブレートの時に、リソースを “邪魔しないで” としてマークします 拡張接続設定 アカウントを設定するときにホスト名とポートの設定を表示します xmpp.example.com diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml index f37f07a33..b8ae856be 100644 --- a/src/main/res/values-ko/strings.xml +++ b/src/main/res/values-ko/strings.xml @@ -306,7 +306,6 @@ 바인드 실패 손상됨 화면이 꺼져있을 경우 자리 비움으로 표시 - 화면이 꺼져있을 경우에 자리 비움으로 상태를 표시함 진동을 자동으로 처리 확장 연결 설정 계정을 설정할 때 호스트 이름과 포트 설정을 표시합니다 diff --git a/src/main/res/values-nb-rNO/strings.xml b/src/main/res/values-nb-rNO/strings.xml index 4ecdbcbc5..c237a8c79 100644 --- a/src/main/res/values-nb-rNO/strings.xml +++ b/src/main/res/values-nb-rNO/strings.xml @@ -335,11 +335,7 @@ Klarte ikke å binde Knekt Borte når skjermen er av - Markerer din ressurs som borte når skjermen er avskrudd - “Ikke forstyrr” i stillemodus. - Merker din ressurs som \"Ikke forstyrr\" når enheten er i stillemodus. Behandle vibrering som stille-modus - Merker din ressurs som \"Ikke forstyrr\" når enheten er skrudd til vibrering Utvidede tilkoblingsinnst. Vis vertsnavn og portinnstillinger når du setter opp en ny konto xmpp.eksempel.no diff --git a/src/main/res/values-nl/strings.xml b/src/main/res/values-nl/strings.xml index 02f5d2e06..23bea3fa6 100644 --- a/src/main/res/values-nl/strings.xml +++ b/src/main/res/values-nl/strings.xml @@ -411,11 +411,7 @@ Gebroken Aanwezigheid Even weg wanneer scherm uit staat - Stelt je bron in als even weg wanneer het scherm uitgeschakeld is - “Niet storen” in stille modus - Stelt je bron in als “Niet storen” wanneer je apparaat in stille modus staat Trillen behandelen als stille modus - Stelt je bron in als “Niet storen” wanneer je apparaat in trilmodus staat Uitgebreide verbindingsinstellingen Toon hostnaam- en poortinstellingen bij instellen van een account xmpp.voorbeeld.be diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index bd17261a4..c27c4b484 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -462,11 +462,7 @@ Zepsute Dostępność Status \"Oddalony\" gdy wyświetlacz jest wyłączony - Oznacza twój zasób jako \"Oddalony\", gdy wyświetlacz jest wyłączony - \"Nie przeszkadzać\" w trybie cichym - Oznacza twój zasób jako \"Nie przeszkadzać\", gdy urządzenie jest w trybie cichym Traktuj tryb wibracji jak tryb cichy - Oznacza twój zasób jako \"Nie przeszkadzać\", gdy urządzenie jest w trybie wibracji Rozszerzone ustawienia połączenia Pokaż nazwę hosta i ustawienia portu przy dodawaniu konta xmpp.example.com diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 7d3d968be..aaadafa17 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -401,6 +401,7 @@ Marcar como lida Entrada Enter envia + Use a tecla Enter para enviar mensagens. Você sempre pode usar Ctrl+Enter para enviar mensagens, mesmo se essa opção estiver desabilitada. Exibir o botão Enter Altera o botão de emoticons para um botão Enter. áudio @@ -459,12 +460,12 @@ O servidor não responde por esse domínio Quebrado Disponibilidade - Afastado quando a tela estiver desligada - Define o seu status como afastado quando a tela estiver desligada. - \"Não perturbe\" em modo silencioso - Define o seu status como \"Não perturbe\" quando o dispositivo estiver em modo silencioso + \"Afastado\" quando a tela estiver desligada + Exibe como Afastado quando a tela for desligada + \"Ocupado\" no modo silencioso + Exibe como Ocupado quando o dispositivo estiver em modo silencioso Considerar o modo de vibração como silencioso - Define o seu status como \"Não perturbe\" quando o dispositivo estiver no modo de vibração + Exibe como Ocupado quando o dispositivo estiver em vibratório Configurações detalhadas da conexão Exibe o nome de host e configurações da porta ao configurar uma conta. xmpp.example.com @@ -900,6 +901,7 @@ Tocando Ocupado Não foi possível conectar à chamada + Conexão perdida Chamada rejeitada Falha no aplicativo Desligar @@ -913,12 +915,16 @@ Chamada perdida Chamada de áudio Chamada de vídeo + Ajuda + Alternar para a conversa Seu microfone não está disponível Você só pode ter uma chamada de cada vez Retornar para a chamada em andamento Não foi possível trocar a câmera Adicionar aos favoritos Remover dos favoritos + Trilha GPX + Não foi possível corrigir a mensagem Ver %1$d participante Ver %1$d participantes diff --git a/src/main/res/values-pt/strings.xml b/src/main/res/values-pt/strings.xml index 1a49ef2d9..48ac1af22 100644 --- a/src/main/res/values-pt/strings.xml +++ b/src/main/res/values-pt/strings.xml @@ -330,8 +330,6 @@ Existe um problema Disponibilidade Ausente quando o ecrã está desligado - Define o seu recurso como ausente quando o ecrã está desligado - \"Não incomodar\" em modo silencioso Tratar vibrar como modo silencioso Definições de conexão Mostrar as definições do hostname e do porto ao configurar uma conta diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index 8910ba10b..ac36077c6 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -463,10 +463,10 @@ Setări de disponibilitate Plecat când ecranul este oprit Prezintă clientul drept \"Plecat\" atunci când ecranul este oprit - \"Nu deranja\" în modul silențios - Prezintă clientul drept \"Nu deranja\" atunci când dispozitivul este în mod silențios + \"Ocupat\" în modul silențios + Prezintă clientul drept \"Ocupat\" atunci când dispozitivul este în mod silențios Tratează modul vibrație ca silențios - Prezintă clientul drept \"Nu deranja\" atunci când dispozitivul este în mod vibrație + Prezintă clientul drept \"Ocupat\" atunci când dispozitivul este în mod vibrație Opțiuni avansate conexiune Arată opțiunea de setare a numelui de gazdă și a portului, atunci când se configurează un cont xmpp.exemplu.ro @@ -931,6 +931,8 @@ Nu s-a putut face comutarea camerei foto Adaugă la favorite Înlătură din favorite + Traseu GPX + Nu s-a putut corecta mesajul Arată %1$d participant Arată %1$d participanți diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 890d2d519..b4328ad26 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -53,7 +53,7 @@ Изменить пароль на сервере Поделиться с Начать беседу - Пригласить собеседника + Пригласить контакт Пригласить Контакты Контакт @@ -298,6 +298,8 @@ размещено на %s Проверка %s на сервере HTTP Вы неподключены. Попробуйте позже + Проверить размер %s + Проверить размер %1$s на %2$s Опции сообщения Цитировать Вставить как цитату @@ -399,6 +401,7 @@ Прочитано Ввод Отправить на \"Enter\" + Отправлять сообщения клавишей Enter. Даже если эта опция отключена, сообщение можно отправить, нажав Ctrl+Enter. Показывать клавишу ввода Поменять кнопку смайликов на кнопку ввода аудио @@ -460,11 +463,7 @@ Повреждено Доступность \"Отошёл\" когда экран выключен - Устанавливает статус \"Отошёл\", когда экран выключен - \"Не беспокоить\" в беззвучном режиме - Устанавливает статус \"Не беспокоить\", когда устройство в беззвучном режиме Не доступен в режиме вибрации - Устанавливает статус \"Не беспокоить\", когда устройство на вибрации Расширенные настройки подключения Показывать имя сервера и порт в настройках аккаунтов xmpp.example.com diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 69102e87b..b89041668 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -349,11 +349,7 @@ Оштећен Доступност Одсутан кад је екран искључен - Означавање вашег ресурса као одсутан кад је екран искључен - „Не ометај“ у нечујном режиму - Означавање вашег ресурса као „не ометај“ кад је уређај у нечујном режиму Вибрација је нечујни режим - Означавање вашег ресурса као „не ометај“ кад је уређај у режиму вибрирања Проширене поставке повезивања Приказ домаћина и порта у поставкама налога xmpp.primer.com diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 2929dba55..95c39ba06 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -370,8 +370,6 @@ Sönder Tillgänglighet Status borta när skärmen är av - Sätter din tillgänglighet till borta när skrämen är av - \"Stör ej\" i tyst läge Hantera vibrationsläge som tyst läge Utökade anslutningsinställningar Visa val av servernamn och port vid inställning av konto diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 23421ad9e..00fe5d929 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -321,7 +321,6 @@ Bağlantı başarısız Bozuk Ekran kapandığında uzakta - Ekran kapandığında çevrim içi durum bildiriminizi uzakta olarak değiştirir Titreşim kipini sessiz kip olarak değerlendir Genişletilmiş bağlantı seçenekleri Hesap oluştururken sunucu adıyla bağlantı noktası seçeneğini göster diff --git a/src/main/res/values-uk/strings.xml b/src/main/res/values-uk/strings.xml index 2f41c4e8c..9d02b5ff9 100644 --- a/src/main/res/values-uk/strings.xml +++ b/src/main/res/values-uk/strings.xml @@ -367,7 +367,7 @@ Увімкнути всі облікові записи Вимкнути всі облікові записи Здійснити дію з - Непов\'язаний + Учасник Поза мережею Вигнанець Учасник @@ -380,7 +380,7 @@ Відкликати права власника Вилучити з групи Прибрати з каналу - Неможливо змінити статус приєднання з %s + Неможливо змінити статус участі %s Заборонити доступ до групи Вилучити з каналу Ви намагаєтеся вилучити %s з публічного каналу. Єдиним способом для цього є заблокувати користувача назавжди. @@ -401,6 +401,7 @@ Позначити як прочитане Введення Enter для надсилання + Використовувати кнопку Enter для надсилання повідомлень. Якщо цей параметр вимкнено, повідомлення можна надсилати за допомогою комбінації Ctrl-Enter. Показувати кнопку Enter Змінити клавішу емоційок на кнопку Enter аудіо @@ -462,11 +463,7 @@ Поламано Доступність Відійшов, якщо екран вимкнено - Показувати стан \"відійшов\", якщо екран вимкнено - \"Не турбувати\" в режимі тиші - Додавати \"Не турбувати\" до імені вашого пристрою, коли пристрій в режимі тиші Вважати вібро за безшумний режим - Додавати \"Не турбувати\" до імені вашого пристрою, коли пристрій в режимі вібрації Розширені налаштування з\'єднання Показати налаштування імені вузла та порту при налаштуванні облікового запису xmpp.example.com diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index ff68350b9..adcd79e96 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -290,7 +290,6 @@ Mạng Tor chưa sẵn sàng Bị hỏng Vắng mặt khi màn hình tắt - Hiện ứng dụng là \'vắng mặt\' khi màn hình tắt Gia hạn chứng nhận Lỗi nhập khoá OMEMO! Khoá OMEMO đã xác minh với chứng nhận! diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 27e8cd01c..cdd7e736a 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -401,6 +401,7 @@ 标记为已读 输入 点击回车发送 + 使用Enter键发送消息。即使禁用此选项,也可以使用Ctrl+Enter发送消息。 显示回车键 将表情键改为回车键 音频 @@ -459,11 +460,7 @@ 损坏 可用性 锁屏时显示离开 - 锁屏时标记为离开状态 - 静音模式时设为“请勿打扰” - 当设备静音时标记为“请勿打扰”状态 将振动看作静音 - 使用振动模式时标记为“请勿打扰”状态 高级连接设置 注册账户时显示主机名和端口 xmpp.example.com @@ -907,6 +904,7 @@ 语音通话 视频通话 帮助 + 切换到对话 麦克风不可用 只能同时打一通电话 返回正在进行的通话 diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml index f3447d8cc..94e26a277 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -309,7 +309,6 @@ 綁定失敗 損壞 關閉螢幕時離開 - 當螢幕關閉時將標記您的資源為離開狀態 靜音模式開啟振動 高級連接設置 註冊帳戶時顯示主機名稱和埠 diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index e67517049..fc05b1696 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -66,6 +66,7 @@ + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index f4cf32218..eb624be38 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -461,11 +461,11 @@ Broken Availability Away when screen is off - Marks your resource as away when the screen is turned off - “Do not disturb” in silent mode - Marks your resource as “Do not disturb” when device is in silent mode + Show as Away when the screen is turned off + Busy in silent mode + Show as Busy when device is in silent mode Treat vibrate as silent mode - Marks your resource as “Do not disturb” when device is on vibrate + Show as Busy when device is on vibrate Extended connection settings Show hostname and port settings when setting up an account xmpp.example.com @@ -925,6 +925,8 @@ Could not switch camera Add to favorites Remove from favorites + GPX track + Could not correct message View %1$d Participant View %1$d Participants diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index d227f3eba..201cecab5 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -85,6 +85,7 @@ @drawable/ic_mic_black_48dp @drawable/ic_headset_black_48dp @drawable/ic_room_black_48dp + @drawable/baseline_tour_black_48 @drawable/ic_person_black_48dp @drawable/ic_android_black_48dp @drawable/ic_event_black_48dp @@ -240,6 +241,7 @@ @drawable/ic_mic_white_48dp @drawable/ic_headset_white_48dp @drawable/ic_room_white_48dp + @drawable/baseline_tour_white_48 @drawable/ic_person_white_48dp @drawable/ic_android_white_48dp @drawable/ic_event_white_48dp