From 994fd9ecad36058b64ac31f1f76fb0ee8af3237e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 16 Jul 2020 11:25:25 +0200 Subject: [PATCH 01/18] restore backup in one transaction --- .../services/ImportBackupService.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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) { From 32d55346ccd81a6474b77d974db6b2643fd9f341 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 18 Jul 2020 16:14:05 +0200 Subject: [PATCH 02/18] ensure server triggered jingle iq-errors get routed properly --- .../java/eu/siacs/conversations/xmpp/XmppConnection.java | 4 +++- .../conversations/xmpp/jingle/stanzas/JinglePacket.java | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7ae1d3b3f..9929a9c81 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -710,7 +710,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/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 { From 28856aaf9fb572a2d1fbf647047b5dfb3ec1cf83 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 19 Jul 2020 21:27:43 +0200 Subject: [PATCH 03/18] add icons for gpx files --- .../ui/adapter/MediaAdapter.java | 13 +- .../conversations/ui/util/Attachment.java | 12 + .../siacs/conversations/utils/MimeUtils.java | 37 +- .../siacs/conversations/utils/UIHelper.java | 1022 +++++++++-------- .../drawable-hdpi/baseline_tour_black_48.png | Bin 0 -> 369 bytes .../drawable-hdpi/baseline_tour_white_48.png | Bin 0 -> 372 bytes .../drawable-mdpi/baseline_tour_black_48.png | Bin 0 -> 273 bytes .../drawable-mdpi/baseline_tour_white_48.png | Bin 0 -> 277 bytes .../drawable-xhdpi/baseline_tour_black_48.png | Bin 0 -> 451 bytes .../drawable-xhdpi/baseline_tour_white_48.png | Bin 0 -> 451 bytes .../baseline_tour_black_48.png | Bin 0 -> 662 bytes .../baseline_tour_white_48.png | Bin 0 -> 662 bytes .../baseline_tour_black_48.png | Bin 0 -> 809 bytes .../baseline_tour_white_48.png | Bin 0 -> 809 bytes src/main/res/values/attrs.xml | 1 + src/main/res/values/strings.xml | 1 + src/main/res/values/themes.xml | 2 + 17 files changed, 564 insertions(+), 524 deletions(-) create mode 100644 src/main/res/drawable-hdpi/baseline_tour_black_48.png create mode 100644 src/main/res/drawable-hdpi/baseline_tour_white_48.png create mode 100644 src/main/res/drawable-mdpi/baseline_tour_black_48.png create mode 100644 src/main/res/drawable-mdpi/baseline_tour_white_48.png create mode 100644 src/main/res/drawable-xhdpi/baseline_tour_black_48.png create mode 100644 src/main/res/drawable-xhdpi/baseline_tour_white_48.png create mode 100644 src/main/res/drawable-xxhdpi/baseline_tour_black_48.png create mode 100644 src/main/res/drawable-xxhdpi/baseline_tour_white_48.png create mode 100644 src/main/res/drawable-xxxhdpi/baseline_tour_black_48.png create mode 100644 src/main/res/drawable-xxxhdpi/baseline_tour_white_48.png 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 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 +54,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 +71,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 +186,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 +245,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 +271,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 +370,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 +396,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 +410,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 +433,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 +462,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 +478,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 +491,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 +504,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 +521,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 +530,7 @@ public final class MimeUtils { return mime; } } - return guessMimeTypeFromUri(context ,uri); + return guessMimeTypeFromUri(context, uri); } public static String guessMimeTypeFromUri(Context context, Uri uri) { @@ -565,7 +580,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/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/res/drawable-hdpi/baseline_tour_black_48.png b/src/main/res/drawable-hdpi/baseline_tour_black_48.png new file mode 100644 index 0000000000000000000000000000000000000000..3652cab68064566102120c5a2b07e60b5e22ed8d GIT binary patch literal 369 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V6^abaSW+oe0z;?nUkYP`^STa z4j5&8o4la)-2oZo{U2V~3@d0>>jCTij+? zo1$#Hg!g8^wr%$3zB#ZT?J%7%_f7w1-V2e}`1-Zqf>=6Bwtp;`V>;PBFZuK19ELAG zxnN5ClD5%3{XNX3&Q*VVzH7gCxIE#W(c`~+-yAo-p!zX=pYraMx?>k~C*-MTw^Z2P zeAxOu`?|xf#W_mZuYUipDi+KCHBF4oVO<#Gl(IF9r|hpWTxEDFpWRR*>X3O{YQdjm v-hf?%!@|zf6MfYFG#)6-k#U+KK8h!_67b8Hy+({sCG_UaQwj8@7D^?@myie zySKHkIBLOzv#cL?U2!;&`QTYk#pX8-(;H`BKN6kBUd3^zM)96_!5qnqJD<$!8GE)| zUCydi%k=rwTI~ei>eW{qmhu+x&zb&y{?xOTY2T04%V&SuDxzU`jp^vIs0C9Uej8o8 zQoLs6^N8T{8<#za`2ELKp^-(%W5E}YXTUyUnhB* z*r&kL6mwW%xk9|3<@0x^EzfA4_+E3c`}XbIe0w)TR?3;%ND^lZ+G#m48@YWM7^Y?%Bq=JC_B z%q7b<_fIjG-e5F0H=%wmuf>dWsvGK0v+dbn;MeZ|fi+r%!L5Ti>_x#L>wWJd{z}z` zu-9x{SI2#qi{Y8(=Lr_aUp2V@zViRp{A5O;9~vD0UEBRUfQ^#_4j9DSwTiP@>Uiyd O@(iA?elF{r5}E*CV{8-v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d220b1447547bc173a0cf9cf29e1ba447e4fbf07 GIT binary patch literal 277 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtJDx6%Ar*{orZcj&I7qaLCkj4e zNM!83Aj{O!Ct&5^{z9O73ZGy?zkt;NnS_gW9FsmO&s+ZgBjdA=4|`{}>&o?~TR$md z(tE&k=gcdnxtknV{8JnIf3bO`HqNZ&Ub}!@b;SV&r47rHxpWlfeA(l$rEj|C1+`sl zuPbIUDP9b{dHl5S3E|lM!#|sJ1Jrj_9sW5{_XF!qPk)BhAK6aNVA}1%TO{DXEtcTB z_@C6=E39=30_wSYUolVC?dAaSW+oe0xLDr^!*`_(N`% zDkXsqw%;8kizYfK2z9bBP0V;-`$e$#iy*IC^TY+>dK+Ipczo>a-t+m@pDpL@v{&|= zq~aOWy6?XYBZC410}F#g0|Ns`X+FcPdom8Yf1kO|e&JnQ@MQ*@c2g*@CpZ(@A-p9*u?q}G~s+Zq?{VX#)w*AWi{`yJl^1n5F z~h3z<=?!$J_}K5C0~X zG&sJxx8u<7*)JG2U)fvQ`u+aruS(1^LEEc-`c3`UmKo7dr2q0;nCSic?(9EKeJ^=@ z{e1D$`A%&U8MzLH{^vY>O8(zbiQksL0@^AIKGxPxd>`?5Z?_@ag>TD%K9h4eoNa%M z;a0z4gV8YtB_LHB!;s9g;0{}ZVIFrv596Ev%)np}U|?WkZ~z7eFrXoUgH6x@7=V-B ay=M2VIWv3vZ^KLmAnVC?dAaSW+oe0xoBf>WSK`$vTn znjD8(YYy4)G%N5NR^T{X`u(JO%o+8Vh?t0)4{N9ByqSG(uW|abx$n)3|DLJ&u%CJA zBo)s|F5Qhh2@GHL8i1ta0S2Y*{LDvgNhq8%H!yOTQ*N$s?F_@m$I|CN@;>{)R$S9; zx$mIN{e+*#t}~QBU^vtG^097%@CQ+YeVI%q2jnFF6`$s?VDHI)&untwQS0n$SIke# zBwT;EuK4Z8vr-SPeOwoK)b~aT-v-?sZ$q+cjM-<1-PyLnbYCX3$UU=O*?49j4RcRf z1_|*yX3OswRyUL$%w<1S%XIh*<2u>*QcUHqB9`%f*zl_0`n)Q=o6LV0^7ZTNrC((> zcs^q|&$0Sj^$kYeudl~O|lO-B!FRH)WDd+zXwWmgWIPw!p|?@O1TaS?83{1ORF`wl)9& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ade2fdb4dc8fa215bd8410ff02dd8edd2f9a581a GIT binary patch literal 662 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=Dgt~$T!Hj}0MQd39YBjUN`m}? z8U7y*f6dXYv44KBg@R4=^3VA!JoVcJWKK_Bz4>wc?RE*1=jZ*e&n{oOj(ZCO1LIOp z7srqa#<#av^Ozk4TrR%5aoXl|+PA;{63@<5xyWu^^Gm&Al2=U2JL?t!r$4$43``sf zjSNg4Hq+iZN@pzNG5J2Fu_lO(Yf7V25Zf$G=Cih`3iGElGNmqfy3(w0s`d($vC!1u%Nmi}TyR*bW>Pp#{wRHu4Q4%InzXJCb z_(Vx;G5NB(3dj)Ivg)OO?aO&vBr<%bNqt|k)Ir*L+N(>pnTi*ttr30a$=ztQY1(de zU6xlTqn^#%BGB<>={wK=`YWDy24!C8Tcc`oF-&TUdbXi|iNT=-ZEIX@LUnkoCg~bK zUm2AsF#kl*(aTE@E!dnYa(B-1+g!~*oc0^*-hYwM;#l+dt>n+NjV2xXZTmv?f0nJa z=r|vL+U&LHZm*+`XO5@sSAMnmZQI>kR)=#gX=j6u-f~!LCb0aLLv-$f=0LVb5z;HJ zvONls&RES`VsOeqCYyQdEr&T-%(+J$KW$FCnUuTW+q`oc42&!S4tNM)sBlFzsbGA2i#3ngQNZQmyBnu%PN#kQ>o4)_OqGl5)-}J>D<*lxw7j!!5pepW+rYrY zq0q>{N+VP1f(cU^(@r@Q`%9is zPhH?3siBg(V5ZT9JCoVZF1GNztfTK$_I$EQMx12T`n)?!9Hg$4eOX&q;1?xfBK0e9 zZ-Gyg#1@k;tE+$vku9rU`q#dkw?!hucbe4qB}*Noou|FJWSgmYVcHtecb?phMw_PX zR@Y^Dbu#MNye$G9Z_d1p}Og}ybaHW$OBwy0+t`j;3STF|z})h1Mj$7+(U z@$;2Yi30Oa1RcG+^w5IMsUmmhEWgdw{KIL#vF`mB2`!E_f8R>}OxtMEq2IPIRR3q$ zT8obJ@u$sRi|+P1>UidO+J5C%o8PwG&1H2s=aP0d=;$qnwPpg#Z#hKgE@%#9dlVtP z;wsyt5b2E7yd?&w9AvVYx88D?lf|5S)bZ2iw3|t}3%<=er@_F;BH)0B0ERlpLiM$( W3iYz-fh&NK%;4$j=d#Wzp$PzyiBvQI literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5c00c8afe7e86a49f95403ab078b5df4df91076 GIT binary patch literal 809 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE8Azrw%`pX16#+gWu0VP~fanR24xq&vB|(0{ z4F3;@zvk%H*kA5%pG#P{Fr0<#@HUpIIzQystFgk|ye+v%b88)_IBm@GV9 z978G?-<;pc%j77+dXWA9jFa|BH8M6!u9c+*{B)h9;yEcUo=3tU;Xnf;GaHYMdBTAM z^Y^}NY+|o^(8z03@Sst;V!?w(xrzj4ewm5~Y;qbE3)tmu9AIXb+tJ9*CHJC{-R#4G zM)tf15G}r+7S8LN#@n_~gXjIy9!C~08P(85U;pJ-ygMhe*!NQ5#U9RH)a2sX9h)yW)EKYeG2>8EGU|D;ERsD=+%zalMUSy=dH7=Pt-eb z{nG8Nr($PtTdYf6pj4iy_du=r>D-tCM!TYf$`esDxHD8Psbwc>JusN;p_UGm*1Nb( zlDn9(*X6cCs3V(=$}C@fAGRw(xpU)Y@Jy(9wi{@(R40#gfY%mLAxpCdS{J8zJYkj$ z@Yuo{6lwH8^P-x^)k%z@6FgKeN$N3pcW!aFa%U@XWV3!d;T*djgTCY$MRjkskYdRf zH7o8Xv>1r}l@| ze3S@2%}^6!+nuc$WIyXSqq1?$fpW{Y4DwT$6Sgu(y=9Pi%dlMbL30tqnlgvgvJ8u5 zA8cW+3E*qE%9pW~IbjR)oU#V~Q;ZvKF|I9R=qYQs%=f|3k|E4`!Qb5bd@P_;%JMUs szet*8gQ66+H2Z)ZnmdKI;Vst0A3>moB#j- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..bf6ee424cc73718a00d84ac28f2b5a94886144fb GIT binary patch literal 809 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE8Azrw%`pX16#+gWu0Z<#K!9T_o~!}dtx*!> z7tHYgaQJJEZjJrr{uT;0!S(0g=SzJ5-7YZO;q!Ha=Q7pt>rGe|Z@--$TEC%&fq}`w z)5S5Qg7MAyoxDtrBCH45|IavSpHw4bv*cP?YQRs|Nh+R`;^KKE3=$4BFfz08*qA3A zI52|Am$8rjW0 z9B5?EdjQem`)T34zG=K|3pIG&FYR$;@sd#uUG()|e#N_UGK+mL6>d!XpZ_8DPtAdp zjWLp&x6OY0qxEOQ-F;K`pLu3blxX(gh2N*JpUr}zIS0Pz$ctX>Xg%4`J#pSTyZS`E z1J^Iz-g+u_2Din!)CEfAiFyyznxD>%DPXiKN~k;$HG?}t<&s)T{Td0d^u7&^g2^^&9>gLmf^cPn?c5=S=cw-e5>>oMp{o>5fyW(z5n zd{N`#rz_d;PG#>*%}+)t3~GCuel?wuk#5M-lV59F^tqAO;VajtYj4V2>Zb8A?0RZ{ zXw657;L{8>A-3JwnnCunelsc?*BmIfe9Is|g*jm>bJSY~iMI^PWgj#bF{~+bSS`!2 zSoXmd=9&P$hO2xTTbUEKFwZG#;6KH<;TGfCGKQYAhRb{(94#5btQY*vz0b!2Oe_p6 vKco4Jq**p7N`ZLj-~l_j2%J9wm?V}k%vf*wXpyv25=hw7)z4*}Q$iB}pp$xd literal 0 HcmV?d00001 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 1e46347c6..9b6568a15 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -925,6 +925,7 @@ Could not switch camera Add to favorites Remove from favorites + GPX track 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 8def180d8..c9d20fcd8 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 From 16300727d1d6e1e03a0177bd024c28d699a144e3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 21 Jul 2020 10:19:21 +0200 Subject: [PATCH 04/18] try to guess mime type via extension in display name --- .../siacs/conversations/utils/MimeUtils.java | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 530310f0b..1aea44565 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -16,7 +16,9 @@ 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; @@ -538,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) @@ -559,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); From 8b26c60f00ef4539d16827d54a0564112c73bb00 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 26 Jul 2020 11:35:09 +0200 Subject: [PATCH 05/18] update gradle plugin --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 636184d05..c6b88a858 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' } } 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 From 9bec186292bf0b59889e49838c529873f620c2fd Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 26 Jul 2020 13:46:01 +0200 Subject: [PATCH 06/18] ignore RTP session logs when looking for LMC. fixes #3843 --- src/main/java/eu/siacs/conversations/entities/Message.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From 1c66772202499755e3347183ff884789418d9697 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 26 Jul 2020 16:30:02 +0200 Subject: [PATCH 07/18] rename DnD to Busy in settings. fixes #3839 --- src/main/res/values/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 9b6568a15..983523a07 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 From 6941d5edd1e6466408048f7de9649cf4ee276641 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 26 Jul 2020 16:39:48 +0200 Subject: [PATCH 08/18] ignore IQ result when MAM query had been killed --- .../services/MessageArchiveService.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) 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); } From 9cd4e1d581453bb16bacbfe43168a0956c1e9156 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 26 Jul 2020 17:03:51 +0200 Subject: [PATCH 09/18] show toast when correction fails --- .../siacs/conversations/ui/ConversationFragment.java | 10 ++++++---- src/main/res/values/strings.xml | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index ce4cc008e..a8fecc6f7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -2753,8 +2753,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 @@ -2763,7 +2765,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); } @@ -2776,7 +2778,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); } @@ -2788,7 +2790,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/res/values/strings.xml b/src/main/res/values/strings.xml index 983523a07..c6a77aaa9 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -926,6 +926,7 @@ Add to favorites Remove from favorites GPX track + Could not correct message View %1$d Participant View %1$d Participants From e10b182d6b58ec7be18d6c0351e401d5123dd03a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 27 Jul 2020 17:21:09 +0200 Subject: [PATCH 10/18] version bump to 2.8.10-beta + changelog --- CHANGELOG.md | 6 ++++++ build.gradle | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) 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 c6b88a858..871c67aac 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 395 - versionName "2.8.9" + versionCode 396 + versionName "2.8.10-beta" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From f5f9075da2c22f899a91c092965270e0f31dac52 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 Jul 2020 12:55:19 +0200 Subject: [PATCH 11/18] FileObserver: start monitoring new directories when they are created --- .../services/XmppConnectionService.java | 13 ++-- .../utils/ConversationsFileObserver.java | 63 ++++++++++++------- 2 files changed, 45 insertions(+), 31 deletions(-) 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/utils/ConversationsFileObserver.java b/src/main/java/eu/siacs/conversations/utils/ConversationsFileObserver.java index 00da3d022..8bf36ea1f 100644 --- a/src/main/java/eu/siacs/conversations/utils/ConversationsFileObserver.java +++ b/src/main/java/eu/siacs/conversations/utils/ConversationsFileObserver.java @@ -6,8 +6,11 @@ import android.util.Log; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Stack; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import eu.siacs.conversations.Config; @@ -19,6 +22,9 @@ import eu.siacs.conversations.Config; */ public abstract class ConversationsFileObserver { + private static final Executor EVENT_EXECUTOR = Executors.newSingleThreadExecutor(); + private static final int MASK = FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.CREATE; + private final String path; private final List 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); + }); } - } } From 5ecd250565155b92715e131e67422e57623690be Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 Jul 2020 13:02:25 +0200 Subject: [PATCH 12/18] pulled translations from transifex --- src/conversations/res/values-it/strings.xml | 3 ++- src/conversations/res/values-pt-rBR/strings.xml | 3 ++- src/conversations/res/values-ru/strings.xml | 3 ++- src/conversations/res/values-zh-rCN/strings.xml | 3 ++- src/main/res/values-ar/strings.xml | 1 - src/main/res/values-bg/strings.xml | 4 ---- src/main/res/values-ca/strings.xml | 4 ---- src/main/res/values-cs/strings.xml | 1 - src/main/res/values-de/strings.xml | 12 +++++++----- src/main/res/values-el/strings.xml | 4 ---- src/main/res/values-es/strings.xml | 10 ++++++---- src/main/res/values-eu/strings.xml | 4 ---- src/main/res/values-fr/strings.xml | 4 ---- src/main/res/values-gl/strings.xml | 10 ++++++---- src/main/res/values-hu/strings.xml | 4 ---- src/main/res/values-it/strings.xml | 6 ++---- src/main/res/values-iw/strings.xml | 1 - src/main/res/values-ja/strings.xml | 4 ---- src/main/res/values-ko/strings.xml | 1 - src/main/res/values-nb-rNO/strings.xml | 4 ---- src/main/res/values-nl/strings.xml | 4 ---- src/main/res/values-pl/strings.xml | 4 ---- src/main/res/values-pt-rBR/strings.xml | 16 +++++++++++----- src/main/res/values-pt/strings.xml | 2 -- src/main/res/values-ro-rRO/strings.xml | 8 +++++--- src/main/res/values-ru/strings.xml | 9 ++++----- src/main/res/values-sr/strings.xml | 4 ---- src/main/res/values-sv/strings.xml | 2 -- src/main/res/values-tr-rTR/strings.xml | 1 - src/main/res/values-uk/strings.xml | 9 +++------ src/main/res/values-vi/strings.xml | 1 - src/main/res/values-zh-rCN/strings.xml | 6 ++---- src/main/res/values-zh-rTW/strings.xml | 1 - 33 files changed, 54 insertions(+), 99 deletions(-) diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml index ccaf6889d..51388be7d 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 conversations.i 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 - \ No newline at end of file + Codice di approvvigionamento formattato male + \ No newline at end of file diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml index d2cacc255..d233046dc 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 - \ No newline at end of file + Código de provisionamento formatado de maneira imprópria + \ No newline at end of file diff --git a/src/conversations/res/values-ru/strings.xml b/src/conversations/res/values-ru/strings.xml index 49a3a5ecb..0c6e5a163 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-адрес. Ваше приглашение - \ No newline at end of file + Неправильный формат кода + \ No newline at end of file diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/src/conversations/res/values-zh-rCN/strings.xml index 0457d02b4..b1edc3b21 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地址来与他们进行交流。 你的服务器邀请 - \ No newline at end of file + 格式不正确的配置代码 + \ No newline at end of file diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index d1395e762..c0a1851cd 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 6349bd0d8..2b7e749e2 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 b65925ff2..e9d2fa29b 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 d301dabaa..03893364d 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 db1eceb20..3cd0f8b7b 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 7180d5256..03f17ff5a 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 ddddc0310..701f8f21f 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 610cc7d60..bb7f98a4e 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 de11cb3eb..aad58e3a5 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 c78987f5a..b2323e52f 100644 --- a/src/main/res/values-zh-rTW/strings.xml +++ b/src/main/res/values-zh-rTW/strings.xml @@ -309,7 +309,6 @@ 綁定失敗 損壞 關閉螢幕時離開 - 當螢幕關閉時將標記您的資源為離開狀態 靜音模式開啟振動 高級連接設置 註冊帳戶時顯示主機名稱和埠 From 47e3504a026d381ae76ba48388b0fa8a6025ba4f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 Jul 2020 16:56:26 +0200 Subject: [PATCH 13/18] remove stale bot configuration --- .github/stale.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/stale.yml 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 From f22e33e3ea4aef71b638e823249c73ae7db92d3e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2020 08:20:08 +0200 Subject: [PATCH 14/18] fixed race condition of WebRTCWrapper being closed before transitioning into terminal state JingleRTPConnection shuts down the WebRTCWrapper before transitioning into a terminal state. (This allows us to make sure it is actually closed when reaching that state); However that means that, when we get a UI redrawn inbetween closing and transitioning we might still be in SESSION_ACCEPTED but with no PeerConnection. This traditionally has triggered an IllegalStateException on getting the EndUserState. This commit catches the ISE and returns 'ENDING' instead. Chances are that this is only visibiliy for a very brief time in the UI before the transition triggers the UI to redraw with the proper state. fixes #3848 --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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..efc6d6b49 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -807,7 +807,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 IllegalStateException 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) { From 1ae7d6be1602cdbda6cb55f3a9bb4142b1c3f9df Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2020 09:50:51 +0200 Subject: [PATCH 15/18] recover from pre-jingle connection states (discover etc) into full fledged jingle connection fixes #3847 --- .../conversations/ui/RtpSessionActivity.java | 12 +++++++-- .../xmpp/jingle/JingleConnectionManager.java | 27 +++++++++++++++++++ .../xmpp/jingle/JingleRtpConnection.java | 5 ++++ 3 files changed, 42 insertions(+), 2 deletions(-) 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/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 efc6d6b49..ec4c67d64 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1263,6 +1263,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); } From 637c0cb15a04cdafc875895128c1d381ce162030 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2020 14:18:00 +0200 Subject: [PATCH 16/18] fixed rare race condition when receiving transport info right after WebRTCWrapper closes fixes #3849 --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 8 ++++++-- .../siacs/conversations/xmpp/jingle/WebRTCWrapper.java | 10 +++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) 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 ec4c67d64..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); } @@ -810,7 +814,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final PeerConnection.PeerConnectionState state; try { state = webRTCWrapper.getState(); - } catch (final IllegalStateException e) { + } 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; 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; From 129f43a34991ea85c64f14e4874879fe7ddb1738 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2020 15:03:20 +0200 Subject: [PATCH 17/18] verify hostname is valid before letting user save it --- .../conversations/ui/EditAccountActivity.java | 14 +++++++++----- .../eu/siacs/conversations/utils/Resolver.java | 13 +++++++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) 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/utils/Resolver.java b/src/main/java/eu/siacs/conversations/utils/Resolver.java index b7e866a80..2afec6225 100644 --- a/src/main/java/eu/siacs/conversations/utils/Resolver.java +++ b/src/main/java/eu/siacs/conversations/utils/Resolver.java @@ -74,8 +74,8 @@ public class Resolver { } } - public static List fromHardCoded(String hostname, int port) { - Result result = new Result(); + public static List fromHardCoded(final String hostname, final int port) { + final Result result = new Result(); result.hostname = DNSName.from(hostname); result.port = port; result.directTls = useDirectTls(port); @@ -83,6 +83,15 @@ public class Resolver { return Collections.singletonList(result); } + public static boolean invalidHostname(final String hostname) { + try { + DNSName.from(hostname); + return false; + } catch (IllegalArgumentException e) { + return true; + } + } + public static boolean useDirectTls(final int port) { return port == 443 || port == 5223; From 59d7bb63e9b262aff1c6b8e8e233c9952058dec3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 3 Aug 2020 07:38:49 +0200 Subject: [PATCH 18/18] version bump to 2.8.10 --- build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/397.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/397.txt diff --git a/build.gradle b/build.gradle index 871c67aac..19daa6537 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 396 - versionName "2.8.10-beta" + versionCode 397 + versionName "2.8.10" archivesBaseName += "-$versionName" applicationId "eu.siacs.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