diff --git a/CHANGELOG.md b/CHANGELOG.md index b9227af42..c2bb0a938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.10.2 + +* Fix crash when rendering some quotes +* Fix crash in welcome screen + ### Version 2.10.1 * Fix issue with some videos not being compressed diff --git a/build.gradle b/build.gradle index 1aa95e63e..f0404c71e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' + classpath 'com.android.tools.build:gradle:7.0.3' } } @@ -64,15 +64,16 @@ dependencies { implementation "com.wefika:flowlayout:0.4.1" implementation 'com.otaliastudios:transcoder:0.10.4' - implementation 'org.jxmpp:jxmpp-jid:1.0.1' + implementation 'org.jxmpp:jxmpp-jid:1.0.2' implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.hsluv:hsluv:0.2' implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'me.drakeet.support:toastcompat:1.1.0' - implementation "com.leinardi.android:speed-dial:2.0.1" + implementation "com.leinardi.android:speed-dial:3.2.0" + implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" - implementation "com.squareup.okhttp3:okhttp:4.9.1" + implementation "com.squareup.okhttp3:okhttp:4.9.2" implementation 'com.google.guava:guava:30.1.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' @@ -92,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42022 - versionName "2.10.1" + versionCode 42023 + versionName "2.10.2" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId @@ -266,7 +267,9 @@ android { variant.outputs.each { output -> def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI)) if (baseAbiVersionCode != null) { - output.versionCodeOverride = (100 * variant.versionCode) + baseAbiVersionCode + output.versionCodeOverride = (100 * project.android.defaultConfig.versionCode) + baseAbiVersionCode + } else { + output.versionCodeOverride = 100 * project.android.defaultConfig.versionCode } } diff --git a/metadata/en-US/changelogs/42023.txt b/metadata/en-US/changelogs/42023.txt new file mode 100644 index 000000000..53c44a175 --- /dev/null +++ b/metadata/en-US/changelogs/42023.txt @@ -0,0 +1,2 @@ +• Fix crash when rendering some quotes +• Fix crash in welcome screen diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index 9f3252ac1..d61c64a9c 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -106,7 +106,8 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi } @Override - public void onNewIntent(Intent intent) { + public void onNewIntent(final Intent intent) { + super.onNewIntent(intent); if (intent != null) { setIntent(intent); } diff --git a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java index 0fd46148b..0b701d27a 100644 --- a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java +++ b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java @@ -23,13 +23,13 @@ public class JabberIdContact extends AbstractPhoneContact { ContactsContract.Data.LOOKUP_KEY, ContactsContract.CommonDataKinds.Im.DATA }; - private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and " + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + "=?))"; + private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and lower(" + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + ")=?))"; private static final String[] SELECTION_ARGS = { ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE, String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER), String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM), - "XMPP" + "xmpp" }; private final Jid jid; diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index e6d3ebf6e..994797779 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -37,11 +37,14 @@ import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.content.ContextCompat; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; @@ -51,6 +54,8 @@ import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; import com.google.android.material.textfield.TextInputLayout; +import com.leinardi.android.speeddial.SpeedDialActionItem; +import com.leinardi.android.speeddial.SpeedDialView; import java.util.ArrayList; import java.util.Collections; @@ -266,8 +271,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne setSupportActionBar(binding.toolbar); configureActionBar(getSupportActionBar()); - binding.speedDial.inflate(R.menu.start_conversation_fab_submenu); - + inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu); binding.tabLayout.setupWithViewPager(binding.startConversationViewPager); binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override @@ -338,6 +342,21 @@ public class StartConversationActivity extends XmppActivity implements XmppConne }); } + private void inflateFab(final SpeedDialView speedDialView, final @MenuRes int menuRes) { + speedDialView.clearActionItems(); + final PopupMenu popupMenu = new PopupMenu(this, new View(this)); + popupMenu.inflate(menuRes); + final Menu menu = popupMenu.getMenu(); + for (int i = 0; i < menu.size(); i++) { + final MenuItem menuItem = menu.getItem(i); + final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon()) + .setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null) + .setFabImageTintColor(ContextCompat.getColor(this, R.color.white)) + .create(); + speedDialView.addActionItem(actionItem); + } + } + public static boolean isValidJid(String input) { try { Jid jid = Jid.ofEscaped(input); diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 5715cd3f1..1e0cf41d3 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -7,24 +7,39 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.util.Log; +import android.view.View; import android.widget.Toast; +import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.databinding.DataBindingUtil; import com.google.common.base.Strings; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.databinding.ActivityUriHandlerBinding; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; public class UriHandlerActivity extends AppCompatActivity { @@ -34,7 +49,9 @@ public class UriHandlerActivity extends AppCompatActivity { private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789; private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION = 0x6790; private static final Pattern V_CARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n"); - private boolean handled = false; + private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("<(.*?)>"); + private ActivityUriHandlerBinding binding; + private Call call; public static void scan(final Activity activity) { scan(activity, false); @@ -77,9 +94,7 @@ public class UriHandlerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.handled = savedInstanceState != null && savedInstanceState.getBoolean("handled", false); - getLayoutInflater().inflate(R.layout.toolbar, findViewById(android.R.id.content)); - setSupportActionBar(findViewById(R.id.toolbar)); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler); } @Override @@ -88,23 +103,17 @@ public class UriHandlerActivity extends AppCompatActivity { handleIntent(getIntent()); } - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putBoolean("handled", this.handled); - super.onSaveInstanceState(savedInstanceState); - } - @Override public void onNewIntent(final Intent intent) { super.onNewIntent(intent); handleIntent(intent); } - private void handleUri(Uri uri) { - handleUri(uri, false); + private boolean handleUri(final Uri uri) { + return handleUri(uri, false); } - private void handleUri(Uri uri, final boolean scanned) { + private boolean handleUri(final Uri uri, final boolean scanned) { final Intent intent; final XmppUri xmppUri = new XmppUri(uri); final List accounts = DatabaseBackend.getInstance(this).getAccountJids(true); @@ -114,19 +123,22 @@ public class UriHandlerActivity extends AppCompatActivity { final Jid jid = xmppUri.getJid(); if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) { - Toast.makeText(this, R.string.account_already_exists, Toast.LENGTH_LONG).show(); - return; + showError(R.string.account_already_exists); + return false; } intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth); startActivity(intent); - return; + return true; } if (accounts.size() == 0 && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); - return; + return true; } + } else if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { + showError(R.string.account_registrations_are_not_supported); + return false; } if (accounts.size() == 0) { @@ -134,15 +146,14 @@ public class UriHandlerActivity extends AppCompatActivity { intent = SignupUtils.getSignUpIntent(this); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); + return true; } else { - Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); + showError(R.string.invalid_jid); + return false; } - - return; } if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) { - final Jid jid = xmppUri.getJid(); final String body = xmppUri.getBody(); @@ -177,11 +188,57 @@ public class UriHandlerActivity extends AppCompatActivity { intent.putExtra("scanned", scanned); intent.setData(uri); } else { - Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); - return; + showError(R.string.invalid_jid); + return false; } - startActivity(intent); + return true; + } + + private void checkForLinkHeader(final HttpUrl url) { + Log.d(Config.LOGTAG, "checking for link header on " + url); + this.call = HttpConnectionManager.OK_HTTP_CLIENT.newCall(new Request.Builder() + .url(url) + .head() + .build()); + this.call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + Log.d(Config.LOGTAG, "unable to check HTTP url", e); + showError(R.string.no_xmpp_adddress_found); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) { + final String linkHeader = response.header("Link"); + if (linkHeader != null && processLinkHeader(linkHeader)) { + return; + } + } + showError(R.string.no_xmpp_adddress_found); + } + }); + + } + + private boolean processLinkHeader(final String header) { + final Matcher matcher = LINK_HEADER_PATTERN.matcher(header); + if (matcher.find()) { + final String group = matcher.group(); + final String link = group.substring(1, group.length() - 1); + if (handleUri(Uri.parse(link))) { + finish(); + return true; + } + } + return false; + } + + private void showError(@StringRes int error) { + this.binding.progress.setVisibility(View.INVISIBLE); + this.binding.error.setText(error); + this.binding.error.setVisibility(View.VISIBLE); } private static Class findShareViaAccountClass() { @@ -192,29 +249,33 @@ public class UriHandlerActivity extends AppCompatActivity { } } - private void handleIntent(Intent data) { - if (handled) { + private void handleIntent(final Intent data) { + final String action = data == null ? null : data.getAction(); + if (action == null) { return; } - if (data == null || data.getAction() == null) { - finish(); - return; - } - - handled = true; - - switch (data.getAction()) { + switch (action) { + case Intent.ACTION_MAIN: + binding.progress.setVisibility(call != null && !call.isCanceled() ? View.VISIBLE : View.INVISIBLE); + break; case Intent.ACTION_VIEW: case Intent.ACTION_SENDTO: - handleUri(data.getData()); + if (handleUri(data.getData())) { + finish(); + } break; case ACTION_SCAN_QR_CODE: - Intent intent = new Intent(this, ScanActivity.class); - startActivityForResult(intent, REQUEST_SCAN_QR_CODE); - return; + Log.d(Config.LOGTAG, "scan. allow=" + allowProvisioning()); + setIntent(createMainIntent()); + startActivityForResult(new Intent(this, ScanActivity.class), REQUEST_SCAN_QR_CODE); + break; } + } - finish(); + private Intent createMainIntent() { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra(EXTRA_ALLOW_PROVISIONING, allowProvisioning()); + return intent; } private boolean allowProvisioning() { @@ -226,6 +287,7 @@ public class UriHandlerActivity extends AppCompatActivity { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { super.onActivityResult(requestCode, requestCode, intent); if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) { + final boolean allowProvisioning = allowProvisioning(); final String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); if (Strings.isNullOrEmpty(result)) { finish(); @@ -234,18 +296,34 @@ public class UriHandlerActivity extends AppCompatActivity { if (result.startsWith("BEGIN:VCARD\n")) { final Matcher matcher = V_CARD_XMPP_PATTERN.matcher(result); if (matcher.find()) { - handleUri(Uri.parse(matcher.group(2)), true); + if (handleUri(Uri.parse(matcher.group(2)), true)) { + finish(); + } + } else { + showError(R.string.no_xmpp_adddress_found); } - finish(); return; - } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning()) { + } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning) { ProvisioningUtils.provision(this, result); finish(); return; } - handleUri(Uri.parse(result), true); + final Uri uri = Uri.parse(result.trim()); + if (allowProvisioning && "https".equalsIgnoreCase(uri.getScheme()) && !XmppUri.INVITE_DOMAIN.equalsIgnoreCase(uri.getHost())) { + final HttpUrl httpUrl = HttpUrl.parse(uri.toString()); + if (httpUrl != null) { + checkForLinkHeader(httpUrl); + } else { + finish(); + } + } else if (handleUri(uri, true)) { + finish(); + } else { + setIntent(new Intent(Intent.ACTION_VIEW, uri)); + } + } else { + finish(); } - finish(); } private static boolean looksLikeJsonObject(final String input) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 077cfb78d..76a8d2be8 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -368,9 +368,7 @@ public class MessageAdapter extends ArrayAdapter { char current = body.length() > i ? body.charAt(i) : '\n'; if (lineStart == -1) { if (previous == '\n') { - if ( - QuoteHelper.isPositionQuoteStart(body, i) - ) { + if (i < body.length() && QuoteHelper.isPositionQuoteStart(body, i)) { // Line start with quote lineStart = i; if (quoteStart == -1) quoteStart = i; @@ -804,12 +802,12 @@ public class MessageAdapter extends ArrayAdapter { } else if (message.treatAsDownloadable()) { try { final URI uri = new URI(message.getBody()); - displayDownloadableMessage(viewHolder, - message, - activity.getString(R.string.check_x_filesize_on_host, - UIHelper.getFileDescriptionString(activity, message), - uri.getHost()), - darkBackground); + displayDownloadableMessage(viewHolder, + message, + activity.getString(R.string.check_x_filesize_on_host, + UIHelper.getFileDescriptionString(activity, message), + uri.getHost()), + darkBackground); } catch (Exception e) { displayDownloadableMessage(viewHolder, message, diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index ac2913037..4beee8a22 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -11,47 +11,47 @@ public class QuoteHelper { public static final char QUOTE_ALT_CHAR = '»'; public static final char QUOTE_ALT_END_CHAR = '«'; - public static boolean isPositionQuoteCharacter(CharSequence body, int pos){ + public static boolean isPositionQuoteCharacter(CharSequence body, int pos) { // second part of logical check actually goes against the logic indicated in the method name, since it also checks for context // but it's very useful return body.charAt(pos) == QUOTE_CHAR || isPositionAltQuoteStart(body, pos); } - public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos){ + public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos) { return body.charAt(pos) == QUOTE_END_CHAR; } - public static boolean isPositionAltQuoteCharacter (CharSequence body, int pos){ + public static boolean isPositionAltQuoteCharacter(CharSequence body, int pos) { return body.charAt(pos) == QUOTE_ALT_CHAR; } - public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos){ + public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos) { return body.charAt(pos) == QUOTE_ALT_END_CHAR; } - public static boolean isPositionAltQuoteStart(CharSequence body, int pos){ + public static boolean isPositionAltQuoteStart(CharSequence body, int pos) { return isPositionAltQuoteCharacter(body, pos) && !isPositionFollowedByAltQuoteEnd(body, pos); } public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { - return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos +1 ); + return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos + 1); } // 'Prequote' means anything we require or can accept in front of a QuoteChar - public static boolean isPositionPrecededByPrequote(CharSequence body, int pos){ + public static boolean isPositionPrecededByPreQuote(CharSequence body, int pos) { return UIHelper.isPositionPrecededByLineStart(body, pos); } - public static boolean isPositionQuoteStart (CharSequence body, int pos){ + public static boolean isPositionQuoteStart(CharSequence body, int pos) { return (isPositionQuoteCharacter(body, pos) - && isPositionPrecededByPrequote(body, pos) + && isPositionPrecededByPreQuote(body, pos) && (UIHelper.isPositionFollowedByQuoteableCharacter(body, pos) - || isPositionFollowedByQuoteChar(body, pos))); + || isPositionFollowedByQuoteChar(body, pos))); } - public static boolean bodyContainsQuoteStart (CharSequence body){ - for (int i = 0; i < body.length(); i++){ - if (isPositionQuoteStart(body, i)){ + public static boolean bodyContainsQuoteStart(CharSequence body) { + for (int i = 0; i < body.length(); i++) { + if (isPositionQuoteStart(body, i)) { return true; } } @@ -76,7 +76,7 @@ public class QuoteHelper { return false; } - public static boolean isNestedTooDeeply (CharSequence line){ + public static boolean isNestedTooDeeply(CharSequence line) { if (isPositionQuoteStart(line, 0)) { int nestingDepth = 1; for (int i = 1; i < line.length(); i++) { @@ -91,9 +91,9 @@ public class QuoteHelper { return false; } - public static String replaceAltQuoteCharsInText(String text){ - for (int i = 0; i < text.length(); i++){ - if (isPositionAltQuoteStart(text, i)){ + public static String replaceAltQuoteCharsInText(String text) { + for (int i = 0; i < text.length(); i++) { + if (isPositionAltQuoteStart(text, i)) { text = text.substring(0, i) + QUOTE_CHAR + text.substring(i + 1); } } diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 76d4911b8..c0a6f4cfd 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -21,6 +21,8 @@ import android.net.Uri; import android.provider.OpenableColumns; import android.util.Log; +import com.google.common.base.Strings; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -274,6 +276,8 @@ public final class MimeUtils { add("image/ico", "ico"); add("image/ief", "ief"); add("image/heic", "heic"); + add("image/heif", "heif"); + add("image/avif", "avif"); // add ".jpg" first so it will be the default for guessExtensionFromMimeType add("image/jpeg", "jpg"); add("image/jpeg", "jpeg"); @@ -587,22 +591,33 @@ public final class MimeUtils { } public static String extractRelevantExtension(final String path, final boolean ignoreCryptoExtension) { - if (path == null || path.isEmpty()) { + if (Strings.isNullOrEmpty(path)) { return null; } - String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase(); - int dotPosition = filename.lastIndexOf("."); + final String filenameQueryAnchor = path.substring(path.lastIndexOf('/') + 1); + final String filenameQuery = cutBefore(filenameQueryAnchor, '#'); + final String filename = cutBefore(filenameQuery, '?'); + final int dotPosition = filename.lastIndexOf('.'); - if (dotPosition != -1) { - 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)); - } else { - return extension; - } + if (dotPosition == -1) { + return null; + } + final 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)); + } else { + return extension; + } + } + + private static String cutBefore(final String input, final char c) { + final int position = input.indexOf(c); + if (position > 0) { + return input.substring(0, position); + } else { + return input; } - return null; } } diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 084982e17..6c3075be9 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -35,6 +35,8 @@ public class XmppUri { private Map parameters = Collections.emptyMap(); private boolean safeSource = true; + public static final String INVITE_DOMAIN = "conversations.im"; + public XmppUri(final String uri) { try { parse(Uri.parse(uri)); @@ -136,10 +138,10 @@ public class XmppUri { return; } this.uri = uri; - String scheme = uri.getScheme(); - String host = uri.getHost(); + final String scheme = uri.getScheme(); + final String host = uri.getHost(); List segments = uri.getPathSegments(); - if ("https".equalsIgnoreCase(scheme) && "conversations.im".equalsIgnoreCase(host)) { + if ("https".equalsIgnoreCase(scheme) && INVITE_DOMAIN.equalsIgnoreCase(host)) { if (segments.size() >= 2 && segments.get(1).contains("@")) { // sample : https://conversations.im/i/foo@bar.com try { 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 7b9caa66c..751fa66f4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/WebRTCWrapper.java @@ -255,6 +255,7 @@ public class WebRTCWrapper { rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED; //XEP-0176 doesn't support tcp rtcConfig.continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY; rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN; + rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.NEGOTIATE; final PeerConnection peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, peerConnectionObserver); if (peerConnection == null) { throw new InitializationException("Unable to create PeerConnection"); diff --git a/src/main/res/layout/activity_uri_handler.xml b/src/main/res/layout/activity_uri_handler.xml new file mode 100644 index 000000000..9eda73c87 --- /dev/null +++ b/src/main/res/layout/activity_uri_handler.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/start_conversation_fab_submenu.xml b/src/main/res/menu/start_conversation_fab_submenu.xml index bfaca0727..2cf545d68 100644 --- a/src/main/res/menu/start_conversation_fab_submenu.xml +++ b/src/main/res/menu/start_conversation_fab_submenu.xml @@ -2,22 +2,22 @@ + android:icon="@drawable/ic_search_white_24dp" + android:title="@string/discover_channels" /> + android:icon="@drawable/ic_input_white_24dp" + android:title="@string/join_public_channel" /> + android:icon="@drawable/ic_public_white_24dp" + android:title="@string/create_public_channel" /> + android:icon="@drawable/ic_group_white_24dp" + android:title="@string/create_private_group_chat" /> + android:icon="@drawable/ic_person_white_48dp" + android:title="@string/add_contact" /> \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 1fffc00df..ff9d08834 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -968,5 +968,7 @@ The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. Plain text document + Account registrations are not supported + No XMPP address found